Giả sử bạn xây dựng một ứng dụng có sử dụng cơ sở dữ liệu – tức là bạn phải có định nghĩa các lớp model, thường thì bạn sẽ phải tạo các form cho người dùng nhập dữ liệu và các form này thường cũng có các field gần như là giống hoàn toàn với các field có trong model, ví dụ như trong một ứng dụng Blog
bạn có một model tên là BlogComment
có các field lưu trữ thông tin các comment do người dùng nhập vào, vậy thì bạn cũng phải tạo form để người dùng nhập comment vào bài viết.
Nhưng việc định nghĩa một lớp Model
và một lớp Form
có các field gần như giống nhau hoàn toàn là rất vô nghĩa, mà lại còn viết từ code HTML hoặc cùng lắm là kế thừa từ lớp Form
của Django đi nữa thì code vẫn rất mệt mỏi, vì thế nên Django có thể tự định nghĩa luôn các lớp form giùm bạn từ các lớp model mà bạn đã định nghĩa trước, tức là trong model có các field nào thì Django cũng sẽ tự tạo lớp form có các field tương ứng như thế, bạn không cần phải định nghĩa lại nữa.
Kiểu dữ liệu
Mỗi lớp Field
của Model
sẽ có một lớp Field
của Form
tương ứng. Dưới đây là bảng tên các lớp Field
tương ứng của Model
và Form.
Model field | Form field |
---|---|
AutoField |
Không có |
BigIntegerField |
IntegerField với min_value là -9223372036854775808 và max_value là 9223372036854775807. |
BooleanField |
BooleanField |
CharField |
CharField với max_length được thiết lập trong thuộc tính max_length của model |
CommaSeparatedIntegerField |
CharField |
DateField |
DateField |
DateTimeField |
DateTimeField |
DecimalField |
DecimalField |
EmailField |
EmailField |
FileField |
FileField |
FilePathField |
FilePathField |
FloatField |
FloatField |
ForeignKey |
ModelChoiceField |
ImageField |
ImageField |
IntegerField |
IntegerField |
IPAddressField |
IPAddressField |
GenericIPAddressField |
GenericIPAddressField |
ManyToManyField |
ModelMultipleChoiceField |
NullBooleanField |
NullBooleanField |
PositiveIntegerField |
IntegerField |
PositiveSmallIntegerField |
IntegerField |
SlugField |
SlugField |
SmallIntegerField |
IntegerField |
TextField |
CharField với widget=forms.Textarea |
TimeField |
TimeField |
URLField |
URLField |
Lưu ý là các lớp ModelField hỗ trợ 3 kiểu khóa ngoại là 1-n, n-n và 1-1 nhưng bên FormField chỉ có 2 kiểu duy nhất được hỗ trợ là ModelChoiceField
tương ứng với ForeignKey
và ModelMultipleChoiceField
tương ứng với ManyToManyField.
Cả 2 lớp trên đều nhận một tham số khi khởi tạo là một đối tượng QuerySet.
Mỗi lớp Form Field có chung một số thuộc tính như sau:
required:
nếu thuộc tínhblank
củaModel
làTrue
thìrequired
=
False
và ngược lại.label:
thuộc tính này được gán tự động bằng thuộc tínhverbose_name
củaModel
và kí tự đầu được viết hoa.help_text:
thuộc tính này được gán tự động bằng thuộc tínhhelp_text
của model.
Ví dụ
Chúng ta định nghĩa các lớp như sau.
from django.db import models from django.forms import ModelForm TITLE_CHOICES = ( ('MR', 'Mr.'), ('MRS', 'Mrs.'), ('MS', 'Ms.'), ) class Author(models.Model): name = models.CharField(max_length=100) title = models.CharField(max_length=3, choices=TITLE_CHOICES) birth_date = models.DateField(blank=True, null=True) class Book(models.Model): name = models.CharField(max_length=100) authors = models.ManyToManyField(Author) class AuthorForm(ModelForm): class Meta: model = Author fields = ['name', 'title', 'birth_date'] class BookForm(ModelForm): class Meta: model = Book fields = ['name', 'authors']
Chúng ta định nghĩa 2 lớp Model là Author
và Book,
sau đó định nghĩa 2 lớp Form có các Field được tạo tự động từ 2 lớp model là AuthorForm
và BookForm.
Các lớp này được kế thừa từ lớp django.forms.ModelForm
thay thì lớp django.forms.Form
như trước.
class Meta: model = Author fields = ['name', 'title', 'birth_date']
Chúng ta định nghĩa model được dùng trong lớp nội Meta,
thuộc tính model
sẽ tạo các field tương ứng từ lớp Model tương ứng, thuộc tính fields
sẽ chọn các trường nào được dùng, nếu chúng ta không khai báo thuộc tính fields
thì mặc định Django sẽ dùng tất cả các thuộc tính có trong lớp Model, ngoại trừ thuộc tính
id
nếu bạn không khai báo thuộc tính khóa chính. Ngoài ra lớp Meta còn có thuộc tính exclude,
thuộc tính này trái ngược với thuộc tính fields,
tức là thuộc tính này sẽ quy định các trường nào không được phép sử dụng.
Định nghĩa từ lớp ModelForm
tương đương với định nghĩa từ lớp Form
trong các bài trước như sau:
from django import forms class AuthorForm(forms.Form): name = forms.CharField(max_length=100) title = forms.CharField(max_length=3, widget=forms.Select(choices=TITLE_CHOICES)) birth_date = forms.DateField(required=False) class BookForm(forms.Form): name = forms.CharField(max_length=100) authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all())
Lưu dữ liệu
Lớp ModelForm
có một phương thức tên là save(),
phương thức này tạo mới hoặc lưu một đối tượng Model vào cơ sở dữ liệu giống như phương thức save()
bên các lớp Model vậy.
Khi khởi tạo các đối tượng ModelForm
thì chúng ta có thể truyền các đối tượng model có sẵn vào hàm khởi tạo, nếu chúng ta chỉ truyền vào không thôi thì Django sẽ tạo mới một đối tượng trên CSDL, nếu chúng ta truyền vào và ghi rõ truyền với tham số là instance
thì Django sẽ cập nhật dữ liệu trong đối tượng đó. Ví dụ:
>>> from myapp.models import Article >>> from myapp.forms import ArticleForm >>> f = ArticleForm(request.POST) >>> new_article = f.save()
Đoạn code trên tạo đối tượng mới từ đối tượng POST
được gửi lên và lưu vào CSDL.
>>> a = Article.objects.get(pk=1) >>> f = ArticleForm(request.POST, instance=a) >>> f.save()
Đoạn code trên tạo một đối tượng ArticleForm,
truyền dữ liệu từ POST
vào rồi truyền cập nhật trên đối tượng a
chứ không tạo mới.
Lưu ý là nếu chúng ta không kiểm tra sự đúng đắn của dữ liệu thì khi gọi phương thức save(),
Django cũng sẽ tự động làm việc đó và sẽ giải phóng lỗi exception ValueError
nếu dữ liệu có lỗi, trong bài trước chúng ta đã dùng một phương thức để kiểm tra dữ liệu là is_valid(),
chúng ta sẽ tìm hiểu thêm về cách kiểm tra dữ liệu trong các bài sau.
Tùy chỉnh lớp Field
Như trong bảng tên các lớp ModelField và FormField tương ứng đã chỉ rõ ở trên, nếu bạn khai báo loại field nào trong lớp model thì Django sẽ tạo lớp field tương ứng bên form. Chẳng hạn như khi bạn khai báo trường DateTimeField
bên model thì Django sẽ tạo một trường DateTimeField
tương ứng bên form.
Mặc dù tạo form bằng cách code từ đầu với HTML hoặc dùng lớp django.forms.Form
sẽ cho bạn quyền điều khiển nhiều hơn nhưng không có nghĩa là ModelForm
không cho phép bạn tùy chỉnh các lớp field có sẵn.
Chẳng hạn như để tùy chỉnh kiểu hiển thị thì chúng ta dùng thuộc tính widgets
có trong lớp nội Meta.
Ví dụ:
from django.forms import ModelForm, Textarea from myapp.models import Author class AuthorForm(ModelForm): class Meta: model = Author fields = ('name', 'title', 'birth_date') widgets = { 'name': Textarea(attrs={'cols': 80, 'rows': 20}), }
Mặc định lớp CharField
sẽ hiển thị thẻ <input type="text">,
nhưng đoạn code trên sẽ chỉ định CharField
hiển thị <input type="textarea">
.
Tương tự bạn có thể tùy chỉnh lại các thuộc tính như label, help_text
trong lớp nội Meta
nếu muốn:
from django.utils.translation import ugettext_lazy as _ class AuthorForm(ModelForm): class Meta: model = Author fields = ('name', 'title', 'birth_date') labels = { 'name': _('Writer'), } help_texts = { 'name': _('Some useful help text.'), }
Bạn cũng có thể quy định các thuộc tính phải dùng lớp field do bạn định nghĩa thông qua thuộc tính field_classes
trong lớp Meta:
from django.forms import ModelForm from myapp.models import Article class ArticleForm(ModelForm): class Meta: model = Article fields = ['pub_date', 'headline', 'content', 'reporter', 'slug'] field_classes = { 'pub_date': MyDateTimeField, } class MyDateTimeField(): #... pass
Thừa kế Form
ModelForm
cũng chỉ là một lớp Python, do đó bạn có thể cho thừa kế và mở rộng các trường hoặc các phương thức của chúng. Ví dụ:
class EnhancedArticleForm(ArticleForm): def clean_pub_date(self): #... pass
Lớp EnhancedArticleForm
kế thừa từ lớp ArticleForm
nên sẽ có tất cả các thuộc tính và phương thức của lớp ArticleForm,
ngoài ra lớp EnhancedArticleForm
còn có phương thức clean_pub_date()
của riêng nó nữa.
Tương tự với các lớp Model, các lớp form cũng có thể thừa kế cả lớp nội Meta:
class RestrictedArticleForm(EnhancedArticleForm): class Meta(ArticleForm.Meta): exclude = ('body',)
Lớp RestrictedArticleForm
kế thừa từ lớp EnhancedArticleForm
ngoại trừ lớp này không sử dụng thuộc tính body.
Lưu ý là khi sử dụng đa thừa kế thì lớp Meta
của lớp con chỉ thừa kế từ lớp Meta
của lớp cha đầu tiên trong danh sách thôi.
Hàm factory
Nếu bạn quá “lười” để ngồi định nghĩa lại một lớp kế thừa từ ModelForm,
sau đó ngồi khai báo các thuộc tính fields, exclude
hay lớp Meta
thì Django cũng cung cấp một hàm cho phép bạn tạo một lớp ModelForm
một cách “cấp tốc” là hàm modelform_factory(),
ví dụ:
from django.forms import modelform_factory from myapp.models import Book BookForm = modelform_factory(Book, fields=("author", "title"))
Hàm này đặc biệt hữu dụng khi lớp Form
mà bạn muốn định nghĩa không có gì khác nhiều so với lớp cha.
Bạn cũng có thể đưa các thuộc tính chỉnh sửa vào ngay trong hàm modelform_factory():
from django.forms import Textarea Form = modelform_factory(Book, form=BookForm, widgets={"title": Textarea()}, fields=['name'])