Hướng dẫn lập trình Flask – Phần 3: Tìm hiểu về Web Form

flask_tutorial_1

Trong phần này, chúng ta sẽ cùng tìm hiểu về khái niệm Web Form và cách sử dụng Web Form trong Flask.

Để giúp cho bạn dễ theo dõi, sau đây là danh sách các bài viết trong loạt bài hướng dẫn này:

Bạn có thể truy cập mã nguồn cho phần này tại GitHub.

Trong Phần 2, chúng ta đã tạo ra một template đơn giản cho trang chủ của ứng dụng và dùng các đối tượng giả lập cho các thành phần mà chúng ta chưa xây dựng như là đối tượng người sử dụng, các bài viết. Trong chương này, chúng ta sẽ tìm hiểu kỹ hơn về các điểm yếu của ứng dụng cho đến thời điểm này, đặt biệt là vấn đề tiếp nhận và xử lý những yêu cầu của người sử dụng thông qua Web form.

Một cách tổng quát, Web form (đừng nhầm lẫn với khái niệm Web form trong các ứng dụng ASP/ASP.NET truyền thống nhé, chúng không có liên quan gì với nhau mặc dù có cùng tên gọi) là một trong những thành phần cơ bản của bất kỳ ứng dụng Web nào. Chúng ta sẽ dùng Web form để người sử dụng có thể gởi dữ liệu như là bài viết, thông tin đăng nhập từ trình duyệt đến ứng dụng của chúng ta.

Trước khi bắt đầu phần mới, hãy chắc rằng ứng dụng myblog mà chúng ta đã xây dựng được thiết lập và chạy thành công.

Giới thiệu Flask-WTF

Để 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:

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

Cho đến thời điểm này, ứng dụng của chúng ta vẫn còn rất sơ khai, và do đó, chúng ta không cần phải để ý nhiều đến vấn đề cấu hình (configuration). Nhưng đối với bất kỳ ứng dụng nào ngoại trừ các ứng dụng rất đơn giản, chúng ta sẽ thấy rằng Flask (và có thể bao gồm các thư viện mở rộng của Flask mà bạn dùng) cho phép chúng ta tự do trong việc thực hiện ở mức độ nhất định. Và khi bạn cần ra quyết định nào đó, bạn sẽ cho Flask biết thông qua một danh sách các tham số cấu hình.

Có một vài định dạng khác nhau trong việc cung cấp cấu hình cho ứng dụng. Giải pháp cơ bản nhất là định nghĩa các tham số cấu hình của bạn qua các khóa (key) trong file app.config. File này dùng kiểu dictionary để mô tả các tham số. Ví dụ như sau:

Dù cấu trúc trên đây là đầy đủ cho việc định nghĩa cấu hình cho Flask, chúng ta vẫn nên tuân theo nguyên tắc phân chia các mối quan tâm (separation of concerns). Cụ thể hơn, thay vì đặt tất cả các thông số cấu hình vào trong mã khởi tạo của ứng dụng, chúng ta sẽ đặt nó vào một file cấu hình riêng rẽ.

Trong khuôn khổ loạt bài này, chúng ta sẽ theo quy ước là dùng một class để chứa tất cả các thông số cấu hình của ứng dụng. Để giữ cho cấu trúc chung của ứng dụng được gọn gàng, chúng ta sẽ tạo ra lớp cấu hình trong một module Python riêng biệt. Sau đây là các khai báo trong lớp cấu hình cho ứng dụng trong file config.py mà chúng ta sẽ lưu ở thư mục ngoài cùng của ứng dụng:

config.py: Cấu hình cho mã khóa

Rất đơn giản phải không? Tham số cấu hình sẽ được định nghĩa là các biến bên trong lớp Config. Nếu chúng ta cần khai báo các tham số mới, chúng ta sẽ thêm các biến mới vào lớp này. Thậm chí nếu sau này chúng ta cần nhiều bộ tham số, chúng ta có thể tạo ra các lớp con của lớp này. Nhưng bây giờ tạm thời chúng ta không cần đi sâu.

Tham số cấu hình SECRET_KEY trong lớp Config là một phần rất quan trọng trong các ứng dụng Flask. Flask và một số cá thư viện mở rộng dùng giá trị của biến này với vai trò là khóa mã hóa để tạo ra các chữ ký hoặc các token bảo mật. Thư viện mở rộng Flask-WTF dùng khóa này để bảo vệ Web form khỏi một hình thức tấn công rất phổ biến là Cross-Site Request Forgery hay là CRSF. Dễ thấy từ tên gọi của tham số này, nó phải là một khóa bí mật bởi vì quyết định độ mạnh yếu của các chữ ký và token bảo mật để bảo vệ cho ứng dụng.

