Category Archives: Python

Scrapy – Ngôn ngữ XPath

Nếu bạn “xuất thân” là một coder truyền thống, thì nhiều khả năng là bạn chưa biết gì về XPath, trích xuất dữ liệu từ một website là công việc đòi hỏi các kĩ năng so sánh chuỗi, tìm các thẻ, xử lí các kí tự…v.v thậm chí là xử lý một cây HTML để lấy cho được cái mà bạn cần. Nhưng thực tế thì bạn không cần làm những việc phức tạp đó. Bạn có thể làm tất cả mọi thứ đó với một ngôn ngữ tên là XPath. 

Bạn có thể vào Console của Google Chrome và thử tính năng $x của XPath. Chẳng hạn bây giờ bạn vào trang example.com và gõ đoạn code $x('//h1')  vào console và xem kết quả:

Console sẽ trả về cho bạn một mảng trong Javascript chứa các phần tử HTML h1 tương ứng đã được lấy ra.

Một số cú pháp XPath hữu dụng

Cấu trúc HTML luôn luôn bắt đầu với <html>, chúng ta có thể kết hợp tên phần tử với dấu slash / để lấy phần tử mong muốn. Ví dụ (khi chạy trên trang example.com):

$x('/html') 
Kết quả: [ html ]
$x('/html/body') Kết quả: [ body ]
$x('/html/body/div') Kết quả: [ div ]
$x('/html/body/div/h1') Kết quả: [ h1 ]
$x('/html/body/div/p') Kết quả: [ p, p ] $x('/html/body/div/p[1]') Kết quả: [ p ] $x('/html/body/div/p[2]') Kết quả: [ p ]

$x('/html/head/title')
Kết quả: [ title ]

Chú ý là bên trong element <div> có 2 element <p>, do đó cú pháp html/body/div/p trả về mảng chứa 2 phần tử <p>. Chúng ta có thể dùng có pháp p[1]p[2] để lấy ra chỉ duy nhất element <p> tương ứng.

Với những trang web lớn thông thường thì để trích xuất một element có thể phải viết đoạn XPath rất dài. Tuy nhiên chúng ta có thể chèn 2 dấu // ở đầu đoạn code để lấy tất cả các phần tử tương ứng mà không cần biết cấu trúc của chúng. Ví dụ. //p lấy tất cả element <p>, //a thì sẽ lấy tất cả element <a>.

$x('//p')
Kết quả: [ p, p ]

$x('//a')
Kết quả: [ a ]

2 dấu /X/ cũng có thể được viết ở các element phía sau. Ví dụ //div//a sẽ lấy tất cả element <a> mà nằm trong element <div>.Lưu ý là //div/a (1 dấu / trước a)  sẽ trả về mảng rỗng. Bởi vì trong trang example.com thì không có element <a> nào nằm ngay trong element <div>.

$x('//div//a')
Kết quả: [ a ]

$x('//div/a')
Kết quả: [ ]

Chúng ta cũng có thể lấy thuộc tính của element. Ví dụ:

$x('//a/@href')
Kết quả: [ href ]

Chúng ta có thể dùng dấu * để lấy tất cả các element con trong một element. Ví dụ:

$x('//div/*') 
Kết quả: [h1, p, p]

Chúng ta có thể trích xuất những element chỉ mang theo nó những attribute nhất định. Chẳng hạn @class, @id... hay những attribute nào mang một giá trị nào đó. Ví dụ //a[@href] sẽ trả về những element <a> có attribute href bên trong. Hoặc //a[@href="https://www.iana.org/domains/example"] sẽ trả về chỉ những element <a> có attribute href có giá trị là đường dẫn như trên.

$x('//a[@href="https://www.iana.org/domains/example"]')
Kết quả: [ a ]

Một kiểu cú pháp nữa là tìm các đường link có chứa một chuỗi nào đó, ví dụ:

Tìm element <a> href chứa có chuỗi iana
$x('//a[contains(@href, "iana")]')

Tìm element <a> có href bắt đầu với http://www.
$x('//a[starts-with(@href, "http://www.")]')

