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ằ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().

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

Capture

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
Để tạo URL chúng ta tạo một file với tên 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.

Capture

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