Giá trị của khóa bí mật này là một biểu thức với hai mệnh đề được ghép lại với nhau qua toán tử OR. Mệnh đề thức nhất sẽ tìm giá trị của một biến môi trường cũng gọi là SECRET_KEY. Mệnh đề thứ hai là một chuỗi được định nghĩa sẵn. Chúng ta sẽ dùng cách này thường xuyên để thiết lập các tham số cấu hình xuyên suốt loạt bài này. Lý do là nó cho phép chúng ta có thể sử dụng giá trị của khóa từ biến môi trường hoặc một chuỗi định sẵn một cách linh hoạt. Trong môi trường phát triển, ít có nguy cơ bảo mật thì chúng ta có thể không cần khai báo biến môi trường và do đó, ứng dụng sẽ sử dụng giá trị của chuỗi định sẵn. Tuy nhiên, khi sử dụng chính thức thì chúng ta sẽ thiết lập một giá trị riêng cho khóa này trong biến môi trường. Giá trị này là duy nhất và khó có thể đoán ra bởi các phần mềm hack, vì thế chúng ta có thể bảo đảm rằng những dữ liệu nhạy cảm không bị lộ.

Sau khi đã có file cấu hình, chúng ta cần cho Flask biết để lấy thông tin từ đó và dùng cho ứng dụng. Chúng ta làm điều này bằng cách cập nhật file __init__.py và thêm một dòng lệnh sử dụng hàm app.config.from_object() như sau:

app/__init__.py: cấu hình Flask

Thoạt nhìn thì cách tham chiếu đến lớp Config có vẻ hơi khó hiểu. Nhưng nếu bạn quan sát kỹ thì nó cũng tương tự như cách chúng ta khai báo tham chiếu đến thực thể flask trong thư viện Flask mà chúng ta đã làm qua trước đây (Xin lưu ý sự khác nhau giữa chữ “f” thường và “F” hoa trong tên flask đại diện cho các đối tượng có ý nghĩa khác nhau). Ở đây, tên “config” (chữ thường) là tên của module config.py, còn tên “Config” là tên của lớp Config trong module config.py.

Như đã nói ở trên, các tham số cấu hình sẽ được truy cập qua từ điển trong app.config. Dưới đây là một ví dụ để lấy giá trị của khóa bí mật bằng trình biên dịch Python:

Form đăng nhập (login form)

Thư viện mở rộng (Flask-WTF) sử dụng các lớp trong Python để đại diện cho các Web form. Các lớp này – gọi tắt là lớp form) sẽ định nghĩa các trường (field) có trên form qua các biến thành viên.

Theo đúng nguyên tắc phân chia các mối quan tâm đã được đề cập ở trên, chúng ta sẽ tạo ra một module mới trong thư mục app có tên là forms.py để chứa các thông tin về form đăng nhập của chúng ta. Chúng ta bắt đầu với một form đơn giản bao gồm hai khung nhập văn bản (text box) để người sử dụng có thể nhập vào tên người sử dụng và mật khẩu (username và password). Form này cũng có thêm một khung đánh dấu (checkbox) tên là “Remember me” và một nút bấm để gởi thông tin đăng nhập từ trình duyệt của người sử dụng về ứng dụng trên máy chủ (submit button).

app/forms.py: form đăng nhập

Đa số các thư viện mở rộng của Flask dùng quy ước flask_<tên-thư-viện> để nhận dạng các tham chiếu đến chúng. Flask-WTF cũng không ngoại lệ và sử dụng tên tham chiếu là flask-wtf. Để sử dụng module FlaskForm từ thư viện Flask-WTF, chúng ta khai báo như ở đầu của file app/forms.py

Ứng dụng của chúng ta cũng sử dụng bốn lớp khác từ gói WTForms để đại diện cho các trường nhập dữ liệu tương ứng. Mỗi một trường sẽ được đại diện bởi một biến trong lớp LoginForm. Tham số đầu tiên khi khởi tạo mỗi trường sẽ là mô tả hoặc nhãn của chúng. Tham số tùy chọn validators khi khởi tạo trường username và password cho phép chúng ta kiểm tra dữ liệu nhập. Biến kiểm tra (validator) DataRequired là một biến được định nghĩa bởi Flask-WTF cho phép ứng dụng kiểm tra và phát sinh thông báo lỗi nếu không có dữ liệu nhập trong trường tương ứng. Có nhiều kiểu biến kiểm tra khác nhau mà chúng ta sẽ tìm hiều về sau.

