ผลต่างระหว่างรุ่นของ "01204223/flask"

จาก Theory Wiki
ไปยังการนำทาง ไปยังการค้นหา
แถว 353: แถว 353:
  
 
เราได้เห็นการแยกโค้ดส่วนของการจัดการข้อมูล (ใน python) และส่วนแสดงผลออกจากกัน โดยเราแยกส่วนแสดงผลให้เป็น template     ในส่วนนี้เราจะแยกส่วนการจัดการ style (ความสวยงาม) ออกจากเนื้อหาใน template อีกที
 
เราได้เห็นการแยกโค้ดส่วนของการจัดการข้อมูล (ใน python) และส่วนแสดงผลออกจากกัน โดยเราแยกส่วนแสดงผลให้เป็น template     ในส่วนนี้เราจะแยกส่วนการจัดการ style (ความสวยงาม) ออกจากเนื้อหาใน template อีกที
 +
 +
เราจะสร้างไฟล์เพื่อเก็บรายละเอียดของ style และส่งไฟล์นั้นให้กับ browser เพื่อระบุวิธีการจัดการความสวยงาม    ไฟล์เหล่านี้จะเป็นเนื้อหาส่วนที่ไม่มีการเปลี่ยนแปลง (เรียกรวม ๆ ว่าเป็น static content) ดังนั้นเรามักจะไม่ให้ web app เป็นผู้รับผิดชอบในการจัดการ
 +
 +
ใน Flask ไฟล์พวกนี้จะถูกเก็บแยกไว้ในไดเรกทอรี <tt>static</tt> และบน development server จะสามารถเรียกใช้ได้ทันที แต่ในการใช้งานจริงบน production เราจะต้องจัดการหาวิธีส่งไฟล์พวกนี้ให้กับ browser แบบอื่น (จะได้เรียนรายละเอียดต่อไป)
 +
 +
เราจะสร้างไฟล์ style.css ในไดเรกทอรี <tt>static/css<tt> ให้สร้าง ไดเรกทอรี <tt>static</tt> และสร้างไดเร็กทอรี <tt>css</tt> ไว้ด้านในอีกที ก่อนจะสร้างไฟล์ style.css
 +
 +
ในตอนแรก ให้เพิ่มรายละเอียดด้านล่างลงในไฟล์ style.css
 +
 +
<syntaxhighlight lang="css">
 +
h1 {
 +
  background: gray;
 +
  padding: 20px;
 +
}
 +
</syntaxhighlight>
 +
 +
จากนั้นให้ลอง refresh หน้าเว็บว่าเห็นการเปลี่ยนแปลงหรือไม่

รุ่นแก้ไขเมื่อ 23:05, 24 ธันวาคม 2568

เป็นส่วนหนึ่งของวิชา 01204223

ในขั้นแรกของการฝึกพัฒนาโปรแกรมประยุกต์บนเว็บ เราจะเริ่มโดยการใช้ Flask ที่เป็นเฟรมเวิร์คในการพัฒนาเว็บบน Python ที่มีความเรียบง่าย เพื่อให้เราให้ภาพรวมคร่าว ๆ ก่อน จากนั้นเราจะขยับขยายไปใช้ระบบอื่น ๆ ต่อไป

เอกสารนี้จะเป็นเอกสารประกอบการเรียนด้วยคลิปตามรายการด้านล่าง

ในคลิปดังกล่าวจะไม่ได้มีการใช้ git ประกอบการทำ แต่เราจะพยายามแสดงจุดที่ควรจะ commit งานเป็นระยะ ดังนั้นระหว่างการหัดใช้ Flask ให้ทดลองใช้ git ไปด้วยเลย

Web application architecture

สถาปัตยกรรมของเว็บแอพลิเคชันนั้น เป็นสถาปัตยกรรมแบบ client-server โดยมี browser เป็น client และเชื่อมต่อกับ web server

