Category Archives: Django

Ebook Django

Mình tập hợp một số ebook về Django cho các bạn tham khảo thêm. Tất cả đều là tiếng Anh hết, hiện tại số lượng tài liệu tiếng Việt rất hiếm.

Các bạn nhấp vào link để tải sách và source code (nếu có). Nếu link die thì comment phía dưới cho mình biết.

Chúc các bạn học tốt

Django By ExamplePDF | Source code

Beginning Django E-Commerce: PDF

Django Design Patterns and Best Practices: PDF

Learning Django Web Development: PDF

Lightweight Django: PDF

Web Development With Django Cookbook 2nd Edition: PDF | Source code

Django – Host website với PythonAnywhere

Từ trước tới nay chúng ta chỉ làm việc trên máy local, tức là máy của chính chúng ta. Trong bài này chúng ta sẽ tìm cách triển khai website đó lên mạng để cả thế giới có thể truy cập được 🙂

Trên mạng có rất nhiều nhà cung cấp dịch vụ host, ở đây chúng ta sẽ sử dụng nhà cung cấp PythonAnywhere vì PythonAnywhere miễn phí dịch vụ cho các website nhỏ lẻ, ít có khách truy cập, thích hợp cho việc test host.

Ngoài ra chúng ta sẽ sử dụng một dịch vụ khác nữa là GitHub, đây là dịch vụ lưu trữ code. Ngoài GitHub còn có rất nhiều dịch vụ lưu trữ code khác, tuy nhiên GitHub khá phổ biến, hầu hết các coder trên thế giới đều có tài khoản GitHub, nếu chưa có thì bạn cũng tạo ngay một cái đi 🙂

Về cơ bản thì bạn sẽ code trang web của bạn trên máy của bạn, sau đó đẩy code lên GitHub rồi lấy code đó xuống PythonAnywhere để hiển thị.

Git

Git là một “hệ thống quản lý phiên bản” được sử dụng bởi rất nhiều coder trên toàn thế giới. Git cho phép theo dõi sự thay đổi của các file theo thời gian để chúng ta có thể xem lại bất cứ lúc nào, trong Microsoft Word cũng có tính năng này nhưng của Git mạnh hơn nhiều.

Bạn có thể tải Git cho Windows về và cài đặt tại địa chỉ https://git-scm.com/. Khi cài đặt bạn nếu không hiểu thì có thể để các thiết lập mặc định là được rồi.

Git theo dõi sự thay đổi của các file trong các repository (viết tắt là repo). Để tạo một repo thì chúng ta mở Command Prompt (cmd), chuyển đến thư mục mà bạn lưu code của website trong đó, rồi gõ các lệnh sau:

C:\PhoCode>git init
Initialized empty Git repository in C:/PhoCode/.git/
C:\PhoCode>git config --global user.name "Pho Code"
C:\PhoCode>git config --global user.email "phocode7@gmail.com"

Bạn lưu ý đặt usernameemail theo ý bạn.

Việc tạo repository chỉ cần thực hiện một lần (tức là sau này chúng ta không cần khai báo username và email nữa). Git sẽ theo dõi sự thay đổi của các file và thư mục trong thư mục này, tuy nhiên sẽ có một số file mà chúng ta muốn bỏ qua. Để làm việc này thì chúng ta tạo một file có tên .gitignore trong thư mục đó. Bạn mở một trình editor (như notepad) lên rồi gõ đoạn sau vào:

*.pyc
__pycache__
myvenv
db.sqlite3
/static
.DS_Store

Lưu ý dấu chấm trong .gitignore rất quan trọng, nếu bạn không lưu tên file như thế được thì dùng Save As…

Nếu trong quá trình làm web mà bạn dùng cơ sở dữ liệu SQLite thì trong thư mục gốc của website sẽ có file db.sqlite3, chúng ta đưa file này vào danh sách trong .gitignore vì trên PythonAnywhere không dùng SQLite.

Tiếp theo chúng ta tạo một ứng dụng Django bình thường rồi đưa tất cả vào trong thư mục của repository. Ví dụ mình có một trang web như sau:

Capture1Capture

(Source: w3schools)

Chúng ta sẽ sử dụng lệnh git add để cập nhật lại nội dung trong repository. Thông thường chúng ta nên dùng lệnh git status trước khi dùng git add để xem mọi sự thay đổi trong repo có đúng như mình đã làm không. Chẳng hạn như bạn tạo file sai, thiếu hoặc thừa file/thư mục…v.v

C:\PhoCode>git status
On branch master

Initial commit

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    .gitignore
    PhoCode/
    blog/
    manage.py

nothing added to commit but untracked files present (use "git add" to track)

Sau khi đã kiểm tra xong thì chúng ta dùng lệnh addcommit để lưu lại như sau:

C:\PhoCode>git add --all
  [...]
C:\PhoCode>git commit -m "Pho Code site, first commit"
  [...]

Đẩy code lên trên GitHub

Nếu bạn chưa có tài khoản GitHub thì lên github.com tạo một cái.

Sau đó chúng ta cũng tạo một repo trên GitHub với tên tùy ý. Khi tạo thì bỏ check phần Initialize this repository with a README, phần Add .gitignore chọn None, phần Add a license chọn None.

Capture2

Sau khi tạo xong, bạn sẽ được cung cấp URL của repo dưới dạng HTTPS hoặc SSH. Ở đây chúng ta dùng HTTPS, copy dòng đó lại.

Capture

Việc tiếp theo là kết nối repo trên GitHub với repo trên máy của chúng ta bằng cách gõ 2 lệnh sau:

C:\PhoCode>git remote add origin https://github.com/phocode/phocode-site.git
C:\PhoCode>git push -u origin master
...

Tiếp theo bạn nhập username và mật khẩu của tài khoản GitHub là xong, lệnh git remote sẽ kết nối repo trên GitHub với repo trong máy bạn, lệnh git push sẽ đẩy code trong máy bạn lên GitHub, bây giờ code của bạn là nằm ở repo trên GitHub.

Lấy code xuống PythonAnywhere

Đầu tiên bạn vào www.pythonanywhere.com tạo một tài khoản Beginner (miễn phí). Sau đó chúng ta đăng nhập và sẽ được chuyển đến trang bảng điều khiển (dashboard), ở tab Consoles, bạn click dòng Bash để mở trình terminal tại phần Start a new console:

Capture

Màn hình terminal sẽ hiện lên và bạn sẽ thao tác với host thông qua màn hình này. Lưu ý là PythonAnywhere chạy trên nền Linux nên nếu bạn sử dụng Windows thì sẽ thấy hơi khác một tí. Tuy nhiên những câu lệnh của các phần mềm bên thứ 3 (như git) thì vẫn như cũ.

Chúng ta tiến hành lấy code từ GitHub xuống bằng cách dùng lệnh git clone:

Capture

Lệnh git clone sẽ tạo một thư mục có tên giống với repo trên tài khoản GitHub của bạn, trong thư mục này sẽ chứa các file và thư mục mà bạn đã đẩy lên đó từ trước. Bạn có thể dùng lệnh ls để xem:

$ cd phocode.site
$ ls
PhoCode blog manage.py

Tiếp theo chúng ta cần cài Django với phiên bản mà chúng ta đang dùng vào host trên PythonAnywhere, tuy nhiên chúng ta không cài như thường mà sẽ cài vào môi trường ảo, môi trường ảo cho phép chúng ta chạy các chương trình hay các gói Python ở các phiên bản khác nhau để tránh vấn đề xung đột (ví dụ website X chạy trên Django 1.8, trong khi website Y chạy Django 1.9…v.v).

Đầu tiên chúng ta tạo một môi trường ảo với Python 3.5 như sau:

$ virtualenv --python=python3.5 phocode_env

Đoạn code trên sẽ tạo một thư mục với tên phocode_env chứa các thư viện và các công cụ cần thiết, đây là một môi trường Python ảo sử dụng Python 3.5.

Tiếp theo chúng ta yêu cầu sử dụng môi trường này bằng cách gõ lệnh sau:

$ source phocode_env/bin/activate

Cuối cùng chúng ta cài đặt Django với phiên bản mà website của bạn đang sử dụng vào môi trường này bằng trình pip:

(phocode_env)$ pip install django==1.9.4
[...]

Tạo cơ sở dữ liệu trên PythonAnywhere

Như đã nói ở trên, PythonAnywhere có thể sử dụng cơ sở dữ liệu khác với cơ sở dữ liệu mà bạn dùng, nên việc tiếp theo bạn cần làm là tạo cơ sở dữ liệu trên PythonAnywhere như sau:

(phocode_env)$ python manage.py migrate
System check identified some issues:
[...]

Tạo web app

Chúng ta đã lấy được code, có cơ sở dữ liệu và có môi trường ảo. Việc cần làm tiếp theo là tạo một Web App trên PythonAnywhere.

Chúng ta thoát khỏi màn hình terminal (bằng cách bấm vào logo của PythonAnywhere), sau đó vào tab Web, click Add a new web app. Một hộp thoại mở lên thông báo cho bạn biết do chúng ta đang dùng bản miễn phí nên trang web sẽ có tên miền là <username>.pythonanywhere.com (ngoài ra web app này chỉ có thời gian hiệu lực là 3 tháng), click Next, tiếp theo chúng ta chọn Manual Configuration (chứ không chọn Django), tiếp theo chọn phiên bản Python 3.5 rồi click Next và đợi.

