Category Archives: Django

Django – Form có sẵn của Django

Trong bài Form và các View có sẵn chúng ta đã tạo form bằng các thẻ HTML, nếu việc code các form bằng tay như thế mệt mỏi quá thì Django cung cấp cho chúng ta lớp Form để chúng ta có thể tạo form ngay từ code Python và có thể nhận request và trả response về cho người dùng.

Trong HTML thì form là một tập các thẻ element nằm giữa cặp thẻ <form>...</form> cho phép người dùng thực hiện một công việc nào đó, sau đó gửi dữ liệu lên server rồi trả về câu trả lời. Hầu hết HTML chỉ hỗ trợ các thẻ element cơ bản như text, checkbox… muốn hiển thị các element cao cấp hơn như DateTimePicker (chọn ngày tháng), Slider (thanh trượt)… bạn phải dùng đến các ngôn ngữ hỗ trợ thêm khác như Javascript, CSS…

Hoặc bạn có thể dùng lớp Form có sẵn của Django, Django cung cấp lớp Form hỗ trợ tạo form một cách nhanh chóng và dễ dàng. Trong phần này chúng ta sẽ tạo form đăng ký user đơn giản sử dụng lớp Form do Django cung cấp.

Tạo app

Đầu tiên chúng ta tạo một app mới với tên là user_auth:

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

Tiếp theo chúng ta khai báo app này trong list INSTALLED_APP:

...
INSTALLED_APPS = [
    'user_auth',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
...

Tạo Form

Trong thư mục user-auth chúng ta tạo một file mới với tên là forms.py để lưu các lớp form.

from django import forms

class RegisterForm(forms.Form):
    username = forms.CharField(label='Username', max_length=100)
    password = forms.CharField(widget=forms.PasswordInput)
    email = forms.EmailField(label='Email')

Các lớp dùng để tạo form được kế thừa từ lớp django.forms.Form.

username = forms.CharField(label='Username', max_length=100)
password = forms.CharField(widget=forms.PasswordInput)
email = forms.EmailField(label='Email')

Bên trong lớp này chúng ta cũng khai báo các trường là các đối tượng Field, nhưng các đối tượng Field này không giống như Field khi tạo model, Field ở đây là để tạo form HTML còn field bên model là để tạo bảng CSDL (hơi rối :()

Mỗi loại lớp Field nhận vào vài tham số khi khởi tạo nhưng không có tham số nào là bắt buộc phải có, chẳng hạn như label của CharField sẽ được dùng cho thuộc tính name trong thẻ <label>...

Ở trên chúng ta có 2 CharField dùng để nhập text và một EmailField dùng để nhập email. Đây là danh sách các lớp và tham số của form Field của Django.

password = forms.CharField(widget=forms.PasswordInput)

Vì HTML có nhiều thẻ element có công dụng chung nhưng lại hiển thị dữ liệu khác nhau, chẳng hạn như một thẻ <input> có thể dùng để nhập tên, số điện thoại, password… do đó Django cung cấp cho mỗi đối tượng Field một đối tượng Widget để chúng ta có thể chỉ định loại text nào hiển thị cái gì.

Ở trên chúng ta dùng CharField để hiển thị password bằng cách truyền tham số widget là forms.PasswordInput. Đây là danh sách các lớp Widget.

Tạo Template

Chúng ta tạo một thư mục với tên là templates, trong thư mục này chúng ta lại tạo một thư mục khác là user_auth rồi tạo một file HTML với tên là register.html:


<form action="" method="post">
    {% csrf_token %}
    {{ form }}
    <input type="submit" value="Submit" />
</form>

Khi chúng ta viết các lớp form, Django sẽ nhìn các trường mà chúng ta khai báo rồi sinh ra các thẻ <input> tương ứng, ngoại trừ cặp thẻ <form>...</form> và thẻ <input type="submit"/>

<form action="" method="post">

Thuộc tính action sẽ chuyển dữ liệu tới đường dẫn tương ứng, ở đây mình để trống tức là gửi lại cho chính trang đó. Phương thức POST sẽ mã hóa dữ liệu được gửi đi.

{{ form }}

Chúng ta có thể tham chiếu đến phần tử form trong list context mà chúng ta sẽ khai báo trong các hàm view bên dưới, và Django sẽ tự động tạo các thẻ <label><input> cho chúng ta.

Tạo View

Chúng ta viết hàm view register() như sau.

from django.shortcuts import render

# Create your views here.

from django.http import HttpResponse
from .forms import RegisterForm

def register(request):

    if request.method == 'POST':
        response = HttpResponse()
        response.write("<h1>Thanks for registering</h1></br>")
        response.write("Your username: " + request.POST['username'] + "</br>")
        response.write("Your email: " + request.POST['email'] + "</br>")
        return response 
 
    registerForm = RegisterForm() 
    return render(request, 'user_auth/register.html', {'form':registerForm})

Đoạn code trên rất đơn giản, chúng ta chỉ import lớp RegisterForm từ module forms.py mà chúng ta viết ở trên. Sau đó tạo một đối tượng RegisterForm rồi đưa vào làm phần tử của list context (tham số thứ 3 trong hàm render()).

if request.method == 'POST':
    response = HttpResponse()
    response.write("<h1>Thanks for registering</h1></br>")
    response.write("Your username: " + request.POST['username'] + "</br>")
    response.write("Your email: " + request.POST['email'] + "</br>")
    return response 

Sau khi người dùng đã nhập các thông tin cần thiết vào các ô text và bấm gửi thì dữ liệu sẽ gửi trả lại hàm register() do chúng ta đã thiết lập nó trong thuộc tính action ở file template. Nên ở đây chúng ta kiểm tra xem nếu phương thức gửi tới là phương thức POST thì trả về câu trả lời với thông tin mà người dùng đã nhập

Tiếp theo chúng ta tạo url tới hàm view này, đầu tiên chúng ta khai báo một module urls.py của riêng app user_auth đã:

from django.conf.urls import url

from . import views

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

Sau đó là khai báo url của project tới app user_auth.

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

urlpatterns = [
    url(r'^admin/', admin.site.urls), 
    url(r'^user-auth/', include('user_auth.urls')),
]

Bây giờ bạn có thể chạy server và trỏ đường dẫn tới localhost:8000/user-auth/register/ để xem giao diện mặc định do Form của Django tạo ra.

Untitled

Như bạn thấy trong hình trên giao diện do Django tạo ra mặc định nhìn rất “chuối” vì nhiệm vụ của Django chỉ là tự sinh code cho 2 thẻ element là <label><input> cho chúng ta rảnh tay hơn mà thôi, bản thân Django cũng cung cấp một số thiết lập để chúng ta tùy chỉnh giao diện nhưng rất hạn chế. Phần lớn chúng ta sẽ phải tự chỉnh bằng tay bằng cách sửa đối code trong file template, thêm các file CSS, JS… để giao diện nhìn đẹp hơn.

Có một số tùy chọn hiển thị mà Django cung cấp cho chúng ta là:

  • form.as_table: bọc các thẻ <label><input> trong thẻ cặp thẻ <tr></tr>
  • form.as_p: bọc các thẻ <label><input> trong cặp thẻ <p></p>
  • form.as_ul: bọc các thẻ <label><input> trong cặp thẻ <li>

Tuy nhiên bạn phải khai báo cặp thẻ <table></table><ul></ul> tương ứng trước khi dùng. Ví dụ:


<form action="" method="post">
    {% csrf_token %}
    <table>
        {{ form.as_table }}
    </table>
    <input type="submit" value="Submit" />
</form>

Untitled

Giao diện nhìn có đỡ hơn một tí 🙂

Hoặc nếu muốn bạn có thể truy xuất từng phần tử của biến form trong file template để hiển thị theo ý muốn:


<form action="" method="post">
    {% csrf_token %}
        <table>
            <tr>
                <th>
                    <label for="{{ form.username.id_for_label }}">User:</label>
                </th>
                <td>
                    {{ form.username }}
                <td>
            </tr>
            <tr>
                <th>
                    <label for="{{ form.password.id_for_label }}">Password:</label>
                </th>
               <td>
                    {{ form.password }}
               </td>
            </tr>
            <tr>
                <th>
                    <label for="{{ form.email.id_for_label }}">Email:</label>
                </th>
                <td>
                    {{ form.email }}
                </td>
            </tr>
            <tr>
                <td></td>
                <td>
                    <input type="submit" value="Submit">
                </td>
            </tr>
    </table>
</form>

Bạn chỉ cần lấy theo cú pháp form.<tên thuộc tính> là Django sẽ tự động sinh code cho các thẻ tương ứng. Ví dụ như form.username...

Ngoài ra bạn còn có thể lấy các thuộc tính field nữa. Trong ví dụ trên là form.username.id_for_label sẽ lấy về id được tạo ra tự động. Đây là danh sách các thuộc tính field có trong Django.

Untitled

Đây là giao diện được thiết kế lại dùng thẻ <table> rất đơn giản.

Untitled

Django – Model nâng cao – Phần 3

Sau khi chúng ta đã tạo các lớp model, Django sẽ tự động tạo các phương thức mới trong model đó cho phép bạn thao tác với CSDL, bạn có thể tạo, sửa, xóa, cập nhật dữ liệu.

Trong bài này chúng ta sẽ tạo và sử dụng các lớp model sau đây để thực hành với các phương thức do Django cung cấp.

from django.db import models

class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def __str__(self): 
        return self.name

class Author(models.Model):
    name = models.CharField(max_length=50)
    email = models.EmailField()

    def __str__(self): 
        return self.name

class Entry(models.Model):
    blog = models.ForeignKey(Blog)
    headline = models.CharField(max_length=255)
    body_text = models.TextField()
    pub_date = models.DateField()
    mod_date = models.DateField()
    authors = models.ManyToManyField(Author)
    n_comments = models.IntegerField()
    n_pingbacks = models.IntegerField()
    rating = models.IntegerField()

    def __str__(self): 
        return self.headline

Ở đây chúng ta có 3 model là Blog, AuthorEntry, các model này đại diện cho các lớp model cơ bản của một website blog.

Sau khi bạn đã tạo các lớp model trên thì bạn chạy 2 lệnh python manage.py makemigrationspython manage.py migrate để Django cập nhật các bảng mới trong CSDL. Sau đó bạn chạy trình shell bằng lệnh python manage.py shell để bắt đầu thao tác với CSDL (nếu bạn muốn chạy trong file .py riêng thì phải thiết lập biến môi trường DJANGO_SETTINGS_MODULE đến mysite.settings trước).

Tạo đối tượng

Mỗi bản ghi hay mỗi dòng trong từng bảng CSDL sẽ tương ứng với một đối tượng cụ thể. Để tạo một bản ghi trong CSDL thì chúng ta chỉ cần tạo một đối tượng tương ứng với trong Python rồi gọi phương thức save() là xong. Vd:

>>> from blog.models import Blog
>>> b = Blog(name='J.R.R Blog', tagline='All the latest J.R.R news.')
>>> b.save()

Đoạn code trên sẽ thực hiện câu lệnh INSERT, bạn phải gọi phương thức save() thì dữ liệu mới được cập nhật lên CSDL.

Ngoài ra nếu bạn muốn vừa tạo đối tượng vừa cập nhật thẳng lên CSDL luôn thì dùng phương thức create() trong đối tượng objects, đối tượng này là một đối tượng tĩnh do Django tạo ra cho chúng ta để đơn giản hóa việc thao tác với CSDL. Vd:

>>> Blog.objects.create(name='J.R.R Blog', tagline='All the latest J.R.R news.')

Cập nhật đối tượng

Bạn có thể thay đổi giá trị của các đối tượng trong Python rồi chỉ cần gọi phương thức save() là Django sẽ cập nhật mới trong CSDL.

>>> b.name = 'New name'
>>> b.save()

Cập nhật các thuộc tính khóa ngoại

Chúng ta tạo đối tượng Author mới như sau:

>>> from blog.models import Entry, Author
>>> a = Author(name='J.R.R. Tolkien', email='jrr@middleeast.com')
>>> a.save()

Bây giờ chúng ta sẽ tạo đối tượng Entry, đối tượng này chứa 2 thuộc tính khóa ngoại, một thuộc tính many-to-oneblogmany-to-manyauthors.

>>> from django.utils import timezone
>>> e = Entry(blog=b,
... headline='The lord of the rings', 
... body_text='Chapter 0', 
... pub_date=timezone.now(), 
... mod_date=timezone.now(), 
... n_comments=0, 
... n_pingbacks=0, 
... rating=0)
>>> e.save()

Việc gán giá trị cho các thuộc tính khóa ngoại many-to-one (ForeignKey) rất đơn giản, bạn chỉ cần gán thuộc tính khóa ngoại với đối tượng cần trỏ đến là xong, trong đoạn code trên chúng ta gán khóa ngoại blog = b, trong đó b là đối tượng Blog mà chúng ta đã tạo ra ở đầu bài.

>>> e.blog = Blog.create(...)
>>> e.save()

Khi cần thay đổi chúng ta cũng chỉ cần dùng phép gán như thường là được.

Đối với các thuộc tính khóa ngoại many-to-many thì chúng ta không được phép gán trực tiếp như đối với ForeignKey mà phải dùng phương thức riêng của thuộc tính khóa ngoại đó, bởi vì khóa ngoại ForeignKey chỉ lưu trữ các đối tượng đơn lẻ trong khi khóa ngoại ManyToManyField thì lại lưu trữ một danh sách các đối tượng khác nhau:

>>> e.authors.add(a)
>>> e.save()

Chúng ta lưu đối tượng a vào trong khóa ngoại authors, trong đó a là đối tượng Author mà chúng ta đã tạo ở trên.

Truy xuất dữ liệu

Khi chúng ta tạo một đối tượng thuộc lớp model do chúng ta tự định nghĩa, chẳng hạn như b=Blog(...), thì mặc định đối tượng b sẽ được Django cung cấp cho các phương thức để thao tác với CSDL trên chính bản ghi của b, chúng ta đã học một số phương thức này trong các ví dụ trên.

Ngoài cách thao tác với dữ liệu thông qua từng bản ghi như trên thì Django còn cung cấp cho chúng ta một đối tượng thuộc lớp django.db.models.manager.Manager để chúng ta thao tác với chính bảng được tạo ra đó, đối tượng này mặc định được đặt tên là objects.

>>> Blog.objects
>>> <django.db.models.manager.Manager object at 0x....>

Lấy toàn bộ dữ liệu

Khi chúng ta lấy dữ liệu từ CSDL về thì objects sẽ trả về một đối tượng QuerySet.

>>> Blog.objects.all()
>>> [<Blog: New name>]

Để lấy toàn bộ bản ghi hiện có trong bảng thì chúng ta dùng phương thức all().

>>> type(Blog.objects.all())
<class 'django.db.models.query.QuerySet'>

Như mình đã nói, danh sách được trả về là một đối tượng QuerySet.

Lọc dữ liệu

Có 2 phương thức hỗ trợ lọc dữ liệu là filter()exclude(). Tham số của 2 phương thức là một biểu thức tìm kiếm, trong đó phương thức filter() sẽ trả về dữ liệu khớp với biểu thức tìm kiếm, còn exclude() sẽ trả về dữ liệu không khớp. Chúng ta sẽ tìm hiểu thêm về các biểu thức tìm kiếm ở cuối bài.

Ví dụ:

>>> Entry.objects.filter(pub_date__year==2016)

Đoạn code trên sẽ lọc những bản ghi Entrypub_date (ngày đăng) vào năm 2016.

Bạn cũng có thể lọc theo nhiều điều kiện liên tiếp như sau:

>>> Entry.objects.filter(headline__startswith='What')
... .exclude(pub_date__gte=datetime.date.today())
... .filter(pub_date__gte=datetime(2016, 1, 30))

Đoạn code trên lọc các bản ghi có chuỗi bắt đầu là “What” và có pub_date nằm trong phạm vi từ ngày 30/1/2016 đến thời điểm hiện tại.

Lấy một dòng dữ liệu

Django cung cấp phương thức get() giúp bạn lấy một bản ghi duy nhất, tham số của phương thức này cũng là một biểu thức tìm kiếm như 2 phương thức filter()exclude(). Bạn cũng chỉ dùng get() khi biết dữ liệu trả về chỉ có 1 bản ghi thôi, nếu có nhiều bản ghi khớp với biểu thức tìm kiếm thì get() sẽ báo lỗi exception MultipleObjectsReturned.

>>> one_entry = Entry.objects.get(pk=1)

Một sự khác nhau nữa giữa get()filter()/exclude() là nếu get() không tìm thấy bản ghi nào thì sẽ trả về một lỗi exception là DoesNotExist còn 2 phương thức kia sẽ trả về một đối tượng QuerySet rỗng.

Ngoài 3 phương thức lọc dữ liệu trên là get(), filter()exclude() thì bạn có thể tìm hiểu thêm các phương thức khác tại đây.

Lọc số lượng bản ghi

Bạn có thể lọc số lượng bản ghi cần lấy theo cú pháp của Python. Ví dụ:

>>> Entry.objects.all()[:5]     # lấy 5 phần tử đầu tiên
>>> Entry.objects.all()[5:10]   # lấy các phần tử từ vị trí 5 đến 10

Python không hỗ trợ lọc theo chỉ số âm.

>>> Entry.objects.all()[-1]

Đoạn code trên sẽ báo lỗi.

Biểu thức tìm kiếm

Các biểu thức tìm kiếm sẽ thực hiện câu truy vấn SQL WHERE trong CSDL. Biểu thức tìm kiếm là tham số cho các phương thức lọc dữ liệu filter(), exclude()get().

Cú pháp lọc có dạng <tên thuộc tính>__<kiểu tìm kiếm>=<giá trị> (lưu ý ở đây có 2 dấu gạch dưới).

Ví dụ

>>> Entry.objects.filter(pub_date__lte='2016-01-01')

Đoạn code trên tương đương với câu lệnh SQL sau:

SELECT * FROM blog_entry WHERE pub_date <= '2016-01-01';

Trong biểu thức tìm kiếm thì <tên thuộc tính> bao giờ cũng là tên do chúng ta đặt khi định nghĩa model, ngoại trừ thuộc tính khóa ngoại phải thêm vào _id ở cuối tên. Ví dụ:

>>> Entry.objects.filter(blog_id=4)

Django cung cấp rất nhiều cú pháp <kiểu tìm kiếm> khác nhau, bạn có thể xem thêm ở đây. Ở đây mình giới thiệu một số.

  • exact: dữ liệu tìm được phải giống chính xác với giá trị cần tìm. Vd Entry.objects.get(headline__exact='Cat bites dog') chỉ trả về những bản ghi có headline“Cat bites dog“.
  • iexact: dữ liệu tìm được chỉ cần giống kí tự với giá trị cần tìm, không phân biệt chữ HOA-thường. Vd Blog.objects.get(name__iexact='beatles blog'), những bản ghi có headline“Beatles Blog”, “beatles blog” hoặc “BeAtlES blOG” đều được nhận.
  • contains: dữ liệu chỉ cần chứa chuỗi giá trị là được. Vd Entry.objects.get(headlines__contains='Lennon'). Ngoài ra còn có icontains cũng có chức năng như contains nhưng không phân biệt chữ HOA-thường.
  • startswith, endswith:  dữ liệu có chuỗi bắt đầu hoặc kết thúc giống với giá trị.

Tìm kiếm đa bảng

Các bảng trong CSDL có quan hệ với nhau nhờ vào các khóa ngoại, khi truy vấn dữ liệu, SQL cho phép bạn truy vấn các bản ghi có liên quan với nhau bằng cách nối các bảng lại bằng câu lệnh JOIN, Django cũng cho phép bạn làm điều đó, bạn chỉ cần đưa vào biểu thức tìm kiếm cú pháp <tên khóa ngoại>__<tên thuộc tính của bảng khác>=<giá trị>. Ví dụ:

>>> Entry.objects.filter(blog__name='Beatles Blog')

Câu lệnh trên sẽ tìm các bản ghi Entry có quan hệ với bảng Blog với name là “Beatles Blog”.

Django – Model nâng cao – Phần 2

Trong phần này chúng ta tiếp tục tìm hiểu về các tính chất của model trong Django.

Metadata

Các lớp trong Django có thể chứa các metadata (siêu dữ liệu), đây là các thông tin lưu trữ về các thiết lập của model, các thông tin này không được dùng để tạo bảng. Ví dụ:

from django.db import models

class Ox(models.Model):
    horn_length = models.IntegerField()

    class Meta:
        ordering = ["horn_length"]
        db_table = "oxen"

Các siêu dữ liệu sẽ được lưu trong một lớp nội có tên là Meta. Lớp này lưu trữ các thông tin cấu hình model, chẳng hạn như ordering tức là sắp xếp dữ liệu được trả về mặc định theo cột nào, db_table là tên bảng được tạo trong CSDL… bạn có thể xem danh sách tất cả các thuộc tính metadata ở đây.

Thừa kế model

Model trong Django cũng có thể thừa kế lẫn nhau. Chúng ta có thể viết các lớp model cơ sở (hay model cha) rồi sau đó viết các model khác thừa kế từ lớp model cơ sở này. Các lớp model cơ sở phải được thừa kế từ lớp django.db.models.Model, rồi từ đó các lớp con sẽ kế thừa từ lớp cha này. Model cha có thể có bảng riêng hoặc không có.

Có 3 cách để một model được thừa kế trong Django:

  • Thừa kế từ lớp trừu tượng.
  • Thừa kế đa bảng.
  • Dùng lớp Proxy.

Thừa kế từ lớp trừu tượng

Chúng ta sử dụng lớp trừu tượng khi muốn model cha chỉ có nhiệm vụ là lưu trữ các thông tin dùng chung trong các model con chứ không phải nhằm mục đích lưu các thông tin thật trong CSDL. Để một model là “trừu tượng” thì chúng ta gán thuộc tính abstract = True của lớp nội Meta.

Ví dụ:

from django.db import models

class CommonInfo(models.Model):
    name = models.CharField(max_length=100)
    age = models.PositiveIntegerField()

    class Meta:
        abstract = True

class Student(CommonInfo):
    home_group = models.CharField(max_length=5)

Model Student thừa kế từ model trừu tượng là CommonInfo, Student sẽ bao gồm 3 trường là name, agehome_group.

Khi chúng ta dùng lệnh manage.py migrate, chỉ có model Student mới được tạo bảng cùng với 3 trường của nó.

Khi một model con thừa kế model cha mà không khai báo lớp nội Meta thì sẽ tự động thừa kế lớp nội đó từ model cha. Nhưng nếu muốn các model vẫn có thể có lớp nội riêng hoặc kế thừa và thêm các thuộc tính mới từ model cha.

Ví dụ:

from django.db import models

class CommonInfo(models.Model):
    # ...
    class Meta:
        abstract = True
        ordering = ['name']

class Student(CommonInfo):
    # ...
    class Meta(CommonInfo.Meta):
        db_table = 'student_info'

Ở đây lớp con sẽ kế thừa cả lớp nội Meta từ lớp cha. Nhưng có gì đó kì kì, nếu như vậy thì lớp con cũng sẽ trở thành lớp trừu tượng luôn sao vì nó sẽ kế thừa cả thuộc tính abstract? Không! Trước khi kế thừa từ lớp nội Meta thì Django sẽ tự động thiết lập biến abstract thành False ở lớp cha rồi sau đó sẽ cho kế thừa đến các lớp con, sau khi kế thừa xong thì Django mới thiết lập thuộc tính abstract lại như cũ ở lớp cha. Tuy nhiên nếu muốn bạn vẫn có thể thiết lập abstract = True ở các lớp con để tiếp tục kế thừa sâu hơn nữa.

Thừa kế đa bảng

Thừa kế đa bảng chỉ khác kiểu thừa kế lớp trừu tượng ở chỗ là lớp cha ở đây là lớp “thật” chứ không phải lớp trừu tượng. Tức là các model cha bây giờ sẽ có cả các bảng riêng của chúng trong CSDL. Ví dụ:

from django.db import models

class Place(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=80)

class Restaurant(Place):
    serves_hot_dogs = models.BooleanField(default=False)
    serves_pizza = models.BooleanField(default=False)

Chúng ta có 2 model là PlaceRestaurant kế thừa từ Place. Khác với kiểu kế thừa từ lớp trừu tượng, ở lớp trừu tượng thì các trường của lớp cha sẽ nằm cùng bảng với các lớp con vì lớp cha không được tạo bảng riêng, với thừa kế đa bảng thì các lớp con cũng sẽ kế thừa các trường của lớp cha nhưng các trường của lớp cha sẽ nằm trong bảng riêng của lớp cha.

>>> Place.objects.filter(name="Bob's Cafe")
>>> Restaurant.objects.filter(name="Bob's Cafe")

Chúng ta vẫn có thể truy xuất các thông tin từ cả 2 lớp con và lớp cha như thường.

Lớp Proxy – lớp ủy quyền

Đôi khi bạn không muốn các lớp con có thêm các thuộc tính mới mà chỉ muốn có các phương thức mới, với 2 kiểu kế thừa ở trên bạn vẫn có thể làm được điều này nhưng Django sẽ tạo ra các bảng CSDL mới mặc dù không có trường nào cả, làm như thế sẽ lãng phí bộ nhớ và cũng khá kì cục.

Do đó Django cho phép chúng ta tạo ra các lớp Proxy (lớp ủy quyền), các lớp này kế thừa từ lớp cha nhưng không được tạo bảng mới trong CSDL, những lớp con này sẽ chỉ dùng để lưu các phương thức mới chứ không lưu các thuộc tính mới. Bạn có thể thực hiện thêm, sửa, xóa dữ liệu được kế thừa từ lớp cha, những dữ liệu này vẫn lưu ở bảng cha.

Ví dụ:

from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

class MyPerson(Person):
    class Meta:
        proxy = True

    def do_something(self):
        # ...
        pass

Chúng ta tạo lớp Person và lớp proxy MyPerson kế thừa từ lớp Person. Chúng ta tạo một lớp Proxy bằng cách khai báo lớp nội Meta và đưa vào thuộc tính proxy = True trong lớp Meta này.

>>> p = Person.objects.create(first_name="foobar")
>>> MyPerson.objects.get(first_name="foobar")
<MyPerson: foobar>

Bất cứ thao tác cập nhật dữ liệu nào từ lớp MyPerson cũng sẽ là thao tác trực tiếp với dữ liệu từ lớp Person.

class OrderedPerson(Person):
    class Meta:
        ordering = ["last_name"]
        proxy = True

Bạn vẫn có thể định nghĩa lại các thông tin khác trong lớp Meta như sử dụng thuộc tính ordering chẳng hạn… vì bản thân các thuộc tính trong lớp Meta cũng không được dùng để tạo bảng mà nó chỉ là các thông tin cấu hình model thôi.

Đa thừa kế

Vì Django được phát triển từ Python nên các model trong Django có thể thừa kế từ nhiều model cha. Lớp Meta của model con chỉ có thể thừa kế từ lớp Meta của lớp cha đầu tiên.

Thường thì chúng ta cũng ít khi thừa kế từ nhiều bảng vì càng thừa kế nhiều thì sẽ càng làm cho ứng dụng trở nên phức tạp, khó quản lý.

Ngoài ra khi sử dụng đa thừa kế mà các model cha có cùng chung tên thuộc tính khóa chính thì Django sẽ báo lỗi. Do đó khi sử dụng đa thừa kế, chúng ta nên khai báo các trường làm khóa chính một cách rõ ràng. Ví dụ:

class Article(models.Model):
    article_id = models.AutoField(primary_key=True)
    ...

class Book(models.Model):
    book_id = models.AutoField(primary_key=True)
    ...

class BookReview(Book, Article):
    pass

Trong đoạn code trên model BookReview thừa kế từ 2 model cha là ArticleBook, cả 2 lớp cha đều có tên thuộc tính khóa chính khác nhau.

Hoặc nếu không muốn phải khai báo các khóa chính thì chúng ta có thể khai báo các model cha kế thừa lại từ một model khác để dùng chung khóa chính:

class Piece(models.Model):
    pass

class Article(Piece):
    ...
 
class Book(Piece):
    ...

class BookReview(Book, Article):
    pass

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ằng NULL.
  • 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ép NULL hay NOT 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ường AutoField để 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 nameApple, 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 namePear.

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 PersonGroup 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 ManyToManyFieldthrough.

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: persongroup.

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 Groupbeatles, trong đó bao gồm 2 Personringopaul, mối quan hệ giữa ringobeatles được lưu trong m1, mối quan hệ giữa paulbeatles đượ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 QuestionChoice 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().

Capture

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ó.

Capture

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.

Capture

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.

Capture

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.

Capture

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 GETPOST 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 namechoiceThuộ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()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à ListViewDetailView, 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 questionlatest_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()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/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, passwordemail là xong.

 Truy cập trang admin

Capture

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

Capture

Sau đó bạn đăng nhập bằng usernamepassword 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à UserGroup. Các bảng QuestionChoice 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.

Capture

Sau khi đã đăng kí xong thì 2 bảng QuestionChoice 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 SQLite
    • django.db.backends.sqlite3 – cơ sở dữ liệu  SQLite
    • django.db.backends.postgresql – cơ sở dữ liệu PostgreSQL
    • django.db.backends.mysql – cơ sở dữ liệu MySQL
    • django.db.backends.oracle – cơ sở dữ liệu Oracle
  • NAME: tên CSDL, mặc định là file db.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ệnh CREATE 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à QuestionChoice.

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_choicepolls_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 QuestionChoice. 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 QuestionChoice 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().