223-web-client-server-arch.jpeg

อย่างไรก็ตาม ภาพดังกล่าวเป็นภาพที่เราเห็นในระยะไกลมากเท่านั้น จริง ๆ แล้วด้านในของแต่ละชิ้นส่วนอาจจะมีส่วนประกอบที่ทำงานภายในมากมาย

ในช่วงแรกที่เราหัดพัฒนาเว็บ เราจะสนใจในส่วน server side ก่อน โดยจะพิจารณาว่า browser มีหน้าที่แสดงผลลัพธ์จาก response ที่เป็น html เพียงอย่างเดียว

ในช่วงถัดไป พอเราได้เรียนรู้เกี่ยวกับ javascript library สำหรับการจัดการฝั่ง client แล้ว เราจะค่อย ๆ ปรับให้ฝั่ง client (ที่ทำงานบน browser) ให้มีบทบาทมากขึ้นต่อไป

เริ่มต้น และการสร้าง repository

เราจะสร้าง directory สำหรับทำโครงงาน todo list แบบง่าย (ไม่มี database) ให้สร้างไดเร็กทอรีและย้ายไปในไดเร็กทอรีนั้น แล้วสั่ง

git init 

เพื่อเริ่มสร้าง git repository สำหรับการทำงาน

การติดตั้งและเริ่มเปิดหน้าเว็บ

Flask เป็น library บน python ซึ่งเราจะติดตั้งผ่านทางคำสั่ง pip อย่างไรก็ตาม เราจะพยายามแบ่งส่วนการติดตั้ง library ของ python ให้แยกกันไปตามแต่ละงาน โดยใช้ virtual environment (venv) ดังนั้นก่อนจะติดตั้ง Flask เราจะสร้าง virtual environment ก่อน โดยสั่ง

python -m venv venv

(พารามิเตอร์ venv ตัวแรกเป็นชื่อ module ใน python พารามิเตอร์ venv ตัวที่สองเป็นชื่อไดเร็กทอรีที่จะเก็บ environment ซึ่งเราเปลี่ยนเป็นชื่ออื่นได้)

ถ้าจะเริ่มใช้จะเรียก activate ใน venv/bin/activate (เรียกแตกต่างกันตาม os)

ถ้าเรียก activate แล้ว prompt ใน terminal จะเปลี่ยนไป (ลองสังเกต) จากนั้นเราจะสามารถสั่งให้ติดตั้ง Flask ได้ โดยสั่ง

pip install Flask

Flask จะมาพร้อม development web server เพื่อการพัฒนาและทดสอบเบื้องต้น เราจะเรียกโดยสั่ง

flask run

ซึ่งในตอนแรกถ้าเรายังไม่ได้เขียนอะไร มันจะแสดง error ออกมา

main.py

เราจะเริ่มโดยการสร้าง main.py ดังด้านล่าง

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello_world():
    return "<p>Hello, World!</p>"

เราต้องตั้งค่าใน environment ก่อนว่าโปรแกรมหลักเราชื่ออะไร

ถ้าใช้ windows สั่ง

set FLASK_APP=main.py

ถ้าใช้ unix (linux,mac) สั่ง

export FLASK_APP=main.py

และสั่งให้ Flask ทำงานใน terminal โดยสั่ง

flask run

จากนั้นเราจะสามารถเปิดเว็บได้ที่ลิงก์ http://localhost:5000 (โดยปกติ Flask จะเปิดเว็บเซิร์ฟเวอร์ไว้ที่พอร์ต 5000)

git commit

ถ้าเริ่มแสดงหน้าเว็บได้ ก็เป็นจุดที่ดีที่เราควรจะ commit เนื่องจากเราใช้ terminal ใน vscode ในการรัน flask run (ซึ่งเราจะรันทิ้งไว้เลย) ในการสั่ง git ควรจะต้องเปิดอีก terminal หนึ่ง