Tiếp theo bạn sẽ được chuyển đến trang cấu hình web app, chúng ta sẽ chỉ định web app sử dụng môi trường ảo mà chúng ta đã tạo ra hồi nãy. Trong phần VirtualEnv, click vào dòng Enter path to a virtualenv, if desired, rồi điền vào đường dẫn đến thư mục chứa môi trường ảo mà chúng ta đã tạo, thường là /home/<PythonAnywhere-username>/phocode-site/phocode_env.

Capture

 

 

 

 

Nếu bạn không biết đường dẫn chính xác là gì hoặc muốn chắc ăn, bạn có thể mở trình terminal lên, dùng lệnh cd để chuyển đến thư mục đó rồi dùng lệnh pwd để biết đường dẫn chính xác.

Capture1

Sau đó click vào dấu tick màu xanh để lưu đường dẫn môi trường ảo.

Cấu hình file WSGI

Django sử dụng giao thức WSGI, đây là chuẩn hiển thị web của Python. Khi bạn tạo một project Django trên máy thì bạn sẽ có một file tên wsgi.py.

Trong tab Web, chúng ta sửa file WSGI bằng cách click vào dòng /var/www/<PythonAnywhere-username>_pythonanywhere_com_wsgi.py trong phần Code. Chúng ta xóa toàn bộ nội dung file đó và thay bằng đoạn code sau:

import os
import sys

path = '/home/<PythonAnywhere-username>/phocode-site'
if path not in sys.path:
 sys.path.append(path)

os.environ['DJANGO_SETTINGS_MODULE'] = 'PhoCode.settings'

from django.core.wsgi import get_wsgi_application
from django.contrib.staticfiles.handlers import StaticFilesHandler
application = StaticFilesHandler(get_wsgi_application())

Lưu ý biến path là đường dẫn tới thư mục gốc chứa code của bạn, biến os.environ có phần đầu là tên thư mục chứa file settings.py. 

Lớp StaticFilesHandler chịu trách nhiệm việc tải các file tĩnh (như CSS, Javascript…) từ các site khác về.

Thế là xong, bây giờ bạn về lại tab Web, click vào nút Reload <PythonAnywhere-username>.pythonanywhere.com màu xanh lá cây rồi đợi, sau đó bạn có thể nhập đường dẫn đó vào trình duyệt để xem website của mình được rồi.

Capture

Nếu có bất kì lỗi gì xảy ra mà trang web không thể hiển thị hay hiển thị sai, bạn có thể xem thông báo lỗi trong file error.log, bạn có thể mở tìm thấy file này ở tab Web, phần Log files. Một số lỗi thường xảy ra là do:

  • Quên tạo môi trường ảo, quên kích hoạt môi trường ảo, quên cài Django vào môi trường ảo, quên tạo lại cơ sở dữ liệu.
  • Đường dẫn đến thư mục môi trường ảo thiết lập trong tab Web bị sai.
  • Thiết lập file WSGI sai
  • Phiên bản Python trong khi tạo môi trường ảo và phiên bản Python trong Web App không trùng khớp.

Django – Sessions

Đôi khi chúng ta muốn lưu lại một số thông tin trong quá trình duyệt web của user để sử dụng lại sau này, session cho phép chúng ta lưu trữ lại một số thông tin trên từng user, session có nhiều loại, có loại lưu dữ liệu trên server, có loại lưu trên client… Trong phần này chúng ta sẽ tìm hiểu về hệ thống session của Django.

Kích hoạt session

Chúng ta kích hoạt session bằng cách khai báo django.contrib.middle.SessionMiddleware trong biến MIDDLEWARE_CLASSES trong file settings.py:

MIDDLEWARE_CLASSES = [
    #...,
    'django.contrib.sessions.middleware.SessionMiddleware',
    #...
]

Mặc định thì session đã được kích hoạt sẵn khi tạo project nên chúng ta cũng không cần phải chỉnh sửa gì trong này.

Cấu hình session

Dữ liệu trong session có thể được lưu trong cơ sở dữ liệu, file hoặc trong cache, mặc định thì Django lưu trong CSDL.

Lưu session trong cơ sở dữ liệu

Để cấu hình session lưu trong cơ sở dữ liệu thì chúng ta khai báo django.contrib.sessions trong biến INSTALLED_APPS:

INSTALLED_APPS = [
    #...
    'django.contrib.sessions',
    #...
]

Sau khi thiết lập thì chúng ta phải chạy lệnh manage.py migrate để Django tạo bảng tương ứng trong CSDL.

Lưu session trong cache

Bạn chỉ nên dùng loại session này nếu bạn thiết lập kiểu cache của server là memcachedĐể lưu session trong cache thì chúng ta thiết lập biến SESSION_ENGINE trong file settings.py là:

  • django.contrib.sessions.backends.cache: session lưu trong loại này không được đảm bảo vì nếu bộ nhớ cache đầy thì dữ liệu session sẽ bị xóa, nhưng loại này truy xuất dữ liệu cũng như ghi dữ liệu rất nhanh.
  • django.contrib.sessions.backends.cached_db: loại này thì vừa lưu dữ liệu session trong cache vừa lưu vào CSDL luôn, nếu session trong cache bị xóa thì Django sẽ tìm session trong CSDL nên dữ liệu được đảm bảo hơn loại trên nhưng cũng vì thế và tốc độ chậm hơn.

Trong thực tế thì chúng ta sẽ dùng loại thứ 2 vì dữ liệu session thường cũng không lớn nên việc đọc ghi sẽ không tốn thời gian mấy. Cũng chính vì loại thứ 2 lưu session trong cơ sở dữ liệu nên chúng ta cũng phải thiết lập luôn cả phần Lưu session trong cơ sở dữ liệu ở trên.

Lưu session trong file

Để lưu session trong file thì chúng ta khai báo SESSION_ENGINEdjango.contrib.sessions.backends.fileSESSION_FILE_PATH là đường dẫn đến tên file dùng để lưu session, đường dẫn phải là đường dẫn tuyệt đối và server phải có quyền đọc/ghi file trên đĩa cứng.

Lưu session trong cookie

Chúng ta khai báo SESSION_ENGINEdjango.contrib.sessions.backends.signed_cookies. Bạn để ý là trong file settings.py có một biến tên là SECRET_KEY có giá trị là một chuỗi được Django tạo ra ngẫu nhiên, biến này sẽ được dùng để mã hóa dữ liệu trong sessions, chúng ta sẽ tìm hiểu về biến này sau.

Truy xuất session trong view

Tham số request trong hàm view sẽ chứa một thuộc tính có tên là session khi SessionMiddleware được kích thoạt, đây là một đối tượng dictionary. Chúng ta có thể đọc/ghi thuộc tính này ở bất kỳ đâu trong hàm view.

Dưới đây là một số thao tác với session:

Lấy giá trị theo khóa:

>>> color = request.session['color'] 
>>> color1 = request.session.get('color', 'red')

Tham số 'red' là tham số trả về mặc định, tức là nếu không tìm thấy khóa 'color' trong session thì trả về giá trị 'red'.

Thiết lập khóa:

>>> request.session['color'] = 'blue'

Xóa khóa ra khỏi session, nếu không tìm thấy khóa thì báo lỗi KeyError:

>>> del request.session['color']

Kiểm tra xem khóa có tồn tại trong session:

>>> 'color' in request.session

Xóa toàn bộ dữ liệu session:

>>> request.session.flush()

Thiết lập thời gian tồn tại cho session, tham số nhận vào là số giây, nếu để 0 thì tồn tại cho đến khi user tắt trình duyệt.

>>> request.session.set_expiry(300)

Lấy thời gian còn lại của sessiontheo giây.

>>> request.session.get_expiry_age()

Ví dụ

Chúng ta sẽ xây dựng một hệ thống login (đăng nhập) có hỗ trợ session đơn giản. Mặc định project của mình có tên cũng như thư mục là mysite. 

Chúng ta tạo app có tên là login (nhớ khai báo trong biến INSTALLED_APP):

C:\Project\mysite>python manage.py startapp login

Bên trong app chúng ta tạo lớp form với tên LoginForm:

from django import forms

class LoginForm(forms.Form):
    username = forms.CharField(max_length = 100)
    password = forms.CharField(widget = forms.PasswordInput())

Tiếp theo chúng ta tạo 2 trang template, một trang dùng để hiển thị form đăng nhập, một trang template để hiển thị thông báo đăng nhập thành công.


<form action="{% url 'login.views.loginView' %}" method="POST">
    {% csrf_token %}
    <table>
        <tr>
            <td>Username:</td>
            <td><input type="text" name="username" /></td>
        </tr>
        <tr>
            <td>Password:</td>
            <td><input type="password" name="password" /></td>
        </tr>
        <tr>
            <td></td>
            <td><input type="submit" value="Login"/></td>
        </tr>
    </table>
</form>

File login.html dùng để hiển thị form đăng nhập. Tại đây thuộc tính action trong thẻ form trỏ đến hàm loginView mà chúng ta sẽ viết ở dưới.

Hello, <strong>{{username}}</strong>

File loggedin.html hiển thị thông báo đăng nhập thành công.

Tiếp theo chúng ta viết các hàm view.

from django.shortcuts import render
from django.http import HttpResponse
# Create your views here.
from login.forms import LoginForm

def loginView(request):
    username = "Wrong username or password"
 
    if request.method == "POST":
        MyLoginForm = LoginForm(request.POST) 
        if MyLoginForm.is_valid(): 
            if MyLoginForm.cleaned_data['username'] == 'admin': 
                if MyLoginForm.cleaned_data['password'] == '123':
                    username = MyLoginForm.cleaned_data['username'] 
                    request.session['username'] = username
                    request.session.set_expiry(15);
    else:
        MyLoginForm = LoginForm()
 
    return render(request, 'loggedin.html', {'username':username})
 