Tìm element <a> có href không chứa chuỗi abc
$x('//a[not(contains(@href, "abc"))]')

not,starts-with,contains()… được gọi là các hàm. ngoài 3 hàm đó thì XPath còn rất nhiều hàm khác. Nhưng chúng ta sẽ tìm hiểu thêm sau.

Dùng XPath với Scrapy

Chúng ta mở console lên và gõ scrapy shell <đường_dẫn_trang_web> để bắt đầu trích xuất dữ liệu từ trang đó. Ví dụ:

>>> scrapy shell example.com

Từ đây chúng ta có thể sử dụng nhiều loại biến toàn cục mà Scrapy cung cấp để truy xuất dữ liệu. Một trong số đó là biến response thuộc lớp HtmlResponse, lớp này có phương thức xpath() dùng để lấy dữ liệu theo cú pháp XPath. Một số ví dụ:

>>> response.xpath('/html').extract()
['<html>...</html>']
>>> response.xpath('/html/body/div/h1').extract()
['<h1>Example Domain</h1>']
>>> response.xpath('/html/body/div/p').extract()
['<p>This domain is...</p>']
>>> response.xpath('//html/head/title').extract()
['<title>Example Domain</title>']
>>> response.xpath('//a').extract()
['<a href="https://www.iana.org/domains/examples">More information...</a>']
>>> response.xpath('//a/@href').extract() 
['http://www.iana.org/domains/example'] 
>>> response.xpath('//a/text()').extract() 
['More information…']

Bạn có thể test thử code XPath trong Chrome Console, sau đó chạy thử trong Scrapy.

Dùng Chrome để lấy XPath

Chúng ta có thể vào Console của Chrome để copy đoạn XPath như trong hình dưới đây:

Xử lý những thay đổi của website

Các trang web đôi khi hay thay đổi giao diện dù ít hay nhiều, và điều đó sẽ làm cho các đoạn XPath của chúng ta bị sai. Do đó chúng ta cần liên tục cập nhật lại XPath cho đúng, thông thường việc này cũng không tốn nhiều thời gian. Tuy nhiên có một số tip mà chúng ta nên để ý để tránh kéo dài thời gian thay đổi:

  • Không hard-code số thứ tự trong mảng. Ví dụ nếu chúng ta lấy XPath từ Chrome thì thường là Chrome sẽ trả về rất nhiều chỉ số mảng như:
//*[@id="myid"]/div/div/div[1]/div[2]/div/div[1]/div[1]/a/img
  • Và chỉ cần trang web thêm một thẻ <div> vào đầu là chúng ta phải cập nhật lại hết các chỉ số trên. Để giải quyết việc này thì chúng ta nên lấy XPath càng gần với thẻ <img> cuối càng tốt. Ví dụ:
//div[@class="thumbnail"]/a/img
  • Tên class hướng dữ liệu sẽ có ích hơn các class hướng giao diện. Chẳng hạn như nhắm đến class thumbnail sẽ tốt hơn là nhắm đến class green,  nhưng chúng vẫn kém hiệu quả hơn class như container. Hai class đầu tiên chủ yếu dùng để tùy chỉnh giao diện web, trong khi cái cuối cùng thì lại có í nghĩa về mặt thiết kế hơn, và do đó ít bị thay đổi hơn.
  • ID thường là thứ đáng tin cậy nhất nên tốt nhất là thường xuyên sử dụng chúng để trích xuất dữ liệu. Ví dụ:
//*[@id="more_info"]//text()

Lưu ý là tránh các id do website tự tạo ra. Chẳng hạn //[@id=”order-F498232″] vì chúng không cố định.

Scrapy – Bản chất của HTML

Để có thể trích xuất thông tin từ các trang web thì chúng ta cần phải hiểu sâu một tí về cấu trúc của chúng. Nếu bạn đã từng làm lập trình web thì bạn có thể bỏ qua phần này vì trong phần này chúng ta sẽ tìm hiểu nhanh về các thành phần của HTML và cấu trúc dạng cây của nó.

