Monday, July 1, 2024

[Flask] – Phần 13: Tìm hiểu về Web Form

-

1. Tổng quan.

Web Form trong Flask là một phần quan trọng của ứng dụng web, cho phép người dùng tương tác với trang web bằng cách gửi dữ liệu lên máy chủ. Form có thể chứa các trường (fields) như ô văn bản, nút bấm, hộp kiểm (checkbox), nút radio, v.v., và người dùng có thể điền thông tin vào các trường này và gửi chúng lại cho máy chủ.

Trong Flask, để làm việc với Web Forms, bạn thường sử dụng thư viện WTForms, một thư viện phổ biến để quản lý và xử lý Forms trong ứng dụng Flask. WTForms cung cấp một cách dễ đọc và dễ quản lý để định nghĩa, hiển thị, và xác thực Forms.

Dưới đây là một số khái niệm quan trọng khi làm việc với Web Forms trong Flask:

  • Form Class (Lớp Form): Để định nghĩa một Form trong Flask, bạn thường sẽ tạo một lớp Form bằng cách sử dụng WTForms. Lớp Form này sẽ đại diện cho cấu trúc của Form và quy định các trường dữ liệu, kiểu dữ liệu, và quy tắc xác thực.
  • Field (Trường): Mỗi Field trong Form đại diện cho một trường dữ liệu cụ thể như văn bản, email, số nguyên, hộp kiểm, v.v. Các Field có thể được thêm vào lớp Form và quy định kiểu dữ liệu và xác thực của nó.
  • Validator (Trình kiểm tra): Validator là một phần của WTForms cho phép bạn xác thực dữ liệu mà người dùng đã nhập vào Form. Ví dụ, bạn có thể kiểm tra xem một trường dữ liệu có được điền hay không, kiểm tra định dạng email, v.v.
  • Render Form (Hiển thị Form): Để hiển thị Form trong một trang HTML, bạn cần render nó từ lớp Form bằng cách sử dụng Jinja2 (hoặc một thư viện template khác). WTForms cung cấp các hàm giúp bạn dễ dàng render Form và các Field.
  • Handle Form Submission (Xử lý việc gửi Form): Khi người dùng gửi Form, bạn cần xử lý dữ liệu được gửi lên từ Form. Trong Flask, bạn thường sử dụng request để truy cập dữ liệu gửi từ Form.
  • Form Validation (Xác thực Form): Sau khi nhận dữ liệu từ Form, bạn thường cần xác thực dữ liệu để đảm bảo tính hợp lệ. WTForms cung cấp các trình kiểm tra (validators) để xác thực dữ liệu dễ dàng.

Sử dụng Web Forms trong Flask giúp bạn xây dựng các ứng dụng tương tác với người dùng một cách hiệu quả và an toàn, đồng thời giúp bạn quản lý và xác thực dữ liệu một cách dễ dàng hơn.

2. Cài đặt Web Form.

Để xử lý Web form trong ứng dụng, chúng ta sẽ dùng thư viện mở rộng (extension) Flask-WTF. Đây là một giao diện cho gói WTForms được tích hợp với Flask. Đây là thư viện mở rộng đầu tiên mà chúng ta sử dụng, nhưng sẽ không phải là cuối cùng. Các thư viện mở rộng là một thành phần quan trọng trong hệ sinh thái của Flask bởi vì chúng cung cấp các giải pháp mà Flask không có (dù là cố tình hay vô ý).

Các thư viện mở rộng của Flask là các gói thư viện Python tiêu chuẩn được cài đặt bằng pip. Bạn có thể cài đặt Flask-WTF trong môi trường ảo của bạn như sau:

 pip3 install flask-wtf

3. Thực hành tạo form đăng nhập.

Dưới đây sẽ trình bày cách bạn cấu hình một ứng dụng web Flask cơ bản với sử dụng Web Forms và templates Jinja2.

Thiết lập cấu hình.