def formView(request):
    if request.session.has_key('username'):
        username = request.session['username']
        return render(request, 'loggedin.html', {'username':username})
    else:
        return render(request, 'login.html', {})
 
def logoutView(request):
    try:
        del request.session['username']
    except:
        pass
    return HttpResponse("Good bye!")

Chúng ta viết 3 hàm view là loginView(), formView()logoutView().

if MyLoginForm.cleaned_data['username'] == 'admin': 
    if MyLoginForm.cleaned_data['password'] == '123':

Hàm loginView() sẽ kiểm tra dữ liệu được gửi lên. Ở đây chúng ta chỉ kiểm tra đơn giản với usernameadminpassword123. 

username = MyLoginForm.cleaned_data['username'] 
request.session['username'] = username
request.session.set_expiry(15);

Nếu dữ liệu phù hợp thì chúng ta thiết lập khóa username và thời gian hiệu lực là 15 giây trong thuộc tính session.

if request.session.has_key('username'):
    username = request.session['username']
    return render(request, 'loggedin.html', {'username':username})
else:
    return render(request, 'login.html', {})

Hàm formView() sẽ kiểm tra xem session có chứa dữ liệu hay không bằng phương thức has_key(), nếu chưa có thì tạo form đăng nhập với template login.html. Còn nếu có rồi thì hiển thị câu chào mừng trong template loggedin.html.

try:
    del request.session['username']

Hàm logoutView() có nhiệm vụ xóa khóa username trong session nếu có.

Cuối cùng là tạo URL:

from django.conf.urls import url, include, patterns

urlpatterns = patterns('login.views',
    url(r'^login/', 'loginView'),
    url(r'^greeting/', 'formView'),
    url(r'^logout/', 'logoutView')
)

Khác với các bài trước là chúng ta tạo URL cho riêng từng app, ở đây chúng ta cho trỏ URL thẳng đến các hàm view trong app luôn. Lớp patterns nhận vào tham số đầu tiên là đường dẫn đến module chứa các hàm view, các tham số tiếp theo là các đối tượng url.

Bây giờ chúng ta có thể chạy server và trỏ đến URL localhost:8000/greeting để đăng nhập.

Capture

Đăng nhập với usernameadminpassword123 để được chuyển đến trang chào mừng.

Capture1

Nếu chưa hết 15 giây mà chúng ta lại trỏ đến localhost:8000/greeting thì Django sẽ hiện ra trang “Hello,…” luôn chứ không hiện ra form đăng nhập nữa.

Ngoài ra chúng ta có thể trỏ đến localhost:8000/logout để xóa session là có thể đăng nhập lại.

Capture2

Django – Cache

Website được tạo ra ngày nay là website động, tức là nội dung HTML sẽ được server sinh ra rồi trả về cho người dùng mỗi khi người dùng gửi request đến, khác với website tĩnh là các file HTML đã có sẵn, người dùng request thì chỉ cần trả về file HTML đó thôi. Điều này cũng có nghĩa là website động sẽ tốn nhiều thời gian trả lời hơn so với website tĩnh, và khi lượt truy cập website càng nhiều thì thời gian này càng tăng lên gấp nhiều lần.

Kỹ thuật cache ra đời là để cắt giảm quá trình tính toán của website để có thể cung cấp nội dung cho người dùng một cách nhanh chóng hơn, cache có nhiều loại nhưng nhìn chung thì đều tuân theo thuật toán sau đây:

user gửi request một trang web, tìm xem trang đó đã có cache hay chưa
nếu đã có cache:
    trả về cache
ngược lại:
    tạo cache
    lưu lại trang cache vừa tạo
    trả về trang cache vừa tạo

Django cung cấp sẵn hệ thống cache rất mạnh mẽ, chúng ta sẽ lần lượt đi tìm hiểu.

Thiết lập Cache

Việc thiết lập cache trong Django rất đơn giản, chỉ là cho Django biết cache sẽ được lưu ở đâu thôi bởi vì cache lưu trên RAM sẽ có hiệu suất khác hẳn so với lưu trên file.

Tất cả các thông tin cài đặt cache đều được lưu trong biến CACHE trong file settings.py, mặc định khi tạo project thì thông tin này chưa có, chúng ta phải tự thêm vô.

Lưu cache trên RAM – Memcached

Memcached đúng với cái tên của nó, là lưu nội dung trên bộ nhớ RAM của máy chủ, do đó loại cache này có tốc độ tìm kiếm cũng như trả về nhanh nhất, thích hợp cho các website lớn có lượng truy cập cao, các website như Facebook hay Wikipedia đều dùng loại cache này.

Memcached là một chương trình dạng dịch vụ (tức là chạy ngầm bên dưới hệ điều hành) được cấp một lượng RAM nhất định, cung cấp các hàm cho phép cập nhật dữ liệu trên cache, không đụng chạm gì tới đĩa cứng hoặc cơ sở dữ liệu.

Để thiết lập memcached thì chúng ta cung cấp những thông tin sau:

  • BACKEND: django.core.cache.backends.memcached.MemcachedCache hoặc django.core.cache.backends.memcached.PyLibMCCache.
  • LOCATION: theo cú pháp IP:PORT, trong đó IP là địa chỉ máy lưu cache, port là cổng tương ứng của trình dịch vụ cache.

Ví dụ:

CACHES = {
    'default': {
    'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
    'LOCATION': '127.0.0.1:11211',
    }
}

Đoạn code trên thiết lập memcache tại máy localhost, tức là lưu trên chính server đó trên port 11211.

Chúng ta cũng có thể cho chạy nhiều trình memcached trên nhiều máy:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': [
        '172.19.26.240:11211',
        '172.19.26.242:11211',
        ]
    }
}

Chỉ cần khai báo thêm IP và Port, ngăn cách nhau bởi dấu phẩy là được, port có thể khác nhau chứ không cần phải giống nhau.

Lưu cache trên cơ sở dữ liệu

Loại này sẽ lưu dữ liệu trong một bảng trên CSDL. Để sử dụng thì chúng ta thiết lập như sau:

  • BACKEND: django.core.cache.backends.db.DatabaseCache
  • LOCATION: tên bảng được dùng để lưu cache trong CSDL, tất nhiên phải là bảng trắng, chưa có gì trong đó

Ví dụ:

CACHES = {
    'default': {
    'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
    'LOCATION': 'my_cache_table',
    }
}

Sau khi đã khai báo trong file settings.py thì chúng ta phải tạo bảng lưu cache bằng cách chạy lệnh:

python manage.py createcachetable

Django sẽ tự động tạo bảng với tên tương ứng trong biến LOCATION cùng các trường cần thiết để lưu cache.

Lưu cache trong file

Loại cache này sẽ lưu dữ liệu trong file, khi nào cần thì sẽ đọc file. Chúng ta thiết lập như sau:

  • BACKEND: django.core.cache.backends.filebased.FileBasedCache
  • LOCATION: đường dẫn đến file

Ví dụ:

CACHES = {
    'default': {
    'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
    'LOCATION': 'c:/cache.txt',
    }
}

Đường dẫn file phải là đường dẫn tuyệt đối – tức là phải có tên ổ đĩa cứng.

Các tham số khác

Ngoài 2 tham số bắt buộc là BACKENDLOCATION thì khi thiết lập cache chúng ta còn có các tham số tùy chọn khác như sau:

  • TIMEOUT: thời gian lưu trữ cache, mặc định là 300 giây (5 phút), bạn có thể đưa vào là None và Django sẽ lưu cache vô thời hạn.
  • OPTIONS: danh sách một số tùy chọn cache, bao gồm MAX_ENTRIES là số lượng trang tối đa được phép cache, mặc định là 300; CULL_FREQUENCY là số lượng trang cache bị xóa khi số lượng trang cache đã đạt mức tối đa, tính bằng 1 / CULL_FREQUENCY, ví dụ CULL_FREQUENCY là 2 thì nếu số trang cache đã đạt đến 300 trang thì số lượng trang bị hủy là 150 trang. Nếu thiết lập CULL_FREQUENCY=0 thì xóa toàn bộ cache.
  • KEY_PREFIX: đây là một chuỗi được thêm vào đầu các khóa được lưu trong cache. Chúng ta sẽ tìm hiểu thêm về khóa ở dưới.
  • VERSION: số phiên bản cache sử dụng. Chúng ta cũng sẽ tìm hiểu ở dưới.
  • KEY_FUNCTION: tên hàm thực hiện việc tạo chuỗi key lưu trong cache.

Ví dụ:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
        'LOCATION': 'c:/cache.txt',
        'TIMEOUT': 60,
        'OPTIONS': {
            'MAX_ENTRIES': 1000
        }
    }
}

Đoạn trên thiết lập cache được lưu trong file cache.txt, thời gian mỗi trang tồn tại là 60 giây, số lượng cache tối đa là 1000.

Nếu chúng ta có lỡ thiết lập sai biến nào thì Django sẽ không báo lỗi mà bỏ qua xem biến khác, do đó khi thiết lập cache chúng ta nên kiểm tra lại cho kỹ.

Cache cả trang

Việc cache cũng có nhiều kiểu, chúng ta có thể cache từng phần hoặc cache nguyên cả trang web, nguyên trang tức là trong toàn bộ website của bạn có trang nào thì cũng đều được cache lại. Để cache cả trang web thì chúng ta khai báo các lớp sau trong biến MIDDLEWARE_CLASSES trong file settings.py:

