Django – Kiểm tra dữ liệu gửi lên form

4.1/5 - (35 votes)

Trong bài này chúng ta sẽ tìm hiểu cách kiểm tra sự đúng đắn của dữ liệu.

Kiểm tra dữ liệu ở đây là kiểm tra xem dữ liệu được gửi lên có đúng với yêu cầu hay không, cũng có thể xem đây là câu lệnh catch trong quá trình kiểm tra lỗi exception vậy, chẳng hạn như chúng ta yêu cầu người dùng nhập vào email nhưng người dùng nhập sai cấu trúc, hay upload ảnh avatar mà lại gửi lên một file PDF, nguy hiểm hơn nữa là up file ảnh giả – tức file có đuôi .jpg, .png… nhưng lại không chứa dữ liệu ảnh mà chứa mã độc… vì thế chúng ta nên kiểm tra dữ liệu được gửi lên trước khi lưu chúng vào CSDL, và Django cũng cung cấp một số phương thức/hàm hỗ trợ chúng ta làm việc này.

Django có một số phương thức kiểm tra dữ liệu có sẵn trong các lớp FieldForm, các phương thức này khi kiểm tra nếu dữ liệu đúng thì trả lại dữ liệu đó, nếu sai thì sẽ trả về một lỗi exception là ValidationError.

Chúng ta đã từng dùng phương thức is_valid() của lớp django.forms.form, phương thức này khi được gọi sẽ tự động gọi các phương thức kiểm tra và dữ liệu nào không có lỗi thì sẽ được lưu trong thuộc tính form.cleaned_data dưới dạng một đối tượng Dictionary, bạn vẫn có thể truy xuất dữ liệu gốc trong thuộc tính request.POST.

Các phương thức kiểm tra khi được gọi sẽ thực hiện kiểm tra dữ liệu theo cách của Django nhưng chúng ta cũng nên override lại các phương thức kiểm tra để chúng làm theo yêu cầu của chúng ta. Chúng ta sẽ tìm hiểu về các phương thức này ở dưới.

Validator

Các lớp Field của Django hỗ trợ một công cụ tiện ích có tên là validatorCác validator sẽ kiểm tra dữ liệu của lớp đó theo một quy luật nào đó và trả về lỗi ValidationError nếu có. Chúng ta có thể định nghĩa validator bằng cách truyền vào ngay hàm khởi tạo Field hoặc định nghĩa bên trong các lớp Field riêng với thuộc tính default_validators. Ví dụ:

from django.forms import CharField
from django.core import validators

class SlugField(CharField):
    default_validators = [validators.validate_slug]

Chúng ta định nghĩa lớp SlugField kế thừa từ CharField, thuộc tính default_validators sẽ quy định kiểu kiểm tra dữ liệu của lớp này.

>>> slugExample = forms.SlugField()
>>> slugExample2 = forms.CharField(validators=[validators.validate_slug])

Cả 2 cách tạo đối tượng trên sẽ sử dụng chung validator.

validators.validate_slug

Validators là một module của Django, module này chứa các lớp như EmailValidator, RegexValidatorURLValidator, Min/MaxValueValidator cho phép chúng ta định nghĩa các luật kiểm tra khác nhau. Ngoài các lớp đó Djang còn chứa sẵn một số đối tượng validator tĩnh quy định sẵn các luật thông dụng, ở ví dụ trên chúng ta dùng validate_slug, đối tượng này quy định chuỗi chỉ được phép chứa kí tự chữ cái, kí tự số, dấu gạch ngang và dấu nối _. Ngoài validate_slug ra còn có các đối tượng khác như validate_email, validate_unicode_slug… bạn có thể xem danh sách tại đây.

Các phương thức kiểm tra dữ liệu

Django cung cấp các hàm/phương thức kiểm tra dữ liệu sau đây:

Phương thức kiểm tra trên Field:

  • Phương thức to_python() có sẵn trong mỗi lớp Field, phương thức này sẽ chuyển đổi kiểu dữ liệu field tương ứng sang kiểu dữ liệu của Python, nếu chuyển được thì không có gì, nếu không sẽ có lỗi ValidationError. Ví dụ như khi chuyển một FloatField thì chúng ta sẽ được một đối tượng float hoặc một lỗi exception.
  • Phương thức validate() có sẵn trong mỗi lớp Field, thường được dùng khi không có sẵn các validator.
  • Phương thức run_validators() có trong mỗi lớp Field tự động kiểm tra tất cả các validator của Field và cái nào có lỗi thì gom lại vào một đối tượng ValidationError. Thường chúng ta không dùng phương thức này.
  • Phương thức clean() có trong các lớp kế thừa từ lớp Field sẽ gọi các phương thức to_python(), validate()run_validators() trên field theo thứ tự và nếu gặp lỗi tại đâu thì dừng quá trình kiểm tra và giải phóng exception tại đó. Nếu dữ liệu hợp lệ thì sẽ được truyền vào thuộc tính cleaned_data của form.