Tạo file config.py tại thư mục myblog. Trong file này bạn định nghĩa một lớp Config chứa các cấu hình cho ứng dụng của bạn. Điều quan trọng là khóa bí mật SECRET_KEY, được sử dụng để bảo vệ ứng dụng khỏi các tấn công CSRF và cho một số tính năng bảo mật khác. Dưới đây là cách nó hoạt động:

  • SECRET_KEY là gì?
    • SECRET_KEY là một chuỗi bí mật được sử dụng để bảo vệ các thông tin quan trọng trong ứng dụng Flask, chẳng hạn như việc tạo và xác minh token phiên làm việc (session), bảo mật dữ liệu đăng nhập, và nhiều tính năng khác.
  • os.environ.get(‘SECRET_KEY’) or ‘khong-doan-noi-dau’:
    • Dòng code trên đang cố gắng lấy giá trị của biến môi trường (environment variable) có tên ‘SECRET_KEY’ bằng cách sử dụng os.environ.get('SECRET_KEY'). Biến môi trường là một cách thường được sử dụng để lưu trữ các thông tin cấu hình bí mật mà bạn không muốn lưu trực tiếp trong mã nguồn.
    • Nếu biến môi trường ‘SECRET_KEY’ tồn tại và có giá trị, thì giá trị đó sẽ được sử dụng làm SECRET_KEY của ứng dụng Flask.
    • Nếu biến môi trường ‘SECRET_KEY’ không tồn tại hoặc có giá trị là None, thì chuỗi ‘default-sectet-key’ sẽ được sử dụng làm SECRET_KEY mặc định.

Mục đích của việc này là để đảm bảo rằng bạn có một SECRET_KEY hợp lệ cho ứng dụng của bạn. Bằng cách này, bạn có thể giữ SECRET_KEY ở một nơi an toàn và không cần đặt trực tiếp trong mã nguồn, giúp tăng tính bảo mật và dễ dàng cấu hình ứng dụng trên các môi trường khác nhau (như phát triển, sản xuất, thử nghiệm, vv.).

cat > myblog/config.py << 'OEF'
import os
 
class Config(object):
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'default-sectet-key'
OEF

Chỉnh sửa app/__init__.py trong file này bạn tạo một ứng dụng Flask và cấu hình nó bằng cách load cài đặt từ lớp Config. Điều này đảm bảo rằng ứng dụng của bạn có thể sử dụng các cài đặt trong Config.

Dưới đây là giải thích từng dòng code:

  • from flask import Flask: Dòng này import class Flask từ module flask. Điều này cho phép bạn sử dụng Flask để tạo ứng dụng web.
  • from config import Config: Dòng này import class Config từ module config. Đây có thể là một module tự định nghĩa chứa các cài đặt cấu hình cho ứng dụng Flask của bạn. Các cài đặt này có thể bao gồm thông tin về cơ sở dữ liệu, cấu hình ứng dụng, và nhiều thứ khác.
  • app = Flask(__name__): Dòng này tạo một đối tượng ứng dụng Flask. __name__ ở đây là biến đặc biệt trong Python, nó đại diện cho tên của module hiện tại. Flask sử dụng nó để xác định nơi lưu trữ các file mẫu (templates) và file tĩnh (static files).
  • app.config.from_object(Config): Dòng này đọc cài đặt cấu hình từ đối tượng Config đã import ở trên và cấu hình ứng dụng Flask với các giá trị cài đặt này. Điều này đảm bảo rằng ứng dụng Flask của bạn sẽ hoạt động với các cài đặt cấu hình đã được xác định trước.
  • from app import routes: Dòng này import module routes từ gói app. Trong ứng dụng web Flask, routes là các hàm xử lý URL và yêu cầu HTTP. Module routes sẽ chứa các định nghĩa route để xử lý các yêu cầu từ người dùng.
from flask import Flask
from config import Config
 
app = Flask(__name__)
app.config.from_object(Config)
 
from app import routes

Form đăng nhập (login form).