MIDDLEWARE_CLASSES = [
    'django.middleware.cache.UpdateCacheMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.cache.FetchFromCacheMiddleware',
]

Lưu ý là bạn phải khai báo đúng thứ tự như trên thì mới sử dụng được.

Lớp FetchFromCacheMiddleware sẽ lưu lại các trang web mà có mã trả về là 200 và có request được gửi lên bởi phương thức GETHEAD. Các yêu cầu đến trang web có tham số khác nhau thì được cache khác nhau, tức là giả sử chúng ta có một trang liệt kê danh sách sản phẩm được phân trang thì mỗi request với số trang khác nhau sẽ được phân trang khác nhau.

UpdateCacheMiddleware sẽ ghi một số thông tin vào cache như ngày/giờ tạo trang cache, thời gian tồn tại…

Cache từng view

Nếu cache toàn bộ website có hơi thừa thì chúng ta cũng có thể cache từng trang tùy vào từng hàm view. Để cache từng view thì chúng ta dùng hàm cache_page() trong lớp django.views.decorators.cache.

Chúng ta cũng không dùng hàm này như các hàm thông thường mà khai báo trước tên hàm view, ví dụ:

from django.views.decorators.cache import cache_page

@cache_page(60 * 15)
def my_view(request):
    ...

Trước hàm này chúng ta thêm dấu @. Hàm cache_page() nhận 1 tham số là thời gian cache tồn tại tính theo giây, ở trên chúng ta cho thời gian này là 60 * 15, tức là 15 phút, bạn có thể ghi ra số giây rõ ràng luôn chứ không nhất thiết phải dùng biểu thức nhân như vậy.

Cũng giống như cache cả trang, cache trên view cũng phân biệt tham số, tức là trang có URL như localhost:8000/cache-page/1localhost:8000/cache-page/2 sẽ được cache riêng.

Cache template

Ngoài việc cache các hàm view, bạn cũng có thể cache các phần của template. Thẻ {% load cache %} sẽ tải cache về nếu có, cặp thẻ {% cache %}...{% endcache %} sẽ cache lại nội dung bên trong nó trong một khoảng thời gian, thẻ này nhận vào 2 tham số bắt buộc là tên cache và thời gian cache. Ví dụ:

{% load cache %}
{% cache 500 sidebar %}
    ...
{% endcache %}

Cache dữ liệu

Nhiều khi việc cache nguyên cả một trang web là quá thừa thãi, đôi khi còn phản tác dụng. Đối với những trang web có nội dung thay đổi liên tục và mỗi lần tải trang là một lần truy vấn một lượng dữ liệu lớn, chẳng hạn như Facebook, thì việc cache nguyên trang là không tối ưu, thay vào đó chúng ta chỉ nên cache những thứ ít thay đổi như giao diện…

Django cũng cung cấp các hàm cache cấp thấp để hỗ trợ bạn làm việc này, bạn có thể cache bất cứ kiểu dữ liệu nào của Python như string, dictionary, list…

Truy vấn dữ liệu cache

Những dữ liệu mà bạn đã cache lại sẽ được lưu trong đối tượng django.core.cache.caches, đây là một đối tượng tĩnh toàn cục, tức là bạn có thể truy xuất ở bất kỳ đâu, đối tượng này lưu dữ liệu theo dạng dictionary, tức là mỗi phần tử là một cặp khóa-giá trị, chúng ta có thể truy xuất dữ liệu cache như sau:

>>> from django.core.cache import caches
>>> cache1 = caches['key1']
>>> cache2 = caches['key1']
>>> cache1 is cache2
True

Nếu chúng ta truy xuất sai tên khóa thì Django sẽ báo lỗi exception InvalidCacheBackendError.

Thiết lập cache

Lớp django.core.cache cung cấp 2 phương thức để thiết lập và lấy dữ liệu cache là set(key,value,timeout)get(key). Ví dụ:

>>> cache.set('my_key', 'hello, world!', 30)
>>> cache.get('my_key')
'hello, world!'

Tham số timeout là tùy chọn, nếu chúng ta không đưa tham số này vào thì Django sẽ sử dụng thông số được thiết lập trong file settings.py. Nếu không tìm thấy dữ liệu thì phương thức get() sẽ trả về đối tượng None.

Nhưng nếu muốn chúng ta có thể thêm tham số default vào phương thức get() và Django sẽ trả về giá trị default nếu không tìm thấy dữ liệu cache:

>>> cache.get('my_key', 'has expired')
'has expired'

Ngoài phương thức set() chúng ta còn có phương thức add() dùng để thiết lập thêm dữ liệu cache. Điểm khác nhau giữa 2 phương thức là set() sẽ cập nhật lại dữ liệu nếu cache đã tồn tại, còn add() thì không:

>>> cache.set('add_key', 'Initial value')
>>> cache.add('add_key', 'New value')
>>> cache.get('add_key')
'Initial value'

Nếu bạn muốn lấy một giá trị mà không biết là đã có trong cache hay chưa, bạn có thể dùng phương thức get_or_set(), đúng như cái tên của nó, là sẽ trả về giá trị nếu đã có, còn nếu chưa có thì tạo mới:

>>> cache.get('my_new_key') # returns None
>>> cache.get_or_set('my_new_key', 'my new value', 100)
'my new value'

Ngoài các dữ liệu thông thường, bạn cũng có thể truyền giá trị là một phương thức nào đó, tất nhiên là phương thức này phải tra về một giá trị nào đó:

>>> import datetime
>>> cache.get_or_set('some-timestamp-key', datetime.datetime.now)
datetime.datetime(2014, 12, 11, 0, 15, 49, 457920)

Nếu bạn muốn lấy nhiều giá tri cùng một lúc thì dùng phương thức get_many(), phương thức này rất hữu ích nếu chúng ta dùng phương pháp cache trên file hoặc trên cơ sở dữ liệu, vì chỉ cần đọc file cache một lần rồi lấy hết dữ liệu ra, không như phương thức get() là mỗi lần chỉ đọc một giá trị:

>>> cache.set('a', 1)
>>> cache.set('b', 2)
>>> cache.set('c', 3)
>>> cache.get_many(['a', 'b', 'c'])
{'a': 1, 'b': 2, 'c': 3}

Tương tự, chúng ta cũng có phương thức set_many() thiết lập nhiều giá trị cùng một lúc:

>>> cache.set_many({'a': 1, 'b': 2, 'c': 3})
>>> cache.get_many(['a', 'b', 'c'])
{'a': 1, 'b': 2, 'c': 3}

Để xóa một giá trị cache nào đó thì chúng ta dùng phương thức delete():

>>> cache.delete('a')

Tương tự với get_many()set_many(), chúng ta có delete_many():

>>> cache.delete_many(['a', 'b', 'c'])

Nếu muốn xóa toàn bộ cache thì dùng phương thức clear():

>>> cache.clear()

Phiên bản cache

Phiên bản ở đây là chúng ta dùng các con số để đánh dấu dữ liệu cache nào thuộc về nhóm nào chứ không phải là phiên bản phần mềm cache của Django hay cái gì đó tương tự 🙂 Mặc định Django sẽ dùng số phiên bản là con số được khai báo trong file settings.py, nhưng chúng ta cũng có thể override lại trong khi viết code bằng cách đưa vào tham số version:

>>> cache.set('my_key', 'hello world!', version=2)
>>> cache.get('my_key')
None
>>> cache.get('my_key', version=2)
'hello world!'

Trong đoạn code trên chúng ta thiết lập khóa my_key với version là 2, khi chúng ta lấy giá trị của khóa này mà không chỉ định rõ version nào thì Django sẽ lấy khóa có version được lưu trong file settings.py, mặc định ở đây trong file settings.py lưu version là 1 nên đoạn code trên trả về đối tượng None.

Chúng ta cũng có thể tăng/giảm giá trị của version thông qua 2 phương thức incr_version()decr_version():

# version = 3
>>> cache.incr_version('my_key') 
>>> cache.get('my_key')
None
>>> cache.get('my_key', version=2)
None
>>> cache.get('my_key', version=3)
'hello world!' 

Django – Gửi Email

Mặc định thì Python có sẵn một module hỗ trợ gửi email là smtplib nhưng Django cũng có module riêng giúp chúng ta gửi mail một cách dễ dàng và nhanh chóng là django.core.mail.

Hàm send_mail()

Cú pháp:

send_mail(subject, message, from_email, recipient_list, fail_silently=False, auth_user=None, auth_password=None, connection=None, html_message=None)

Trong đó subject, message, from_emailrecipient_list là bắt buộc phải có.

  • subject: tiêu đề mail.
  • message: nội dung mail gửi đi.
  • from_email: địa chỉ mail dùng để gửi.
  • recipient_list: danh sách địa chỉ mail gửi tới.
  • fail_silently: nếu là True thì Django sẽ giải phóng lỗi smtplib.SMTPException nếu mail không gửi được, mặc định False.
  • auth_user: địa chỉ mail dùng để gửi, không cần đưa vào nếu đã thiết lập trong file settings.py.
  • auth_password: mật khẩu của địa chỉ mail dùng để gửi, không cần đưa vào nếu đã thiết lập trong file settings.py.
  • connection: tên đối tượng mail backend xử lý việc gửi mail, nếu không truyền vào thì Django sẽ tự động tạo một đối tượng mặc định, thường chúng ta cũng không quan tâm đến tham số này.
  • html_message: nội dung mail gửi đi dưới dạng HTML.