Template cho Form

Bước tiếp theo là tạo một HTML template sử dụng form của chúng ta. Việc tạo template này không quá phức tạp vì các trường được định nghĩa trong lớp LoginForm biết cách để tạo ra mã HTML cho chính chúng. Sau đây là đoạn mã để tạo ra template đăng nhập, chúng ta sẽ đặt mã này vào file app/templates/login.html:

app/templates/login.html: template cho form đăng nhập

Trong template này chúng ta sẽ sử dụng lại template base.html được giới thiệu trong Phần 2 bằng cách sử dụng lệnh extends để kế thừa template. Chúng ta sẽ sử dụng cấu trúc này cho tất cả các template mới để bảo đảm rằng tất cả các trang trong ứng dụng của chúng ta đều có cấu trúc (layout) giống nhau với thanh định hướng ở đầu trang.

Template này cần có một tham số là một thực thể đã được khởi tạo của lớp LoginForm được đại diện bởi thẻ form trong đoạn mã ở trên. Tham số này cần được truyền từ hàm hiển thị login mà chúng ta vẫn chưa tạo ra.

Thẻ HTML <form> trong đoạn mã trên thực tế không phải chỉ là một thẻ HTML, nó là một vật chứa (container) cho web form. Thuộc tính action của form dùng để thông báo cho trình duyệt biết nó sẽ cần gởi dữ liệu do người dùng nhập vào đến địa chỉ (URL) nào. Nếu chúng ta gán một chuỗi trống cho thuộc tính này thì theo mặc định, nó sẽ gởi tất cả dữ liệu đến địa chỉ hiện hành – URL mà chúng ta đang thấy trên thanh địa chỉ của trình duyệt khi truy nhập trang Web này. Thuộc tính method báo cho trình duyệt phải dùng phương thức nào để gởi dữ liệu trong giao thức HTTP. Phương thức mặc định là gởi yêu cầu theo dạng GET, tuy nhiên, trong phần lớn trường hợp chúng ta nên dùng POST để gởi dữ liệu vì POST có nhiều ưu điểm hơn cho tác vụ này, ví dụ như nó có thể gởi các dữ liệu bên trong gói dữ liệu của yêu cầu HTTP trong khi GET sẽ chèn dữ liệu của form vào URL và do đó vừa làm cho URL thêm phức tạp, lại vừa tạo ra nguy cơ lộ dữ liệu (nếu chưa quen với các khái niệm HTTP request, GET, POST …, bạn nên tham khảo trên Wikipedia). Thuộc tính novalidate dùng để thông báo với trình duyệt không kiểm tra dữ liệu của các trường trong form – bởi vì việc kiểm tra dữ liệu sẽ do Flask đảm nhiệm thông qua mã mà chúng ta đã viết trong file forms.py. Việc dùng thuộc tính novalidate là tùy chọn, tuy nhiên cho form login của chúng ta thì chúng ta cần sử dụng nó vì nó cho phép chúng ta kiểm tra việc xác nhận dữ liệu ở máy chủ (server-side validation) mà chúng ta sẽ tìm hiểu ở phần sau của bài này.

Tham số form.hidden_tag() sẽ tạo ra một trường ẩn (hidden field) để chứa một token dùng để bảo vệ form khỏi tấn công CSRF mà chúng ta đã đề cập ở trên. Tất cả những gì chúng ta phải làm để ngăn ngừa dạng tấn công này là thêm một trường ẩn vào form và định nghĩa biến SECRET_KEY trong file cấu hình Flask. Nếu chúng ta thực hiện đủ các điều kiện này, Flask-WTF đủ thông minh để thực hiện phần còn lại.