Tạo app/forms.py, tại đây bạn định nghĩa lớp LoginForm là một lớp kế thừa từ FlaskForm. Lớp này đại diện cho Form đăng nhập và bao gồm các trường như “username” (tên người dùng), “password” (mật khẩu), “remember_me” (ghi nhớ đăng nhập), và nút “submit”. Bạn cũng xác thực dữ liệu trong các trường này bằng cách sử dụng DataRequired().

Dưới đây là giải thích từng dòng code:

  • from flask_wtf import FlaskForm: Dòng này import class FlaskForm từ module flask_wtf. Flask-WTF là một phần mở rộng (extension) của Flask, cho phép bạn làm việc với biểu mẫu web trong ứng dụng Flask.
  • from wtforms import StringField, PasswordField, BooleanField, SubmitField: Dòng này import các loại trường (fields) từ module wtforms để định nghĩa các trường trong biểu mẫu. Cụ thể là StringField cho trường văn bản, PasswordField cho trường mật khẩu, BooleanField cho trường boolean (chẳng hạn như checkbox), và SubmitField cho nút gửi biểu mẫu.
  • from wtforms.validators import DataRequired: Dòng này import validator DataRequired từ module wtforms.validators. Validators được sử dụng để kiểm tra dữ liệu người dùng nhập vào các trường biểu mẫu. DataRequired kiểm tra xem trường có dữ liệu (không rỗng) hay không.
  • class LoginForm(FlaskForm):: Dòng này định nghĩa một lớp (class) LoginForm kế thừa từ FlaskForm. Đây là lớp biểu mẫu của bạn, chứa các trường và quy tắc kiểm tra dữ liệu cho biểu mẫu đăng nhập.
  • username = StringField('Username', validators=[DataRequired()]): Đây là định nghĩa của trường “username” trong biểu mẫu. StringField được sử dụng để tạo một trường văn bản. Tham số đầu tiên là nhãn (label) của trường, và tham số thứ hai là danh sách các validators được áp dụng cho trường này, trong trường hợp này là DataRequired() để đảm bảo người dùng phải điền vào trường này.
  • password = PasswordField('Password', validators=[DataRequired()]): Tương tự như trường “username”, đây là định nghĩa của trường “password” trong biểu mẫu. PasswordField được sử dụng để tạo một trường mật khẩu.
  • remember_me = BooleanField('Remember me'): Đây là định nghĩa của trường “remember_me” trong biểu mẫu. BooleanField được sử dụng để tạo một trường boolean, có thể dùng để cho phép người dùng chọn một tùy chọn.
  • submit = SubmitField('Sign In'): Đây là định nghĩa của nút gửi biểu mẫu “submit”. SubmitField được sử dụng để tạo một nút gửi biểu mẫu, và tham số đầu tiên là văn bản hiển thị trên nút này (“Sign In” trong trường hợp này).
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired
 
class LoginForm(FlaskForm):
    username = StringField('Username', validators=[DataRequired()])
    password = PasswordField('Password', validators=[DataRequired()])
    remember_me = BooleanField('Remember me')
    submit = SubmitField('Sign In')

Template cho Form.

Chỉnh sửa app/templates/login.html, file này bạn sử dụng Jinja2 để kế thừa từ template cha (base.html). Trong khối {% block content %}{% endblock %}, bạn hiển thị Form đăng nhập và sử dụng form.hidden_tag() để thêm một trường ẩn chứa dữ liệu bảo mật. Dữ liệu Form và các trường của nó được hiển thị bằng cách sử dụng {{ form.field_name }}. Form này sẽ được gửi đi bằng phương thức POST.

