Category: Developer

งานพัฒนาระบบ, เขียนโปรแกรม

  • Run automated test ด้วย Chrome แบบที่ไม่มี web browser แสดงขึ้นมา (Headless Mode)

    สืบเนื่องจากการทำ Automated test แล้วรำคาญการเปิดหน้าต่างการทำงานขึ้นมา เพราะอยากจะรู้แค่ผลลัพธ์ก็เท่านั้น วันนี้เลยมาเสนอ Headless Mode feature เป็นของ Chrome สามารถ run automated test ด้วย Chrome แบบที่ไม่มี web browser แสดงขึ้นมา หรือที่เรียกกันว่า Headless Mode นั่นเองค่ะ ไม่ต้องDownload อะไรใด ๆ เลย เพียงแค่เราติดตั้ง Chrome เท่านั้นก็สามารถใช้งาน feature นี้ได้แร่ะ (Version 60 ขึ้นไป) ขั้นตอนมาดูกันเลย ในตัวอย่างนี้จะเป็นการเขียน Code ทดสอบด้วย Selenium Library จ้า

    การตั้งค่า Headless Mode คำสั่งดังนี้

    ตัวอย่าง Test script สำหรับรันเทส web แบบ Headless mode ตามด้านล่างเลยค่ะ

    หาก Run แล้วจะพบว่าไม่มี Chrome Browser เปิดขึ้นมาให้กวนใจอีกต่อไป แต่เมื่อเข้าไปดูใน Test Report ก็จะพบว่ายังสามารถ Run Automated Test ได้ถูกต้องด้วยค่ะ

  • วิธีเชื่อมต่อ PostgreSQL จาก python

    ในการเชื่อมต่อกับ PostgreSQL จาก Python นั้น จะต้องใช้ Package ‘psycopg2’

    คำสั่งในการติดตั้งคือ

    pip install psycopg2

    วิธีการเรียกใช้จาก python

    
    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    import psycopg2
    from IPython.display import display, HTML # used to print out pretty pandas dataframes
    import matplotlib.dates as dates
    import matplotlib.lines as mlines
    
    %matplotlib inline
    plt.style.use('ggplot') 
    
    # specify user/password/where the database is
    sqluser = 'username'
    sqlpass = 'userpasswordgohere'
    dbname = 'dbname'
    schema_name = 'someschema'
    host = 'host.postgresql.server'
    
    query_schema = 'SET search_path to ' + schema_name + ';'
    
    # connect to the database
    con = psycopg2.connect(dbname=dbname, user=sqluser, password=sqlpass, host=host)
    
    query = query_schema + 'select * from sometable'
    df = pd.read_sql_query(query,con)

    ก็จะได้ df เป็น Dataframe เอาไปใช้งานต่อได้

    จบ

  • การสร้าง RESTful API สำหรับใช้งานจริง ง่าย ๆ ด้วย Flask และ Waitress

    จาก From LAB to Production – จาก Machine Learning Model สู่ Flask RESTful ซึ่งได้นำ Machine Learning แบบ Statistical Approach อย่าง Logistic Regression ที่สร้างโมเดล และ Train กับข้อมูลเรียบร้อยแล้ว (บนเครื่อง Development) จากนั้น ได้นำโมเดลออกมาใช้งาน โดยใช้ วิธีการ Serialization ด้วย joblib ในภาษา Python ได้เป็นไฟล์ออกมา แล้วจึงนำไปใช้เพื่อใช้ในการทำนาย (predict) ชนิดของดอก Iris บนเครื่อง Production โดยรับ Input จากผู้ใช้ผ่าน HTTP Protocol ทั้ง GET/POST ที่ TCP/5000

    ตัวอย่างดังกล่าว ยังเป็นเพียงการ “ทดสอบ” แต่ในบทความนี้ จะเป็นวิธีการ ซึ่งนำไปสู่การใช้งานจริง ๆ ซึ่ง Flask แนะนำให้ใช้งานกับ “waitress” (น่าจะเลียนแบบจาก Server) ซึ่งเป็น WSGI (Web Server Gateway Interface) อีกตัวหนึ่ง ใช้งานง่าย เพราะไม่ต้องติดตั้ง Apache/Nginx เลย

    ติดตั้ง waitress

    pip install waitress

    predict.py

    from flask import Flask, request, jsonify
    from flask_restful import Resource, Api, reqparse
    from flask_cors import CORS
    
    app = Flask(__name__)
    # Enable CORS
    CORS(app)
    
    @app.route("/predict", methods=["POST"])
    def predict():
    	result = 0
    	if request.method == "POST":    		
    		input_value = request.form["input_value"]
    		# ประมวลผล
    		# ...
    		# ตัวอย่างเช่น รับค่ามา แล้ว คูณ 2
    		result=input_value * 2
    		# ###
    	return jsonify(
    		prediction=result
    	),201

    ไฟล์ predict.py เป็นตัวอย่าง python ซึ่งรับค่า input_value จาก HTML form ผ่าน POST method เข้ามา ที่ /predict ซึ่งเขียนด้วย Flask ที่จะไปเรียกใช้ฟังก์ชั่น prediction() แล้วก็ทำการคำนวณที่ต้องการ จากนั้น ตอบค่ากลับไปเป็น JSON ด้วยฟังก์ชั่น jsonify โดยสามารถกำหนด key ชื่อ prediction และ value เป็น result ที่คำนวณได้ และแจ้ง Response Code เป็น 201

    waitress_server.py

    from waitress import serve
    import predict
    serve(predict.app, host='0.0.0.0', port=8080)

    ไฟล์ waitress_server.py ก็เพียงแค่ import serve จาก waitress ที่ติดตั้งไป และ import predict ซึ่งก็คือไฟล์ predict.py ข้างต้น (อยู่ในไดเรคทอรีเดียวกัน) จากนั้น ก็เรียก predict.app โดยรับได้จากทุก IP (0.0.0.0) ที่ TCP port 8080

    วิธีใช้งาน

    ก็แค่เปิด Command Prompt (ตัวอย่างนี้ทำบน Windows Server) แล้วใช้คำสั่ง

    python waitress_server.py

    จากนั้น ก็พัฒนา web application หรือ จะใช้ postman ทดสอบติดต่อเข้ามาก็ได้ ที่ http://server-ip-address:8080/predict แล้ว POST ข้อมูลเข้ามา ก็จะได้ผลลัพธ์กลับไปเป็น JSON

    หวังว่าจะเป็นประโยชน์ครับ

  • Stencil : Styling

    Shadow DOM

    Shadow DOM เป็น API ที่อยู่ใน browser ที่ให้ความสามารถในทำ DOM encapsulation และ style encapsulation โดย Shadow DOM จะแยก component ออกจากภายนอก ทำให้ไม่ต้องกังวลกับ scope ของ css หรือผลกระทบกับ component ภายนอก หรือ component ภายนอกจะกระทบกับ component ภายใน

    ใน Stencil ค่า default การใช้งาน Shadow DOM ใน web component ที่สร้างด้วย Stencil จะถูกปิดอยู่  หากต้องการเปิดใช้งาน Shadow DOM ใน web component ต้องกำหนดค่า shadow param ใน component decorator ดังตัวอย่างด้านล่างนี้

    @Component({
      tag: 'shadow-component',
      styleUrl: 'shadow-component.scss',
      shadow: true
    })
    export class ShadowComponent {
    
    }
    

    สิ่งจำเป็นเมื่อเปิดใช้งาน Shadow DOM

    • QuerySelector เมื่อต้องการ query element ที่อยู่ภายใน web component จะต้องใช้ this.el.shadowRoot.querySelector() เนื่องจาก DOM ภายใน web component อยู่ภายใน shadowRoot
    • Global Styles จะต้องใช้ CSS custom properties
    • css selector สำหรับ web component element คือ “:host”  selector

    โดยทั่วไป จะเก็บ styles ไว้ภายไต้ ชื่อ tag ของ component นั้น

    my-element {
      div {
        background: blue;
      }
    }
    

    ในกรณีของ Shadow DOM  อยู่ภายใต้ tag :host

    :host {
      div {
        background: blue;
      }
    }
    

    Scoped CSS

    สำหรับ browser ที่ไม่สนับสนุน Shadow DOM, web component ที่สร้างโดย Stencil จะกลับไปใช้ scoped CSS แทนที่จะ load Shadow DOM polyfill ที่มีขนาดใหญ่  Scoped CSS จะทำการกำหนดขอบเขต CSS ให้กับ element โดยอัตโนมัตตอน runtime

    CSS Variables

    CSS Variables เหมือนกับ Sass Variables แต่ต่างกันตรงที่ CSS Variables รวมอยู่ใน browser โดยที่ CSS Variables ให้ความสามารถในการกำหนด CSS properties ที่ใช้ได้ภายใน app  ตัวอย่างการใช้งานที่พบบ่อยคือ การกำหนดสี (color) ถ้ามีสีหลักที่ต้องการใช้ร่วมกันทั้ง app แทนที่จะกำหนดสีนั้นๆในแต่ละที่ที่ใช้งาน ก็จะสร้าง variable ขึ้นมาและใช้ variable นั้นในทุกๆที่ที่ต้องการ ซึ่งถ้าต้องการเปลี่ยนสี ก็สามารถเปลี่ยนที่ variable ที่เดียว

    การใช้งาน CSS Variables ใน Stencil

    สร้าง file : variables.css ที่ใช้เก็บ CSS Variabless ใน “src/global/” directory  และเพิ่ม config globalStyle: ‘src/global/variables.css’  ใน stencil.config.js

    ตัวอย่าง การกำหนด CSS Variable ใน src/global/variables.css

    :root {
      --app-primary-color: #488aff;
    }
    

    จากตัวอย่างด้านบน สร้าง CSS Variable ชื่อ –app-primary-color ที่เก็บค่าสี #488aff อยู่ภายใต้ :root selector (:root selector คือ CSS pseudo selector ที่หมายถึง root element ของ app) การใช้ CSS Variable ที่กำหนดไว้ทำได้ดังนี้

    h1 {
      color: var(--app-primary-color)
    }
    

    เป็นการกำหนดสี ที่เก็บไว้ใน CSS Variable –app-primary-color ให้กับ h1 element

     

    อ้างอิง : https://stenciljs.com/docs/styling

  • python #02 – ติดตั้ง Tensorflow และ Keras

    ต่อจาก python #01 – ติดตั้ง jupyter notebook บน Windows

    ต่อไปเราจะใช้ Machine Learning Library “Tensorflow” และใช้ “Keras” ซึ่งเป็น High-level Neuron Network API ซึ่งจะไปเรียกใช้ Backend คือ Tensorflow อีกชั้นหนึ่ง

    จาก jupyter notebook ทำตามขั้นตอนต่อไปนี

    1.  คำสั่งต่อไปนี้ เพื่อติดตั้ง Tensorflow

    ! pip install tensorflow

    2.  คำสั่งต่อไปนี้ เพื่อติดตั้ง Keras

    ! pip install keras

    จะได้ผลประมาณนี้

    สร้าง Neural Network ด้วย Keras

    เริ่มจาก import ส่วนต่าง ๆ ได้แก่ Models และ Layers

    import keras
    from keras.models import Sequential
    from keras.layers import Input, Dense

    สมมุติเราจะสร้าง Model แบบนี้

    model = Sequential([
        Dense(8, activation='relu', input_shape=(10,), name="Hidden_Layer_1"),
        Dense(5, activation='relu', name='Hidden_Layer_2'),
        Dense(3, activation='softmax' , name='Output_Layer')
    ])

    เสร็จแล้วก็ต้อง compile ตั้งค่า Hyperparameters ต่าง ๆ

    model.compile( loss='categorical_crossentropy',
        optimizer=keras.optimizers.adam(lr=0.001),
        metrics=['accuracy'])

    ดู Summary

    model.summary()

    ได้ผลประมาณนี้

  • python #01 – ติดตั้ง jupyter notebook บน Windows

    jupyter Notebook เป็น Open Source Web Application ให้เราสามารถเขียนภาษา python ได้ง่ายขึ้น สามารถ Share ได้ด้วย และยังสามารถใส่คำอธิบาย (Markdown) ได้ด้วย ติดตั้งลงในเครื่องส่วนตัวได้ ในบทความนี้ แสดงวิธีติดตั้งและใช้งานบน Microsoft Windows ดังนี้

    1. ขั้นแรก ต้องมี Python ก่อน (หมายเหตุ: ณ เวลานี้ 27/11/61 รุ่นล่าสุดคือ 3.7.1 แต่เนื่องจากจะแนะนำการใช้งาน Tensorflow, Keras จึงยังต้องเลือกใช้ Python 3.6.7)

    https://www.python.org/ftp/python/3.6.7/python-3.6.7.exe

    เมื่อติดตั้งแล้ว ควร Restart เครื่องสักหนึ่งครั้ง

    2. ติดตั้ง pip ซึ่งใช้ในการติดตั้งเครื่องมือต่าง ๆ
    เปิด Windows Console (กดปุ่ม Win + R) แล้วพิมพ์คำสั่ง cmd
    จากนั้น ใช้คำสั่งต่อไปนี้

    python -m pip install --upgrade pip setuptools wheel

    3. (Optional) ติดตั้ง virtualenv เพื่อให้สามารถจัดการ Virtual Environment ได้ง่ายขึ้น เพราะอาจจะต้องทำหลาย Project ซึ่งใช้รุ่นของ Package/Module ที่แตกต่างกันบนเครื่องเดียวกัน

    pip install virtualenv

    4. ติดตั้ง jupyter notebook

    pip install jupyter

    5. เมื่อติดตั้งเสร็จแล้ว ก็ใช้คำสั่งต่อไปนี้

    jupyter notebook

    ระบบจะเปิด Web Browser มายัง http://localhost:8888

    จากนั้น คลิกที่ปุ่ม New > Python3

    จากนั้น เราก็จะสามารถใช้คำสั่ง Python ทั่ว ๆ ไปได้

    นอกจากนี้ ยัง Save ได้ และสามารถส่งต่อให้ผู้อื่นใช้ได้ด้วย

  • Stencil : JSX

    Stencil component ใช้ JSX template syntax ในการกำหนดรูปแบบการแสดงผลที่จะถูก render ของ component  ซึ่งแต่ละ component จะมี render function ที่จะทำหน้าที่ return โครงสร้างของ component ที่จะเปลี่ยนเป็น DOM ตอน runtime เพื่อแสดงผลบนหน้าจอ

    class MyComponent {
        render() {
           return (
              <div>
                <h1>Hello World</h1>
                <p>This is JSX!</p>
              </div>
           );
        }
    }
    

    จาก class Mycomponent ด้านบน render function จะ return div element ที่ภายในประกอบไปด้วย h1 และ p

    Data Binding

    เมื่อ component ต้องการที่จะ render ข้อมูลที่มีการเปลี่ยนแปลง dynamic data ทำได้โดยการ bindind ข้อมูลนั้นๆ โดยใช้ {variable}  (ซึ่งจะใกล้เคียงกับ ES6 template ที่ใช้ ${variable})

    render() {
         return (
            <div>Hello {this.name}</div>
         )
    }
    

    Conditionals

    เมื่อ component มีการแสดงผลที่ขึ้นอยู่กับเงื่อนไข สามารถใช้ JavaScript if/else statements ใน render function ดังเช่นตัวอย่างด้านล่างนี้ ถ้า this.name ไม่มีการกำหนดค่า จะแสดงผล “Hello, World”

    render() {
        if (this.name) {
            return ( <div>Hello {this.name}</div> )
        } else {
            return ( <div>Hello, World</div> )
        }
    }
    

    หรือ จะเขียนในรูปแบบ inline conditionals ก็ได้เช่นกัน

    render() { 
           return ( 
              <div> 
              {this.name 
                   ? <p>Hello {this.name}</p> 
                   : <p>Hello World</p> 
              } 
              </div> 
           ); 
    }
    

    Loops

    ใน JSX สามารถใช้ array operators : map ในการทำงานแบบ loop  จากตัวอย่างด้านล่าง มี lists ของ todo object ใน this.Objects ซึ่ง map function ทำหน้าที่ loop ในแต่ละ todo object ใน this.Objects แล้วสร้าง new JSX sub tree และ add เข้าไปใน array ที่จะ return ออกจาก map function ซึ่งจะเพิ่มเข้าไปใน JSX tree ด้านนอกนั่นคือ div element

    render() {
       return (
         <div>
           {this.todos.map((todo) =>
               <div>
                 <div>{todo.taskName}</div>
                 <div>{todo.isCompleted}</div>
               </div>
           )}
         </div>
      )
    }
    

    Handling User Input

    Stencil สามารถใช้ native DOM events ได้ดังตัวอย่างด้านล่าง

    export class MyComponent 
    { 
           handleClick(event: UIEvent) { 
                alert('Received the button click!'); 
           } 
           render() { 
               return ( 
                  <button onClick={ (event: UIEvent) => this.handleClick(event)}>Click Me!               
                  </button> 
               ); 
           } 
    }
    

    หรือจะเขียนในรูปแบบ  onClick={this.handleClick.bind(this)} ก็ได้เช่นกัน

      handleClick(event: UIEvent) {
        alert('Received the button click!');
      }
    
      render() {
        return (
          <button onClick={this.handleClick.bind(this)}>Click Me!</button>
        );
      }
    

     

    อ้างอิง : https://stenciljs.com/docs/templating-jsx

     

  • django – as a Dialogflow Webhook #03

    บทความนี้ จะกล่าวถึง การใช้ django ทำหน้าที่เป็น Webhook จาก Dialogflow ผ่าน Fulfillment ทาง HTTP Post Request ด้วย JSON object และ ทำการประมวลผล แล้วตอบกลับไปเป็น JSON Object เช่นกัน เพื่อให้ Dialogflow ตอบสนองต่อผู้ใช้ได้ตามต้องการ เช่น อาจจะให้ไปค้นข้อมูลจากฐานข้อมูลในองค์กรมาตอบ เป็นต้น

    Source: https://dialogflow.com/docs/intro/fulfillment

    ในมุมของ django django (ดี)จังโก้ ดีอย่างไร #01 ได้กล่าวถึงการสร้าง Web Application จาก Model โดยกำหนด Fields ต่าง ๆ จากนั้น django ก็จะสร้าง Web Form ต่าง ๆ ให้อัตโนมัติ และยังสามารถสร้าง Users ของระบบ พร้อมทั้ง กำหนดสิทธิ์การเข้าถึงของแต่ละคนได้อีกด้วย แล้วนำไปผูกกับส่วน Admin เพื่อให้ผู้ใช้ทำการ Authentication ก่อนเข้าจัดการกับข้อมูลต่างได้ และในบทความ django – Deploy to Production #02 ได้แนะนำวิธีการ Deploy ระบบที่สร้างขึ้นสู่ Production ตามลำดับ

    ในบทความนี้ จะใช้ “view” ซึ่งเป็นอีกส่วนของ django ตามขั้นตอนต่อไปนี้

    ใน myproject สร้าง App ใหม่ ชื่อ fulfillment

    python manage.py startapp fulfillment

    เพิ่ม ‘fulfillment’ app ลงใน myproject/settings.py ที่ INSTALLED_APPS
    ( ในตัวอย่างก่อนหน้า เราเพิ่ม worklog app ไว้แล้ว) 

    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'worklog',
        'fulfillment'
    ]

    จากนั้น แก้ไขไฟล์ myproject/fulfillment/views.py ตามนี้

    from django.http import HttpRequest, HttpResponse
    from django.views.decorators.csrf import csrf_exempt
    import json
    # Create your views here.
    @csrf_exempt
    def sayHi(request):
        j = json.loads(request.body)
        x = {  "fulfillmentText": "This is a text response"
            }
        return HttpResponse(json.dumps(x))

    ในส่วนนี้ จะ import packages ต่อไปนี้

    • HttpRequest เพื่อรับ Input ผ่าน HTTP
    • HttpResponse เพื่อตอบ Output ผ่าน HTTP
    • csrf_exempt เพื่อบอกว่า ยอมให้ทำงานผ่าน HTTP POST โดยไม่ต้องมี CSRF Token (ถ้าไม่ใส่ อยู่ ๆ จะส่ง POST เข้ามาไม่ได้ )
    • json เพื่อจัดการ JSON object

    จากนั้น สร้าง Function ชื่อ “sayHi” มี function ที่เรียกใช้งานดังนี้

    • json.loads(request.body) ทำหน้าที่แปลง JSON Object จาก HTTP Request เข้ามาอยู่ในรูป Python Object ในที่นี้ จะนำข้อมูลจาก Dialogflow Fulfillment ที่ได้จาก Intent Matching และ Parameter Extraction ส่งมา
    • json.dumps(x) ทำหน้าที่แปลงข้อมูล จาก Python Object (ในรูปแบบ dict) ไปเป็น JSON Object ในที่นี้ แปลง { “fulfillmentText” : “This is a text response” } ไปเป็น JSON Object แล้วตอบกลับไปทาง HTTP Response

    ต่อไป สร้างไฟล์ myproject/fulfillment/urls.py เพื่อกำหนด URL ที่จะเรียกใช้ function ดังนี้

    from django.urls import path
    from . import views
    urlpatterns = [    
    	path('hi', views.sayHi )
    ]

    ในส่วนนี้ import package “path” เข้ามา และ import views ที่สร้างขึ้นใน ‘fulfillment’ app และกำหนดว่า เมื่อการเรียก “hi” ให้ไปเรียก function “sayHi” ซึ่งเป็น views

    สุดท้าย แก้ไขไฟล์ myproject/urls.py เพื่อเพิ่ม URL path ให้ fulfillment

    from django.contrib import admin
    from django.urls import path, include
    urlpatterns = [
    	path('admin/', admin.site.urls),
    	path('fulfillment/', include('fullfillment.urls'))
    ]
    

    เมื่อมีการเรียก http://server-domain/fulfillment/ ก็จะส่งไปให้ views ใน fulfillment ทำงาน โดย Include fulfillment.urls เข้ามา

    เมื่อเรียก  http://server-domain/fulfillment/hi ก็จะไปเรียก function sayHi ที่เขียนข้างต้นนั่นเอง

    Dialogflow Fulfillment

    ใน Dialogflow Console คลิกที่ Fulfillment แล้ว Enable Webhook จากนั้นใส่ URL ของ django production server ที่เราสร้างขึ้น ในตัวอย่างจะเป็น

    https://xxxxxxxxx.psu.ac.th/fulfillment/hi

    จากนั้น กดปุ่ม Save ด้านล่าง (ในทางปฏิบัติจริง ๆ ต้องมีเรื่อง Authentication/Authorization อีกพอสมควร แต่ในตัวอย่างนี้ ทำแบบง่าย ๆ ก่อน)

    ต่อไป สร้าง Intent ชุดที่ต้องการให้ส่งให้ Fulfillment ทำงาน แล้วก็คลิกที่ Fulfillment > Enable webhook call for this intent แล้ว Save

    จากนั้น ลองทดสอบดู จะเห็นได้ว่า Default Response จะแสดงข้อความ “This is a text response” แทนที่จะเป็น Text Response ที่กำหนดใน Intent นั้น

    ลองคลิก Diagnostic Info

    จะเห็นว่า มี fulfillmentText เป็น “This is a text response” ตามที่เขียนไว้ เป็นอันว่า Dialogflow สามารถเชื่อมต่อไปยัง django Webhook ที่สร้างขึ้นได้แล้ว

    หวังว่าจะเป็นประโยชน์ครับ

  • django – Deploy to Production #02

    ต่อจาก django #01

    Development Environment

    เริ่มจาก ไปที่ command prompt แล้วใช้คำสั่งต่อไปนี้ เพื่อ เก็บรายละเอียดของ Package และ Version ที่ใช้ในการพัฒนา ไว้ในไฟล์ myenv.txt และ เก็บไฟล์ myproject ทั้งหมดไว้ในไฟล์ myproject.tar.gz (บน Windows อาจจะใช้ 7zip สร้าง)

    pip freeze > myenv.txt
    tar -zxvf myproject.tar.gz myproject

    แล้ว Upload ไฟล์ myenv.txt และ myproject.tar.gz ขึ้นบน Projection Server

    Production Server

    ในที่นี้ ลองไปใช้ Google Compute Engine สร้าง Instance ขึ้นมา เป็น Ubuntu 18.04 และ ได้ Upload ไฟล์ myenv.txt และ myproject.tar.gz จาก Development ขึ้นไว้แล้ว (ใน /home/user01)

    ติดตั้ง Python3, PIP, Apache2, และ mod_wsgi

    sudo apt update
    sudo apt install python3 python3-pip apache2 libapache2-mod-wsgi-py3

    ติดตั้ง virtualenv

    pip3 install virtualenv

    สร้าง Virtual Environment ชื่อ production

    virtualenv production
    source production/bin/activate

    ติดตั้ง Package ต่าง ๆ ตามที่สร้างไว้จาก Development Environment (จากไฟล์ myenv.txt)

    pip install -r myenv.txt

    แตกไฟล์ myproject.tar.gz ออกมา

    tar -zxvf myproject.tar.gz

    สร้าง Apache Site Configuration ที่ /etc/apache2/sites-available/001-myproject.conf

    เนื้อหาตามนี้

    <VirtualHost *:80>
        Alias /static /home/user01/myproject/static
        <Directory /home/user01/myproject/static>
            Require all granted
        </Directory>
        <Directory /home/user01/myproject/myproject>
            <Files wsgi.py>
                Require all granted
            </Files>
        </Directory>
    
        WSGIDaemonProcess myproject python-home=/home/user01/production python-path=/home/user01/myproject
        WSGIProcessGroup myproject
        WSGIScriptAlias / /home/user01/myproject/myproject/wsgi.py
    </VirtualHost>

    จากนั้น สร้าง Symbolic Link จาก /home/user01/production/lib/python3.6/site-packages/django/contrib/admin/static มาที่ /home/user01/myproject/static

    ln -s /home/user01/production/lib/python3.6/site-packages/django/contrib/admin/static /home/user01/myproject/static

    ปรับ Permission ให้ www-data สามารถแก้ไข Database ได้ (เพราะในที่นี้ใช้ SQLite)

    sudo chown :www-data /home/user01/myproject/db.sqlite3
    sudo chown :www-data /home/user01/myproject

    สั่ง Apache ให้เอา Default Site ออก และ นำ myproject ขึ้นแทน และ ทำการ Reload

    sudo a2dissite 000-default
    sudo a2ensite 001-myproject
    sudo systemctl reload apache2

    ก็จะใช้ได้แล้ว

    มีหน้า Admin ให้ใช้
    admin เข้าใช้งานได้
    User ก็สามารถเข้าใช้งานได้

    หวังว่าจะเป็นประโยชน์ครับ