Phương thức kiểm tra trên Form:

  • Phương thức clean_<tên thuộc tính field>() được Django tạo ra trong các lớp kế thừa từ lớp Form, trong đó <tên thuộc tính field> là tên do chúng ta đặt cho từng thuộc tính field khi định nghĩa form. Phương thức này kiểm tra dữ liệu theo yêu cầu chứ không quan tâm đến kiểu dữ liệu. Chẳng hạn như form của chúng ta có một thuộc tính tên là serialnumber có kiểu CharField và thuộc tính này phải là duy nhất – tức là không có 2 bản ghi nào có serialnumber giống nhau, thì việc kiểm tra sẽ được thực hiện trong phương thức clean_serialnumber() và chúng ta phải override lại phương thức này.

  • Phương thức clean() có trong các lớp kế thừa từ lớp Form kiểm tra dữ liệu trên từng field, chúng ta sẽ override lại khi cần dùng. Phương thức này được gọi sau khi các phương thức kiểm tra trên Field đã thực thi.

Khi phương thức is_valid() được gọi, từng thuộc tính field mà chúng ta khai báo theo thứ tự từ trên xuống dưới khi định nghĩa Form sẽ gọi từng phương thức kiểm tra đã nêu trên, phương thức Field.clean() sẽ được gọi đầu tiên, sau đó là Form.clean_<tên thuộc tính field>(), cuối cùng là phương thức Form.clean(), phương thức Form.clean() sẽ luôn được gọi bất kế các phương thức trước đó có trả về lỗi hay không.

Ví dụ

Chúng ta sẽ override một số phương thức đã nói trên.

from django import forms
from django.core.validators import validate_email

class MultiEmailField(forms.Field):
    def to_python(self, value):        
        if not value:
            return []
        return value.split(',')

    def validate(self, value):   
        super(MultiEmailField, self).validate(value)

Chúng ta định nghĩa lớp MultiEmailField kế thừa từ lớp Field. Ở đây chúng ta override lại 2 phương thức to_python()validate().

if not value:
    return []
return value.split(',')

Trong phương thức to_python(), chúng ta chỉ đơn giản là kiểm tra xem dữ liệu có rỗng hay không, nếu không thì trả về một list các string tách ra từ string gốc dùng dấu phẩy.

super(MultiEmailField, self).validate(value)

Trong phương thức validate(), chúng ta gọi lại phương thức validate() của lớp cha là lớp Field. 

Bây giờ chúng ta định nghĩa lớp ContactForm và override một số phương thức kiểm tra của Form.

from django import forms

class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    message = forms.CharField()
    sender = forms.EmailField()
    recipients = MultiEmailField()
    cc_myself = forms.BooleanField(required=False)

    def clean_recipients(self):
        data = self.cleaned_data['recipients']
        if "fred@example.com" not in data:
            raise forms.ValidationError("You have forgotten about Fred!")

        return data

    def clean(self):
        cleaned_data = super(ContactForm, self).clean()
        cc_myself = cleaned_data.get("cc_myself")
        subject = cleaned_data.get("subject")

        if cc_myself and subject:
            if "help" not in subject:
                raise forms.ValidationError(
                    "Did not send for 'help' in the subject despite "
                    "CC'ing yourself."
                )

Chúng ta override 2 phương thức là clean_recipients()clean().

recipients = MultiEmailField()

Chúng ta sử dụng lớp MultiEmailField như các lớp field bình thường. Khi phương thức is_valid() được gọi thì các phương thức to_python()validate() của từng field sẽ được gọi, bất kể có override hay không.

def clean_recipients(self):
    data = self.cleaned_data['recipients']     
    if "fred@example.com" not in data:
        raise forms.ValidationError("You have forgotten about Fred!")
    return data   

Sau khi các phương thức to_python()validate() đã được gọi thì Django sẽ lưu các dữ liệu đã được kiểm tra là hợp lệ trong thuộc tính cleaned_data. Chúng ta có thể lấy ra để kiểm tra tiếp.  Trong đoạn code trên chúng ta kiểm tra nếu fred@example.com không có trong list thì giải phóng lỗi ValidationError.

def clean(self):
    cleaned_data = super(ContactForm, self).clean()
    ...
    if cc_myself and subject:
        if "help" not in subject:
            raise forms.ValidationError(
                "Did not send for 'help' in the subject despite "
                "CC'ing yourself."
            )

Phương thức clean() khá đơn giản, chúng ta chỉ kiểm tra xem nếu cc_myselfsubject không rỗng thì trong subject phải có chứa chuỗi “help”, nếu không thì báo lỗi. Ngoài ra trước đó chúng ta cũng gọi lại phương thức clean() của lớp cha.

5 1 vote
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