Dưới đây là giải thích từng dòng code:

  • {% extends "base.html" %}: Dòng này xác định rằng file mẫu login.html mở rộng (kế thừa) từ file mẫu base.html. Điều này có nghĩa là các phần nội dung chung đã được định nghĩa trong base.html sẽ được sử dụng lại ở đây và nội dung cụ thể cho trang đăng nhập sẽ được đặt trong một khối (block) có tên là “content”.
  • {% block content %}: Đây là đánh dấu bắt đầu của khối “content”. Mọi thứ nằm giữa {% block content %}{% endblock %} sẽ được đặt trong khối “content”. Điều này cho phép bạn định nghĩa nội dung cụ thể cho trang đăng nhập.
  • <h1>Sign In</h1>: Đây là tiêu đề trang đăng nhập.
  • <form action="" method="post" novalidate>: Đây là thẻ HTML <form> để tạo biểu mẫu đăng nhập. Thuộc tính action là URL mà biểu mẫu sẽ gửi dữ liệu đến (ở đây là trống, nghĩa là gửi đến URL hiện tại), method là phương thức gửi dữ liệu (ở đây là “post”), và novalidate là thuộc tính để tắt việc kiểm tra dữ liệu HTML5 trước khi gửi biểu mẫu.
  • {{ form.hidden_tag() }}: Dòng này thêm một trường ẩn vào biểu mẫu để bảo vệ khỏi tấn công Cross-Site Request Forgery (CSRF). Đây là một cách để đảm bảo tính bảo mật của biểu mẫu.
  • {{ form.username.label }}{{ form.username(size=32) }}: Đây là cách sử dụng biểu mẫu WTForms để hiển thị trường “username”. form.username.label hiển thị nhãn của trường và form.username(size=32) hiển thị trường văn bản với kích thước 32.
  • Tương tự, {{ form.password.label }}{{ form.password(size=32) }} được sử dụng để hiển thị trường “password”.
  • {{ form.remember_me() }} {{ form.remember_me.label }}: Đây là cách hiển thị trường “remember_me” là một checkbox và nhãn của nó.
  • {{ form.submit() }}: Dòng này hiển thị nút gửi biểu mẫu.
  • {% endblock %}: Đây là đánh dấu kết thúc của khối “content”.
{% extends "base.html" %}
 
{% block content %}
    <h1>Sign In</h1>
    <form action="" method="post" novalidate>
        {{ form.hidden_tag() }}
        <p>
            {{ form.username.label }}<br>
            {{ form.username(size=32) }}
        </p>
        <p>
            {{ form.password.label }}<br>
            {{ form.password(size=32) }}
        </p>
        <p>{{ form.remember_me() }} {{ form.remember_me.label }}</p>
        <p>{{ form.submit() }}</p>
    </form>
{% endblock %}

Hiển thị form.

Trong file app/routes.py, bạn định nghĩa hai route. Route ‘/’ và ‘/index’ hiển thị một trang chào mừng và danh sách các bài viết. Route ‘/login’ hiển thị Form đăng nhập bằng cách sử dụng LoginForm. Bạn cũng import LoginForm từ app.forms để sử dụng nó trong route ‘/login’.

Dưới đây là giải thích từng đoạn code:

  • from flask import render_template: Dòng này import hàm render_template từ Flask. Hàm này cho phép bạn render (hiển thị) các trang HTML mẫu với dữ liệu được truyền vào từ ứng dụng.
  • from app import app: Dòng này import biến app từ module app. Biến app là một đối tượng Flask chính của ứng dụng web.
  • from app.forms import LoginForm: Dòng này import lớp LoginForm từ module forms trong ứng dụng của bạn. LoginForm là một biểu mẫu WTForms đã được định nghĩa trước.
  • @app.route('/')@app.route('/index'): Đây là các decorator (bộ trang trí) Flask được sử dụng để định nghĩa các route của ứng dụng. Trong trường hợp này, có hai route: ‘/’ và ‘/index’. Khi người dùng truy cập các URL này, các hàm dưới đây sẽ được gọi tương ứng.
  • def index():: Đây là hàm xử lý route ‘/index’. Hàm này trả về trang chủ của ứng dụng và đưa dữ liệu như tiêu đề trang (“Home”), thông tin người dùng và danh sách các bài viết vào trang HTML mẫu index.html.
  • def login():: Đây là hàm xử lý route ‘/login’. Hàm này tạo một biểu mẫu đăng nhập (sử dụng lớp LoginForm đã import) và trả về trang đăng nhập (login.html) với biểu mẫu này và tiêu đề trang (“Sign In”).
  • form = LoginForm(): Trong hàm login(), một đối tượng biểu mẫu LoginForm được tạo ra. Điều này sẽ cho phép người dùng nhập thông tin đăng nhập vào biểu mẫu.
  • return render_template('index.html', title='Home', user=user, posts=posts): Trong hàm index(), hàm render_template được sử dụng để hiển thị trang HTML mẫu index.html. Các biến như title, user, và posts được truyền vào để sử dụng trong template để hiển thị dữ liệu động.