Hàm này sẽ trả về 1 nếu mail gửi thành công và 0 nếu thất bại.

Ví dụ:

from django.core.mail import send_mail

send_mail('Subject', 'Message', 
          'from@example.com', 
          ['to@example.com'], 
          fail_silently=False)

Các thiết lập của mail được lưu trong file settings.py, trong file này chúng ta khai báo các biến sau:

  • EMAIL_HOST: tên máy chủ mail, ví dụ smtp.google.com
  • EMAIL_PORT: số port của máy chủ mail, ví dụ 587
  • EMAIL_HOST_USER: địa chỉ email dùng để gởi đi, ví dụ from@example.com
  • EMAIL_HOST_PASSWORD: mật khẩu đăng nhập email dùng để gởi đi.
  • EMAIL_USE_TLSEMAIL_USE_SSL: True nếu muốn dùng các giao thức bảo mật SSL/TLS

Hàm send_mass_mail()

Hàm send_mass_mail() đơn giản là dùng để gửi một lúc nhiều mail.

Cú pháp:

send_mass_mail(datatuple, fail_silently=False, auth_user=None, auth_password=None, connection=None)

Các tham số cũng giống như trong hàm send_mail() ngoại trừ tham số datatuple, tham số này nhận một đối tượng tuple, mỗi phần tử lại là một tuple khác lưu những thông tin về mail được gửi đi có dạng như sau:

(subject, message, from_email, recipient_list)

Ví dụ:

message1 = ('Subject 1', 
            'Message 1', 
            'from@example.com', 
            ['first@example.com', 'other@example.com'])
message2 = ('Subject 2', 
            'Message 2', 
            'from@example.com', 
            ['second@test.com'])
send_mass_mail((message1, message2), fail_silently=False)

Hàm send_mass_mail() cũng trả về 0 hoặc 1 tương ứng với thành công hoặc thất bại.

Sự khác nhau giữa send_mail()send_mass_mail()send_mail() khi gửi mail nào thì phải mở và đóng kết nối tới server SMTP, trong khi send_mass_mail() chỉ cần mở một kết nối rồi gửi tất cả luôn, do đó send_mass_mail() hiệu quả hơn send_mail().

Hàm mail_admins()

Hàm này có tác dụng gửi mail cho ban quản trị website, mail của quản trị website (admin) được liệt kê trong biến ADMINS trong file settings.py dưới dạng:

...
ADMINS = [('John', 'john@example.com'), ('Mary', 'mary@example.com')]
...

Cú pháp:

mail_admins(subject, message, fail_silently=False, connection=None, html_message=None)

Lưu ý: đoạn chuỗi được lưu trong biến EMAIL_SUBJECT_PREFIX trong file settings.py sẽ được chèn vào trước tham số subject, mặc định biến này có giá trị ” [Django] “.

Các thông tin khác như server, port, mail người gửi sẽ được dùng trong file settings.py.

Hàm mail_managers()

Hàm này có công dụng giống như hàm mail_admins(), chỉ khác là gửi cho danh sách mail trong biến MANAGERS trong file settings.py.

Cú pháp:

mail_managers(subject, message, fail_silently=False, connection=None, html_message=None)

Lớp EmailMessage

Các hàm send_mail() hay send_mass_mail() chỉ là các hàm cấp cao hỗ trợ chúng ta gửi mail cho dễ dàng, thực chất việc gửi mail được thực hiện qua lớp EmailMessage.

Cũng chính vì thế mà có nhiều tính năng có trong lớp EmailMessagemà chúng ta không sử dụng được với 2 hàm trên chẳng hạn như BCC, gửi file đính kèm, gửi nội dung đa phương tiện…

Bản chất thì lớp EmailMessage chỉ làm công việc là tạo nội dung email sẽ được gửi đi, còn phần gửi được thực hiện bởi email-backend phía dưới nữa. Lớp EmailMessage cũng chỉ hỗ trợ gửi từng mail đơn lẻ với phương thức send(). Để gửi nhiều mail cùng một lúc thì chúng ta phải can thiệp vào backend.

Lớp EmailMessage có các thuộc tính và cũng là tham số trong hàm khởi tạo như sau:

  • subject: tiêu đề mail
  • body: nội dung mail
  • from_email: địa chỉ mail gửi đi
  • to: danh sách các mail gửi tới
  • bcc: danh sách các địa chỉ được dùng trong Bcc
  • connection: đối tượng backend, nếu chúng ta không khai báo thì Django sẽ tự tạo một đối tượng mặc định
  • attachment: danh sách các file đính kèm, chúng ta có thể dùng lớp email.MIMEBase.MIMEBase hoặc khai báo theo dạng (filename, content, mimetype).
  • headers: một đối tượng dictionary dùng cho header của mail.
  • cc: list hoặc tuple các địa chỉ mail dùng cho Carbon Copy (Cc).
  • reply_to: list hoặc tuple địa chỉ mail dùng khi trả lời mail.

Ví dụ:

from django.core.mail import EmailMessage

email = EmailMessage('Hello',
                     'Body',
                     'from@example.com',
                     ['to1@example.com', 'to2@example.com'],
                     ['bcc@example.com'],
                     reply_to=['another@example.com'],
                     headers={'Message-ID': 'foo'})

Lớp EmailMessage có các phương thức sau đây:

  • send(fail_silently=False) sẽ gửi mail đi. Tham số fail_silently sẽ giải phóng lỗi exception nếu là True, ngược lại (và mặc định) là False.
  • message() khởi tạo một đối tượng django.core.mail.SafeMIMEText hoặc django.core.mail.Safe.MIMEMultipart, đây là các lớp kế thừa từ lớp email.MIMEText.MIMEText trong Python, có nhiệm vụ lưu thông tin về nội dung mail được gửi đi.
  • recipients() trả về các danh sách địa chỉ mail có trong đối tượng EmailMessage, bất kể là to, bcc, hay cc...
  • attach(): gửi file đính kèm, chúng ta có thể truyền vào một đối tượng email.MIMEBase.MIMEBase hoặc một tuple có dạng (filename, content, mimetype). Ví dụ:
message.attach('design.png', img_data, 'image/png')
  • attach_file() cũng gửi file đính kèm với tham số là đường dẫn đến file trong máy. Ví dụ:
message.attach_file('images/weather_map.png') 

Django – Ngôn ngữ Template

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, elifelse: 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ệ.

  • blockextends: 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, contentsidebar, 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à contenttitle, 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 &lt;
  • Dấu > chuyển thành &gt;
  • Dấu nháy đơn ' chuyển thành &#39;
  • Dấu nháy kép " chuyển thành &quot;
  • Dấu & chuyển thành &amp;

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: &lt;b&gt;
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ố.

Django – Phân trang

Trong phần này chúng ta sẽ học cách phân trang. Django cung cấp lớp django.core.paginator hỗ trợ phân trang rất tốt.

Ví dụ

Chúng ta tạo một app có tên là pagination.

C:\Project\mysite>python manage.py startapp pagination

Thêm app vào danh sách INSTALLED_APPS:

INSTALLED_APPS = [
    #...
    'pagination',
    #...
]

Tiếp theo chúng ta tạo model và thêm vào một số dòng dữ liệu mẫu để test.

from django.db import models

# Create your models here.

class Customer(models.Model):
    name = models.CharField(max_length=100)
    country = models.CharField(max_length=20)

Chúng ta định nghĩa model Customer có 2 trường là namecountry.

Cập nhật lại CSDL:

python manage.py makemigrations
python manage.py migrate

Đoạn script sau sẽ tạo một số dòng dữ liệu mẫu trong bảng pagination_customer.

import os
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings");
django.setup()

from pagination.models import Customer

Customer.objects.create(name='Alfreds Futterkiste', country='Germany')
Customer.objects.create(name='Ana Trujillo Emparedados y helados', country='Mexico')
Customer.objects.create(name='Antonio Moreno Taquería', country='Mexico')
Customer.objects.create(name='Around the Horn', country='UK')
Customer.objects.create(name='Berglunds snabbköp', country='Sweden')
Customer.objects.create(name='Blauer See Delikatessen', country='Germany')
Customer.objects.create(name='Blondel père et fils', country='France')
Customer.objects.create(name='Bólido Comidas preparadas', country='Spain')
Customer.objects.create(name='Bon app', country='France')
Customer.objects.create(name='Bottom-Dollar Marketse', country='Canada')
Customer.objects.create(name='Bs Beverages', country='UK')
Customer.objects.create(name='Cactus Comidas para llevar', country='Argentina')
Customer.objects.create(name='Centro comercial Moctezuma', country='Mexico')
Customer.objects.create(name='Chop-suey Chinese', country='Switzerland')
Customer.objects.create(name='Comércio Mineiro', country='Brazil')
Customer.objects.create(name='Consolidated Holdings', country='UK')
Customer.objects.create(name='Drachenblut Delikatessend', country='Germany')
Customer.objects.create(name='Du monde entier', country='France')
Customer.objects.create(name='Eastern Connection', country='UK')

Tiếp theo chúng ta viết template và hàm view.

from django.shortcuts import render

# Create your views here.
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from .models import Customer

def listing(request): 
    customer_list = Customer.objects.all()
    paginator = Paginator(customer_list, 5)
 
    pageNumber = request.GET.get('page')
    try:
        customers = paginator.page(pageNumber)
    except PageNotAnInteger:
        customers = paginator.page(1)
    except EmptyPage:
        customers = paginator.page(paginator.num_pages)
 
    return render(request, 'list.html', {'customers':customers})

