Author Archives: Phở Code
Django – Model nâng cao – Phần 1
Trong các bài trước chúng ta đã tìm hiểu sơ qua về model trong Django, trong phần này chúng ta sẽ tìm hiểu kĩ hơn.
Model trong Django là một cách định nghĩa về cách lưu trữ thông tin, một model bao gồm các trường và các hành động về dữ liệu mà bạn sẽ lưu trữ, định nghĩa này không khác gì định nghĩa lớp trong lập trình hướng đối tượng cả, nói cách khác Model chính là cách gọi khác của Class trong Django. Mỗi model sẽ đại diện cho một bảng trong cơ sở dữ liệu.
Model trong Django có các tính chất sau:
- Mỗi model phải được kế thừa từ lớp
django.db.models.Model
. - Mỗi thuộc tính của model đại diện cho một trường trong cơ sở dữ liệu.
- Từ đó Django sẽ tự động tạo các hàm thao tác với cơ sở dữ liệu cho bạn.
Ví dụ
Chúng ta định nghĩa model (class) Person
có 2 thuộc tính là first_name
và last_name
:
from django.db import models class Person(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30)
first_name
và last_name
là một trường của model – một thuộc tính của một lớp – một cột trong một bảng CSDL.
Tương ứng với model Person
trên thì Django sẽ tạo ra một bảng trong CSDL như sau:
CREATE TABLE myapp_person ( "id" serial NOT NULL PRIMARY KEY, "first_name" varchar(30) NOT NULL, "last_name" varchar(30) NOT NULL );
Lưu ý:
- Tên của bảng được cấu thành từ
<tên app>_<tên model>
nhưng chúng ta có thể thiết lập trước để Django đặt theo tên do chúng ta quy định. - Trường id được Django tạo tự động và cũng có thể thiết lập trước.
- Đoạn code SQL trên là theo cú pháp của CSDL PostgreSQL, tùy vào CSDL nào được dùng mà Django sẽ dùng cú pháp tương ứng.
Sử dụng model
Sau khi đã định nghĩa xong các model thì chúng ta phải thiết lập để Django sử dụng các model đó, bằng cách chỉnh sửa biến INSTALLED_APPS
trong file settings.py
.
Ví dụ nếu bạn định nghĩa model trong module myapp.models
(được tạo ra bằng câu lệnh manage.py startapp) thì trong biến INSTALLED_APPS
, bạn khai báo đường dẫn dẫn tới lớp cấu hình của ứng dụng, hoặc đường dẫn (theo cú pháp import của Python) dẫn tới package của ứng dụng như sau:
INSTALLED_APPS = [ #... 'myapp', #... ]
Sau khi đã khai báo trong file settings.py
, bạn phải chạy 2 lệnh là manage.py
makemigrations
để bảo Django là bạn đã thay đổi cái gì đó trong cấu trúc CSDL, sau đó là lệnh manage.py migrate
để thực hiện cập nhật lại CSDL.
Các trường
Thành phần quan trọng và cũng là thành phần bắt buộc phải có trong một model là danh sách các trường hay các thuộc tính của một lớp. Chú ý khi đặt tên bạn nhớ tránh đặt tên trùng với các từ khóa có sẵn trong Django.
Ví dụ:
from django.db import models class Musician(models.Model): first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) instrument = models.CharField(max_length=100) class Album(models.Model): artist = models.ForeignKey(Musician, on_delete=models.CASCADE) name = models.CharField(max_length=100) release_date = models.DateField() num_stars = models.IntegerField()
Mỗi trường phải là một đối tượng kế thừa từ lớp Field
, đây là một lớp ảo trong Django. Các lớp Field
sẽ cho Django biết kiểu dữ liệu của các cột trong bảng (chẳng hạn như INTEGER
, VARCHAR
, TEXT)
và loại thẻ HTML được dụng để làm form nhập (vd <input type="text">, <select>...).
Django có rất nhiều các lớp kiểu dữ liệu Field
có sẵn, bạn có thể xem thêm ở đây. Bạn cũng có thể viết một lớp Field cho riêng mình, nhưng mình sẽ đề cập đến trong bài khác.
Tham số trong Field
Mỗi lớp Field nhận một vài tham số khi khởi tạo. Ví dụ lớp CharField
nhận tham số max_length
để biết số lượng kí tự sẽ được dùng trong cột VARCHAR
trong CSDL, đây là tham số bắt buộc phải có.
Ngoài các tham số bắt buộc thì còn có một số tham số tùy chọn nữa, ở đây mình chỉ giới thiệu một số:
null
: có giá trị True hoặc False. Tham số này cho Django biết sẽ dùng giá trịNULL
thay cho các bản ghi có giá trị rỗng, tức là trong CSDL mà kiểu chuỗi mà có giá trị""
thì sẽ thay bằngNULL
.blank
: có giá trị True hoặc False. Tham số này cho Django biết cột tương ứng trong bảng CSDL được phépNULL
hayNOT NULL.
default
: thiết lập giá trị mặc định cho trường.help_text
: đây một giá trị chuỗi dùng để hiển thị một đoạn text ngắn mô tả về trường đó, vd như khi di chuột vào cột trên form HTML thì hiện ra đoạn text.primary_key
: True hoặc False, chỉ định trường đó có phải là khóa chính hay không. Như bạn đã biết, nếu bạn không chỉ định trường nào làm khóa chính thì Django sẽ tự động tạo một trườngAutoField
để làm khóa chính luôn. Có một điều nữa là khi bạn thay đổi giá trị khóa chính của một model trong Python thì Django không báo lỗi mà thay vào đó là tạo một đối tượng model mới với khóa chính mới, ví dụ:
from django.db import models class Fruit(models.Model): name = models.CharField(max_length=100, primary_key=True)
Chúng ta tạo model Fruit
với khóa chính là trường name
.
>>> fruit = Fruit.objects.create(name='Apple') >>> fruit.name = 'Pear' >>> fruit.save() >>> Fruit.objects.values_list('name', flat=True) ['Apple', 'Pear']
Sau khi tạo một đối tượng với name
là Apple, chúng ta thay đổi Apple thành Pear thì Django không đổi mà thay vào đó là tạo một đối tượng mới với name
là Pear.
Khóa chính tự tạo
Khóa chính do Django tự tạo ra có dạng sau:
id = models.AutoField(primary_key=True)
Đây là một giá trị integer tự động tăng. Mỗi model chỉ được phép có một trường làm khóa chính.
Quan hệ
Sức mạnh của các cơ sở dữ liệu quan hệ nằm ở tính năng mối quan hệ giữa các bảng. Django hỗ trợ 3 loại quan hệ phổ biến của CSDL quan hệ là: many-to-one (một-nhiều), many-to-many (nhiều-nhiều), one-to-one (một-một).
Quan hệ Many-to-one
Chúng ta đã làm việc với kiểu quan hệ này trong các bài trước bằng cách dùng lớp django.db.models.ForeignKey
. Tham số bắt buộc phải có của ForeignKey
là model cha của nó.
Ví dụ chúng ta có một model là Car
có model cha là Manufacturer
(tiếng Anh nghĩa là nhà sản xuất), quan hệ này có nghĩa là một Manufacturer
sản xuất nhiều Car
nhưng một Car chỉ có một Manufacturer
. Tương ứng chúng ta có đoạn code khai báo trong Django như sau:
from django.db import models class Manufacturer(models.Models): #... pass class Car(models.Model): manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE) #...
Bạn còn có thể tạo mối quan hệ đệ quy, tức là một bảng tự quan hệ với chính nó.
Quan hệ Many-to-many
Để sử dụng mối quan hệ này thì chúng ta sử dụng lớp ManyToManyField
với tham số bắt buộc là tên model mà nó sẽ trỏ đến.
Ví dụ chúng ta có 2 model là Pizza
và Topping
(nước sốt), một chiếc bánh pizza có thể có nhiều loại sốt và một loại sốt có thể được “xịt” lên nhiều bánh pizza.
from django.db import models class Topping(models.Model): #... pass class Pizza(models.Model): #... toppings = models.ManyToManyField(Topping)
Cũng giống như với ForeignKey
, bạn cũng có thể dùng quan hệ đệ quy với ManyToManyField
.
Bạn cũng chỉ nên cho một model trỏ đến model kia chứ không nên để 2 model trỏ đến nhau. Lý do tại sao chúng ta lại cho Pizza
trỏ đến Topping
chứ không phải ngược lại là vì tùy theo tình huống mà bạn chọn cho phù hợp, ví dụ như khi đi ăn pizza chúng ta thường mua một chiếc bánh rồi “xịt” nhiều loại nước sốt lên chứ không ai mua nhiều chiếc bánh rồi “xịt” mỗi bánh một loại sốt cả 🙂
Thuộc tính của một mối quan hệ many-to-many
Một mối quan hệ không đơn giản chỉ là quan hệ giữa bảng này với bảng kia, mà chúng còn có các thông tin riêng nữa.
Ví dụ chúng ta có model Person
và Group
có quan hệ many-to-many, ý nghĩa là một người có thể tham gia nhiều group và một group có thể có nhiều người, ý nghĩa của quan hệ này không chỉ có bao nhiêu đó mà còn có các thông tin khác như người đó tham gia vào nhóm vào ngày nào, lý do tham gia chẳng hạn…
Để thêm các trường vào một mối quan hệ thì chúng ta sẽ dùng đến một tham số tùy chọn trong hàm khởi tạo ManyToManyField
là through
.
from django.db import models class Person(models.Model): name = models.CharField(max_length=128) class Group(models.Model): name = models.CharField(max_length=128) members = models.ManyToManyField(Person, through='Membership') class Membership(models.Model): person = models.ForeignKey(Person, on_delete=models.CASCADE) group = models.ForeignKey(Group, on_delete=models.CASCADE) date_joined = models.DateField() invite_reason = models.CharField(max_length=64)
Chúng ta phải định nghĩa một model khác lưu trữ các thông tin của một mối quan hệ, tham số through
sẽ nhận tên của model này.
Trong Django thì các model kiểu này (Membership
) được gọi mà model trung gian (Intermediate model), trong đó bạn phải khai báo rõ ràng các model tham gia vào mối quan hệ này, ở đây là 2 đối tượng ForeignKey
: person
và group
.
Sau khi đã có lớp trung gian, chúng ta có thể tạo các mối quan hệ many-to-many.
>>> ringo = Person.objects.create(name="Ringo Starr") >>> paul = Person.objects.create(name="Paul McCartney") >>> beatles = Group.objects.create(name="The Beatles") >>> m1 = Membership(person=ringo, group=beatles, ... date_joined=date(1962, 8, 16), ... invite_reason="Needed a new drummer.") >>> m1.save() >>> beatles.members.all() [<Person: Ringo Starr>] >>> ringo.group_set.all() [<Group: The Beatles>] >>> m2 = Membership.objects.create(person=paul, group=beatles, ... date_joined=date(1960, 8, 1), ... invite_reason="Wanted to form a band.") >>> beatles.members.all() [<Person: Ringo Starr>, <Person: Paul McCartney>]
Chúng ta tạo 1 Group
là beatles
, trong đó bao gồm 2 Person
là ringo
và paul
, mối quan hệ giữa ringo
và beatles
được lưu trong m1
, mối quan hệ giữa paul
và beatles
được lưu trong m2
.
Đối với mối quan hệ many-to-many mà có sử dụng model trung gian thì chúng ta không thể sử dụng các phương thức như add()
, create()
hoặc dùng toán tử gán =
.
# Error >>> beatles.members.add(john) # Error >>> beatles.members.create(name="George Harrison") # Error >>> beatles.members = [john, paul, ringo, george]
Bởi vì bạn không thể tự nhiên mà tạo một mối quan hệ cho 2 đối tượng được, bạn phải khai báo rõ ràng các thông tin của mối quan hệ đó ra (bằng cách dùng hàm khởi tạo Membership()
).
Sau khi đã tạo lập các mối quan hệ, chúng ta có thể truy vấn chúng như thường.
>>> Group.objects.filter(members__name__startswith='Paul') [<Group: The Beatles>] >>> ringos_membership = Membership.objects.get(group=beatles, person=ringo) >>> ringos_membership.date_joined datetime.date(1962, 8, 16) >>> ringos_membership.invite_reason 'Needed a new drummer.'
Mối quan hệ one-to-one
Để định nghĩa một mối quan hệ one-to-one thì chúng ta sử dụng lớp OneToOneField
, lớp này cũng nhận tham số là tên của model mà nó sẽ trỏ tới.
Cũng giống như 2 loại mối quan hệ trên, chúng ta cũng có thể dùng quan hệ đệ quy với one-to-one.
Mối quan hệ này cũng ít được dùng nhiều nên mình không đi sâu ở đây.
Tham chiếu model thông qua file
Bạn có thể định nghĩa các lớp model trong các file khác nhau và khi cần tham chiếu đến chúng thì chỉ cần import vô là được.
from django.db import models from geography.models import ZipCode class Restaurant(models.Model): # ... zip_code = models.ForeignKey( ZipCode, on_delete=models.SET_NULL, blank=True, null=True, )
Django – Tùy chỉnh trang Admin
Trong phần này chúng ta sẽ tùy chỉnh lại hệ thống Admin đã được trình bày trong bài Hệ thống Admin.
Tùy chỉnh form
Khi chúng ta đăng ký các lớp model Question
và Choice
bằng hàm admin.site.register()
, Django sẽ dựa vào các thuộc tính mà chúng ta khai báo trong các lớp đó để hiển thị form trên trang admin. Chúng ta có thể quy định Django chỉ chọn một số thuộc tính được phép hiện ra để chỉnh sửa, thay vì hiện ra hết, bởi vì khi làm một ứng dụng có sử dụng CSDL, có những thuộc tính mà bạn muốn máy tính tự động sinh ra chứ không phải do con người nhập vào, chẳng hạn như ID, ngày giờ…
Chúng ta sẽ thay đổi bằng cách sửa lại code trong file polls/admin.py
.
from django.contrib import admin # Register your models here. from .models import Question, Choice class QuestionAdmin(admin.ModelAdmin): fields = ['pub_date', 'question_text'] admin.site.register(Question, QuestionAdmin) admin.site.register(Choice)
Việc này làm rất đơn giản, chúng ta chỉ cần viết một lớp kế thừa từ lớp admin.ModelAdmin
, ở đây mình đặt tên là QuestionAdmin
, sau đó khai báo list có tên là fields
có các item là tên các thuộc tính trong model mà chúng ta muốn hiện ra trong trang Admin, sau đó truyền lớp này vào hàm admin.site.register()
.
Có thể bạn không nhận ra sự thay đổi vì chúng ta trước sau cũng cho hiện ra có 2 thuộc tính, nhưng thực ra khi bạn để Django tự động cho hiện ra hết thì nó sẽ hiển thị các thuộc tính theo thứ tự mà chúng ta khai báo trong file models.py
, còn ở đây Django hiển thị theo thứ tự mà chúng ta khai báo trong biến fields
.
Ngoài ra Django còn cho phép gom nhóm các thuộc tính lại với nhau để hiển thị.
from django.contrib import admin # Register your models here. from .models import Question, Choice class QuestionAdmin(admin.ModelAdmin): fieldsets = [ (None, {'fields': ['question_text']}), ('Date information', {'fields': ['pub_date']}), ] admin.site.register(Question, QuestionAdmin) admin.site.register(Choice)
Chúng ta khai báo các biến fields
bên trong biến fieldsets
để gom nhóm các thuộc tính lại với nhau. Mỗi nhóm đều có thể có tiêu đề hoặc không có.
Tùy chỉnh model chứa khóa ngoại
Tùy chỉnh trang chỉnh sửa
Hiện tại thì trang chỉnh sửa lớp Question có giao diện như hình dưới.
Mặc định thì Django chỉ để hiển thị đoạn text lấy từ phương thức str()
mà chúng ta đã override trong phương thức __str__()
cho từng đối tượng, chúng ta có thể tùy chỉnh Django hiển thị những thuộc tính khác.
class QuestionAdmin(admin.ModelAdmin): ... list_display = ('question_text', 'pub_date')
Để làm việc này thì chúng ta chỉ cần khai báo danh sách các thuộc tính cần hiển thị ra trong thuộc tính list_display
.
Bạn thậm chí còn có thể sắp xếp các đối tượng Question
bằng cách click chuột vào thanh tiêu đề.
Ngoài ra bạn có thể thêm các ô lọc theo thuộc tính.
class QuestionAdmin(admin.ModelAdmin): ... list_filter = ['pub_date']
Chúng ta khai báo tên các thuộc tính cần lọc trong thuộc tính list_filter
.
Vì thuộc tính pub_date
là kiểu DateTime
nên Django biết cách đưa các tùy chọn vào phần lọc chẳng hạn như Any date, Today… còn đối với các thuộc tính kiểu text hay kiểu số nguyên, số thực thì dường như là không có hiệu quả mấy.
Tiếp theo, chúng ta có thể thêm ô tìm kiếm vào trang admin:
class QuestionAdmin(admin.ModelAdmin): ... search_fields = ['question_text']
Biến search_fields
sẽ hiển thị ô tìm kiếm và chỉ tìm theo thuộc tính mà chúng ta đã khai báo trong đó, bạn cũng có thể thêm các thuộc tính khác vào nhưng nên thêm ít thôi vì Django sử dụng câu truy vấn LIKE với CSDL nên thêm nhiều thuộc tính vào sẽ làm quá trình tìm kiếm chậm đi.
Django – File tĩnh
Trong phần này chúng ta sẽ tìm hiểu cách sử dụng các file tĩnh.
Ngoài nội dung HTML được sinh ra bởi server thì một ứng dụng web còn cần đến các file bổ sung khác, chẳng hạn như các file hình ảnh, Javascript, CSS… Trong Django thì các file này được gọi là file tĩnh.
Module django.contrib.staticfiles
được liệt kê trong biến INSTALLED_APP
(trong file mysite/settings.py
) sẽ quản lý các file tĩnh này.
Tùy biến CSS
Đầu tiên chúng ta tạo một thư mục có tên là static
trong thư mục polls
. Django sẽ tự động tìm các file tĩnh trong thư mục này giống như tìm các file template trong thư mục polls/templates
vậy.
Trong thư mục static
này chúng ta lại tạo một thư mục khác với tên là polls
và tạo một file CSS có tên là style.css
.
li a { color: green }
Trong file này chúng ta chỉ đơn giản là thiết lập màu chữ cho các thẻ li và a.
Tiếp theo chúng ta cần sửa lại file index.html
một tí.
{% load staticfiles %} <link rel="stylesheet" type="text/css" href="{% static 'polls/style.css' %}" />
Dòng {% load staticfiles %}
sẽ tự động gán đường dẫn đến thư mục mysite/polls/static/
vào một biến có tên static
do Django tự đặt, từ đó bạn chỉ cần lấy biến static
là có thể lấy được đường dẫn tuyệt đối đến thư mục này.
<link rel="stylesheet" type="text/css" href="{% static 'polls/style.css' %}" />
Sau đó chúng ta chỉ cần lấy biến static
trong cặp thẻ {% %}
ra và gắn thêm đường dẫn đến các file css, js… của bạn.
Django – Form và các View có sẵn
Trong phần này chúng ta sẽ viết form cho các hàm view, sau đó tìm hiểu về hệ thống view có sẵn của Django.
Tạo form
Chúng ta sẽ viết lại template detail
.
<h1>{{ question.question_text }}</h1> {% if error_messsage %} <strong>{{ error_messsage }}</strong> {% endif %} <form action="{% url 'polls:vote' question.id %}" method="post"> {% csrf_token %} {% for choice in question.choice_set.all %} <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" /> <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label> {% endfor %} <input type="submit" value="Vote" /> </form>
Chúng ta tạo một form cho phép người dùng vote các câu trả lời.
{% csrf_token %}
Bạn chỉ cần biết câu lệnh trên sẽ giúp website của chúng ta chống lại kiểu tấn công CSRF là đủ, nếu muốn bạn có thể tìm hiểu thêm trên mạng, ở đây mình không đi sâu.
<form action="{% url 'polls:vote' question.id %}" method="post"> ... </form>
Chúng ta tạo thẻ form và đưa url dẫn đến view vote()
với tham số question.id
là ID của đối tượng Choice trong CSDL,
phương thức gửi lên là phương thức POST. Nếu bạn chưa biết sự khác nhau giữa GET và POST thì cứ nhớ là nếu gửi dữ liệu lên server mà có thay đổi CSDL thì phải dùng phương thức POST.
{% for choice in question.choice_set.all %} <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" /> <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label> {% endfor %}
Chúng ta hiển thị các radio button, mỗi radio button hiển thị một câu trả lời (Choice
) cho từng Question. Các radio button sẽ được gán thuộc tính name
là choice
.
Thuộc tính forloop.counter
là số thứ tự của vòng lặp giống như khi bạn viết for(int i...)
trong C++ vậy.
Tiếp theo chúng ta sẽ viết lại hàm view vote()
xử lý từng thao tác vote.
from django.shortcuts import get_object_or_404, render from django.http import HttpResponse, Http404, HttpResponseRedirect from django.core.urlresolvers import reverse from .models import Question, Choice def vote(request, question_id): question = get_object_or_404(Question, pk=question_id) try: selected_choice = question.choice_set_get(pk=request.POST['choice']) except (KeyError, Choice.DoesNotExist): return render(request, 'polls/detail.html', { 'question': question, 'error_message': "You didn't select a choice.", }) else: selected_choice.votes += 1 selected_choice.save() return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
Hàm view vote()
sẽ xử lý việc vote các câu trả lời.
question = get_object_or_404(Question, pk=question_id)
Khác với phần trước, chúng ta phải dùng khối lệnh try...except
để giải phóng một lỗi 404, ở đây chúng ta có thể dùng hàm get_object_or_404()
, hàm này sẽ chạy phần try
và sẽ tự giải phóng một lỗi 404 luôn cho chúng ta nếu có.
try: selected_choice = question.choice_set_get(pk=request.POST['choice']) except (KeyError, Choice.DoesNotExist): return render(request, 'polls/detail.html', { 'question': question, 'error_message': "You didn't select a choice.", })
Khi một hàm view được gọi, chúng ta có thể lấy các dữ liệu được gửi kèm theo qua đối tượng request
, tùy vào phương thức được gọi và tên biến được đặt mà chúng ta lấy ra cho phù hợp, nếu không sẽ có lỗi ngoại lệ xảy ra, chẳng hạn như KeyError
là lỗi sai tên biến, có thể tên biến là choice
mà bạn gửi lên theo url là choiec
hay chce
…, lỗi DoesNotExist
là lỗi đối tượng không tồn tại…
else: selected_choice.votes += 1 selected_choice.save() return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
Nếu không có lỗi xảy ra thì chúng ta tăng thuộc tính votes trong đối tượng Choice
lên 1 và lưu vào CSDL. Sau đó trả về một đối tượng HttpResponseRedirect
chứ không dùng đối tượng HttpResponse
như thường để tránh các trường hợp người dùng nhấn nút back trên trình duyệt (bạn vẫn có thể dùng HttpResponse
nếu muốn), HttpResponseRedirect
nhận một đối tượng url thông qua hàm reverse()
, hàm này sẽ trả về một đường dẫn có dạng như /polls/1/results/.
Tiếp theo chúng ta sẽ viết lại hàm view result()
.
from django.shortcuts import get_object_or_404, render def results(request, question_id): question = get_object_or_404(Question, pk=question_id) return render(request, 'polls/results.html', {'question': question})
View results()
sẽ hiển thị số lượng vote của mỗi câu trả lời Choice
.
Hàm này khá đơn giản, chúng ta chỉ lấy về đối tượng Question
và gửi đến template results.html
ở dưới để hiển thị.
<h1>{{ question.question_text }}</h1> <ul> {% for choice in question.choice_set.all %} <li> {{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes | pluralize }} </li> {% endfor %} </ul> <a href="{% url 'polls:detail' question.id %}">Vote again?</a>
Đoạn code HTML trên cũng rất đơn giản, chúng ta chỉ lấy từng đối tượng Choice và in tên cùng với số lượng votes của chúng ra, ngoài ra chúng ta còn thêm một đường dẫn về trang detail
. Tham số pluralize
sẽ thêm kí tự ‘s‘ để biểu thị số nhiều trong tiếng Anh.
Các lớp View của Django
Các hàm view mà chúng ta đã viết như index()
, detail()
và result()
rất đơn giản… và rất thừa.
Thường thì các trang web được viết ra đều làm một công việc gần như là tương tự nhau, đó là lấy dữ liệu từ CSDL dựa vào lệnh request được gửi lên thông qua URL, sau đó tải template và gắn dữ liệu đó vào template rồi trả về cho người dùng. Vì tính chất lặp đi lặp lại như thế nên Django cung cấp cho chúng ta hệ thống View có sẵn.
Các View này là các lớp của Django, mục đích chính của các lớp này là giúp bạn đỡ phải viết code Python nhiều, mặc dù thực ra thì cũng có nhiều trường hợp mà việc tự viết các View sẽ tốt hơn là dùng View có sẵn, cũng giống như việc học lập trình vậy, bạn phải biết viết các thuật toán trước khi sử dụng thư viện có sẵn.
Chúng ta sẽ sửa lại ứng dụng polls
để sử dụng các lớp View này.
Đầu tiên chúng ta phải sửa lại các đường dẫn url một tí.
from django.conf.urls import url from . import views app_name = "polls" urlpatterns = [ url(r'^$', views.IndexView.as_view(), name='index'), url(r'^(?P<pk>[0-9]+)/$', views.DetailView.as_view(), name='detail'), url(r'^(?P<pk>[0-9]+)/results/$', views.ResultsView.as_view(), name='results'), url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'), ]
Tên 3 hàm view được thay đổi và tên biến tham số gửi lên cũng được thay đổi từ question_id
thành pk
.
Tiếp theo chúng ta sẽ viết lại các hàm view mới.
from django.shortcuts import get_object_or_404, render from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse from django.views import generic from .models import Choice, Question class IndexView(generic.ListView): template_name = 'polls/index.html' context_object_name = 'latest_question_list' def get_queryset(self): return Question.objects.order_by('-pub_date')[:5] class DetailView(generic.DetailView): model = Question template_name = 'polls/detail.html' class ResultsView(generic.DetailView): model = Question template_name = 'polls/results.html' def vote(request, question_id): question = get_object_or_404(Question, pk=question_id) try: selected_choice = question.choice_set.get(pk=request.POST['choice']) except (KeyError, Choice.DoesNotExist): return render(request, 'polls/detail.html', { 'question': question, 'error_message': "You didn't select a choice.", }) else: selected_choice.votes += 1 selected_choice.save() return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
Chúng ta sẽ sử dụng các lớp View có sẵn của Django ngoại trừ hàm vote()
là giữ nguyên.
from django.views import generic
Đầu tiên chúng ta import module generic
.
class IndexView(generic.ListView): ... class DetailView(generic.DetailView): ...
Chúng ta sẽ sử dụng 2 loại view là ListView
và DetailView,
ListView
lưu trữ danh sách các đối tượng, DetailView
lưu thông tin về một đối tượng cụ thể.
model = Question
Mỗi lớp View cần biết về mô hình dữ liệu mà nó sẽ lưu trữ thông qua thuộc tính model
. Khi đã biết loại model mà mình sẽ dùng, các view này tự động “nghĩ” rằng khóa chính trong CSDL có tên là pk
, do đó trong các đối tượng url chúng ta gửi lên tham số có tên là pk
.
template_name = 'polls/detail.html'
Mặc định các lớp View này cũng “nghĩ” rằng template sẽ được dùng có dạng <app_name>/<model_name>_detail.html
(hoặc <app_name>/<model_name>_list.html
), nếu không có file nào như vậy tồn tại thì Django sẽ tự động tạo các file này và sử dụng, chúng ta có thể “bảo” Django sử dụng template do chúng ta tự viết bằng cách gán vào thuộc tính template_name, như thế Django sẽ không tạo template cho nó nữa.
class IndexView(generic.ListView): ... context_object_name = 'latest_question_list'
Trong hàm detail()
cũ, chúng ta tự khai báo các biến question
và latest_question_list
để dùng trong template, mặc định các biến này đã có sẵn trong các lớp DetailView.
Tuy nhiên đối với hàm ListView
thì biến mặc định lại có tên là question_list
, nên để Django dùng tên do chúng ta tự đặt thì chúng ta phải gắn tên đó vào thuộc tính context_object_name
hoặc bạn phải dùng tên do Django đặt trong các file template.
Django – View và Template
Trong phần này chúng ta sẽ tìm hiểu về View và Template trong Django.
Trong Django thì một View là một hàm/phương thức làm một công việc cụ thể nào đó, một View thường đi kèm với một Template (chúng ta sẽ tìm hiểu ở dưới). Ví dụ, một ứng dụng Blog có một số View sau:
- Home – hiển thị các bài viết mới nhất.
- Entry – hiển thị bài viết nào đó.
- Archive – Lưu trữ các bài viết theo năm/tháng.
- Comment – xử lý việc đăng bình luận của độc giả.
Trong ứng dụng “Poll” mà chúng ta đã viết từ đầu series tới bây giờ, chúng ta sẽ xây dựng các View sau:
- Index – Hiển thị các câu hỏi mới.
- Detail – Hiển thị một câu hỏi nhất định nào đó và đưa các câu trả lời để người dùng chọn.
- Result – Hiển thị kết quả bầu chọn của người dùng.
- Vote – Xử lý việc trả lời của người dùng.
Trong Django, một trang web được tạo ra bởi các hàm View, Django sẽ chọn View nào tùy thuộc vào URL mà chúng ta đã thiết lập. Có thể bạn đã từng thấy những đường dẫn URL nhìn rất “gớm” như “ME2/Sites/dirmod.asp?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B”
do website tự tạo ra, Django cho phép chúng ta tạo những đường dẫn dễ nhìn hơn, chẳng hạn như đường dẫn bài viết mà bạn đang đọc 🙂
Để từ một đường dẫn URL đến một View thì Django sử dụng khái niệm URLConf, đây là một module Python của Django làm nhiệm vụ phân tích đường dẫn và chuyển đến một hàm View nhất định.
Tạo View
Chúng ta sẽ viết thêm một số hàm View mới.
def detail(request, question_id): return HttpResponse("You're looking at question %s." % question_id) def results(request, question_id): response = "You're looking at the results of question %s." return HttpResponse(response % question_id) def vote(request, question_id): return HttpResponse("You're voting on question %s." % question_id)
Chúng ta viết thêm 3 view mới là detail()
, results()
và vote()
.
Tiếp theo chúng ta tạo thêm các URL trỏ đến từng view này.
from django.conf.urls import url from . import views urlpatterns = [ # ex: /polls/ url(r'^$', views.index, name='index'), # ex: /polls/5/ url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'), # ex: /polls/5/results/ url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'), # ex: /polls/5/vote/ url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'), ]
Bạn có thể gõ trên thanh địa chỉ trình duyệt đến localhost:8000/polls/34/, Django sẽ gọi đến hàm detail()
và hiển thị chuỗi text tương ứng, bạn có thể thử với đường dẫn polls/34/results/ và polls/34/vote/ nữa.
Khi bạn gõ địa chỉ lên thanh URL của trình duyệt, Django sẽ đọc biến urlpatterns
trong file mysite.urls
vì mặc định file này được trỏ tới trong biến ROOT_URLCONF
trong file mysite/settings.py,
các đối tượng url sẽ được đọc dần dần từ trên xuống dưới cho đến khi có một đường dẫn vừa khít với URL mà bạn nhập vào. Khi tìm thấy localhost:8000/polls
vừa khít với '^polls/'
, Django sẽ cắt đoạn phía sau URL ra (vd như /polls/34/
) và gửi đến file polls/urls.py
để tiếp tục quá trình tìm kiếm, tại đây đoạn tiếp theo vừa khít với chuỗi Regex r'^(?P<question_id>[0-9]+)/$'
, mà chuỗi Regex này trỏ đến phương thức views.detail()
nên Django sẽ gọi phương thức này với tham số như sau:
detail(request=<HttpRequest object>, question_id='34')
Chuỗi Regex (?P<question_id>[0-9]+)
cho Django biết có một chuỗi con trong đoạn URL có dạng một con số (có 1 hoặc nhiều chữ số) và biến được truyền vào phương thức detail()
có tên là question_id
. Với sức mạnh của Regex thì bạn có thể tạo mẫu URL như thế nào cũng được, tất nhiên là tốt nhất nên làm cho nó ngắn gọn và dễ đọc.
Template
Các hàm View trả về một trong 2 thứ: hoặc là trả về một đối tượng HttpReponse
chứa nội dung HTML để hiển thị lên trình duyệt, hoặc là một lỗi exception 404.
Bạn muốn trả về thứ gì là tùy bạn thiết kế, có thể là đọc dữ liệu từ CSDL và dùng template để hiển thị dữ liệu, tạo file PDF, hiển thị nội dung XML, tạo file ZIP… Django không quan tâm bạn làm gì mà chỉ cần cái đối tượng HttpResponse
hoặc exception cuối cùng mà bạn tạo ra thôi.
Chúng ta sẽ sửa lại hàm index()
một tí.
from django.http import HttpResponse
from .models import Question
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
output = ', '.join([q.question_text for q in latest_question_list])
return HttpResponse(output)
Đoạn code trên rất đơn giản, chỉ là lấy ra 5 đối tượng Question
được thêm vào gần đây nhất, sau đó lấy thuộc tính question_text
đưa vào biến output rồi trả về thôi.
Bạn có thể vào địa chỉ localhost:8000/polls/
để xem kết quả và thấy là nếu chỉ in text ra như thế này thì giao diện quá xấu, mà nếu muốn thay đổi giao diện thì bạn phải điều chỉnh lại trong hàm index()
như thêm các thẻ h1, h2, thêm các mã CSS… như thế rất bất tiện, thế nên Django cung cấp cho chúng ta hệ thống Template, chúng ta có thể viết phần giao diện ở Template và dùng nó để hiển thị dữ liệu của các View cho đẹp mắt hơn, vậy tóm lại mục đích chính của View chính là kết nối giữa Template và Model.
Đầu tiên chúng ta tạo một thư mục có tên là templates
trong thư mục polls
, Django sẽ tự động tìm các file template trong thư mục này. Ở đây bạn tạo thêm một thư mục khác nữa tên là polls
, trong thư mục này bạn tạo một file tên index.html.
{% if latest_question_list %} <ul> {% for question in latest_question_list %} <li> <a href="/polls/{{question.id}}/"> {{question.question_text}} </a> <li> {% endfor %} </ul> {% else %} <p>No polls are available.</p> {% endif %}
Trong file index.html
chúng ta viết như trên.
Trình duyệt chỉ hiểu code HTML chứ không hiểu code Python, để có thể sử dụng code Python thì Django cung cấp cho chúng ta các thẻ template, thẻ template bắt đầu và kết thúc bằng cặp kí tự {% %}
hoặc {{ }}
, các câu lệnh Python nằm trong cặp dấu {% %}
, còn các biến thì nằm trong cặp {{ }}
.
from django.shortcuts import render from django.http import HttpResponse from django.template import loader from .models import Question def index(request): latest_question_list = Question.objects.order_by('-pub_date')[:5] template = loader.get_template('polls/index.html') context = { 'latest_question_list': latest_question_list, } return HttpResponse(template.render(context, request))
Chúng ta cũng sửa lại đoạn code trong hàm index()
để sử dụng template.
from django.template import loader
Để có thể dùng template thì trước hết chúng ta import module loader
trong gói django.template
.
template = loader.get_template('polls/index.html')
Để dùng template thì chúng ta dùng phương thức get_template()
, mặc định Django đã biết là sẽ tìm template trong thư mục polls/templates/
nên chúng ta chỉ cần đưa vào đường dẫn đến polls/index.html
là đủ.
return HttpResponse(template.render(context, request))
Sau đó chúng ta gọi đến phương thức template.render()
để tạo nội dung HTML có sử dụng template. Tham số đầu tiên là nội dung HTML trả về, tham số thứ 2 là đối tượng request
được gửi đến.
from django.shortcuts import render def index(request): latest_question_list = Question.objects.order_by('-pub_date')[:5] context = { 'latest_question_list': latest_question_list, } return render(request, 'polls/index.html', context)
Ngoài ra bạn cũng có thể dùng hàm render()
để trả về một đối tượng HttpResponse
một cách trực tiếp luôn, với tham số thứ nhất là đối tượng request, tham số thứ 2 là đường dẫn đến file template, tham số thứ 3 là nội dung HTML trả về.
Giải phóng lỗi 404
from django.shortcuts import render from django.http import HttpResponse, Http404 from django.template import loader from .models import Question def detail(request, question_id): try: question = Question.objects.get(pk=question_id) except Question.DoesNotExist: raise Http404("Question does not exist") return render(request, 'polls/detail.html', {'question':question})
Chúng ta sẽ giải phóng lỗi exception 404 nếu đường dẫn ID sai trong hàm detail()
.
from django.http import Http404
Đầu tiên chúng ta import module Http404
except Question.DoesNotExist: raise Http404("Question does not exist")
Phương thức get()
sẽ trả về một exception DoesNotExist
nếu không tìm thấy bản ghi nào trong list, tại đây chúng ta giải phóng một đối tượng Http404
. Còn nếu không có gì thì chúng ta trả về nội dung HTML như thường.
<h1>{{ question.question_text }}</h1> <ul> {% for choice in question.choice_set.all %} <li>{{ choice.choice_set }}</li> {% endfor %} </ul>
Trên đây là đoạn code template cho hàm view detail()
.
Bạn có thể thử nhập đường dẫn không có thật chẳng hạn như localhost:8000/polls/123456
để thấy Django trả về trang 404.
URL động
Bây giờ có một vấn đề như thế này, trong các file template như index.html
, các đường link chúng ta tham chiếu đến trong các thẻ <a>
là do chúng ta tự viết, chẳng hạn như href="/polls/..."
ở dưới đây:
<li> <a href="/polls/{{ question.id }}/"> {{ question.question_text }} </a> </li>
Giả sử trong trang web của chúng ta bắt buộc phải có 100 thẻ <a>
như thế, việc code từng đường dẫn là cực kỳ mệt mỏi, và khi muốn thay đổi chẳng hạn như chúng ta muốn đổi URL từ localhost:8000/polls/1/ thành localhost:8000/polls/details/1/ thì chúng ta phải đổi bằng tay hết 100 thẻ <a>
đó, thế thì chỉ có chết 🙂
Do đó chúng ta có thể tham chiếu đến các đối tượng url đã định nghĩa trong file urls.py
của ứng dụng. Khi chúng ta tạo các đối tượng url trong file urls.py
, tham số thứ 3 là name
, tham số này do chúng ta tự đặt, và Django cho phép chúng ta tham chiếu đến chúng trong các file template.
<a href="{% url 'detail' question.id %}"> {{ question.question_text }} </a>
Bằng cách này chúng ta có thể tham chiếu đến đối tượng url
trong file urls.py
, và khi nào cần thay đổi URL mới thì chúng ta chỉ cần thay đổi trong file urls.py
là được:
... url(r'^details/(?P<question_id>[0-9]+)/$', views.detail, name='detail'), ...
Đặt namespace cho URL
Khi dùng đến URL động thì lại phát sinh một vấn đề nữa, mặc định thì Django tự động tìm các file template bên trong thư mục template, vậy thì giả sử khi chúng ta có thêm nhiều ứng dụng khác ngoài polls
, chẳng hạn như một ứng dụng blog
, trong đó cũng có hàm view detail()
, và hàm view này cũng sử dụng một template tên là detail.html
, vậy thì khi đó Django sẽ gắn template của ứng dụng polls
vào view detail()
của ứng dụng blog
, như thế sẽ báo lỗi vì ứng dụng blog
sẽ không có các biến giống như polls
.
Để giải quyết vấn đề này, chúng ta sẽ đặt namespace cho các biến url
, nếu bạn chưa biết namespace là gì thì cứ nghĩ rằng chúng cũng giống như một cách gộp nhóm những thứ giống nhau lại với nhau thôi, nếu muốn tìm hiểu thêm thì bạn có thể search trên mạng, mình không đi sâu ở đây.
from django.conf.urls import url from . import views app_name = "polls" urlpatterns = [ # ex: /polls/ url(r'^$', views.index, name='index'), # ex: /polls/detils/5/ #url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'), url(r'^details/(?P<question_id>[0-9]+)/$', views.detail, name='detail'), # ex: /polls/5/results/ url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'), # ex: /polls/5/vote/ url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote') ]
Để đặt tên namespace cho các đối tượng url thì chúng ta chỉ cần đặt giá trị cho biến app_name
trong file urls.py
là được.
<a href="{% url 'polls:detail' question.id %}"> {{ question.question_text }} </a>
Trong file template, vd như index.html
thì chúng ta chỉ cần viết đủ tên <namespace>:<tên_biến_url>
là xong, vd url có name là detail
thì viết đầy đủ thành polls:detail
.
Django – Hệ thống Admin
Trong phần này chúng ta sẽ tìm hiểu về hệ thống Admin có sẵn trong Django.
Thường thì khi viết một ứng dụng nào đó, chẳng hạn như website bán hàng, blog, web tin tức, diễn đàn…v.v. ngoài các trang hiển thị thông tin thì chúng ta còn phải xây dựng một trang nữa là trang admin, trong đó lại bao gồm nhiều trang nhỏ hơn như thêm, sửa, xóa bài viết, cài đặt trang web… việc làm các trang này khá đơn giản, không cầu kỳ nhưng cũng rất nhàm chán. Chính vì vậy Django cung cấp sẵn một trang admin cho riêng chúng ta.
Tạo user
Để có thể đăng nhập vào admin thì trước hết chúng ta phải tạo một tài khoản admin đã vì Django không tạo sẵn cho chúng ta khi tạo project.
C:\Project\mysite>python manage.py createsuperuser
Để tạo tài khoản thì chúng ta chạy file manage.py
với tham số createsuperuser
.
Username: admin Email address: admin@example.com Password: Password (again): Superuser created successfully.
Tiếp theo bạn cung cấp username, password và email là xong.
Truy cập trang admin
Để truy cập vào trang admin thì bạn chỉ cần thêm /admin vào đường dẫn trang chủ là được.
Sau đó bạn đăng nhập bằng username và password mà chúng ta đã tạo hồi nãy là sẽ được chuyển đến giao diện admin.
Mặc định Django đã bật chức năng dịch nên có thể trang admin sẽ được hiển thị bằng ngôn ngữ mà bạn dùng trên trình duyệt.
Tại trang admin chúng ta có thể thao tác với 2 bảng là User và Group. Các bảng Question và Choice mà chúng ta đã tạo ra không được hiển thị ở đây là vì chúng ta chưa đăng ký các bảng đó với trang admin.
from django.contrib import admin
# Register your models here.
from .models import Question, Choice
admin.site.register(Question)
admin.site.register(Choice)
Để đăng ký các bảng (hay các mô hình) với admin thì chúng ta chỉ cần dùng phương thức admin.site.register()
trong file admin.py
mà Django đã tạo cho chúng ta.
Sau khi đã đăng kí xong thì 2 bảng Question
và Choice
sẽ hiện ra trong giao diện admin.
Giao diện admin mặc định của Django rất đơn giản, bạn có thể thực hiện thêm, sửa, xóa các bảng này một cách dễ dàng.
Django – Model
Trong phần này chúng ta sẽ học cách tạo các Model để có thể tương tác với cơ sở dữ liệu.
Thiết lập cơ sở dữ liệu
Trong thư mục mysite
chứa một file tên là settings.py
, file này chứa các thông tin cấu hình server của bạn.
Mặc định thì Django server sử dụng cơ sở dữ liệu SQLite, và trong bài này mình cũng sẽ sử dụng SQLite cho đơn giản, nếu bạn dùng SQLite thì bạn không cần quan tâm đến việc tạo CSDL và cấu hình user, nhưng nếu bạn muốn sử dụng CSDL khác thì trong file settings.py
, bạn tìm đến đối tượng DATABASES
và thay đổi các giá trị sau:
ENGINE
: tên module dành cho từng CSDL, mặc định sử dụng SQLitedjango.db.backends.sqlite3
– cơ sở dữ liệu SQLitedjango.db.backends.postgresql
– cơ sở dữ liệu PostgreSQLdjango.db.backends.mysql
– cơ sở dữ liệu MySQLdjango.db.backends.oracle
– cơ sở dữ liệu Oracle
NAME
: tên CSDL, mặc định là filedb.sqlite3
được tạo ra ở thư mục gốc của server. Nếu bạn không dùng CSDL SQLite thì bạn phải tạo CSDL với tên trùng với NAME trong CSDL mà bạn dùng (bằng câu lệnhCREATE DATABASE
<name>)
Ngoài ra nếu bạn không dùng SQLite thì bạn cũng phải cung cấp thêm các thông tin USER
, PASSWORD
, HOST
nữa và user phải có quyền truy cập CSDL cũng như một số quyền như ghi, xem…
Trong file settings.py
còn có một list có tên là INSTALLED_APPS
, mặc định khi tạo một project, Django cung cấp cho chúng ta một số ứng dụng thường dùng trong list này. Trong bài sau chúng ta sẽ dùng một ứng dụng trong list này đó là django.contrib.admin
.
Mỗi ứng dụng trong list này sẽ giúp Django tìm các model để tạo bảng tương ứng trong file CSDL, nhưng khi tạo project thì các bảng này không tự động được tạo trong file db.sqlite3
, để tạo các bảng này thì chúng ta chạy lệnh sau:
C:\Project\mysite>python manage.py migrate
Lệnh migrate
sẽ tìm các module được liệt kê trong list INSTALLED_APPS
(trong file mysite/settings.py
) và tạo các bảng CSDL tương ứng.
Tạo mô hình dữ liệu cho ứng dụng
Mô hình ở đây giống như lớp trong lập trình hướng đối tượng hoặc Model trong mô hình MVC vậy thôi, không có gì khó hiểu cả 🙂
Mặc định khi tạo một ứng dụng web trong một project Django thì Django đã tạo sẵn cho chúng ta một file có tên là models.py
để chúng ta khai báo các mô hình trong này rồi nhưng bạn cũng có thể viết trong các file khác nếu thích.
from django.db import models
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
Chúng ta sẽ tạo 2 mô hình (2 lớp…) là Question
và Choice
.
class Question(models.Model): ... class Choice(models.Model):
Mỗi mô hình được tạo ra phải được kế thừa từ lớp django.db.models.Model
.
question_text = models.CharField(max_length=200) pub_date = models.DateTimeField('date published') ... votes = models.IntegerField(default=0)
Mỗi thuộc tính trong mô hình được kế thừa từ một lớp Field
, đây là một lớp ảo trong Django, lớp này lại có nhiều lớp kế thừa khác đại diện cho mỗi kiểu dữ liệu ví dụ như CharField
là kiểu text, DateTimeField
là kiểu DateTime
…
Tham số max_length
là số lượng ký tự tối đa, đây là tham số bắt buộc phải có. Các tham số còn lại như default
… là tham số tùy chọn, không có cũng được.
question = models.ForeignKey(Question, on_delete=models.CASCADE)
Ngoài các kiểu dữ liệu thường dùng thì chúng ta còn có kiểu khóa ngoại được định nghĩa trong lớp ForeignKey
, tham số đầu tiên là bảng mà khóa ngoại này tham chiếu tới, on_delete=models.CASCADE
tức là khi dữ liệu trong bảng cha có sự thay đổi thì dữ liệu trong bảng con cũng sẽ thay đổi theo, chẳng hạn như bản ghi trong bảng Question
bị xóa thì các bản ghi trong bảng Choice
có tham chiếu tới bản ghi trong bảng Question
này cũng sẽ bị xóa.
Tạo bảng trong CSDL từ Model
Sau khi đã tạo các lớp mô hình trong Python, Django sẽ nhìn các thuộc tính trong từng lớp để tạo các bảng tương ứng trong CSDL và tạo thêm các lớp mới trong Python cung cấp các hàm để bạn thao tác với các bảng trong CSDL.
INSTALLED_APPS = [
'polls.apps.PollsConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
Để Django tạo các bảng mới trong CSDL thì chúng ta phải khai báo trước trong list INSTALLED_APPS
đã bằng cách thêm dòng polls.apps.PollsConfig
.
C:\Project\mysite>python manage.py makemigrations polls
Tiếp theo bạn chạy lệnh makemigration polls
để báo cho Django biết là bạn đã thay đổi một số mô hình, ở đây là thêm 2 lớp mới trong gói polls
.
Migrations for 'polls':
0001_initial.py:
- Create model Choice
- Create model Question
- Add field question to choice
Django sẽ tạo một file python để lưu các thông tin về sự thay đổi này, ở đây là file 0001_initial.py
.
C:\Project\mysite>python manage.py migrate
Operations to perform:
Apply all migrations: admin, contenttypes, polls, auth, sessions
Running migrations:
Rendering model states... DONE
Applying polls.0001_initial... OK
Chúng ta chạy lại lệnh migrate
để Django cập nhật lại CSDL. Lúc này CSDL chúng ta sẽ có thêm 2 bảng mới là polls_choice
và polls_question
(tên bảng được đặt theo cú pháp <tên
package>_<tên lớp>
).
Thao tác với các bảng
Sau khi Django đã tạo các bảng bạn có thể bắt đầu thực hiện các công việc thường dùng như thêm-sửa-xóa… bản ghi.
C:\Project\mysite>python manage.py shell
Bạn có thể sử dụng trình shell mà Django cung cấp sẵn trong file manage.py
. Nếu bạn không muốn chạy trực tiếp trong Command Prompt mà muốn chạy trong file .py riêng thì trong file .py đấy bạn phải khai báo các dòng dưới đây:
import os import django os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings"); django.setup()
Các dòng này sẽ thiết lập biến môi trường DJANGO_SETTINGS_MODULE
trỏ đến module mysite.settings
, mục đích là để chỉ cho Python biết đường dẫn import. Mặc định khi chạy file manage.py
từ cmd thì biến này sẽ được Django thiết lập luôn.
>>> from polls.models import Question, Choice
>>> Question.objects.all()
[]
Chúng ta import 2 lớp Question
và Choice
. Phương thức Question.objects.all()
liệt kê toàn bộ đối tượng Question
đang có trong CSDL, hiện tại là không có bản ghi nào nên kết quả trả về list rỗng.
>>> from django.utils import timezone
>>> q = Question(question_text="What's new?", pub_date=timezone.now())
Chúng ta tạo một đối tượng Question
và thiết lập pub_date
là ngày giờ hiện tại của máy tính, chúng ta lấy thông tin này từ phương thức timezone.now()
trong module django.utils.timezone
.
>>> q.save()
Để lưu đối tượng Question mới này vào CSDL thì chỉ cần gọi phương thức save()
là được.
>>> q.id
1
Khi viết lớp Question
và Choice
chúng ta không cung cấp thuộc tính id
hay bất cứ thuộc tính nào để làm khóa chính, vì vậy khi tạo CSDL Django sẽ tự động thêm 1 thuộc tính id
để làm khóa chính và thuộc tính này sẽ tự động tăng.
>>> q.question_text
What's new?
>>> q.question_text = "What's up?"
>>> q.save()
Bạn có thể thay đổi trực tiếp giá trị trên các đối tượng này sau đó gọi phương thức save()
là dữ liệu trên CSDL sẽ được cập nhật.
from django.db import models
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
def __str__(self):
return self.question_text
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
def __str__(self):
return self.choice_text
Chúng ta override phương thức __str__()
, nếu không khi gọi phương thức str()
mặc định sẽ cho output khá kì quái.
Sau đó chúng ta tắt và mở lại shell, rồi chạy thử đoạn lệnh sau sẽ được kết quả khác:
>>> from polls.models import Question, Choice >>> Question.objects.all() [<Question: What's up?>]
Phương thức objects.all()
sẽ gọi đến phương thức __str__()
.
>>> Question.objects.filter(id=1) [<Question: What's up?>] >>> Question.objects.filter(question_text__startswith='What') [<Question: What's up?>]
Bạn có thể lọc các bản ghi theo thuộc tính bằng phương thức filter()
.
>>> q = Question.objects.get(pk=1)
Bạn cũng có thể lấy từng bản ghi đơn lẻ bằng phương thức get()
, ở trên chúng ta lấy theo khóa chính pk
(Primary Key).
>>> q.choice_set.all() []
Bất cứ bảng nào có một bảng khác chứa khóa ngoại tham chiếu đến khóa chính của nó đều sẽ có một thuộc tính được tạo tự động là một tập hợp các đối tượng của bảng kia. Ở đây bảng Choice
chứa khóa ngoại tham chiếu đến bảng Question
, do đó bảng Question
sẽ có một danh sách các đối tượng Choice
, chúng ta có thể lấy danh sách này qua choice_set.all()
(tên danh sách được đặt theo <tên bảng>_set
). Ở đây chúng ta vẫn chưa tạo đối tượng Choice
nào nên danh sách trả về rỗng.
>>> q.choice_set.create(choice_text='Not much', votes=0) <Choice: Not much> >>> q.choice_set.create(choice_text='The sky', votes=0) <Choice: The sky> >>> c = q.choice_set.create(choice_text='Just hacking again', votes=0)
Chúng ta tạo 3 đối tượng Choice bằng phương thức choice_set.create()
.
>>> q.choice_set.all() [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>] >>> q.choice_set.count() 3
Chúng ta có thể lấy số lượng các bản ghi trong bảng bằng phương thức count()
.
>>> c = q.choice_set.filter(choice_text__startswith='Just hacking')
>>> c.delete()
Để xóa một bản ghi trong bảng thì chúng ta dùng phương thức delete()
.
Django – Tạo project Django
Trong phần này chúng ta sẽ bắt đầu học cách tạo một ứng dụng web bằng django.
Tạo project
C:\Project>django-admin startproject mysite
Để tạo một project Django thì bạn mở Command Prompt (cmd) lên và chuyển đến thư mục mà bạn muốn tạo, sau đó gõ đoạn lệnh phía trên, ở trên chúng ta tạo một project với tên là mysite
. Lệnh startproject sẽ tạo một thư mục có tên là mysite
, cấu trúc bên trong thư mục sẽ như thế này:
mysite/
manage.py
mysite/
__init__.py
settings.py
urls.py
wsgi.py
Chúng ta sẽ từ từ tìm hiểu các file này sau.
Khi đặt tên project bạn tránh đặt những tên trùng với các từ khóa có sẵn trong Python như sys
, os
, django
…. để tránh bị xung đột.
Làm web trong Python Django không giống với các ngôn ngữ khác như PHP, ASP.NET hay Java… bạn phải cài sẵn một web server (như Apache hoặc IIS…) trên máy và cài các phần mềm đi kèm trên web server đó, sau đó bạn viết code và bỏ vào thư mục gốc của web server (vd như www hay htdocs…), mỗi ứng dụng web Django tự chạy một server cho chính nó, do đó bạn có thể đặt code của bạn ở bất kỳ đâu mà bạn muốn.
Chạy server
Thư mục mysite
có chức năng tương tự như thư mục gốc trong các web server khác như www hay htdocs… bên trong thư mục này có chứa một file tên là manage.py
, file này cung cấp các chức năng để bạn vận hành server của bạn.
C:\Project\mysite>python manage.py runserver
Để chạy server thì bạn chạy file manage.py
và đưa vào tham số runserver
.
Performing system checks... System check identified no issues (0 silenced). You have unapplied migrations; your app may not work properly until they are applied. Run 'python manage.py migrate' to apply them. March 21, 2016 - 15:50:53 Django version 1.9, using settings 'mysite.settings' Starting development server at <a class="reference external" href="http://127.0.0.1:8000/">http://127.0.0.1:8000/</a> Quit the server with CTRL-BREAK.
Chúng ta đã chạy một web server viết bằng Python. Mặc định Django server sẽ chạy trên cổng 8000 (chúng ta có thể cấu hình để server chạy trên một cổng khác).
Bạn có thể vào trình duyệt và trỏ đường dẫn đến 127.0.0.1:8000 hoặc localhost:8000 để xem thông báo của server.
Để đổi port mặc định của server thì bạn thêm số port mà bạn muốn sau tham số runserver.
C:\Project\mysite>python manage.py runserver 8080
Đoạn code trên sẽ chạy server trên port 8080.
Cũng giống như các web server khác, vì Python cũng là một ngôn ngữ thông dịch nên khi bạn viết code xong và bấm save, muốn test thì bạn chỉ cần refresh lại trang web trên trình duyệt là sẽ thấy sự thay đổi, bạn không cần phải khởi động lại server làm gì.
Tạo web app
Những gì chúng ta vừa làm ở trên là tạo một webserver cho project của chúng ta. Bây giờ chúng ta sẽ tạo ứng dụng web trên server này.
C:\Project\mysite>python manage.py startapp polls
Bạn mở một trình Command Prompt khác lên và trỏ đến thư mục chứa file manage.py
, sau đó chạy file này với tham số startapp
để tạo một ứng dụng web, ở đây chúng ta tạo một ứng dụng web với tên là polls
, nếu như bạn chưa biết thì đây là một ứng dụng đặt câu hỏi và cho phép người dùng trả lời trắc nghiệm, thường dùng để làm các chương trình khảo sát người dùng… đây sẽ là ứng dụng mà chúng ta làm trong những bài đầu tiên.
Một thư mục với tên polls
sẽ được tạo ra và có cấu trúc như sau:
polls/
__init__.py
admin.py
apps.py
migrations/
__init__.py
models.py
tests.py
views.py
Thư mục này chứa các file chuẩn của một ứng dụng web Django.
Sự khác nhau giữa một Project và một Web App là project thì bao gồm nhiều app, trong đó mỗi app thực hiện một công việc riêng biệt.
Tạo View
Trong thư mục polls
có chứa một file tên là views.py
, bản chất thì file này giống như một file .php, .asp… trong các ngôn ngữ khác vậy.
from django.http import HttpResponse
def index(request):
response = HttpResponse()
response.write("<h1>Welcome</h1>")
response.write("This is the polls app")
return response
Bên trong file views.py
này chúng ta viết hàm index
, hàm này trả về một đối tượng HttpResponse
. Để gọi được tới hàm index
này và lấy nội dung HTML thì chúng ta phải tạo đường dẫn URL tới file này.
polls/
__init__.py
admin.py
apps.py
migrations/
__init__.py
models.py
tests.py
urls.py
views.py
urls.py
bên trong thư mục polls.
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.index, name='index'),
]
Bên trong file urls.py
chúng ta khai báo một đối tượng List có tên urlpatterns
, Django sẽ tìm thông tin về các url trong list này, list này chứa các đối tượng url, mỗi url bao gồm 3 tham số, tham số thứ nhất là biểu thức chính quy (Regex – Regular Expression), tham số thứ hai là tên module và tên hàm sẽ trả về nội dung HTML, ở đây là hàm index
trong module views
(file views.py
), tham số thứ 3 là tên biến toàn cục trong toàn bộ ứng dụng, trong đó 2 tham số đầu tiên là bắt buộc phải có.
Regex (biểu thức chính quy) là một chủ đề khá lớn, Regex cho phép chúng ta tạo ra các mẫu định dạng text dùng trong tìm kiếm, xác thực… ở đây Regex giúp tạo các đường dẫn tới các hàm trả về nội dung HTML một cách tự động. Để tạo các chuỗi Regex thì có một số quy luật sau đây:
^
– bắt đầu regex$
– kết thúc regex\d
– một kí tự số+
– kí tự phía trước có thể lặp lại một hoặc nhiều lần/
– có một dấu/
()
– gom nhóm một số kí tự nhất định lại với nhau
Chúng ta sẽ tìm hiểu thêm về Regex sau. Đối với trường hợp của chúng ta thì Regex chỉ đơn giản là ^$
, tức là đường dẫn trắng, không có gì cả, khi nhập đường dẫn này thì trả về nội dung từ hàm views.index
.
Sau khi đã có đường dẫn cho riêng polls
, thì tiếp theo chúng ta phải tạo đường dẫn đến ứng dụng polls
này nữa, chúng ta sẽ tạo đường dẫn cho từng ứng dụng trong file mysite/urls.py
.
from django.conf.urls import include, url
from django.contrib import admin
urlpatterns = [
url(r'^polls/', include('polls.urls')),
url(r'^admin/', admin.site.urls),
]
Bên trong file mysite/urls.py
cũng có một list urlpatterns
, list này chứa các đối tượng url dẫn đến các ứng dụng web khác mà chúng ta viết.
Ở đây tham số thứ 2 chúng ta không chỉ đến một hàm index nào đó trong một module nào đó, mà chúng ta chỉ đến một module có chứa đối tượng urlpatterns
khác, trong trường hợp này là polls.urls
, nên chúng ta phải dùng đến hàm include()
, đối với các url chỉ đến một module url khác thì chuỗi regex không kết thúc bằng dấu $
, hay nói cách khác là không có kết thúc.
Khi chúng ta gõ localhost:8000
thì django sẽ đọc các url trong file mysite/urls.py
, trong file này chúng ta có một đường dẫn đến admin/
, đường dẫn này trỏ đến hàm get_url()
trong module admin.site
của django, do đó bạn có thể gõ localhost:8000/admin và django sẽ trả về một trang đăng nhập mẫu (chúng ta sẽ tìm hiểu về trang admin này sau), url thứ hai là đường dẫn polls/
trỏ đến một file urls.py
khác ở module polls.urls do chúng ta tự viết, file này lại chứa một list urlpatterns
khác chứa các đường dẫn đến các hàm trả về HTML riêng của chúng, thế nên chúng ta có thể gõ localhost:8000/polls
, và django sẽ trả về trang HTML tạo ra từ hàm index()
trong module polls.urls
.
Bạn có thể điều chỉnh chuỗi Regex để hiểu thêm.
Django – Giới thiệu
Django là một web framework miễn phí mã nguồn mở được viết bằng Python. Django sử dụng mô hình Model-View-Control (MVC). Django được phát triển bởi Django Software Foundation(DSF) – một tổ chức phi lợi nhuận độc lập.
Mục tiêu chính của Django là đơn giản hóa việc tạo các website phức tạp có sử dụng cơ sở dữ liệu. Django tập trung vào tính năng “có thể tái sử dụng” và “có thể tự chạy” của các component, tính năng phát triển nhanh, không làm lại những gì đã làm. Một số website phổ biến được xây dựng từ Django là Pinterest, Instagram, Mozilla, và Bitbucket.
Cài đặt Django
Để có thể sử dụng Django thì bạn nhất định phải cài Python trong máy mình rồi, và khi cài thì Python có kèm theo một chương trình có tên là pip,
đây là một phần mềm quản lý các gói mở rộng dành cho Python. Để cài đặt Django thì bạn sẽ dùng đến pip.
Bạn mở Command Prompt (cmd) lên và gõ lệnh:
C:\User\PhoCode>pip install Django
để Python cài đặt gói Django mới nhất, gói này sẽ nằm trong thư mục Lib/site-packages trong thư mục cài đặt Python, hoặc gõ lệnh
C:\User\PhoCode>pip install Django==1.9.4
để cài đặt gói Django phiên bản 1.9.4, đây là phiên bản mà mình sử dụng để viết series này.
Nếu khi cài Python bạn không cài pip thì bạn có thể lên trên trang GitHub của Django để tải về tại địa chỉ https://github.com/django/django.git
Xem phiên bản Django
Sau khi cài đặt xong gói Django, bạn có thể kiểm tra một số thông tin của gói này.
import django print(django.get_version())
Bằng cách dùng phương thức django.get_version()
.
1.9.4