Khi một người dùng duyệt web, đầu tiên họ phải gõ đoạn đường dẫn URL lên trình duyệt (hoặc click vào một đường link nào đó) và đợi trang web đó hiện lên. Chúng ta có thể hình dung quá trình này gồm 4 bước:

  • Đầu tiên đường dẫn được gõ vào trình duyệt. Các thông tin như tên miền (chẳng hạn phocode.com) – là thứ dùng để định vị máy chủ lưu trữ trang web đó, cookie, các form…v.v là các dữ liệu sẽ được gửi tới máy chủ đó.
  • Tiếp theo máy chủ sẽ trả lời lại bằng cách gửi về một “trang” HTML cho trình duyệt. Ngoài ra máy chủ còn có thể gửi về các dạng dữ liệu khác như XML hay JSON. Trong series này thì chúng ta chủ yếu làm việc với HTML thôi.
  • Đoạn văn bản HTML sẽ được chuyển đổi sang một kiểu cấu trúc dạng cây và được lưu trong trình duyệt. Tên của nó là Document Object Model (DOM).
  • Trình duyệt sẽ dựa trên “cây” HTML đó để hiển thị tất cả mọi thứ lên màn hình cho chúng ta xem.

Bây giờ chúng ta sẽ tìm hiểu kĩ từng bước đó.

Đường dẫn – URL

Trong khuôn khổ series này thì chúng ta chỉ cần biết URL có 2 phần. Phần đầu tiên giúp chúng ta định vị máy chủ của trang web thông qua các dịch vụ phân giải tên miền – Domain Name System (DNS). Chẳng hạn như khi chúng ta gõ https://phocode.com/python/scrapy/, thì các máy chủ DNS sẽ dịch đoạn tên miền đó ra địa chỉ IP là 128.199.120.120. Và đoạn URL lúc này sẽ là http://128.199.120.120/python/scrapy. Phần còn lại trong đoạn URL là thứ dành cho máy chủ web, máy chủ sẽ dựa vào đó mà trả lại đúng trang HTML tương ứng. Đó có thể là một trang tin tức, một tấm ảnh, hoặc mở email…v.v

File HTML

Máy chủ sẽ đọc đoạn URL, phân tích xem người dùng yêu cầu cái gì và gửi lại một đoạn văn bản HTML. Đoạn văn bản này là một file text mà bạn có thể mở ra đọc bằng các trình soạn văn bản như Notepad, Microsoft Word, vim… Tuy nhiên đoạn văn bản này được viết theo cấu trúc chuẩn được quy định bởi hiệp hội web toàn cầu – World Wide Web Consortium (W3C). Chúng ta sẽ chỉ tìm hiểu sơ chứ không đi sâu vào tiêu chuẩn này. Nếu bạn gõ vào http://example.com, bạn sẽ thấy đoạn mã HTML do máy chủ trả về bằng cách xem mã nguồn trang web (xem trên Chrome bằng cách bấm Ctrl-U) như sau:

 
<!doctype html>
  <html>
    <head>
      <title>Example Domain</title>
      <meta charset="utf-8" />
      <meta http-equiv="Content-type" content="text/html; charset=utf-8" />          
      <meta name="viewport" content="width=device-width, initial-scale=1" /> 
      <style type="text/css">              
        body {                  
          background-color: ...              
      }
      </style>
    </head>
  <body>
    <div>
      <h1>Example Domain</h1>
      <p>This domain is established to be used for illustrative examples examples in documents. You may use this domain in examples without prior coordination or asking for permission.</p>
      <p><a href="http://www.iana.org/domains/example">More information</a></p>          
    </div>     
  </body>
</html>

Thông thường một đoạn HTML có thể chỉ nằm trên một dòng duy nhất, đoạn code ở trên mình đã viết lại cho dễ đọc. Trong HTML thì khoảng trống và xuống dòng không thật sự có nhiều công dụng. Những từ nằm trong cặp dấu <> được gọi là các thẻ, chẳng hạn <html> hay <head>. Thẻ có thêm dấu / được gọi là thẻ đóng (vd </html).

