Daily Archives: 06/04/2016

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