from flask import render_template
from app import app
from app.forms import LoginForm

@app.route('/')
@app.route('/index')
def index():
    user = {'username': 'HoangHD'}
    posts = [
        {
            'author': {'username': 'sda'},
            'body': 'Disk sda!'
        },
        {
            'author': {'username': 'sdb'},
            'body': 'Disk sdb!'
        }
    ]
    return render_template('index.html', title='Home', user=user, posts=posts)

@app.route('/login')
def login():
    form = LoginForm()
    return render_template('login.html', title='Sign In', form=form)

Trong file template app/templates/base.html, bạn định nghĩa một template cha chứa cấu trúc chung của trang web. Tiêu đề của trang web có thể được đặt bằng cách sử dụng biến title. Dưới đó, bạn hiển thị một menu với các liên kết đến trang chủ (‘/index’) và trang đăng nhập (‘/login’). Nội dung của mỗi trang con sẽ được thêm vào khối {% block content %}{% endblock %}.

Đây là giải thích từng dòng code:

  • <html>: Bắt đầu của thẻ HTML chính.
  • <head>: Thẻ <head> chứa thông tin về tài liệu HTML, như tiêu đề, các file CSS, JavaScript, và các thẻ meta khác.
  • {% if title %}: Đây là một điều kiện Jinja2. Nó kiểm tra xem biến title đã được định nghĩa hay chưa. Nếu title đã tồn tại (không rỗng), đoạn code sau nó sẽ được thực hiện.
  • <title>{{ title }} - Myblog</title>: Đây là thẻ <title> của tài liệu HTML. Nếu biến title đã tồn tại, nó sẽ được sử dụng để đặt tiêu đề của trang, bao gồm cả phần tiêu đề truyền vào từ biến title và một phần cố định ” – Myblog”. Nếu biến title không tồn tại, tiêu đề mặc định sẽ là “Welcome to Myblog”.
  • {% else %}: Đây là một phần của điều kiện if. Nếu biến title không tồn tại, đoạn code sau nó sẽ được thực hiện.
  • <title>Welcome to Myblog</title>: Đây là tiêu đề mặc định của trang nếu biến title không tồn tại. Nó được đặt thành “Welcome to Myblog”.
  • </head>: Kết thúc thẻ <head>.
  • <body>: Bắt đầu thẻ <body>, nơi chứa nội dung chính của trang web.
  • <div>Myblog: ... </div>: Đây là một div chứa các liên kết dẫn đến các trang web chính của ứng dụng. Dưới đây là hai liên kết “Home” và “Login” dẫn đến các URL /index/login.
  • <hr>: Đây là một đường ngang ngăn cách phần đầu và phần nội dung của trang.
  • {% block content %}{% endblock %}: Đây là một khối Jinja2 được đặt tên là “content”. Nội dung cụ thể của từng trang con (như login.html) sẽ được đặt vào đây bằng cách định nghĩa lại khối này trong template con. Điều này cho phép template gốc này làm cơ sở cho các trang web khác và nội dung chính của từng trang con sẽ được thay đổi tùy theo nhu cầu.
<html>
    <head>
      {% if title %}
      <title>{{ title }} - Myblog</title>
      {% else %}
      <title>Welcome to Myblog</title>
      {% endif %}
    </head>
    <body>
        <div>Myblog:
          <a href="/index">Home</a>
          <a href="/login">Login</a>
        </div>
        <hr>
        {% block content %}{% endblock %}
    </body>
</html>