Chúng ta sử dụng lớp Pagination để thực hiện phân trang.

from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger

Đầu tiên chúng ta import một số lớp cần thiết.

paginator = Paginator(customer_list, 5)

Hàm khởi tạo Paginator() nhận vào 2 tham số, tham số đầu tiên là một đối tượng QuerySet, tham số thứ 2 là số tượng item trên mỗi “trang”. Trong ví dụ trên chúng ta đưa đối tượng customer_list vào với số lượng 5 item mỗi trang.

pageNumber = request.GET.get('page')

URL của chúng ta có thêm tham số page là số thứ tự của trang muốn xem.

try:
    customers = paginator.page(pageNumber)
except PageNotAnInteger:
    customers = paginator.page(1)
except EmptyPage:
    customers = paginator.page(paginator.num_pages)

Nếu tham số page không hợp lệ, chẳng hạn như page=abc thì Paginator sẽ giải phóng lỗi PageNotAnInterger, trong trường hợp này chúng ta trả về trang đầu tiên với phương thức Paginator.page(), hoặc nếu page nằm ngoài pham vi trang cho phép, chẳng hạn như chúng ta chỉ có 4 trang nhưng tham số page=1000 thì Paginator sẽ giải phóng lỗi EmptyPage, ở đây chúng ta xử lý bằng cách trả về trang cuối cùng bằng thuộc tính num_pages.

Template:

<table>
    <tr>
        <th>Customer name</th>
        <th>Country</th>
    </tr>
    {% for customer in customers %}
    <tr>
        <td>{{ customer.name }}</td>
        <td>{{ customer.country }}</td>
    </tr>
    {% endfor %}
</table>
<div class="pagination">
    <span class="step-links">
        {% if customers.has_previous %}
            <a href="?page={{ customers.previous_page_number }}">Previous</a>
        {% endif %}
    </span>

    <span class="current">
        Page {{ customers.number }} of {{ customers.paginator.num_pages }}.
    </span>
    
    <span>
        {% if customers.has_next %}
            <a href="?page={{ customers.next_page_number }}">Next</a>
        {% endif %}
    </span>
</div>

Cuối cùng chúng ta tạo URL và chạy server để xem kết quả.

from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^$', views.listing),
]
from django.conf.urls import url, include
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^pagination/', include('pagination.urls')),
]

Capture

Django – Kiểm tra dữ liệu gửi lên form

Trong bài này chúng ta sẽ tìm hiểu cách kiểm tra sự đúng đắn của dữ liệu.

Kiểm tra dữ liệu ở đây là kiểm tra xem dữ liệu được gửi lên có đúng với yêu cầu hay không, cũng có thể xem đây là câu lệnh catch trong quá trình kiểm tra lỗi exception vậy, chẳng hạn như chúng ta yêu cầu người dùng nhập vào email nhưng người dùng nhập sai cấu trúc, hay upload ảnh avatar mà lại gửi lên một file PDF, nguy hiểm hơn nữa là up file ảnh giả – tức file có đuôi .jpg, .png… nhưng lại không chứa dữ liệu ảnh mà chứa mã độc… vì thế chúng ta nên kiểm tra dữ liệu được gửi lên trước khi lưu chúng vào CSDL, và Django cũng cung cấp một số phương thức/hàm hỗ trợ chúng ta làm việc này.

Django có một số phương thức kiểm tra dữ liệu có sẵn trong các lớp FieldForm, các phương thức này khi kiểm tra nếu dữ liệu đúng thì trả lại dữ liệu đó, nếu sai thì sẽ trả về một lỗi exception là ValidationError.

Chúng ta đã từng dùng phương thức is_valid() của lớp django.forms.form, phương thức này khi được gọi sẽ tự động gọi các phương thức kiểm tra và dữ liệu nào không có lỗi thì sẽ được lưu trong thuộc tính form.cleaned_data dưới dạng một đối tượng Dictionary, bạn vẫn có thể truy xuất dữ liệu gốc trong thuộc tính request.POST.

Các phương thức kiểm tra khi được gọi sẽ thực hiện kiểm tra dữ liệu theo cách của Django nhưng chúng ta cũng nên override lại các phương thức kiểm tra để chúng làm theo yêu cầu của chúng ta. Chúng ta sẽ tìm hiểu về các phương thức này ở dưới.

Validator

Các lớp Field của Django hỗ trợ một công cụ tiện ích có tên là validatorCác validator sẽ kiểm tra dữ liệu của lớp đó theo một quy luật nào đó và trả về lỗi ValidationError nếu có. Chúng ta có thể định nghĩa validator bằng cách truyền vào ngay hàm khởi tạo Field hoặc định nghĩa bên trong các lớp Field riêng với thuộc tính default_validators. Ví dụ:

from django.forms import CharField
from django.core import validators

class SlugField(CharField):
    default_validators = [validators.validate_slug]

Chúng ta định nghĩa lớp SlugField kế thừa từ CharField, thuộc tính default_validators sẽ quy định kiểu kiểm tra dữ liệu của lớp này.

>>> slugExample = forms.SlugField()
>>> slugExample2 = forms.CharField(validators=[validators.validate_slug])

Cả 2 cách tạo đối tượng trên sẽ sử dụng chung validator.

validators.validate_slug

Validators là một module của Django, module này chứa các lớp như EmailValidator, RegexValidatorURLValidator, Min/MaxValueValidator cho phép chúng ta định nghĩa các luật kiểm tra khác nhau. Ngoài các lớp đó Djang còn chứa sẵn một số đối tượng validator tĩnh quy định sẵn các luật thông dụng, ở ví dụ trên chúng ta dùng validate_slug, đối tượng này quy định chuỗi chỉ được phép chứa kí tự chữ cái, kí tự số, dấu gạch ngang và dấu nối _. Ngoài validate_slug ra còn có các đối tượng khác như validate_email, validate_unicode_slug… bạn có thể xem danh sách tại đây.

Các phương thức kiểm tra dữ liệu

Django cung cấp các hàm/phương thức kiểm tra dữ liệu sau đây:

Phương thức kiểm tra trên Field:

  • Phương thức to_python() có sẵn trong mỗi lớp Field, phương thức này sẽ chuyển đổi kiểu dữ liệu field tương ứng sang kiểu dữ liệu của Python, nếu chuyển được thì không có gì, nếu không sẽ có lỗi ValidationError. Ví dụ như khi chuyển một FloatField thì chúng ta sẽ được một đối tượng float hoặc một lỗi exception.
  • Phương thức validate() có sẵn trong mỗi lớp Field, thường được dùng khi không có sẵn các validator.
  • Phương thức run_validators() có trong mỗi lớp Field tự động kiểm tra tất cả các validator của Field và cái nào có lỗi thì gom lại vào một đối tượng ValidationError. Thường chúng ta không dùng phương thức này.
  • Phương thức clean() có trong các lớp kế thừa từ lớp Field sẽ gọi các phương thức to_python(), validate()run_validators() trên field theo thứ tự và nếu gặp lỗi tại đâu thì dừng quá trình kiểm tra và giải phóng exception tại đó. Nếu dữ liệu hợp lệ thì sẽ được truyền vào thuộc tính cleaned_data của form.

Phương thức kiểm tra trên Form:

  • Phương thức clean_<tên thuộc tính field>() được Django tạo ra trong các lớp kế thừa từ lớp Form, trong đó <tên thuộc tính field> là tên do chúng ta đặt cho từng thuộc tính field khi định nghĩa form. Phương thức này kiểm tra dữ liệu theo yêu cầu chứ không quan tâm đến kiểu dữ liệu. Chẳng hạn như form của chúng ta có một thuộc tính tên là serialnumber có kiểu CharField và thuộc tính này phải là duy nhất – tức là không có 2 bản ghi nào có serialnumber giống nhau, thì việc kiểm tra sẽ được thực hiện trong phương thức clean_serialnumber() và chúng ta phải override lại phương thức này.

  • Phương thức clean() có trong các lớp kế thừa từ lớp Form kiểm tra dữ liệu trên từng field, chúng ta sẽ override lại khi cần dùng. Phương thức này được gọi sau khi các phương thức kiểm tra trên Field đã thực thi.

Khi phương thức is_valid() được gọi, từng thuộc tính field mà chúng ta khai báo theo thứ tự từ trên xuống dưới khi định nghĩa Form sẽ gọi từng phương thức kiểm tra đã nêu trên, phương thức Field.clean() sẽ được gọi đầu tiên, sau đó là Form.clean_<tên thuộc tính field>(), cuối cùng là phương thức Form.clean(), phương thức Form.clean() sẽ luôn được gọi bất kế các phương thức trước đó có trả về lỗi hay không.

Ví dụ

Chúng ta sẽ override một số phương thức đã nói trên.

from django import forms
from django.core.validators import validate_email

class MultiEmailField(forms.Field):
    def to_python(self, value):        
        if not value:
            return []
        return value.split(',')

    def validate(self, value):   
        super(MultiEmailField, self).validate(value)

Chúng ta định nghĩa lớp MultiEmailField kế thừa từ lớp Field. Ở đây chúng ta override lại 2 phương thức to_python()validate().

if not value:
    return []
return value.split(',')

Trong phương thức to_python(), chúng ta chỉ đơn giản là kiểm tra xem dữ liệu có rỗng hay không, nếu không thì trả về một list các string tách ra từ string gốc dùng dấu phẩy.

super(MultiEmailField, self).validate(value)

Trong phương thức validate(), chúng ta gọi lại phương thức validate() của lớp cha là lớp Field. 

