Django – Tạo form từ model

4.8/5 - (34 votes)

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

Lưu ý là các lớp ModelField hỗ trợ 3 kiểu khóa ngoại là 1-n, n-n1-1 nhưng bên FormField chỉ có 2 kiểu duy nhất được hỗ trợ là ModelChoiceField tương ứng với ForeignKeyModelMultipleChoiceField 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ính blank của ModelTrue 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ính verbose_name của Model 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ính help_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 ModelAuthorBook, sau đó định nghĩa 2 lớp Form có các Field được tạo tự động từ 2 lớp model là AuthorFormBookForm. 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 ModelFieldFormField 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'])
1.5 2 votes
Article Rating
Subscribe
Thông báo cho tôi qua email khi
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments