Trong phần này chúng ta sẽ tìm hiểu kỹ hơn về cú pháp của Template trong Django.
Ngôn ngữ Template của Django được thiết kế với mục đích chính là hỗ trợ những người đã từng làm việc với HTML, do đó nếu bạn đã từng học HTML thì sẽ không quá khó khăn để làm quen với Template.
Nếu bạn đã từng làm việc với các ngôn ngữ như Javascript, PHP, JSP… hay các ngôn ngữ có thể trộn chung với code HTML thì bạn cũng nên phân biệt là Template của Django không giống các ngôn ngữ đó. Các ngôn ngữ như Javascript, PHP… là ngôn ngữ lập trình, dùng để thực hiện các công việc mang tính logic, còn HTML chỉ là ngôn ngữ đánh dấu, tức là chỉ dùng để hiển thị giao diện chứ không mang nặng phần tính toán, Template cũng vậy, đây chỉ là ngôn ngữ hỗ trợ hiển thị giao diện.
Hệ thống Template của DJango cung cấp các thẻ có các chức năng tương tự như các câu lệnh trong Python, chẳng hạn như thẻ if
dùng để kiểm tra điều kiện, thẻ for
dùng trong vòng lặp… các thẻ này cũng không hoạt động giống như trong Python. Khi dịch thì Django chỉ dịch các thẻ Template chứ không đụng chạm gì tới HTML.
Template
Một Template đơn giản chỉ là một file text, có thể là bất cứ định dạng nào như .html, .xml, .csv
…v.v
Template chứa các biến sẽ được thay thế bằng giá trị thực khi dịch, và các thẻ dùng để thực hiện các câu lệnh logic.
Đây là một đoạn template cơ bản:
{% extends "base_generic.html" %} {% block title %} {{ section.title }} {% endblock %} {% block content %} <h1>{{ section.title }}</h1> {% for story in story_list %} <h2> <a href="{{ story.get_absolute_url }}"> {{ story.headline|upper }} </a> </h2> {{ story.tease|truncatewords:"100" }} {% endfor %} {% endblock %}
Biến
Biến là những thứ giống như {{
variable
}}
. Khi trình dịch template đọc đến một biến thì biến sẽ được thay thế bằng một giá trị thật (mà chúng ta truyền vào từ hàm render()
trong các view). Biến chỉ được đặt tên bằng các kí tự chữ cái và dấu gạch dưới (_)
.
Chúng ta dùng dấu chấm (.)
để truy xuất các thuộc tính của biến.
Trong đoạn code trên, {{ section.title }}
sẽ được thay thế bởi thuộc tính title
của đối tượng section.
Nếu bạn gõ sai tên biến thì Django sẽ thay bằng chuỗi rỗng chứ không báo lỗi.
Bộ lọc – Filter
Django cung cấp các bộ lọc để hỗ trợ chúng ta hiển thị dữ liệu theo nhiều cách khác nhau.
Ví dụ {{
name|lower
}}
,
trong đó lower là một bộ lọc, có tác dụng chuyển toàn bộ chữ cái thành chữ thường. Để dùng các bộ lọc thì chúng ta kèm theo dấu |
và tên bộ lọc vào sau tên biến.
Chúng ta cũng có thể dùng nhiều bộ lọc cùng một lúc, các bộ lọc được thực hiện tuần tự từ trái sang phải, ví dụ {{ text|escape|linebreaks }}
có tác dụng xuống dòng sau khi in dữ liệu.
Một số bộ lọc cần có cả tham số nữa, ví dụ như {{
bio|truncatewords:30
}}
có nghĩa là lấy 30 từ đầu tiên của biến bio
.
Nếu tham số của bộ lọc có khoảng trống thì chúng ta phải kẹp chúng trong cặp dấu nháy kép “”. Ví dụ {{ list|join:", " }}
sẽ nối các item trong biến list
thành một string, ngăn cách nhau bởi dấu phẩy và dấu cách.
Django có khoảng 60 bộ lọc. Bạn có thể tìm hiểu chúng tại đây. Ở đây mình chỉ giới thiệu một số bộ lọc thường dùng:
default:
nếu biến không có giá trị hoặc giá trị rỗng thì thay thế bằng giá trịdefault.
Ví dụ{{value|default:"nothing"}}
length:
trả về độ dài của dữ liệu, có thể áp dụng cho string và list. Ví dụ{{value|length}}
filesizeformat:
đổi kiểu số thành định dạng file, ví dụ{{value|filesizeformat}}
sẽ chuyển con số 123456789 thành 117.7 MB.
Thẻ – Tag
Thẻ có cú pháp {% tag %}
. Thẻ thì phức tạp hơn biến một tí, có thể dùng để tạo chuỗi, thực hiện các luồng điều khiển hoặc load các thông tin khác vào template.
Có một số thẻ đi kèm với cả thẻ kết thúc nữa, ví dụ {% tag %}
thì sẽ có {% endtag %}
.
Cũng giống như các bộ lọc, số lượng thẻ trong Django rất nhiều, bạn có thể xem danh sách các thẻ ở đây. Trong bài này mình cũng chỉ giới thiệu các thẻ thường dùng:
for:
duyệt qua một đối tượng danh sách. Ví dụ:
{% for athlete in athlete_list %} <li>{{ athlete.name }}</li> {% endfor %}
if, elif
vàelse:
kiểm tra một biến, nếu biến đúng thì thực hiện đoạn code bên trong.
{% if athlete_list %} Number of athletes: {{ athlete_list|length }} {% elif athlete_in_locker_room_list %} Athletes should be out of the locker room soon! {% else %} No athletes. {% endif %}
Trong đoạn code trên, nếu athlete_list
không rỗng thì in ra biến athlete_list,
ngược lại thì kiểm tra nếu athlete_in_locker_room_list
không rỗng thì in ra đoạn text “Athletes should…”, còn nếu không thì in ra đoạn text “No athletes.”
Ngoài kiểm tra các biến thì bạn cũng có thể áp dụng bộ lọc vào biến khi dùng thẻ if
:
{% if athlete_list|length > 1 %} Team: {% for athlete in athlete_list %} ... {% endfor %} {% else %} Athlete: {{ athlete_list.0.name }} {% endif %}
Hầu hết các bộ lọc chỉ trả về giá trị là kiểu chuỗi nên thường sẽ không dùng được các biểu thức so sánh với số nguyên như trên, length
chỉ là một trong số ít ngoại lệ.
block
vàextends:
kế thừa template, tức là dùng các file template khác. Chúng ta sẽ tìm hiểu thêm sau.
Bình luận – Comment
Bình luận được đặt trong cặp dấu {# #}
, các đoạn code bên trong cặp dấu này sẽ không được thực thi, ví dụ:
{# greeting #}hello
Django chỉ hỗ trợ bình luận trên một dòng. Nếu muốn bình luận trên nhiều dòng thì bạn dùng thẻ comment.
Thừa kế template
Tính năng mạnh mẽ nhất và cũng là phức tạp nhất của Template trong Django là tính năng thừa kế. Tính năng thừa kế cho phép bạn xây dựng một bộ template tổng quát và các template con, trong đó template tổng quát sẽ chứa các template con.
Ví dụ:
<!DOCTYPE html> <html lang="en"> <head> <link rel="stylesheet" href="style.css" /> <title>{% block title %}My amazing site{% endblock %}</title> </head> <body> <div id="sidebar"> {% block sidebar %} <ul> <li><a href="/">Home</a></li> <li><a href="/blog/">Blog</a></li> </ul> {% endblock %} </div> <div id="content"> {% block content %}{% endblock %} </div> </body> </html>
Đoạn code trên là template thiết kế bộ khung của một trang web, cấu trúc của template này bao gồm 2 cột, nằm giữa các thẻ block
. Nhiệm vụ của các template con là lấp đầy các khoảng trống của 2 cột đó.
Trong đoạn code trên có 3 thẻ block
là title, content
và sidebar,
nhiệm vụ của thẻ block
là báo cho Django biết đây là nơi mà các template con có thể override lại và chèn dữ liệu cần hiển thị vào đó.
Ví dụ về một template con:
{% extends "base.html" %} {% block title %}My amazing blog{% endblock %} {% block content %} {% for entry in blog_entries %} <h2>{{ entry.title }}</h2> {{ entry.body }} {% endfor %} {% endblock %}
Để một template con có thể override lại các thẻ block của template khác thì ở đầu template chúng ta khai báo thẻ extends
với tên file template. Trong ví dụ trên, trình biên dịch Django sẽ đọc trong template cha và thấy các thẻ block
trong template cha cũng có trong template con nên phần block
trong template con sẽ được chèn vào trong template cha.
Trong ví dụ trên thì tùy thuộc vào giá trị của blog_entries
mà kết quả là template cha có thể sẽ có nội dung như sau:
<!DOCTYPE html> <html lang="en"> <head> <link rel="stylesheet" href="style.css" /> <title>My amazing blog</title> </head> <body> <div id="sidebar"> <ul> <li><a href="/">Home</a></li> <li><a href="/blog/">Blog</a></li> </ul> </div> <div id="content"> <h2>Entry one</h2> This is my first entry. <h2>Entry two</h2> This is my second entry. </div> </body> </html>
Trong đoạn code template con chúng ta chỉ định nghĩa 2 block là content
và title,
nếu chúng ta không override block sidebar
thì nội dung của sidebar
sẽ được dùng là nội dung trong template cha.
Một số lưu ý:
- Thẻ
{%
extends
%}
luôn được đặt trước tất cả các thẻ còn lại. - Nên override từng thẻ block trong từng file template chứ không nên “ôm” tất cả vào trong một file.
- Thẻ block trong template con cũng có thể dùng lại nội dung của template cha, chỉ cần gọi
{{block.super}}
- Thẻ
{% endblock
%}
không cần phải có tên block theo sau nhưng chúng ta cũng nên đưa tên block vào để code dể đọc và dễ quản lý hơn. Ví dụ:
{% block content %} ... {% endblock content %}
- Không được có 2 thẻ block có tên giống nhau.
Tự động thoát HTML
Thoát HTML tức là trang web tự động chuyển đổi các kí tự đặc biệt trong HTML sang một dạng mã, dùng để bảo vệ website.
Ví dụ chúng ta có đoạn code template như sau:
Hello, {{ name }}
Thoạt nhìn thì có vẻ đơn giản, chúng ta có thể yêu cầu người dùng nhập vào một textbox rồi lưu vào biến name,
sau đó in nội dung trong biến name
ra thôi.
Nhưng nếu người dùng không nhập vào biến name
một đoạn chuỗi bình thường mà là đoạn chuỗi kì lạ như:
<script>alert('hello')</script>
Lúc này Django sẽ dịch đoạn template sang đoạn code HTML như sau:
Hello, <script>alert('hello')</script>
Khi chạy, trang web sẽ hiển thị một hộp thoại thông báo. Đó chỉ là trường hợp đơn giản, trong thực tế hacker có thể lợi dụng lỗ hổng này để khai thác nhiều thứ hơn nữa. Đây gọi là kỹ thuật tấn công Cross Site Scripting (XSS).
May mắn là mặc định trình dịch Template của Django tự động “thoát” (auto-escape) các kí tự đặc biệt, tức là chuyển đổi những kí tự sau đây thành những kí tự mã khác:
- Dấu
<
chuyển thành<
- Dấu
>
chuyển thành>
- Dấu nháy đơn
'
chuyển thành'
- Dấu nháy kép
"
chuyển thành"
- Dấu
&
chuyển thành&
Vì tính năng tự động thoát này mà bạn không cần phải lo đến vấn đề bảo mật XSS nữa.
Nhưng nếu bạn muốn tắt tính năng này thì làm sao?
Trước hết là tại sao bạn lại muốn tắt tính năng này, đó là vì trong một số trường hợp bạn thật sự muốn in các đoạn mã HTML, Javascript… lên trang web, chẳng hạn như bạn dự định xây dựng một blog về lập trình, như blog Phở Code 🙂 thì việc đăng các đoạn code lên trang web là thường xuyên, và do đó bạn cần các kí tự HTML hiển thị nguyên gốc – tức là không được “thoát”.
Để tắt “thoát” trên từng biến: chúng ta dùng bộ lọc safe:
This will be escaped: {{ data }} This will not be escaped: {{ data|safe }}
Đoạn code trên sẽ cho ra HTML như sau:
This will be escaped: <b> This will not be escaped: <b>
Tắt “thoát” trên template: chúng ta đặt nội dung file template hoặc một phần nào đó của template trong cặp thẻ autoescape:
{% autoescape off %} Hello {{ name }} {% endautoescape %}
Thẻ autoescape
nhận tham số on
hoặc off
tương ứng với bật và tắt.
Bạn cũng có thể lồng các cặp thẻ autoescape
vào nhau.
Auto-escaping is on by default. Hello {{ name }} {% autoescape off %} This will not be auto-escaped: {{ data }}. Nor this: {{ other_data }} {% autoescape on %} Auto-escaping applies again: {{ name }} {% endautoescape %} {% endautoescape %}
khi kế thừa template thì nếu template cha tắt “thoát” thì các template con cũng sẽ tự tắt tính năng này, nếu muốn bật tính năng này thì template con phải override lại.
Gọi phương thức
Bạn không chỉ có thể truy xuất dữ liệu từ các thuộc tính trong các biến mà còn có thể gọi phương thức của chúng nữa, tất nhiên là bạn chỉ có thể gọi các phương thức có trả về dữ liệu để hiển thị chứ không thể gọi các phương thức thực hiện tính toán mà không trả về thứ gì được.
Ví dụ, các đối tượng Queryset
có phương thức count()
trả vê số lượng phần tử của nó, chúng ta có thể gọi phương thức này như sau:
{{ task.comment_set.all.count }}
Bạn cũng có thể gọi các phương thức do bạn tự định nghĩa:
class Task(models.Model): def foo(self): return "bar"
{{ task.foo }}
Đáng tiếc là bạn không thể truyền tham số vào các lời gọi hàm trong Template vì mục đích chính của template cũng chỉ là hiển thị dữ liệu chứ không phải tính toán, do đó bạn chỉ có thể gọi các phương thức không có tham số.