Khi bạn chạy ứng dụng Flask này, bạn sẽ thấy trang chào mừng với các liên kết đến trang chủ và đăng nhập. Trang đăng nhập sử dụng Form đăng nhập đã được định nghĩa bởi LoginForm. Khi bạn gửi Form, ứng dụng sẽ xử lý dữ liệu và hiển thị trả lời tương ứng.

Và đây là kết quả.

Nhưng nếu bạn nhập thông tin và bấm vào Sign in thì bạn sẽ nhận được thông báo Method Not Allowed.

Nhận dữ liệu từ form.

Xử lý Form đăng nhập trong ứng dụng Flask của bạn với sự hỗ trợ của Flask-WTF và Jinja2.

Nhận thông tin người sử dụng chỉnh sửa tại app/routes.py.

  • Trong route /login, bạn đã thêm methods=['GET', 'POST'] để cho phép route này xử lý cả GET và POST requests. Điều này cho phép bạn hiển thị Form đăng nhập khi người dùng truy cập trang /login bằng phương thức GET và xử lý khi họ gửi Form bằng phương thức POST.
  • Bạn đã sử dụng form.validate_on_submit() để kiểm tra xem Form đã được gửi lên và đã được xác thực thành công hay chưa. Nếu Form hợp lệ, bạn đã sử dụng flash() để tạo một thông báo và sau đó chuyển hướng người dùng đến trang chủ /index.
  • Dưới đây là giải thích từng dòng code:
    • @app.route('/login', methods=['GET', 'POST']): Đây là decorator Flask định nghĩa một route có đường dẫn là ‘/login’. Điều đặc biệt ở đây là chúng ta đã cung cấp tham số methods=['GET', 'POST']. Điều này cho phép route này xử lý cả yêu cầu GET và POST. GET được sử dụng để hiển thị trang đăng nhập, và POST được sử dụng khi người dùng gửi biểu mẫu đăng nhập.
    • def login():: Đây là hàm xử lý cho route ‘/login’. Hàm này được gọi khi có yêu cầu đến đường dẫn ‘/login’.
    • form = LoginForm(): Trong hàm login(), một đối tượng biểu mẫu LoginForm được tạo ra. Điều này sẽ tạo ra một biểu mẫu đăng nhập trống để người dùng nhập thông tin đăng nhập.
    • if form.validate_on_submit():: Đây là một điều kiện kiểm tra nếu biểu mẫu đã được gửi đi (validate_on_submit) và thông tin đã được nhập đúng và hợp lệ (được kiểm tra bởi các validators trong biểu mẫu). Nếu điều này đúng, nghĩa là người dùng đã gửi thông tin đăng nhập hợp lệ, điều kiện trong khối này sẽ được thực hiện.
    • flash('Yeu cau dang nhap tu user {}, remember_me={}'.format(form.username.data, form.remember_me.data)): Trong khối điều kiện, một thông báo (flash message) được tạo bằng cách sử dụng hàm flash. Thông báo này thông báo rằng yêu cầu đăng nhập đã được gửi từ người dùng với tên người dùng (lấy từ form.username.data) và tùy chọn “remember me” (lấy từ form.remember_me.data). Thông báo này sẽ được hiển thị cho người dùng sau khi họ đăng nhập thành công.
    • return redirect('/index'): Sau khi thông báo đã được tạo, hàm redirect được sử dụng để chuyển hướng người dùng đến trang ‘/index’. Điều này thường thực hiện sau khi người dùng đăng nhập thành công để họ được định hướng đến trang chính của ứng dụng.
    • return render_template('login.html', title='Sign In', form=form): Nếu biểu mẫu không được gửi đi hoặc thông tin không hợp lệ, ứng dụng sẽ tiếp tục hiển thị trang đăng nhập. Hàm render_template được sử dụng để hiển thị trang đăng nhập với tiêu đề “Sign In” và biểu mẫu LoginForm đã được tạo.
from flask import render_template, flash, redirect
from app import app
from app.forms import LoginForm

@app.route('/')
@app.route('/index')
def index():
    user = {'username': 'HoangHD'}
    posts = [
        {
            'author': {'username': 'sda'},
            'body': 'Disk sda!'
        },
        {
            'author': {'username': 'sdb'},
            'body': 'Disk sdb!'
        }
    ]
    return render_template('index.html', title='Home', user=user, posts=posts)