Điều này có nghĩa là các thẻ luôn đi cùng nhau, có thẻ “mở” thì phải có thẻ “đóng”. Tuy nhiên đôi khi cũng có các ngoại lệ, nhiều khi chúng ta sẽ thấy có thẻ mở nhưng không hề có thẻ đóng đi sau, tuy nhiên website vẫn hiển thị bình thường, trình duyệt sẽ tự động tìm cách đặt thẻ đóng thay thế.

Những gì bên trong một cặp thẻ đóng-mở được gọi là một phần tử HTML. Một phần tử HTML có thể chứa các phần tử HTML khác. Một số thẻ thì cao cấp hơn một tí, chẳng hạn như <a href="...">, trong đó href là một thuộc tính của thẻ.

Và các thẻ có thể chứa đoạn văn bản nào đó, chẳng hạn “Example Domain” nằm trong cặp thẻ <h1></h1>.
Chúng ta không cần phải hiểu rõ toàn bộ mọi thứ trong HTML. Thứ chúng ta cần biết đó là những thứ nằm trong cặp thẻ <body></body>, còn những thứ còn lại như <head></head> không quan trọng vì chúng chỉ cung cấp thông tin thêm cho trình duyệt và Scrapy hầu như cũng sẽ tự lo phần đó cho chúng ta.

Cấu trúc cây

Mỗi trình duyệt có cách lưu trữ cấu trúc dữ liệu khác nhau và do đó trang web hiển thị lên cũng không giống nhau (một cách hoàn toàn). Nhưng hầu hết các trình duyệt đều hỗ trợ DOM.
Chúng ta có thể xem mã nguồn và thậm chí tìm xem phần tử nào phụ trách phần nào trên màn hình bằng cách bấm F12 khi dùng Chrome (hoặc click chuột phải và chọn Inspect Element).
Bạn có thể thấy nó khá giống một đoạn code HTML, nhưng thực chất nó không hẳn là một đoạn text HTML mà là một “cây” HTML. Bạn có thể trỏ con chuột vào từng phần tử trong cây này và thấy phần nào được tô đậm trên trang web, bạn còn có thể chỉnh sửa các phần tử này như thay đổi, thêm, xóa phần tử…v.v.
Nói chung bạn cần biết rằng HTML chỉ là một đoạn văn bản (mà bạn có thể gõ trong Office Word). Còn thứ nằm trong trình duyệt là một cấu trúc dữ liệu dạng cây biểu thị cho đoạn HTML đó và được lưu trong bộ nhớ của trình duyệt và chúng ta có thể chỉnh sửa trực tiếp.


Scrapy – Giới thiệu

Scrapy là một web framework rất mạnh mẽ trong việc trích xuất dữ liệu. Scrapy có kiến trúc sự kiện, cho phép chúng ta thực hiện các công việc như dọn dẹp, tạo, lưu trữ, làm giàu dữ diệu…v.v

Scrapy đã hiện diện từ năm 2008, một số tính năng của scrapy như sau:

  • Có thể làm việc với các đoạn code HTML “xấu”
  • Cộng đồng lớn
  • Mã nguồn ổn định
  • Tốc độ xử lí nhanh theo dạng bất đồng bộ
  • Có thể lưu dữ liệu theo nhiều định dag như JSON, CSV, XML.
  • Có thể trích xuất dữ liệu bằng cách sử dụng các biểu thức XPath hay CSS

Cài đặt

Để có thể cài đặt và sử dụng Scrapy thì bạn cần có Python 3 trong máy. Để cài đặt Scrapy thì chúng ta dùng trình pip:

pip install scrapy

Lưu ý là bạn có thể phải cài thêm Visual C++ Build Tools thì pip mới có thể cài Scrapy được.

Kiểm tra phiên bản

Chạy lệnh scrapy version để xem phiên bản scrapy mà chúng ta đang vừa cài:

$ scrapy version
Scrapy 1.8.0

Serie này sử dụng phiên bản 1.8.0

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