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 Field
và Form,
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à validator. Cá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, RegexValidator,
URLValidator, 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ớpField
, 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ỗiValidationError.
Ví dụ như khi chuyển mộtFloatField
thì chúng ta sẽ được một đối tượngfloat
hoặc một lỗi exception. - Phương thức
validate()
có sẵn trong mỗi lớpField,
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ớpField
tự động kiểm tra tất cả các validator củaField
và cái nào có lỗi thì gom lại vào một đối tượngValidationError.
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ớpField
sẽ gọi các phương thứcto_python(), validate()
và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ínhcleaned_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ớpForm
, 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ểuCharField
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ứcclean_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ớpForm
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()
và 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()
và 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()
và 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()
và 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_myself
và subject
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.