Bây giờ chúng ta định nghĩa lớp ContactForm và override một số phương thức kiểm tra của Form.

from django import forms

class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    message = forms.CharField()
    sender = forms.EmailField()
    recipients = MultiEmailField()
    cc_myself = forms.BooleanField(required=False)

    def clean_recipients(self):
        data = self.cleaned_data['recipients']
        if "fred@example.com" not in data:
            raise forms.ValidationError("You have forgotten about Fred!")

        return data

    def clean(self):
        cleaned_data = super(ContactForm, self).clean()
        cc_myself = cleaned_data.get("cc_myself")
        subject = cleaned_data.get("subject")

        if cc_myself and subject:
            if "help" not in subject:
                raise forms.ValidationError(
                    "Did not send for 'help' in the subject despite "
                    "CC'ing yourself."
                )

Chúng ta override 2 phương thức là clean_recipients()clean().

recipients = MultiEmailField()

Chúng ta sử dụng lớp MultiEmailField như các lớp field bình thường. Khi phương thức is_valid() được gọi thì các phương thức to_python()validate() của từng field sẽ được gọi, bất kể có override hay không.

def clean_recipients(self):
    data = self.cleaned_data['recipients']     
    if "fred@example.com" not in data:
        raise forms.ValidationError("You have forgotten about Fred!")
    return data   

Sau khi các phương thức to_python()validate() đã được gọi thì Django sẽ lưu các dữ liệu đã được kiểm tra là hợp lệ trong thuộc tính cleaned_data. Chúng ta có thể lấy ra để kiểm tra tiếp.  Trong đoạn code trên chúng ta kiểm tra nếu fred@example.com không có trong list thì giải phóng lỗi ValidationError.

def clean(self):
    cleaned_data = super(ContactForm, self).clean()
    ...
    if cc_myself and subject:
        if "help" not in subject:
            raise forms.ValidationError(
                "Did not send for 'help' in the subject despite "
                "CC'ing yourself."
            )

Phương thức clean() khá đơn giản, chúng ta chỉ kiểm tra xem nếu cc_myselfsubject không rỗng thì trong subject phải có chứa chuỗi “help”, nếu không thì báo lỗi. Ngoài ra trước đó chúng ta cũng gọi lại phương thức clean() của lớp cha.

Django – Tạo form từ model

Giả sử bạn xây dựng một ứng dụng có sử dụng cơ sở dữ liệu – tức là bạn phải có định nghĩa các lớp model, thường thì bạn sẽ phải tạo các form cho người dùng nhập dữ liệu và các form này thường cũng có các field gần như là giống hoàn toàn với các field có trong model, ví dụ như trong một ứng dụng Blog bạn có một model tên là BlogComment có các field lưu trữ thông tin các comment do người dùng nhập vào, vậy thì bạn cũng phải tạo form để người dùng nhập comment vào bài viết.

Nhưng việc định nghĩa một lớp Model và một lớp Form có các field gần như giống nhau hoàn toàn là rất vô nghĩa, mà lại còn viết từ code HTML hoặc cùng lắm là kế thừa từ lớp Form của Django đi nữa thì code vẫn rất mệt mỏi, vì thế nên Django có thể tự định nghĩa luôn các lớp form giùm bạn từ các lớp model mà bạn đã định nghĩa trước, tức là trong model có các field nào thì Django cũng sẽ tự tạo lớp form có các field tương ứng như thế, bạn không cần phải định nghĩa lại nữa.

Kiểu dữ liệu

Mỗi lớp Field của Model sẽ có một lớp Field của Form tương ứng. Dưới đây là bảng tên các lớp Field tương ứng của ModelForm.

Lưu ý là các lớp ModelField hỗ trợ 3 kiểu khóa ngoại là 1-n, n-n1-1 nhưng bên FormField chỉ có 2 kiểu duy nhất được hỗ trợ là ModelChoiceField tương ứng với ForeignKeyModelMultipleChoiceField tương ứng với ManyToManyField. Cả 2 lớp trên đều nhận một tham số khi khởi tạo là một đối tượng QuerySet.

Mỗi lớp Form Field có chung một số thuộc tính như sau:

  • required: nếu thuộc tính blank của ModelTrue thì required = False và ngược lại.
  • label: thuộc tính này được gán tự động bằng thuộc tính verbose_name của Model và kí tự đầu được viết hoa.
  • help_text: thuộc tính này được gán tự động bằng thuộc tính help_text của model.

Ví dụ

Chúng ta định nghĩa các lớp như sau.

from django.db import models
from django.forms import ModelForm

TITLE_CHOICES = (
    ('MR', 'Mr.'),
    ('MRS', 'Mrs.'),
    ('MS', 'Ms.'),
)

class Author(models.Model):
    name = models.CharField(max_length=100)
    title = models.CharField(max_length=3, choices=TITLE_CHOICES)
    birth_date = models.DateField(blank=True, null=True)
   
class Book(models.Model):
    name = models.CharField(max_length=100)
    authors = models.ManyToManyField(Author)

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = ['name', 'title', 'birth_date']

class BookForm(ModelForm):
    class Meta:
        model = Book
        fields = ['name', 'authors']

Chúng ta định nghĩa 2 lớp ModelAuthorBook, sau đó định nghĩa 2 lớp Form có các Field được tạo tự động từ 2 lớp model là AuthorFormBookForm. Các lớp này được kế thừa từ lớp django.forms.ModelForm thay thì lớp django.forms.Form như trước.

class Meta:
    model = Author
    fields = ['name', 'title', 'birth_date']

Chúng ta định nghĩa model được dùng trong lớp nội Meta, thuộc tính model sẽ tạo các field tương ứng từ lớp Model tương ứng, thuộc tính fields sẽ chọn các trường nào được dùng, nếu chúng ta không khai báo thuộc tính fields thì mặc định Django sẽ dùng tất cả các thuộc tính có trong lớp Model, ngoại trừ thuộc tính id nếu bạn không khai báo thuộc tính khóa chính. Ngoài ra lớp Meta còn có thuộc tính exclude, thuộc tính này trái ngược với thuộc tính fields, tức là thuộc tính này sẽ quy định các trường nào không được phép sử dụng.

Định nghĩa từ lớp ModelForm tương đương với định nghĩa từ lớp Form trong các bài trước như sau:

from django import forms

class AuthorForm(forms.Form):
    name = forms.CharField(max_length=100)
    title = forms.CharField(max_length=3,
    widget=forms.Select(choices=TITLE_CHOICES))
    birth_date = forms.DateField(required=False)

class BookForm(forms.Form):
    name = forms.CharField(max_length=100)
    authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all())

Lưu dữ liệu

Lớp ModelForm có một phương thức tên là save(), phương thức này tạo mới hoặc lưu một đối tượng Model vào cơ sở dữ liệu giống như phương thức save() bên các lớp Model vậy.

Khi khởi tạo các đối tượng ModelForm thì chúng ta có thể truyền các đối tượng model có sẵn vào hàm khởi tạo, nếu chúng ta chỉ truyền vào không thôi thì Django sẽ tạo mới một đối tượng trên CSDL, nếu chúng ta truyền vào và ghi rõ truyền với tham số là instance thì Django sẽ cập nhật dữ liệu trong đối tượng đó. Ví dụ:

>>> from myapp.models import Article
>>> from myapp.forms import ArticleForm
>>> f = ArticleForm(request.POST)
>>> new_article = f.save()

Đoạn code trên tạo đối tượng mới từ đối tượng POST được gửi lên và lưu vào CSDL.

>>> a = Article.objects.get(pk=1)
>>> f = ArticleForm(request.POST, instance=a)
>>> f.save()

Đoạn code trên tạo một đối tượng ArticleForm, truyền dữ liệu từ POST vào rồi truyền cập nhật trên đối tượng a chứ không tạo mới.

Lưu ý là nếu chúng ta không kiểm tra sự đúng đắn của dữ liệu thì khi gọi phương thức save(), Django cũng sẽ tự động làm việc đó và sẽ giải phóng lỗi exception ValueError nếu dữ liệu có lỗi, trong bài trước chúng ta đã dùng một phương thức để kiểm tra dữ liệu là is_valid(), chúng ta sẽ tìm hiểu thêm về cách kiểm tra dữ liệu trong các bài sau.

Tùy chỉnh lớp Field

Như trong bảng tên các lớp ModelFieldFormField tương ứng đã chỉ rõ ở trên, nếu bạn khai báo loại field nào trong lớp model thì Django sẽ tạo lớp field tương ứng bên form. Chẳng hạn như khi bạn khai báo trường DateTimeField bên model thì Django sẽ tạo một trường DateTimeField tương ứng bên form.

Mặc dù tạo form bằng cách code từ đầu với HTML hoặc dùng lớp django.forms.Form sẽ cho bạn quyền điều khiển nhiều hơn nhưng không có nghĩa là ModelForm không cho phép bạn tùy chỉnh các lớp field có sẵn.

Chẳng hạn như để tùy chỉnh kiểu hiển thị thì chúng ta dùng thuộc tính widgets có trong lớp nội Meta. Ví dụ:

from django.forms import ModelForm, Textarea
from myapp.models import Author

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = ('name', 'title', 'birth_date')
        widgets = {
            'name': Textarea(attrs={'cols': 80, 'rows': 20}),
        }

Mặc định lớp CharField sẽ hiển thị thẻ <input type="text">, nhưng đoạn code trên sẽ chỉ định CharField hiển thị <input type="textarea">.

Tương tự bạn có thể tùy chỉnh lại các thuộc tính như label, help_text trong lớp nội Meta nếu muốn:

from django.utils.translation import ugettext_lazy as _

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = ('name', 'title', 'birth_date')
        labels = {
            'name': _('Writer'),
        }
        help_texts = {
            'name': _('Some useful help text.'),
        }

Bạn cũng có thể quy định các thuộc tính phải dùng lớp field do bạn định nghĩa thông qua thuộc tính field_classes trong lớp Meta:

from django.forms import ModelForm
from myapp.models import Article

class ArticleForm(ModelForm):
    class Meta:
        model = Article
        fields = ['pub_date', 'headline', 'content', 'reporter', 'slug']
        field_classes = {
            'pub_date': MyDateTimeField,
        }

class MyDateTimeField():
    #...
    pass

Thừa kế Form

ModelForm cũng chỉ là một lớp Python, do đó bạn có thể cho thừa kế và mở rộng các trường hoặc các phương thức của chúng. Ví dụ:

class EnhancedArticleForm(ArticleForm):
    def clean_pub_date(self):
        #... 
        pass

Lớp EnhancedArticleForm kế thừa từ lớp ArticleForm nên sẽ có tất cả các thuộc tính và phương thức của lớp ArticleForm, ngoài ra lớp EnhancedArticleForm còn có phương thức clean_pub_date() của riêng nó nữa.

Tương tự với các lớp Model, các lớp form cũng có thể thừa kế cả lớp nội Meta:

class RestrictedArticleForm(EnhancedArticleForm):
    class Meta(ArticleForm.Meta):
        exclude = ('body',)

Lớp RestrictedArticleForm kế thừa từ lớp EnhancedArticleForm ngoại trừ lớp này không sử dụng thuộc tính body.

Lưu ý là khi sử dụng đa thừa kế thì lớp Meta của lớp con chỉ thừa kế từ lớp Meta của lớp cha đầu tiên trong danh sách thôi.

Hàm factory

Nếu bạn quá “lười” để ngồi định nghĩa lại một lớp kế thừa từ ModelForm, sau đó ngồi khai báo các thuộc tính fields, exclude hay lớp Meta thì Django cũng cung cấp một hàm cho phép bạn tạo một lớp ModelForm một cách “cấp tốc” là hàm modelform_factory(), ví dụ:

from django.forms import modelform_factory
from myapp.models import Book

BookForm = modelform_factory(Book, fields=("author", "title"))

Hàm này đặc biệt hữu dụng khi lớp Form mà bạn muốn định nghĩa không có gì khác nhiều so với lớp cha.

Bạn cũng có thể đưa các thuộc tính chỉnh sửa vào ngay trong hàm modelform_factory():

from django.forms import Textarea
Form = modelform_factory(Book, form=BookForm, widgets={"title": Textarea()}, fields=['name'])

Django – Upload file

Trong phần này chúng ta sẽ tạo form thực hiện chức năng upload file lên thư mục gốc của server.

Các hàm view mà chúng ta đã viết đều nhận một tham số đầu vào là một đối tượng HttpRequest, đối tượng này lưu trữ những thông tin về dữ liệu được gửi từ người dùng lên server, ví dụ như khi bạn nhập localhost:8000/ thì trình duyệt sẽ tạo một đối tượng lưu trữ những thông tin chẳng hạn như phương thức gửi lên, địa chỉ ip, url, ngày giờ gửi… rồi nén tất cả những thông tin đó lại và gửi đến server của chúng ta qua giao thức HTTP. Khi dữ liệu được gửi đến, server Django sẽ tạo một đối tượng thuộc lớp HttpRequest và đưa những thông tin được gửi đến vào đối tượng này rồi truyền vào làm tham số cho hàm view tương ứng, trong hàm view đó chúng ta có thể lấy các thông tin từ đối tượng HttpRequest này để xử lý.

Bên trong lớp HttpRequest có một thuộc tính tên là FILES, thuộc tính này lưu trữ những thông tin về file được gửi lên để chúng ta xử lý. Ngoài ra nếu muốn bạn có thể tìm hiểu các thuộc tính khác ở đây.

Bạn cũng nên lưu ý là vì một số vấn đề bảo mật nên bạn nên cấu hình server chỉ cho phép nhận các file có dung lượng giới hạn tại mức nào đó thôi, ví dụ như chỉ cho phép nhận từ 2 đến 5 MB đối với như file ảnh… Mình sẽ đề cập đến vấn đề này trong bài khác.

Tạo form upload file

Chúng ta tạo một app mới với tên là file_uploader:

C:\Project\mysite>python manage.py startapp file_uploader

Chúng ta khai báo app cho project.

INSTALLED_APPS = [
    #...
    'file_uploader',
    #...
]

Kế tiếp chúng ta tạo file forms.py bên trong thư mục file_uploader:

from django import forms

class UploadFileForm(forms.Form):
    title = forms.CharField(max_length=50)
    file = forms.FileField()

Để hiển thị thẻ element chọn file thì chúng ta dùng lớp FileField().

Tiếp theo chúng ta tạo thư mục templates và file template:


<form action="" method="POST" enctype="multipart/form-data"> 
    {% csrf_token %} 
    <table>
        {{ form.as_table }}
    </table>
    <input type="submit" value="Submit" />
</form>

Để gửi file lên thì trong thẻ <form> chúng ta phải khai báo thuộc tính enctype="multipart/form-data".

Sau khi đã có template và form để hiển thị thì chúng ta tiến hành tạo view để kết nối chúng với nhau.

from django.db import models

# Create your models here.
from django.http import HttpResponse
from .forms import UploadFileForm

def fileUploaderView(request):
    if request.method == 'POST':
        form = UploadFileForm(request.POST, request.FILES)
        if form.is_valid():
            upload(request.FILES['file'])
            return HttpResponse("<h2>File uploaded successful!</h2>")
        else:
            return HttpResponse("<h2>File uploaded not successful!</h2>")

    form = UploadFileForm()
    return render(request, 'fileUploaderTemplate.html', {'form':form})
 
def upload(f): 
    file = open(f.name, 'wb+') 
    for chunk in f.chunks():
        file.write(chunk)

Chúng ta viết hàm view fileUploaderView() và hàm xử lý việc ghi file là upload().

if form.is_valid():

Phương thức is_valid() có trong lớp django.forms.Form sẽ kiểm tra tính hợp lệ của dữ liệu được gửi lên. Mục đích chính của lớp Form trong Django chính là hỗ trợ chúng ta kiểm tra sự đúng đắn của dữ liệu được gửi lên. Chẳng hạn như khi bạn xây dựng chức năng up ảnh avatar thì bạn chỉ cho phép user gửi các file ảnh lên thôi, nhưng kẻ tấn công có thể gửi các file ảnh “giả” lên mà trình duyệt không biết, do đó khi dữ liệu được gửi tới server, chúng ta nên kiểm tra trước các dữ liệu này trước khi lưu vào hệ thống.

Ở đây phương thức is_valid() chỉ có chức năng đơn giản là kiểm tra xem ô text title có dữ liệu hay không vì mặc định các field của Form trong Django bắt buộc phải có dữ liệu mới được nhận.

upload(request.FILES['file'])
return HttpResponse("<h2>File uploaded successful!</h2>")

Nếu dữ liệu là hợp lệ thì chúng ta tiến hành lưu file vào thư mục gốc của server và trả lời thông báo gửi thành công về cho người dùng.

def upload(f): 
    file = open(f.name, 'wb+') 
    for chunk in f.chunks():
        file.write(chunk)

Hàm upload() sẽ thực hiện copy file vào thư mục gốc của server (thư mục có file manage.py). 

Cuối cùng chúng ta định nghĩa các url. Chúng ta sẽ tạo url trỏ tới hàm view trong app.

from django.conf.urls import url

from . import views

urlpatterns = [
    url(r'^$', views.fileUploaderView),
]

Tiếp theo là url từ địa chỉ server tới app:

from django.conf.urls import url, include
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^file-upload/', include('file_uploader.urls'))
]

Bây giờ chúng ta có thể chạy server và trỏ tới url localhost:8000/file-upload để thực hiện upload file.

Untitled

File được upload thành công.

Untitled

Thiết lập file

Khi Django server nhận file được gửi đến, trước khi lưu vào đĩa cứng thì dữ liệu của file được Django lưu vào đâu đó rồi mới thực sự được lưu trên đĩa.

Mặc định nếu kích thước file nhỏ hơn 2.5 MB thì Django sẽ lưu toàn bộ dữ liệu của file trong RAM, khi lưu vào đĩa thì Django chỉ cần đọc lại nội dung đó trong RAM rồi lưu vào đĩa thôi, do đó tốc độ lưu file rất nhanh.

Tuy nhiên nếu file tải lên quá lớn, Django sẽ lưu toàn bộ file này vào thư mục temp của hệ thống, trên Windows 7 thì thư mục này nằm tại địa chỉ C:\Users\<username>\AppData\Local\Temp rồi sau đó mới copy từ thư mục đó sang thư mục của serverMà tốc độ đọc ghi với đĩa cứng lại chậm hơn RAM nên giả sử chúng ta có 2 file có dung lượng 2.4 và 2.5 MB thì thời gian up file có dung lượng 2.4 MB không khác gì file có dung lượng 2.5 MB, nhưng thời gian upload file có dung lượng 2.5 MB với file có dung lượng 2.6 MB lại khác nhau rất nhiều.

Tuy nhiên bạn có thể thiết lập lại một số thông tin cấu hình về file nhưng mình không đi sâu ở đây, nếu muốn bạn có thể xem thêm ở đây.