เราจะเพิ่ม main.py ลงไปใน repo โดยสั่ง

git add main.py

ignore venv. ปกติเราจะไม่เก็บ virtual environment ลงใน version control ดังนั้นให้สร้างไฟล์ .gitignore และเพิ่มบรรทัด

venv/

ลงไปในนั้นด้วย เพื่อบอก git ให้ไม่ต้องสนใจไดเร็กทอรีดังกล่าว จากนั้นให้เพิ่ม .gitignore ลงใน repo โดยสั่ง

git add .gitignore

อาจจะสั่ง git status ดูอีกทีว่าเราเพิ่มไฟล์ครบแล้ว เมื่อเรียบร้อยแล้วให้สั่ง git commit

git commit -m "first hello page"

เราจะใส่ commit message สั้น ๆ ให้พอทราบว่าในแต่ละขั้นเราทำอะไร

ในการทำงานต่อไป จะมีกล่องสีฟ้าดังด้านล่างเพื่อบอกเตือนจุดที่ควรจะ commit

🄶 ได้เวลา commit แล้ว

ให้ add ไฟล์ที่เกี่ยวข้องและ commit งานที่ทำขณะนั้นลงใน git เลือก commit message ให้เหมาะสมด้วย

route

การเรียกใช้งานเว็บแอพลิเคชันทำผ่านทางการส่ง request มาที่ web server โดยสิ่งที่ระบุเป้าหมายของการทำงานคือ url (ดูตรงหัวของ browser) ตัวอย่างของ url เช่น

ในการเขียน Flask เราจะระบุให้ฟังก์ชันใด เป็นฟังก์ชันที่รับผิดชอบเป้าหมาย (target หรือ end point) ใด ๆ ผ่านทาง annotation @app.route ดังตัวอย่างด้านบนที่เราระบุ

@app.route("/")

ไว้ก่อนฟังก์ชัน ซึ่งเป็นการแจ้งกับ web server ของ Flask ว่าถ้ามี request มาที่ / ให้เรียกใช้ฟังก์ชันดังกล่าว

เราจะเพิ่มอีกฟังก์ชันเพื่อรับ request ที่ /hello/ ดังด้านล่าง

@app.route('/hello/')
def hello_world1():
    return 'Hi my name is someone'

ให้ทดลองเรียกใช้ และเรียกไปยัง url http://localhost:5000/hello/

🄶 ถ้าเปิดได้ ให้ commit ไว้ก่อนเลย

เรามักจะตั้งชื่อ function ให้สื่อความหมาย เดี๋ยวให้แก้ main.py โดยลบ hello_world1 ที่เราทดสอบ route ออกและแก้ชื่อฟังก์ชัน hello_world เป็น index เพื่อเตรียมหัดใช้ template ในส่วนต่อไป

🄶 เมื่อแก้เสร็จแล้ว ให้ทดลองกดหน้าเว็บสักนิด ถ้าทุกอย่างโอเค อย่าลืม commit

โครงสร้างปัจจุบัน

สำหรับโค้ดที่เรามีอยู่ในปัจจุบันจะพิจารณาโครงสร้างได้ดังรูปด้านล่าง

223-web-flask-raw-html.jpeg

Browser จะส่ง request มา โดยระบุ end point ผ่านทาง url     ระบบ routing ภายใน Flask จะส่ง request นั้นต่อมาให้กับฟังก์ชันที่เราเขียนไว้ โดยใช้ url mapping ที่เราระบุผ่านทาง annotation @app.route     จากนั้นฟังก์ชันของเราจะคืน html กลับไป (เป็น string)

การแสดง template