Nếu bạn đã từng viết HTML Web form, có thể bạn sẽ thấy lạ vì không có một thẻ HTML nào trong template này. Lý do là vì đối tượng form mà chúng ta khai báo trong forms.py biết cách để sinh ra mã HTML cho các trường tương ứng. Việc chúng ta cần làm là đặt khai báo {{ form.<field_name>.label }} ở tại nhãn của trường và {{ form.<field_name>() }} tại vị trí của trường đó. Nếu có trường nào cần có thêm những thuộc tính khác ngoài nhãn, chúng ta có thể thêm tham số tương ứng khi khởi tạo đối tượng cho trường đó trong lớp LoginForm (trong module forms.py). Ví dụ như khi bạn khởi tạo các đối tượng username và password, bạn có thể thêm vào tham số size để chỉ định độ dài cho thẻ HTML <input> do các đối tượng này sinh ra. Đây cũng là cách để bạn có thể chỉ định các thuộc tính CSS và mã xác định (ID) cho các trường trong form.

Hiển thị form

Chúng ta đã làm hầu hết các bước để tạo ra form đăng nhập (login). Tuy nhiên, để hiển thị form này trong trình duyệt, chúng ta cần tạo ra hàm hiển thị tương ứng và ánh xạ hàm này vào một URL – cụ thể là /login. Khi người sử dụng nhập URL này vào trình duyệt (http://localhost:5000/login) thì ứng dụng sẽ tạo ra form login và truyền nó vào template tương ứng để kết xuất. Chúng ta sẽ viết mã cho hàm hiển thị mới này trong module app/routes.py mà chúng ta đã tạo ra trong các phần trước:

app/routes.py: Hàm hiển thị cho form Login

Ở đây, chúng ta thêm tham chiếu đến lớp LoginForm từ module forms.py, khởi tạo một đối tượng từ lớp này và truyền đối tượng này vào template login.html bằng cách gọi hàm render_template(). Đừng ngạc nhiên với tham số cuối cùng khi gọi hàm (form = form), thật ra nó chỉ đơn giản là truyền đối tượng form mà chúng ta tạo ra từ lớp LoginForm trong dòng mã ở trên và truyền nó vào một Web form có tên là form (được định nghĩa trong template login.html). Tất cả chỉ có vậy, chúng ta đã thành công trong việc liên kết form mà chúng ta tạo ra bằng mã Python với template theo định dạng của Jinja2. Flask sẽ làm phần còn lại.

Để giúp cho việc truy cập form Login dễ dàng hơn, chúng ta sẽ thay đổi một chút thanh định hướng ở template base.html:

app/templates/base.html: Liên kết đến trang Login trong thanh định hướng:

Đến đây, bạn có thể chạy thử ứng dụng một lần nữa và kiểm tra form mà chúng ta đã tạo ra.  Sau khi ứng dụng được kích hoạt, hãy nhập địa chỉ http://localhost:5000/ vào trình duyệt và bấm vào liên kết “Login” trên thanh định hướng để truy nhập trang Login. Bạn thấy thế nào?

Nhận dữ liệu từ form

Đến đây, chúng ta đã có một form đăng nhập thật hấp dẫn. Tuy nhiên, nếu bạn thử bấm vào nút “Sign in”, chúng ta sẽ thấy một thông báo lỗi không mấy dễ chịu: “Method Not Allowed”. Chuyện gì xảy ra ở đây? Lý do là vì hàm hiển thị của chúng ta mới chỉ làm có một nửa chuyện mà nó cần làm: nó hiển thị form nhưng hoàn toàn không có mã lệnh nào để làm công việc xử lý dữ liệu nhận được khi người dùng bấm nút gởi (submit) dữ liệu – nút “Sign in” trên form của chúng ta. Tiếp theo, chúng ta sẽ cập nhật hàm hiển thị để nhận và kiểm tra dữ liệu do người sử dụng nhập vào form như sau:

app/routes.py: Nhận thông tin người sử dụng

Dễ thấy điểm mới đầu tiên trong đoạn mã trên là tham số methods trong phần decorator @app.route. Tham số này báo cho Flask là hàm hiển thị chấp nhận các yêu cầu theo dạng GET lẫn POST (theo mặc định, hàm hiển thị chỉ nhận yêu cầu theo dạng GET). Giao thức HTTP định nghĩa các yêu cầu GET là các yêu cầu trả về thông tin cho khách – hay là trình duyệt trong trường hợp này. Cho đến hiện tại, tất cả các yêu cầu trong ứng dụng của chúng ta đều theo dạng GET. Các yêu cầu POST thường được dùng khi cần gởi dữ liệu trong form về máy chủ (trong thực tế, chúng ta cũng có thể sử dụng yêu cầu GET để gởi dữ liệu, nhưng phương pháp này không được khuyến khích như đã nói trong phần trên). Lý do chúng ta nhận được thông báo lỗi “Method Not Allowed” vừa rồi là do form của chúng ta gởi dữ liệu về máy chủ theo dạng POST trong khi hàm hiển thị chưa được thay đổi và chỉ chấp nhận các yêu cầu GET. Bây giờ, chúng ta cung cấp tham số methods với cả POSTGET để báo với Flask rằng nó phải chấp nhận yêu cầu theo cả hai định dạng.

Hàm form.validate_on_submit() xử lý mọi sự kiện cho form. Khi trình duyệt gởi yêu cầu dạng GET về máy chủ để lấy và hiển thị trang đăng nhập, hàm này sẽ trả về giá trị  False, do đó hàm hiển thị sẽ bỏ qua các câu lệnh ở trong điều kiện if và thực hiện câu lệnh cuối cùng để hiện thị template Login.

Nếu người sử dụng bấm nút “Sign in”, trình duyệt sẽ gởi yêu cầu dưới dạng POST về máy chủ. Khi đó hàm form.validate_on_submit() sẽ tiến hành thu thập các dữ liệu từ các trường và chạy các đoạn mã kiểm tra dữ liệu. Nếu tất cả dữ liệu đều hợp lệ, hàm này sẽ trả về giá trị True và cho phép ứng dụng bắt đầu xử lý dữ liệu. Nếu một trong các trường có dữ liệu không hợp lệ, hàm này sẽ trả về False và do đó, sẽ hiển thị trang đăng nhập lần nữa tương tự như khi trình duyệt gởi yêu cầu dạng GET. Sau này chúng ta sẽ thêm một thông báo lỗi và hiển thị cho người dùng nếu có dữ liệu không hợp lệ.

Tiếp theo, khi hàm form.validate_on_submit() trả về True, hàm hiển thị sẽ gọi hai hàm mới được tham chiếu từ Flask. Hàm flash() sẽ giúp chúng ta hiển thị một thông điệp trên trang Web. Nhiều ứng dụng Web sử dụng kỹ thuật này để báo với người dùng các hoạt động tương tác của họ với ứng dụng có thành công hay không.Trong trường hợp này, chúng ta chỉ tạm thời sử dụng thông điệp để thông báo người dùng đã thành công đăng nhập vì chúng ta còn chưa có hệ thống quản lý và xác thực định danh của người dùng (chúng ta sẽ thêm phần này vào ứng dụng sau này). Hiện giờ, điều chúng ta có thể làm là hiển thị một thông điệp để xác nhận ứng dụng đã nhận được các thông tin của người dùng.

Hàm thứ nhì được gọi trong hàm hiển thị là redirect(). Hàm này báo cho trình duyệt biết để tự động chuyển sang một trang khác. Địa chỉ của trang sẽ được chuyển đến là tham số được truyền vào hàm. Trong trường hợp này, thông số đó là /index, vì vậy, ứng dụng sẽ tự động chuyển đến trang chủ nếu người dùng đăng nhập thành công.

Khi bạn gọi hàm flash(), Flask sẽ chứa thông điệp bạn truyền vào, nhưng thông điệp này sẽ không tự động hiển thị trên trang Web. Vì vậy, chúng ta cần thực hiện thêm một vài thao tác để hiển thị thông điệp này. Chúng ta sẽ thêm một số mã lệnh mới vào template base.html để nó được hiển thị trên mọi template được kế thừa từ template này. Sau đây là template base.html đã được cập nhật:

app/templates/base.html: Hiển thị thông điệp được truyền vào hàm flash()

Ở đây chúng ta sử dụng mệnh đề with để gán giá trị trả về từ hàm get_flashed_messages() vào biến messages.  Hàm get_flashed_messages() được định nghĩa bởi Flask, hàm này sẽ trả về danh sách các thông điệp đã được truyền vào hàm flash() trước đây. Lệnh điều kiện if tiếp theo sau kiểm tra xem biến messages có được gán giá trị nào không. Nếu có thì template sẽ tạo ra một danh sách (dùng thẻ HTML <ul>) và trình bày mỗi thông điệp trả về trong messages dưới dạng một phần từ của danh sách(dùng thẻ HTML <li>). Cách trình bày này có thể không được đẹp mắt lắm, nhưng cứ tạm chấp nhận như vậy. Sau này chúng ta sẽ cải tiến cách trình bày cho đẹp hơn.

Một điểm đáng lưu ý ở đây là các thông điệp được lưu lại bằng hàm flash() sẽ bị xóa đi khỏi danh sách thông điệp sau khi hàm get_flash_messages() được gọi. Vì vậy các thông điệp này chỉ có thể xuất hiện một lần sau khi chúng ta gọi hàm flash() để lưu lại chúng.

Đến đây bạn có thể thử chạy chương trình một lần nữa để tìm hiểu cách làm việc của form nếu bạn cảm thấy chưa hiểu rõ. Khi chạy chương trình, bạn hãy thử bấm nút “Sign in” để gởi dữ liệu về máy chủ nhưng chừa trống trường “username” hoặc “password”, làm như vậy bạn sẽ thấy cách hoạt động của các biến kiểm tra dữ liệu DataRequired – Chương trình sẽ báo lỗi và bạn sẽ không thể gởi dữ liệu (submit) được.

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

Các biến kiểm tra dữ liệu (validator) mà chúng ta đã liên kết vào các trường nhập liệu sẽ không cho phép các dữ liệu bất hợp lệ được gởi về ứng dụng trên máy chủ. Khi có dữ liệu bất hợp lệ, ứng dụng sẽ hiển thị lại form để người sử dụng nhập lại dữ liệu.

Nếu bạn cố submit dữ liệu không hợp lệ với form login của chúng ta, có lẽ bạn sẽ để ý thấy rằng dù form hoạt động đúng theo ý đồ của chúng ta (hiển thị lại form với các dữ liệu nhập để bạn sửa đổi), nhưng không có gợi ý gì trên form để cho bạn biết dữ liệu nào sai hoặc thiếu và cần được sửa đổi. Vì vậy chúng ta có thể cải tiến mã của chúng ta để đưa ra các thông báo lỗi thích hợp và giúp cho người dùng dễ dàng nhận ra dữ liệu không hợp lệ.

Thật ra thì khi có dữ liệu không hợp lệ, các biến kiểm tra dữ liệu đã sinh ra các thông báo lỗi rồi, điều chúng ta cần làm là thêm một vài dòng lệnh để hiển thị các thông báo lỗi này mà thời.

Sau đây là phiên bản mới của template login với các thông báo lỗi cho các trường username và password:

app/templates/login.html: Thông báo lỗi nhập liệu trong template Login

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 đỏ.

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. Ví dụ như trong thanh định hướng ở template base.html:

Hàm hiển thị cho trang login cũng có một liên kết dưới dạng tham số cho hàm redirect():

Tạm thời các liên kết này vẫn hoạt động bình thường. Tuy nhiên, nếu vì lý do nào đó bạn thay đổi địa chỉ (URL) cho các template này thì bạn sẽ phải tìm và thay đổi các liên kết này trong mã của bạn bằng địa chỉ mới.

Để tránh tình trạng này, Flask cung cấp một hàm rất hữu ích là url_for(). Hàm này sẽ tạo ra URL cho các liên kết của bạn từ hàm hiển thị. Ví dụ như url_for(‘login’) sẽ trả về /login, và url_for(‘index’) sẽ trả về /index. Tham số mà hàm này cần là tên của điểm truy nhập (endpoint) – cũng là tên của hàm hiển thị.

Bạn có thể thắc mắc tại sao dùng tên hàm hiển thị lại tốt hơn là dùng địa chỉ trực tiếp. Lý do chính là các URL thường có xu hướng dễ bị thay đổi hơn là các tên hàm hiển thị vì các hàm hiển thị thuộc lớp trong của ưng dụng và ít khi bị thay đổi. Một lý do nữa mà bạn sẽ thấy sau này là một số URL có các thành phần không cố định. Vì vậy, việc sử dụng các URL một cách thủ công sẽ đòi hỏi bạn cắt ghép nhiều phần lại với nhau – một quá trình rất mất công và dễ gây ra sai sót. Chúng ta hoàn toàn có thể tránh được việc này bằng cách sử dụng hàm url_for() để tạo ra các URL phức tạp từ tên hàm hiển thị.

Vì thế, từ bây giờ chúng ta sẽ tập làm quen bằng cách sử dụng hàm url_for() mỗi khi chúng ta cần tạo ra các địa chỉ (hay liên kết – URL) cho ứng dụng. Theo đó, mã cho thanh định hướng trong template base.html sẽ được sửa lại như sau:

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

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

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

Đế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

Your email address will not be published. Required fields are marked *