@app.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        flash('Yeu cau dang nhap tu user {}, remember_me={}'.format(
            form.username.data, form.remember_me.data))
        return redirect('/index')
    return render_template('login.html', title='Sign In', form=form)

Hiển thị thông điệp được truyền vào hàm flash() tại app/templates/base.html. Trong khối {% with messages = get_flashed_messages() %}, bạn đã sử dụng get_flashed_messages() để lấy danh sách các thông báo đã được tạo bởi flash() từ route trước đó. Sau đó, bạn đã sử dụng vòng lặp {% for message in messages %} để hiển thị các thông báo này dưới dạng danh sách.

<html>
    <head>
        {% if title %}
        <title>{{ title }} - Myblog</title>
        {% else %}
        <title>Myblog</title>
        {% endif %}
    </head>
    <body>
        <div>
            Myblog:
            <a href="/index">Home</a>
            <a href="/login">Login</a>
        </div>
        <hr>
        {% with messages = get_flashed_messages() %}
        {% if messages %}
        <ul>
            {% for message in messages %}
            <li>{{ message }}</li>
            {% endfor %}
        </ul>
        {% endif %}
        {% endwith %}
        {% block content %}{% endblock %}
    </body>
</html>

Nâng cấp code để kiểm tra dữ liệu cho các trường nhập liệu.

Nâng cấp mã kiểm tra dữ liệu cho các trường nhập liệu tại app/templates/login.html. Đoạn code này sẽ thông báo lỗi nhập liệu trong template Login.

  • Trong khối {% block content %}{% endblock %}, bạn đã mở rộng từ template cha base.html.
  • Bạn đã thêm một phần hiển thị lỗi trên mỗi trường của Form bằng cách sử dụng vòng lặp {% for error in form.field_name.errors %}. Nếu có lỗi xảy ra trong trường field_name, nó sẽ được hiển thị bên cạnh trường đó với màu đỏ.

Như vậy, mã này đã tạo ra một Form đăng nhập cho người dùng và hiển thị thông báo lỗi nếu người dùng điền thông tin không hợp lệ hoặc nếu họ thử đăng nhập với thông tin không đúng. Sau khi đăng nhập thành công, họ sẽ được chuyển hướng đến trang chủ.

{% extends "base.html" %}
 
{% block content %}
    <h1>Sign In</h1>
    <form action="" method="post" novalidate>
        {{ form.hidden_tag() }}
        <p>
            {{ form.username.label }}<br>
            {{ form.username(size=32) }}<br>
            {% for error in form.username.errors %}
            <span style="color: red;">[{{ error }}]</span>
            {% endfor %}
        </p>
        <p>
            {{ form.password.label }}<br>
            {{ form.password(size=32) }}<br>
            {% for error in form.password.errors %}
            <span style="color: red;">[{{ error }}]</span>
            {% endfor %}
        </p>
        <p>{{ form.remember_me() }} {{ form.remember_me.label }}</p>
        <p>{{ form.submit() }}</p>
    </form>
{% endblock %}

Thay đổi duy nhất mà chúng ta đã làm ở đây là thêm một vòng lặp ngay sau mã cho các trường username và password để hiển thị các thông báo lỗi sinh ra bởi biến kiểm tra bằng màu đỏ. Theo quy ước, các thông báo lỗi cho các trường có liên kết với biến kiểm tra dữ liệu sẽ được thêm vào biến form.<tên-trường>.errors. Biến errors sẽ là một danh sách (list) vì mỗi trường có thể liên kết với nhiều biến kiểm tra dữ liệu khác nhau và có khả năng sinh ra nhiều lỗi đồng thời. Sau khi bạn cập nhật template login.html, nếu bạn thử submit form mà không nhập username hoặc password, bạn sẽ thấy các thông báo lỗi màu đỏ.

Và đây là kết quả khi bạn không nhập thông tin username và password.

Tạo liên kết (link).