ในการเขียนโปรแกรมที่ดีนั้น เราจะแยกส่วนของโค้ดออกตามความรับผิดชอบและภาระหน้าที่ สิ่งหนึ่งที่เราพยายามแยกจากกันคือส่วนประมวลผลกับส่วนแสดงผล (จัดการหน้าจอต่างๆ) โค้ดที่เขียนมาในตอนต้นมีโค้ด html (สำหรับนำเสนอ) ปนอยู่กับโค้ด python (สำหรับประมวลผล) ซึ่งเป็นเครื่องหมายที่ดีว่าเราน่าจะต้องแยกของสองอย่างนี้ออกจากกัน

เราจะแยกส่วนแสดงผลออกมาไว้ใน template สาเหตุที่เรียกว่า template เพราะว่าในส่วนนี้เรายังสามารถเขียนโค้ดบางอย่างเพื่อใช้ในการแสดงผลได้ เช่นการทำซ้ำ แต่ลักษณะโครงสร้างของไฟล์ template นั้นจะเหมือนเป็นไฟล์ html ที่มีการเจาะช่องเป็นโครงเอาไว้เพื่อให้เราใส่ข้อมูลไปแสดงผลได้

เราจะ import function render_template

from flask import render_template

จากนั้นปรับฟังก์ชัน index ให้แสดงผลจากการ render template index.html

@app.route('/')
def index():
    return render_template('index.html')

ให้ทดลองเรียกเว็บ จะพบ error แจ้งเตือนว่าไม่พบไฟล์ index.html เนื่องจากเรายังไม่ได้สร้าง template เลย

ให้สร้างไดเร็กทอรี templates และเพิ่มไฟล์ templates/index.html ดังด้านล่าง

Hello! This is my first template.

ทดลองดูว่าข้อความดังกล่าวแสดงหรือไม่ ถ้าแสดงผลเรียบร้อย ให้แก้ไฟล์ให้อยู่ในรูปแบบ html ที่ดีขึ้นดังด้านล่าง

<html>
  <body>
    <h1>Hello!!</h1>
    Hi
  </body>
</html>
🄶 เมื่อทดสอบว่าแสดงผลเรียบร้อย อย่าลืม commit อย่าลืม add ไฟล์ template ที่เราสร้างลงใน repo ก่อนด้วย

ในคลิปแรกอาจจะมีการทดลองแก้โค้ดต่าง ๆ ให้ดูผล อย่าลืมลองทำตามและพิจารณาเหตุผลว่าทำไมได้รับผลลัพธ์ดังกล่าว

โครงสร้างปัจจุบัน

เราได้แยกการ render html ออกมาเป็นส่วน template แล้ว ทำให้โครงสร้างของระบบเราปรับเป็นรูปด้านล่าง

223-web-flask-template.jpeg

การใช้งาน template

main.py

from flask import Flask
from flask import render_template
from datetime import datetime

app = Flask(__name__)

news_items = {
    1: {'id': 1, 
        'title': 'COVID-19 update', 
        'body': 'This is a news on COVID-19'},
    2: {'id': 2, 
        'title': 'Facemasks found', 
        'body': 'Recent news on facemask production'},
    3: {'id': 3,
        'title':'Python 4', 
        'body':'Python 4 will be out soon.... this is FAKE'},
}

@app.route('/')
def index():
    name = 'Somchai'
    time = datetime.now()
    return render_template('index.html', 
                           name=name, 
                           time=time,
                           news_items=news_items.values())

@app.route('/news/<id>/')
def show_news_item(id):
    news_item = news_items[int(id)]
    return render_template('news_item.html',
                           id=news_item['id'],
                           title=news_item['title'],
                           body=news_item['body'])

templates/index.html

<html>
  <body>
    <h1>Hello!! {{ name }}</h1>
    Hi. This is {{ time }}.  This is recent news.
    <ul>
      {% for item in news_items %}
        <li>
          <a href="{{ url_for('show_news_item', id=item.id) }}">{{ item.title }}</a>
        </li>
      {% endfor %}
    </ul> 
  </body>
</html>

templates/news_item.html

<html>
  <body>
    <h1>{{ title }}</h1>
    <p>{{ body }}</p>

    <a href="{{ url_for('index') }}">Home</a>
  </body>
