Week 9 Flask
Chào mừng!
Trong những tuần trước, bạn đã học được nhiều ngôn ngữ lập trình, kỹ thuật và chiến lược khác nhau.
Thực tế, lớp học này không hẳn là một lớp học về C hay lớp học về Python mà thiên về một lớp học về lập trình nhiều hơn, để bạn có thể tiếp tục bắt kịp các xu hướng trong tương lai.
Trong vài tuần qua, bạn đã học được cách để học về lập trình.
Hôm nay, chúng ta sẽ chuyển từ HTML và CSS sang việc kết hợp HTML, CSS, SQL, Python và JavaScript để bạn có thể tạo ra các ứng dụng web của riêng mình.
Bạn có thể cân nhắc sử dụng những kỹ năng học được trong tuần này để thực hiện dự án cuối khóa (final project) của mình.
http-server
Cho đến thời điểm này, tất cả các mã HTML bạn thấy đều được viết sẵn và ở dạng tĩnh (static).
Trước đây, khi bạn truy cập một trang web, trình duyệt sẽ tải xuống một trang HTML và bạn có thể xem nó. Đây được coi là các trang tĩnh, theo nghĩa là những gì được lập trình trong HTML chính xác là những gì người dùng nhìn thấy và tải xuống ở phía máy khách (client-side) về trình duyệt internet của họ.
Trang web động (dynamic pages) đề cập đến khả năng của Python và các ngôn ngữ tương tự trong việc tạo ra HTML ngay lập tức (on-the-fly). Theo đó, bạn có thể có các trang web được tạo ra ở phía máy chủ (server-side) bằng mã lệnh dựa trên thông tin nhập vào hoặc hành vi của người dùng.
Bạn đã từng sử dụng
http-servertrong quá khứ để phục vụ các trang web của mình. Hôm nay, chúng ta sẽ sử dụng một máy chủ mới có thể phân tích địa chỉ web và thực hiện các hành động dựa trên URL được cung cấp.
Hơn nữa, tuần trước, bạn đã thấy các URL như sau:
https://www.example.com/folder/file.html
Lưu ý rằng file.html là một tệp HTML nằm trong một thư mục có tên là folder tại example.com.
Flask
- Tuần này, chúng ta giới thiệu khả năng tương tác với các tuyến đường (routes) chẳng hạn như
https://www.example.com/route?key=value, nơi các chức năng cụ thể có thể được tạo ra trên máy chủ thông qua các khóa (keys) và giá trị (values) được cung cấp trong URL.
Flask là một thư viện của bên thứ ba cho phép bạn lưu trữ các ứng dụng web bằng khung làm việc (framework) Flask, hoặc một micro-framework, bên trong Python.
Bạn có thể chạy Flask bằng cách thực thi lệnh
flask runtrong cửa sổ terminal tại cs50.dev.Để làm được như vậy, bạn sẽ cần một tệp có tên là
app.pyvà một tệp khác tên làrequirements.txt.app.pychứa mã lệnh hướng dẫn Flask cách chạy ứng dụng web của bạn.requirements.txtbao gồm danh sách các thư viện cần thiết để ứng dụng Flask của bạn có thể hoạt động.
Đây là một ví dụ về tệp requirements.txt:
Flask
Lưu ý rằng chỉ có Flask xuất hiện trong tệp này. Đó là vì Flask là yêu cầu bắt buộc để chạy ứng dụng Flask.
Đây là một ứng dụng Flask rất đơn giản trong tệp app.py:
# Says hello to world by returning a string of text
from flask import Flask, render_template, request
app = Flask(__name__)
@app.route("/")
def index():
return "hello, world"
Lưu ý rằng route / chỉ đơn giản trả về chuỗi văn bản hello, world.
Chúng ta cũng có thể tạo mã lệnh để triển khai HTML:
# Says hello to world by returning a string of HTML
from flask import Flask, render_template, request
app = Flask(__name__)
@app.route("/")
def index():
return '"en">hellohello, world'
Lưu ý rằng thay vì trả về văn bản đơn thuần, đoạn mã này cung cấp HTML.
Để cải thiện ứng dụng, chúng ta cũng có thể cung cấp HTML dựa trên các bản mẫu (templates) bằng cách tạo một thư mục có tên là templates và tạo một tệp có tên là index.html với mã lệnh sau bên trong thư mục đó:
lang="en">
name="viewport" content="initial-scale=1, width=device-width">
hello
hello, {{ name }}
Lưu ý cặp dấu ngoặc kép {{ name }} đóng vai trò là một trình giữ chỗ (placeholder) cho một giá trị nào đó sẽ được máy chủ Flask cung cấp sau này.
Sau đó, trong cùng thư mục chứa thư mục templates, hãy tạo một tệp tên là app.py và thêm đoạn mã sau:
# Uses request.args.get
from flask import Flask, render_template, request
app = Flask(__name__)
@app.route("/")
def index():
name = request.args.get("name", "world")
return render_template("index.html", name=name)
Lưu ý rằng mã này định nghĩa app là ứng dụng Flask. Sau đó, nó định nghĩa route / của app sẽ trả về nội dung của index.html với đối số là name. Theo mặc định, hàm request.args.get sẽ tìm kiếm name do người dùng cung cấp. Nếu không có tên nào được cung cấp, nó sẽ mặc định là world. @app.route còn được gọi là một decorator.
Bạn có thể chạy ứng dụng web này bằng cách nhập flask run trong cửa sổ terminal. Nếu Flask không chạy, hãy đảm bảo rằng cú pháp của bạn chính xác trong từng tệp ở trên. Hơn nữa, nếu Flask vẫn không chạy, hãy kiểm tra xem các tệp của bạn đã được tổ chức như sau chưa:
/templates
index.html
app.py
requirements.txt
- Khi ứng dụng đã chạy, bạn sẽ được nhắc nhấp vào một liên kết. Sau khi điều hướng đến trang web đó, hãy thử thêm
?name=[Tên của bạn]vào cuối URL gốc trong thanh địa chỉ của trình duyệt.
Biểu mẫu (Forms)
Cải thiện chương trình của mình, chúng ta biết rằng hầu hết người dùng sẽ không nhập các đối số vào thanh địa chỉ. Thay vào đó, các lập trình viên dựa vào việc người dùng điền vào các biểu mẫu trên trang web. Theo đó, chúng ta có thể sửa đổi index.html như sau:
lang="en">
name="viewport" content="initial-scale=1, width=device-width">
hello
action="/greet" method="get">
autocomplete="off" autofocus name="name" placeholder="Name" type="text">
type="submit">Greet
Lưu ý rằng một biểu mẫu hiện đã được tạo để nhận tên người dùng và sau đó chuyển nó đến một route có tên là /greet. autocomplete được tắt đi. Ngoài ra, một placeholder với văn bản name được bao gồm. Hơn nữa, hãy chú ý cách thẻ meta được sử dụng để làm cho trang web có khả năng hiển thị tốt trên thiết bị di động (mobile-responsive).
Tiếp theo, chúng ta có thể thay đổi app.py như sau:
# Adds a form, second route
from flask import Flask, render_template, request
app = Flask(__name__)
@app.route("/")
def index():
return render_template("index.html")
@app.route("/greet")
def greet():
return render_template("greet.html", name=request.args.get("name", "world"))
Lưu ý rằng đường dẫn mặc định sẽ hiển thị một biểu mẫu để người dùng nhập tên của họ. Route /greet sẽ chuyển name đó đến trang web tương ứng.
Để hoàn tất việc triển khai này, bạn sẽ cần một template khác cho greet.html trong thư mục templates như sau:
lang="en">
name="viewport" content="initial-scale=1, width=device-width">
hello
hello, {{ name }}
Lưu ý rằng route này hiện sẽ hiển thị lời chào gửi tới người dùng, kèm theo tên của họ.
Templates
- Cả hai trang web của chúng ta,
index.htmlvàgreet.html, đều có nhiều dữ liệu giống nhau. Sẽ thật tốt nếu chúng ta có thể để phần nội dung (body) là duy nhất nhưng sao chép cùng một bố cục (layout) từ trang này sang trang khác?
Đầu tiên, hãy tạo một template mới tên là layout.html và viết mã như sau:
lang="en">
name="viewport" content="initial-scale=1, width=device-width">
hello
{% block body %}{% endblock %}
Lưu ý rằng cặp thẻ {% block body %}{% endblock %} cho phép chèn mã khác từ các tệp HTML khác vào.
Sau đó, sửa đổi index.html của bạn như sau:
{% extends "layout.html" %}
{% block body %}
action="/greet" method="get">
autocomplete="off" autofocus name="name" placeholder="Name" type="text">
type="submit">Greet
{% endblock %}
Lưu ý rằng dòng {% extends "layout.html" %} cho máy chủ biết nơi lấy bố cục cho trang này. Sau đó, {% block body %}{% endblock %} cho biết mã nào sẽ được chèn vào layout.html.
Cuối cùng, thay đổi greet.html như sau:
{% extends "layout.html" %}
{% block body %}
hello, {{ name }}
{% endblock %}
Hãy để ý xem đoạn mã này ngắn gọn và súc tích hơn như thế nào.
Phương thức yêu cầu (Request Methods)
- Bạn có thể hình dung ra các tình huống mà việc sử dụng
getlà không an toàn, vì tên đăng nhập và mật khẩu sẽ hiển thị ngay trên URL.
Chúng ta có thể sử dụng phương thức post để giúp giải quyết vấn đề này bằng cách sửa đổi app.py như sau:
# Switches to POST
from flask import Flask, render_template, request
app = Flask(__name__)
@app.route("/")
def index():
return render_template("index.html")
@app.route("/greet", methods=["POST"])
def greet():
return render_template("greet.html", name=request.form.get("name", "world"))
Lưu ý rằng POST được thêm vào route /greet, và chúng ta sử dụng request.form.get thay vì request.args.get.
- Điều này yêu cầu máy chủ tìm kiếm sâu hơn vào bên trong phong bì ảo và không tiết lộ các mục trong
posttrên URL.
Tuy nhiên, đoạn mã này còn có thể được nâng cao hơn nữa bằng cách sử dụng một route duy nhất cho cả get và post. Để thực hiện việc này, hãy sửa đổi app.py như sau:
# Uses a single route
from flask import Flask, render_template, request
app = Flask(__name__)
@app.route("/", methods=["GET", "POST"])
def index():
if request.method == "POST":
return render_template("greet.html", name=request.form.get("name", "world"))
return render_template("index.html")
Lưu ý rằng cả get và post đều được thực hiện trong một lộ trình duy nhất. Tuy nhiên, request.method được sử dụng để điều hướng chính xác dựa trên loại yêu cầu (routing) mà người dùng yêu cầu.
Theo đó, bạn có thể sửa đổi index.html của mình như sau:
{% extends "layout.html" %}
{% block body %}
action="/" method="post">
autocomplete="off" autofocus name="name" placeholder="Name" type="text">
type="submit">Greet
{% endblock %}
Lưu ý rằng thuộc tính action của biểu mẫu đã được thay đổi.
Tuy nhiên, vẫn còn một lỗi trong đoạn mã này. Với cách triển khai mới, khi ai đó không nhập tên vào biểu mẫu, chuỗi Hello, sẽ hiển thị mà không có tên. Chúng ta có thể cải thiện mã của mình bằng cách chỉnh sửa app.py như sau:
# Moves default value to template
from flask import Flask, render_template, request
app = Flask(__name__)
@app.route("/", methods=["GET", "POST"])
def index():
if request.method == "POST":
return render_template("greet.html", name=request.form.get("name"))
return render_template("index.html")
Lưu ý rằng phần name=request.form.get("name")) đã được thay đổi.
Cuối cùng, thay đổi greet.html như sau:
{% extends "layout.html" %}
{% block body %}
hello,
{% if name -%}
{{ name }}
{%- else -%}
world
{%- endif %}
{% endblock %}
Hãy chú ý cách hello, {{ name }} được thay đổi để cho phép đầu ra mặc định khi không xác định được tên.
- Vì chúng ta đã thay đổi nhiều tệp, bạn có thể muốn so sánh mã cuối cùng của mình với mã cuối cùng của chúng tôi.
Frosh IMs
Frosh IMs (hay froshims) là một ứng dụng web cho phép sinh viên đăng ký tham gia các môn thể thao nội bộ.
Đóng tất cả các cửa sổ liên quan đến
hellocủa bạn và tạo một thư mục bằng cách nhậpmkdir froshimstrong cửa sổ terminal. Sau đó, nhậpcd froshimsđể chuyển đến thư mục này. Bên trong đó, hãy tạo một thư mục tên là templates bằng cách nhậpmkdir templates.
Tiếp theo, trong thư mục froshims, nhập code requirements.txt và soạn mã như sau:
Flask
Như trước đây, Flask là yêu cầu bắt buộc để chạy ứng dụng Flask.
Cuối cùng, nhập code app.py và viết mã như sau:
# Implements a registration form using a select menu, validating sport server-side
from flask import Flask, render_template, request
app = Flask(__name__)
SPORTS = [
"Basketball",
"Soccer",
"Ultimate Frisbee"
]
@app.route("/")
def index():
return render_template("index.html", sports=SPORTS)
@app.route("/register", methods=["POST"])
def register():
# Validate submission
if not request.form.get("name") or request.form.get("sport") not in SPORTS:
return render_template("failure.html")
# Confirm registration
return render_template("success.html")
Lưu ý rằng một tùy chọn failure được cung cấp, để thông báo lỗi sẽ được hiển thị cho người dùng nếu trường name hoặc sport không được điền đúng cách.
Tiếp theo, hãy tạo một tệp trong thư mục templates tên là index.html bằng cách nhập code templates/index.html và viết mã như sau:
{% extends "layout.html" %}
{% block body %}
Register
action="/register" method="post">
autocomplete="off" autofocus name="name" placeholder="Name" type="text">
name="sport">
disabled selected value="">Sport
{% for sport in sports %}
value="{{ sport }}">{{ sport }}
{% endfor %}
type="submit">Register
{% endblock %}
Tiếp theo, tạo một tệp tên là layout.html bằng cách nhập code templates/layout.html và viết mã như sau:
lang="en">
name="viewport" content="initial-scale=1, width=device-width">
froshims
{% block body %}{% endblock %}
Thứ tư, tạo một tệp trong templates tên là success.html như sau:
{% extends "layout.html" %}
{% block body %}
You are registered!
{% endblock %}
Cuối cùng, tạo một tệp trong templates tên là failure.html như sau:
{% extends "layout.html" %}
{% block body %}
You are not registered!
{% endblock %}
- Thực thi
flask runvà kiểm tra ứng dụng ở giai đoạn này.
Bạn có thể hình dung cách chúng ta muốn xem các tùy chọn đăng ký khác nhau bằng các nút chọn (radio buttons). Chúng ta có thể cải thiện index.html như sau:
{% extends "layout.html" %}
{% block body %}
Register
action="/register" method="post">
autocomplete="off" autofocus name="name" placeholder="Name" type="text">
{% for sport in sports %}
name="sport" type="radio" value="{{ sport }}"> {{ sport }}
{% endfor %}
type="submit">Register
{% endblock %}
Lưu ý cách type đã được thay đổi thành radio.
- Một lần nữa, khi thực thi
flask run, bạn có thể thấy giao diện đã thay đổi như thế nào.
Bạn có thể hình dung cách chúng ta muốn chấp nhận đăng ký của nhiều người khác nhau. Chúng ta có thể cải thiện app.py như sau:
# Implements a registration form, storing registrants in a dictionary, with error messages
from flask import Flask, redirect, render_template, request
app = Flask(__name__)
REGISTRANTS = {}
SPORTS = [
"Basketball",
"Soccer",
"Ultimate Frisbee"
]
@app.route("/")
def index():
return render_template("index.html", sports=SPORTS)
@app.route("/register", methods=["POST"])
def register():
# Validate name
name = request.form.get("name")
if not name:
return render_template("error.html", message="Missing name")
# Validate sport
sport = request.form.get("sport")
if not sport:
return render_template("error.html", message="Missing sport")
if sport not in SPORTS:
return render_template("error.html", message="Invalid sport")
# Remember registrant
REGISTRANTS[name] = sport
# Confirm registration
return redirect("/registrants")
@app.route("/registrants")
def registrants():
return render_template("registrants.html", registrants=REGISTRANTS)
Lưu ý rằng một từ điển (dictionary) tên là REGISTRANTS được sử dụng để ghi lại môn thể thao được chọn bởi REGISTRANTS[name]. Ngoài ra, hãy chú ý rằng registrants=REGISTRANTS chuyển từ điển này sang template.
Hơn nữa, chúng ta có thể triển khai error.html:
{% extends "layout.html" %}
{% block body %}
Error
{{ message }}
alt="Grumpy Cat" src="/static/cat.jpg">
{% endblock %}
Tiếp theo, hãy tạo một template mới tên là registrants.html như sau:
{% extends "layout.html" %}
{% block body %}
Registrants
Name
Sport
{% for name in registrants %}
{{ name }}
{{ registrants[name] }}
{% endfor %}
{% endblock %}
Lưu ý rằng {% for name in registrants %}...{% endfor %} sẽ lặp qua từng người đăng ký. Rất mạnh mẽ khi có thể lặp trên một trang web động!
Cuối cùng, hãy tạo một thư mục tên là
staticcùng cấp với tệpapp.py. Tại đó, tải lên tệp hình ảnh một con mèo.Thực thi
flask runvà trải nghiệm ứng dụng.Bây giờ bạn đã có một ứng dụng web! Tuy nhiên, vẫn còn một số lỗ hổng bảo mật! Vì mọi thứ đều ở phía máy khách (client-side), một kẻ tấn công có thể thay đổi HTML và hack trang web. Hơn nữa, dữ liệu này sẽ không còn tồn tại nếu máy chủ bị tắt. Liệu có cách nào để chúng ta lưu trữ dữ liệu bền vững ngay cả khi máy chủ khởi động lại không?
Flask và SQL
Giống như cách chúng ta đã thấy Python có thể giao tiếp với cơ sở dữ liệu SQL, chúng ta có thể kết hợp sức mạnh của Flask, Python và SQL để tạo ra một ứng dụng web nơi dữ liệu sẽ được lưu trữ lâu dài!
Để thực hiện điều này, bạn cần thực hiện một số bước.
Đầu tiên, tải xuống cơ sở dữ liệu SQL sau vào thư mục
froshimscủa bạn.Thực thi trong terminal lệnh
sqlite3 froshims.dbvà nhập.schemađể xem nội dung của tệp cơ sở dữ liệu. Nhập tiếpSELECT * FROM registrants;để tìm hiểu nội dung bên trong. Bạn sẽ thấy rằng hiện tại không có lượt đăng ký nào trong tệp.
Tiếp theo, sửa đổi requirements.txt như sau:
cs50
Flask
Sửa đổi index.html như sau:
{% extends "layout.html" %}
{% block body %}
Register
action="/register" method="post">
autocomplete="off" autofocus name="name" placeholder="Name" type="text">
{% for sport in sports %}
name="sport" type="checkbox" value="{{ sport }}"> {{ sport }}
{% endfor %}
type="submit">Register
{% endblock %}
Sửa đổi layout.html như sau:
lang="en">
name="viewport" content="initial-scale=1, width=device-width">
froshims
{% block body %}{% endblock %}
Đảm bảo error.html hiển thị như sau:
{% extends "layout.html" %}
{% block body %}
Error
{{ message }}
alt="Grumpy Cat" src="/static/cat.jpg">
{% endblock %}
Sửa đổi registrants.html để hiển thị như sau:
{% extends "layout.html" %}
{% block body %}
Registrants
Name
Sport
{% for registrant in registrants %}
{{ registrant.name }}
{{ registrant.sport }}
action="/deregister" method="post">
name="id" type="hidden" value="{{ registrant.id }}">
type="submit">Deregister
{% endfor %}
{% endblock %}
Lưu ý rằng một giá trị ẩn registrant.id được bao gồm để có thể sử dụng id này sau này trong app.py.
Cuối cùng, sửa đổi app.py như sau:
# Implements a registration form, storing registrants in a SQLite database, with support for deregistration
from cs50 import SQL
from flask import Flask, redirect, render_template, request
app = Flask(__name__)
db = SQL("sqlite:///froshims.db")
SPORTS = [
"Basketball",
"Soccer",
"Ultimate Frisbee"
]
@app.route("/")
def index():
return render_template("index.html", sports=SPORTS)
@app.route("/deregister", methods=["POST"])
def deregister():
# Forget registrant
id = request.form.get("id")
if id:
db.execute("DELETE FROM registrants WHERE id = ?", id)
return redirect("/registrants")
@app.route("/register", methods=["POST"])
def register():
# Validate name
name = request.form.get("name")
if not name:
return render_template("error.html", message="Missing name")
# Validate sports
sports = request.form.getlist("sport")
if not sports:
return render_template("error.html", message="Missing sport")
for sport in sports:
if sport not in SPORTS:
return render_template("error.html", message="Invalid sport")
# Remember registrant
for sport in sports:
db.execute("INSERT INTO registrants (name, sport) VALUES(?, ?)", name, sport)
# Confirm registration
return redirect("/registrants")
@app.route("/registrants")
def registrants():
registrants = db.execute("SELECT * FROM registrants")
return render_template("registrants.html", registrants=registrants)
Lưu ý rằng thư viện cs50 được sử dụng. Một route được bao gồm cho register cho phương thức post. Route này sẽ lấy tên và môn thể thao từ biểu mẫu đăng ký và thực thi một truy vấn SQL để thêm name và sport vào bảng registrants. Route deregister thực hiện một truy vấn SQL để lấy id của người dùng và sử dụng thông tin đó để hủy đăng ký cá nhân này.
Bạn có thể thực thi
flask runvà kiểm tra kết quả.Nếu bạn muốn tải xuống bản triển khai
froshimscủa chúng tôi, bạn có thể thực hiện tại đây.Bạn có thể đọc thêm về Flask trong tài liệu hướng dẫn của Flask.
Cookies và Session
app.py được coi là một bộ điều khiển (controller). Phần nhìn (view) là những gì người dùng nhìn thấy. Một mô hình (model) là cách dữ liệu được lưu trữ và thao tác. Kết hợp lại, điều này được gọi là MVC (model, view, controller).
Mặc dù cách triển khai
froshimstrước đó hữu ích từ góc độ quản trị, nơi quản trị viên có thể thêm và xóa các cá nhân khỏi cơ sở dữ liệu, nhưng có thể thấy mã này không an toàn để triển khai trên một máy chủ công cộng.Chẳng hạn, những kẻ xấu có thể thay mặt người dùng khác đưa ra quyết định bằng cách nhấn nút hủy đăng ký – thực tế là xóa câu trả lời đã ghi lại của họ khỏi máy chủ.
Các dịch vụ web như Google sử dụng thông tin đăng nhập để đảm bảo người dùng chỉ có quyền truy cập vào đúng dữ liệu của mình.
Chúng ta thực sự có thể tự triển khai điều này bằng cách sử dụng cookies. Cookies là các tệp nhỏ được lưu trữ trên máy tính của bạn để máy tính có thể giao tiếp với máy chủ và nói một cách hiệu quả rằng, “Tôi là một người dùng được ủy quyền đã đăng nhập.” Sự ủy quyền thông qua cookie này được gọi là một phiên làm việc (session).
Cookies có thể được lưu trữ như sau:
GET / HTTP/2
Host: accounts.google.com
Cookie: session=value
Tại đây, một id session được lưu trữ với một value cụ thể đại diện cho phiên đó.
- Ở dạng đơn giản nhất, chúng ta có thể triển khai điều này bằng cách tạo một thư mục tên là
loginvà sau đó thêm các tệp sau.
Đầu tiên, tạo một tệp tên là requirements.txt với nội dung như sau:
Flask
Flask-Session
Lưu ý rằng ngoài Flask, chúng ta cũng bao gồm Flask-Session, thư viện cần thiết để hỗ trợ các phiên đăng nhập.
Thứ hai, trong thư mục templates, tạo một tệp tên là layout.html hiển thị như sau:
lang="en">
name="viewport" content="initial-scale=1, width=device-width">
login
{% block body %}{% endblock %}
Lưu ý rằng phần này cung cấp một bố cục rất đơn giản với tiêu đề và phần thân.
Thứ ba, tạo một tệp trong thư mục templates tên là index.html hiển thị như sau:
{% extends "layout.html" %}
{% block body %}
{% if name -%}
You are logged in as {{ name }}. href="/logout">Log out.
{%- else -%}
You are not logged in. href="/login">Log in.
{%- endif %}
{% endblock %}
Lưu ý rằng tệp này kiểm tra xem session["name"] có tồn tại hay không (sẽ được giải thích kỹ hơn trong app.py bên dưới). Nếu có, nó sẽ hiển thị thông báo chào mừng. Nếu không, nó sẽ đề nghị bạn truy cập trang để đăng nhập.
Thứ tư, tạo một tệp tên là login.html và thêm đoạn mã sau:
{% extends "layout.html" %}
{% block body %}
action="/login" method="post">
autocomplete="off" autofocus name="name" placeholder="Name" type="text">
type="submit">Log In
{% endblock %}
Lưu ý đây là bố cục của một trang đăng nhập cơ bản.
Cuối cùng, tạo một tệp tên là app.py và viết mã như sau:
from flask import Flask, redirect, render_template, request, session
from flask_session import Session
# Configure app
app = Flask(__name__)
# Configure session
app.config["SESSION_PERMANENT"] = False
app.config["SESSION_TYPE"] = "filesystem"
Session(app)
@app.route("/")
def index():
return render_template("index.html", name=session.get("name"))
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
session["name"] = request.form.get("name")
return redirect("/")
return render_template("login.html")
@app.route("/logout")
def logout():
session.clear()
return redirect("/")
Hãy chú ý các phần import đã được sửa đổi ở đầu tệp, bao gồm cả session, cho phép bạn hỗ trợ các phiên. Quan trọng nhất, hãy để ý cách session["name"] được sử dụng trong các route login và logout. Route login sẽ gán tên đăng nhập được cung cấp cho session["name"]. Tuy nhiên, trong route logout, việc đăng xuất được thực hiện bằng cách xóa giá trị của session.
Sự trừu tượng hóa của
sessioncho phép bạn đảm bảo chỉ một người dùng cụ thể mới có quyền truy cập vào dữ liệu và tính năng cụ thể trong ứng dụng của chúng ta. Nó giúp đảm bảo rằng không ai có thể hành động thay mặt người dùng khác, dù với mục đích tốt hay xấu!Nếu muốn, bạn có thể tải xuống bản triển khai của chúng tôi về
login.Bạn có thể đọc thêm về các phiên (sessions) trong tài liệu hướng dẫn của Flask.
Giỏ hàng (Shopping Cart)
- Chuyển sang ví dụ cuối cùng về việc sử dụng khả năng hỗ trợ phiên của Flask.
Chúng ta đã xem xét đoạn mã sau cho store (cửa hàng) trong tệp app.py:
from cs50 import SQL
from flask import Flask, redirect, render_template, request, session
from flask_session import Session
# Configure app
app = Flask(__name__)
# Connect to database
db = SQL("sqlite:///store.db")
# Configure session
app.config["SESSION_PERMANENT"] = False
app.config["SESSION_TYPE"] = "filesystem"
Session(app)
@app.route("/")
def index():
books = db.execute("SELECT * FROM books")
return render_template("books.html", books=books)
@app.route("/cart", methods=["GET", "POST"])
def cart():
# Ensure cart exists
if "cart" not in session:
session["cart"] = []
# POST
if request.method == "POST":
book_id = request.form.get("id")
if book_id:
session["cart"].append(book_id)
return redirect("/cart")
# GET
books = db.execute("SELECT * FROM books WHERE id IN (?)", session["cart"])
return render_template("cart.html", books=books)
Lưu ý rằng cart được triển khai bằng một danh sách (list). Các mục có thể được thêm vào danh sách này bằng các nút Add to Cart (Thêm vào giỏ hàng) trong tệp books.html. Khi nhấp vào nút như vậy, phương thức post được gọi, tại đó id của mặt hàng được thêm vào cart. Khi xem giỏ hàng bằng phương thức get, mã SQL sẽ được thực thi để hiển thị danh sách các cuốn sách có trong giỏ hàng.
Chúng ta cũng đã thấy nội dung của books.html:
{% extends "layout.html" %}
{% block body %}
Books
{% for book in books %}
{{ book["title"] }}
action="/cart" method="post">
name="id" type="hidden" value="{{ book['id'] }}">
type="submit">Add to Cart
{% endfor %}
{% endblock %}
Hãy để ý cách đoạn mã này tạo ra khả năng Add to Cart cho từng cuốn sách bằng cách sử dụng vòng lặp for book in books.
- Bạn có thể xem các tệp còn lại hỗ trợ việc triển khai
flasknày trong mã nguồn.
Chương trình (Shows)
Chúng ta đã xem xét một chương trình được thiết kế sẵn tên là shows, trong tệp app.py:
# Searches for shows using LIKE
from cs50 import SQL
from flask import Flask, render_template, request
app = Flask(__name__)
db = SQL("sqlite:///shows.db")
@app.route("/")
def index():
return render_template("index.html")
@app.route("/search")
def search():
shows = db.execute("SELECT * FROM shows WHERE title LIKE ?", "%" + request.args.get("q") + "%")
return render_template("search.html", shows=shows)
Lưu ý cách route search cho phép tìm kiếm một chương trình (show). Tìm kiếm này tìm các tiêu đề có tính chất LIKE (giống) với tiêu đề do người dùng cung cấp.
Chúng ta cũng đã xem xét index.html:
lang="en">
name="viewport" content="initial-scale=1, width=device-width">
shows
autocomplete="off" autofocus placeholder="Query" type="text">
let input = document.querySelector('input');
input.addEventListener('input', async function() {
let response = await fetch('/search?q=' + input.value);
let shows = await response.json();
let html = '';
for (let id in shows) {
let title = shows[id].title.replace('', '').replace('&', '&');
html += '' + title + '';
}
document.querySelector('ul').innerHTML = html;
});
Lưu ý rằng đoạn mã JavaScript script tạo ra chức năng tự động hoàn thành (autocomplete), nơi các tiêu đề khớp với input sẽ được hiển thị.
Bạn có thể xem các tệp còn lại của bản triển khai này trong mã nguồn.
APIs
- Một giao diện lập trình ứng dụng (application program interface) hay API là một chuỗi các thông số kỹ thuật cho phép bạn giao tiếp với một dịch vụ khác. Ví dụ, chúng ta có thể sử dụng API của IMDB để giao tiếp với cơ sở dữ liệu của họ. Thậm chí, chúng ta có thể tích hợp các API để xử lý các loại dữ liệu cụ thể có thể tải xuống từ máy chủ.
Tiếp tục cải thiện shows, nhìn vào phiên bản nâng cấp của app.py, chúng ta thấy nội dung sau:
# Searches for shows using Ajax
from cs50 import SQL
from flask import Flask, render_template, request
app = Flask(__name__)
db = SQL("sqlite:///shows.db")
@app.route("/")
def index():
return render_template("index.html")
@app.route("/search")
def search():
q = request.args.get("q")
if q:
shows = db.execute("SELECT * FROM shows WHERE title LIKE ? LIMIT 50", "%" + q + "%")
else:
shows = []
return render_template("search.html", shows=shows)
Lưu ý rằng route search thực thi một truy vấn SQL.
Nhìn vào search.html, bạn sẽ thấy nó rất đơn giản:
{% for show in shows %}
{{ show["title"] }}
{% endfor %}
Nó chỉ cung cấp một danh sách các gạch đầu dòng.
Cuối cùng, khi nhìn vào index.html, hãy chú ý rằng mã AJAX được sử dụng để hỗ trợ tìm kiếm:
lang="en">
name="viewport" content="initial-scale=1, width=device-width">
shows
autocomplete="off" autofocus placeholder="Query" type="search">
let input = document.querySelector('input');
input.addEventListener('input', async function() {
let response = await fetch('/search?q=' + input.value);
let shows = await response.text();
document.querySelector('ul').innerHTML = shows;
});
Một trình lắng nghe sự kiện (event listener) được sử dụng để truy vấn động máy chủ nhằm cung cấp danh sách khớp với tiêu đề được cung cấp. Việc này sẽ định vị thẻ ul trong HTML và sửa đổi trang web tương ứng để bao gồm danh sách các kết quả khớp.
- Bạn có thể đọc thêm trong tài liệu AJAX.
JSON
JavaScript Object Notation hay JSON là một tệp văn bản chứa các từ điển với các khóa và giá trị. Đây là một cách thô, thân thiện với máy tính để lấy nhiều dữ liệu.
- JSON là một cách rất hữu ích để nhận lại dữ liệu từ máy chủ.
Bạn có thể thấy điều này trong thực tế qua tệp index.html mà chúng ta đã cùng xem xét:
lang="en">
name="viewport" content="initial-scale=1, width=device-width">
shows
autocomplete="off" autofocus placeholder="Query" type="text">
let input = document.querySelector('input');
input.addEventListener('input', async function() {
let response = await fetch('/search?q=' + input.value);
let shows = await response.json();
let html = '';
for (let id in shows) {
let title = shows[id].title.replace('', '').replace('&', '&');
html += '' + title + '';
}
document.querySelector('ul').innerHTML = html;
});
Mặc dù đoạn mã trên có vẻ hơi khó hiểu, nhưng nó cung cấp một điểm bắt đầu để bạn tự nghiên cứu JSON và xem cách nó có thể được triển khai trong các ứng dụng web của chính mình.
Hơn nữa, chúng ta đã xem xét app.py để thấy cách nhận phản hồi JSON:
# Searches for shows using Ajax with JSON
from cs50 import SQL
from flask import Flask, jsonify, render_template, request
app = Flask(__name__)
db = SQL("sqlite:///shows.db")
@app.route("/")
def index():
return render_template("index.html")
@app.route("/search")
def search():
q = request.args.get("q")
if q:
shows = db.execute("SELECT * FROM shows WHERE title LIKE ? LIMIT 50", "%" + q + "%")
else:
shows = []
return jsonify(shows)
Lưu ý cách jsonify được sử dụng để chuyển đổi kết quả thành định dạng dễ đọc, được chấp nhận bởi các ứng dụng web hiện đại.
Bạn có thể đọc thêm trong tài liệu JSON.
Tóm lại, giờ đây bạn đã có khả năng hoàn thành các ứng dụng web của riêng mình bằng cách sử dụng Python, Flask, HTML và SQL.
Tổng kết
Trong bài học này, bạn đã học cách sử dụng Python, SQL và Flask để tạo ra các ứng dụng web. Cụ thể, chúng ta đã thảo luận về…
Flask
Biểu mẫu (Forms)
Bản mẫu (Templates)
Các phương thức yêu cầu (Request Methods)
Flask và SQL
Cookies và Session
APIs
JSON
Hẹn gặp lại các bạn trong buổi học cuối cùng của học kỳ này tại Nhà hát Sanders!