Đến đây thì form login gần như hoàn tất. Nhưng trước khi kết thúc phần này, chúng ta sẽ nói qua một chút về cách đặt các liên kết (link) và chuyển trang (redirect). Cho đến giờ, bạn đã thấy một số trường hợp chúng ta đặt các liên kết vào ứng dụng.

Sử dụng hàm url_for() để tạo các liên kết app/templates/base.html. Đoạn code trong file base.html của ứng dụng Flask xác định thanh điều hướng của trang web và sử dụng url_for để tạo các liên kết đến các route trong ứng dụng. Dưới đây là giải thích chi tiết:

  • <div> Myblog: ... </div>: Đây là một div chứa thanh điều hướng của trang web. Nó bao gồm các liên kết đến các trang chính của ứng dụng.
  • Myblog:: Đây là một phần văn bản đơn giản (text) được hiển thị trước các liên kết. Trong trường hợp này, nó là văn bản “Myblog:”.
  • <a href="{{ url_for('index') }}">Home</a>: Đây là một liên kết (anchor tag) sử dụng thẻ <a>. Thuộc tính href của liên kết được đặt bằng giá trị được trả về bởi hàm url_for('index'). Hàm url_for này tạo URL cho route với tên ‘index’. Điều này có nghĩa là khi người dùng nhấp vào liên kết ‘Home’, họ sẽ được chuyển hướng đến route ‘/index’ của ứng dụng.
  • <a href="{{ url_for('login') }}">Login</a>: Tương tự như liên kết ‘Home’, liên kết ‘Login’ cũng sử dụng url_for('login') để tạo URL chuyển hướng đến route ‘/login’ của ứng dụng.

Sử dụng url_for để tạo các liên kết giúp bạn duy trì tính nhất quán trong việc xây dựng URL trong ứng dụng Flask. Nó cho phép bạn thay đổi route hoặc đường dẫn mà không cần phải sửa đổi thủ công tất cả các liên kết liên quan.

<html>
    <head>
        {% if title %}
        <title>{{ title }} - Myblog</title>
        {% else %}
        <title>Myblog</title>
        {% endif %}
    </head>
    <body>
        <div>
            Myblog:
            <a href="{{ url_for('index') }}">Home</a>
            <a href="{{ url_for('login') }}">Login</a>
        </div>
        <hr>
        {% with messages = get_flashed_messages() %}
        {% if messages %}
        <ul>
            {% for message in messages %}
            <li>{{ message }}</li>
            {% endfor %}
        </ul>
        {% endif %}
        {% endwith %}
        {% block content %}{% endblock %}
    </body>
</html>

Và cuối cùng là hàm hiển thị login() sử dụng hàm url_for:

Sử dụng hàm url_for() để tạo các liên kết app/routes.py.

from flask import render_template, flash, redirect, url_for
from app import app
from app.forms import LoginForm

@app.route('/')
@app.route('/index')
def index():
    user = {'username': 'HoangHD'}
    posts = [
        {
            'author': {'username': 'sda'},
            'body': 'Disk sda!'
        },
        {
            'author': {'username': 'sdb'},
            'body': 'Disk sdb!'
        }
    ]
    return render_template('index.html', title='Home', user=user, posts=posts)

@app.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        flash('Yeu cau dang nhap tu user {}, remember_me={}'.format(
            form.username.data, form.remember_me.data))
        return redirect(url_for('index'))
    return render_template('login.html', title='Sign In', form=form)

Đến đây, chúng ta có thể chạy thử lần nữa và lần này, bạn hãy thử nhập vào đầy đủ username, password và sau cùng, bấm nút “Sign In“. Bạn sẽ thấy kết quả tương tự như sau:

Xin chúc mừng, bạn đã thành công tạo ra form đăng nhập đầu tiên.

Chúng ta sẽ kết thúc ở đây, hẹn gặp lại bạn trong phần tiếp theo.

LEAVE A REPLY

Please enter your comment!
Please enter your name here

4,956FansLike
256FollowersFollow
223SubscribersSubscribe
spot_img

Related Stories