</html>

การสร้าง form

main.py

from flask import Flask
from flask import render_template, redirect, url_for
from flask import request

from datetime import datetime

app = Flask(__name__)

news_items = {
    1: {'id': 1, 
        'title': 'COVID-19 update', 
        'body': 'This is a news on COVID-19'},
    2: {'id': 2, 
        'title': 'Facemasks found', 
        'body': 'Recent news on facemask production'},
    3: {'id': 3,
        'title':'Python 4', 
        'body':'Python 4 will be out soon.... this is FAKE'},
}

@app.route('/')
def index():
    name = 'Somchai'
    time = datetime.now()
    return render_template('index.html', 
                           name=name, 
                           time=time,
                           news_items=news_items.values())

@app.route('/news/<id>/')
def show_news_item(id):
    news_item = news_items[int(id)]
    return render_template('news_item.html',
                           id=news_item['id'],
                           title=news_item['title'],
                           body=news_item['body'])

def new_news_item(title, body):
    new_id = max(news_items.keys()) + 1
    return {
        'id': new_id,
        'title': title,
        'body': body
    }

@app.route('/news/create/', methods=['POST'])
def create_news_item():
    item = new_news_item(request.form['title'],
                         request.form['body'])
    news_items[item['id']] = item
    return redirect(url_for('index'))

templates/index.html

<html>
  <body>
    <h1>Hello!! {{ name }}</h1>
    Hi. This is {{ time }}.  This is recent news.
    <ul>
      {% for item in news_items %}
        <li>
          <a href="{{ url_for('show_news_item', id=item.id) }}">{{ item.title }}</a>
        </li>
      {% endfor %}
    </ul> 

    <form action="{{ url_for('create_news_item') }}" method="POST">
      Title: <input name="title"/><br />
      Body: <textarea name="body"></textarea><br />
      <input type="submit" value="Save"/>
    </form>
  </body>
</html>

Stylesheet (CSS)

เนื้อหาส่วนนี้ไม่มีใน clip ให้ทดลองทำตามด้านล่างได้เลย

เราได้เห็นการแยกโค้ดส่วนของการจัดการข้อมูล (ใน python) และส่วนแสดงผลออกจากกัน โดยเราแยกส่วนแสดงผลให้เป็น template     ในส่วนนี้เราจะแยกส่วนการจัดการ style (ความสวยงาม) ออกจากเนื้อหาใน template อีกที

เราจะสร้างไฟล์เพื่อเก็บรายละเอียดของ style และส่งไฟล์นั้นให้กับ browser เพื่อระบุวิธีการจัดการความสวยงาม    ไฟล์เหล่านี้จะเป็นเนื้อหาส่วนที่ไม่มีการเปลี่ยนแปลง (เรียกรวม ๆ ว่าเป็น static content) ดังนั้นเรามักจะไม่ให้ web app เป็นผู้รับผิดชอบในการจัดการ

ใน Flask ไฟล์พวกนี้จะถูกเก็บแยกไว้ในไดเรกทอรี static และบน development server จะสามารถเรียกใช้ได้ทันที แต่ในการใช้งานจริงบน production เราจะต้องจัดการหาวิธีส่งไฟล์พวกนี้ให้กับ browser แบบอื่น (จะได้เรียนรายละเอียดต่อไป)

เราจะสร้างไฟล์ style.css ในไดเรกทอรี static/css ให้สร้าง ไดเรกทอรี static และสร้างไดเร็กทอรี css ไว้ด้านในอีกที ก่อนจะสร้างไฟล์ style.css

ในตอนแรก ให้เพิ่มรายละเอียดด้านล่างลงในไฟล์ style.css

h1 {
  background: gray;
  padding: 20px;
}

จากนั้นให้ลอง refresh หน้าเว็บว่าเห็นการเปลี่ยนแปลงหรือไม่