Tổng quan về Thành phần Form trong Django
Thành phần Form của Django cung cấp một giải pháp mạnh mẽ và hiệu quả để quản lý các biểu mẫu trên web. Nó giúp đơn giản hóa đáng kể quá trình xử lý biểu mẫu, từ việc tạo các phần tử HTML trên trang cho đến việc xác thực dữ liệu người dùng và giữ lại nội dung đã nhập trước đó.
Các chức năng chính của thành phần Form:
- Tự động tạo các thẻ HTML cần thiết cho biểu mẫu.
- Xác thực dữ liệu do người dùng gửi một cách an toàn và nhất quán.
- Tự động điền lại các giá trị đã nhập trước đó khi có lỗi xác thực, cải thiện trải nghiệm người dùng.
Xử lý biểu mẫu theo cách thủ công
Trước khi đi sâu vào Django Forms, hãy xem xét cách xử lý một biểu mẫu đăng ký đơn giản bằng phương pháp truyền thống. Trong cách này, bạn tự tạo cấu trúc HTML cho biểu mẫu và quản lý logic xác thực cũng như xử lý dữ liệu hoàn toàn trong hàm view của Django.
views.py (Thủ công)
from django.http import HttpResponse
from django.shortcuts import render
def xu_ly_dang_ky_thu_cong(request):
thong_bao_loi = ""
if request.method == "POST":
ten_nguoi_dung = request.POST.get("ten_tai_khoan")
mat_khau = request.POST.get("mat_khau")
# Kiểm tra cơ bản thông tin đăng ký
if not ten_nguoi_dung or len(ten_nguoi_dung) < 6:
thong_bao_loi = "Tên đăng nhập không được rỗng và phải có ít nhất 6 ký tự."
elif not mat_khau:
thong_bao_loi = "Mật khẩu không được rỗng."
else:
# Ở đây bạn sẽ lưu thông tin vào cơ sở dữ liệu
return HttpResponse("Đăng ký tài khoản thành công!")
return render(request, "bieu_mau_dang_ky_thu_cong.html", {"thong_bao_loi": thong_bao_loi})
bieu_mau_dang_ky_thu_cong.html
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Trang Đăng Ký Thủ Công</title>
<style>
.error-message { color: red; margin-top: 5px; }
form div { margin-bottom: 15px; }
label { display: block; margin-bottom: 5px; font-weight: bold; }
input[type="text"], input[type="password"] {
width: 300px;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
}
input[type="submit"] {
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style>
</head>
<body>
<h1>Đăng Ký Tài Khoản (Cách Thủ Công)</h1>
<form action="/dang-ky-thu-cong/" method="post">
{% csrf_token %}
<div>
<label for="id_ten_tai_khoan">Tên Tài Khoản:</label>
<input type="text" id="id_ten_tai_khoan" name="ten_tai_khoan">
</div>
<div>
<label for="id_mat_khau">Mật Khẩu:</label>
<input type="password" id="id_mat_khau" name="mat_khau">
</div>
<div>
<input type="submit" value="Đăng Ký">
{% if thong_bao_loi %}
<p class="error-message">{{ thong_bao_loi }}</p>
{% endif %}
</div>
</form>
</body>
</html>
Sử dụng Django Forms để thực hiện chức năng đăng ký
Với Django Forms, bạn định nghĩa cấu trúc biểu mẫu và các quy tắc xác thực trong một lớp Python riêng biệt. Sau đó, bạn truyền đối tượng biểu mẫu này tới template để hiển thị và để nó tự động xử lý dữ liệu gửi về.
forms.py
Thông thường, các lớp Form sẽ được đặt trong một file forms.py riêng biệt, cùng cấp với models.py hoặc trong một thư mục con của ứng dụng Django để dễ quản lý.
from django import forms
class BieuMauDangKyNguoiDung(forms.Form):
ten_dang_nhap = forms.CharField(
label="Tên đăng nhập",
min_length=4,
max_length=20,
error_messages={
'required': 'Vui lòng nhập tên đăng nhập.',
'min_length': 'Tên đăng nhập tối thiểu 4 ký tự.',
'max_length': 'Tên đăng nhập tối đa 20 ký tự.'
}
)
mat_khau_nguoi_dung = forms.CharField(
label="Mật khẩu",
widget=forms.PasswordInput, # Sử dụng widget PasswordInput để hiển thị dấu *
min_length=6,
error_messages={
'required': 'Vui lòng nhập mật khẩu.',
'min_length': 'Mật khẩu tối thiểu 6 ký tự.'
}
)
views.py (Sử dụng Django Form)
from django.shortcuts import render
from django.http import HttpResponse
from .forms import BieuMauDangKyNguoiDung # Giả sử forms.py nằm trong cùng thư mục
def dang_ky_voi_django_form(request):
if request.method == "POST":
form_instance = BieuMauDangKyNguoiDung(request.POST) # Truyền dữ liệu POST vào form
if form_instance.is_valid():
# Dữ liệu đã được xác thực nằm trong form_instance.cleaned_data
# print(form_instance.cleaned_data)
# Thực hiện lưu dữ liệu vào cơ sở dữ liệu hoặc xử lý khác
return HttpResponse("Đăng ký thành công bằng Django Form!")
else:
form_instance = BieuMauDangKyNguoiDung() # Khởi tạo form trống cho GET request
return render(request, "bieu_mau_dang_ky_form_django.html", {"form_data": form_instance})
bieu_mau_dang_ky_form_django.html
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Đăng ký với Django Form</title>
<style>
.error-list { color: red; margin-top: 5px; list-style-type: none; padding-left: 0; }
.error-list li { margin-bottom: 3px; }
form div { margin-bottom: 15px; }
label { display: block; margin-bottom: 5px; font-weight: bold; }
input[type="text"], input[type="password"] {
width: 300px;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
}
.btn-submit {
padding: 10px 20px;
background-color: #28a745;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style>
</head>
<body>
<h1>Đăng Ký Tài Khoản (Sử dụng Django Form)</h1>
<form action="/dang-ky-voi-form/" method="post" novalidate autocomplete="off">
{% csrf_token %}
<div>
<label for="{{ form_data.ten_dang_nhap.id_for_label }}">{{ form_data.ten_dang_nhap.label }}:</label>
{{ form_data.ten_dang_nhap }}
{% if form_data.ten_dang_nhap.errors %}
<ul class="error-list">
{% for error in form_data.ten_dang_nhap.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
<div>
<label for="{{ form_data.mat_khau_nguoi_dung.id_for_label }}">{{ form_data.mat_khau_nguoi_dung.label }}:</label>
{{ form_data.mat_khau_nguoi_dung }}
{% if form_data.mat_khau_nguoi_dung.errors %}
<ul class="error-list">
{% for error in form_data.mat_khau_nguoi_dung.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
<div>
<input type="submit" class="btn-submit" value="Đăng Ký">
</div>
</form>
</body>
</html>
Các Trường và Widget Phổ biến của Django Form
Django cung cấp nhiều loại trường và widget để tùy chỉnh biểu mẫu của bạn.
from django import forms
from django.forms import widgets, fields
class CauHinhNguoiDungForm(forms.Form):
# 'label': Nhãn hiển thị cho trường.
ten_tai_khoan = forms.CharField(
min_length=5,
label="Tên Người Dùng",
max_length=50,
help_text="Tên người dùng phải có ít nhất 5 ký tự."
)
# 'initial': Giá trị mặc định ban đầu của trường trong biểu mẫu.
email_lien_he = forms.EmailField(
label="Email Liên Hệ",
initial="example@domain.com",
required=True
)
# 'error_messages': Tùy chỉnh thông báo lỗi cho các loại lỗi xác thực cụ thể.
mat_khau_moi = forms.CharField(
min_length=8,
label="Mật khẩu mới",
widget=widgets.PasswordInput, # Sử dụng widget để che mật khẩu
error_messages={
"required": "Trường mật khẩu không được để trống.",
"min_length": "Mật khẩu phải có ít nhất 8 ký tự."
}
)
# 'widget': Một đối tượng Widget để tùy chỉnh cách trường được hiển thị trong HTML.
# attrs={'k':'v'} cho phép thêm các thuộc tính HTML tùy chỉnh (ví dụ: class, id, placeholder).
# render_value=True giữ lại giá trị đã nhập khi có lỗi xác thực (thường dùng cho PasswordInput).
xac_nhan_mat_khau = forms.CharField(
label="Xác nhận mật khẩu",
widget=widgets.PasswordInput(attrs={'class': 'input-password-confirm'}, render_value=True),
help_text="Nhập lại mật khẩu mới của bạn."
)
# 'RadioSelect': Widget cho các lựa chọn radio (chỉ chọn một).
# 'choices': Một iterable gồm các cặp (giá_trị_thực_tế, nhãn_hiển_thị).
loai_tai_khoan = fields.ChoiceField(
choices=(('free', "Miễn phí"), ('premium', "Cao cấp"), ('vip', "VIP")),
label="Loại Tài Khoản",
initial='free', # Giá trị mặc định được chọn
widget=widgets.RadioSelect
)
# 'SelectMultiple': Widget cho phép chọn nhiều mục từ danh sách thả xuống.
ngon_ngu_yeu_thich = fields.MultipleChoiceField(
choices=(('py', "Python"), ('js', "JavaScript"), ('java', "Java"), ('cpp', "C++")),
label="Ngôn Ngữ Yêu Thích",
initial=['py', 'js'], # Các giá trị mặc định được chọn (dạng list)
widget=widgets.SelectMultiple(attrs={'size': '5'}) # Thuộc tính size cho phép hiển thị nhiều dòng
)
# 'CheckboxInput': Widget cho một hộp kiểm duy nhất.
dong_y_dieu_khoan = fields.BooleanField(
label="Đồng ý với các điều khoản dịch vụ",
initial=True, # Mặc định được chọn
required=True,
widget=widgets.CheckboxInput()
)
# 'CheckboxSelectMultiple': Widget cho nhiều hộp kiểm.
thong_bao_muon_nhan = fields.MultipleChoiceField(
choices=(('email', "Qua Email"), ('sms', "Qua SMS"), ('app', "Trong Ứng Dụng")),
label="Thông Báo Muốn Nhận",
initial=['email'], # Các giá trị mặc định được chọn (dạng list)
widget=widgets.CheckboxSelectMultiple
)
Lưu ý về trường ChoiceField
Các trường lựa chọn (ChoiceField, ModelChoiceField, v.v.) thường có các tùy chọn được định nghĩa tĩnh. Tuy nhiên, trong nhiều trường hợp, bạn muốn các tùy chọn này được lấy từ cơ sở dữ liệu và cần cập nhật theo thời gian thực. Để làm điều này, bạn có thể ghi đè phương thức khởi tạo __init__ của lớp Form.
Cách 1: Ghi đè phương thức __init__
Trong phương thức __init__, bạn gọi super().__init__(*args, **kwargs) để đảm bảo lớp cha được khởi tạo đúng cách, sau đó bạn có thể tùy chỉnh thuộc tính choices của trường bằng cách truy vấn cơ sở dữ liệu hoặc nguồn dữ liệu động khác.
from django.forms import Form, widgets, fields
# from .models import Category # Nếu bạn lấy dữ liệu từ một model thực tế
class FormTuyChonDong(Form):
phan_loai = fields.ChoiceField(
label="Chọn Phân Loại",
widget=widgets.Select
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Ví dụ: Lấy dữ liệu từ một danh sách tĩnh hoặc từ DB
# self.fields['phan_loai'].choices = Category.objects.all().values_list('id', 'name')
# Hoặc một ví dụ cụ thể hơn với choices lấy từ một list
categories_data = [
(101, 'Sản Phẩm Điện Tử'),
(102, 'Sách và Văn Phòng Phẩm'),
(103, 'Thời Trang')
]
self.fields['phan_loai'].choices = categories_data
self.fields['phan_loai'].initial = 101 # Giá trị khởi tạo
Cách 2: Sử dụng ModelChoiceField và ModelMultipleChoiceField
Django cung cấp các trường đặc biệt ModelChoiceField (chọn một đối tượng) và ModelMultipleChoiceField (chọn nhiều đối tượng) giúp bạn dễ dàng liên kết trực tiếp với QuerySet của các model. Các trường này tự động tạo các tùy chọn từ các đối tượng trong QuerySet được cung cấp.
from django import forms
from django.forms import models as form_models_fields
# from .models import Author, Book # Giả sử bạn có các model Author và Book
class BieuMauDuLieuDoiTuong(forms.Form):
# ModelChoiceField cho phép người dùng chọn một đối tượng từ QuerySet
sach_yeu_thich = form_models_fields.ModelChoiceField(
# queryset=Book.objects.filter(is_published=True), # Lấy sách đã xuất bản
queryset=[], # Placeholder, sẽ được điền trong __init__
empty_label="--- Chọn một cuốn sách ---",
label="Cuốn Sách Ưa Thích"
)
# ModelMultipleChoiceField cho phép người dùng chọn nhiều đối tượng từ QuerySet
cac_tac_gia_tham_gia = form_models_fields.ModelMultipleChoiceField(
# queryset=Author.objects.all().order_by('name'), # Lấy tất cả tác giả
queryset=[], # Placeholder, sẽ được điền trong __init__
label="Các Tác Giả Tham Gia",
widget=forms.SelectMultiple
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Để minh họa mà không cần model thực tế, tạo các đối tượng giả (dummy objects)
class DummyAuthor:
def __init__(self, id, name): self.id, self.name = id, name
def __str__(self): return self.name
class DummyBook:
def __init__(self, id, title): self.id, self.title = id, title
def __str__(self): return self.title
dummy_authors_list = [DummyAuthor(1, "Nguyễn Nhật Ánh"), DummyAuthor(2, "Haruki Murakami")]
dummy_books_list = [DummyBook(1, "Mắt Biếc"), DummyBook(2, "Rừng Na Uy")]
self.fields['cac_tac_gia_tham_gia'].queryset = dummy_authors_list
self.fields['sach_yeu_thich'].queryset = dummy_books_list
Các Trường Form Tích hợp sẵn của Django
Dưới đây là danh sách các trường form cơ bản được cung cấp bởi Django, cùng với các thuộc tính phổ biến của chúng:
Field (Lớp cơ sở cho tất cả các trường)
required=True, # Trường có bắt buộc phải nhập hay không
widget=None, # Widget HTML dùng để hiển thị trường
label=None, # Nhãn hiển thị cho trường
initial=None, # Giá trị ban đầu của trường
help_text='', # Văn bản trợ giúp (hiển thị gần nhãn)
error_messages=None, # Từ điển tùy chỉnh thông báo lỗi, ví dụ: {'required': 'Không thể bỏ trống'}
validators=[], # Danh sách các hàm xác thực tùy chỉnh
localize=False, # Có hỗ trợ bản địa hóa dữ liệu không
disabled=False, # Trường có bị vô hiệu hóa (không thể chỉnh sửa) không
label_suffix=None # Hậu tố cho nhãn
CharField(Field)
max_length=None, # Độ dài tối đa của chuỗi
min_length=None, # Độ dài tối thiểu của chuỗi
strip=True # Có xóa khoảng trắng đầu/cuối của dữ liệu nhập không
IntegerField(Field)
max_value=None, # Giá trị số nguyên tối đa
min_value=None, # Giá trị số nguyên tối thiểu
FloatField(IntegerField) # Tương tự IntegerField nhưng cho số thực
DecimalField(IntegerField)
max_value=None, # Giá trị thập phân tối đa
min_value=None, # Giá trị thập phân tối thiểu
max_digits=None, # Tổng số chữ số (bao gồm cả phần thập phân)
decimal_places=None, # Số chữ số sau dấu thập phân
BaseTemporalField(Field)
input_formats=None # Định dạng thời gian/ngày tháng đầu vào
DateField(BaseTemporalField) # Định dạng: YYYY-MM-DD
TimeField(BaseTemporalField) # Định dạng: HH:MM:SS
DateTimeField(BaseTemporalField) # Định dạng: YYYY-MM-DD HH:MM:SS
DurationField(Field) # Khoảng thời gian (ví dụ: "DD HH:MM:SS")
RegexField(CharField)
regex, # Biểu thức chính quy tùy chỉnh để xác thực
max_length=None,
min_length=None,
# error_message=None, # Sử dụng error_messages={'invalid': '...'} thay thế
EmailField(CharField) # Xác thực định dạng email
FileField(Field)
allow_empty_file=False # Có cho phép upload file rỗng không
ImageField(FileField) # Yêu cầu thư viện Pillow (pip install Pillow)
# Lưu ý khi sử dụng FileField/ImageField:
# - Form HTML cần có: enctype="multipart/form-data"
# - Hàm view cần xử lý: form_instance = MyForm(request.POST, request.FILES)
URLField(Field) # Xác thực định dạng URL
BooleanField(Field) # Trường boolean (True/False)
NullBooleanField(BooleanField) # Trường boolean cho phép giá trị None (True/False/None)
ChoiceField(Field)
choices=(), # Các tùy chọn lựa chọn, ví dụ: ((0,'Tùy chọn A'),(1,'Tùy chọn B'))
required=True,
widget=None, # Mặc định là Select widget
label=None,
initial=None,
help_text=''
ModelChoiceField(ChoiceField) # Chọn một đối tượng từ QuerySet
queryset, # QuerySet để lấy các đối tượng lựa chọn
empty_label="---------", # Nhãn cho tùy chọn rỗng
to_field_name=None, # Trường của model sẽ được dùng làm giá trị HTML 'value'
limit_choices_to=None # Điều kiện lọc bổ sung cho QuerySet trong ModelForm
ModelMultipleChoiceField(ModelChoiceField) # Chọn nhiều đối tượng từ QuerySet
TypedChoiceField(ChoiceField)
coerce = lambda val: val # Hàm chuyển đổi giá trị đã chọn
empty_value= '' # Giá trị mặc định cho trường rỗng
MultipleChoiceField(ChoiceField) # Chọn nhiều giá trị từ các tùy chọn
TypedMultipleChoiceField(MultipleChoiceField)
coerce = lambda val: val # Hàm chuyển đổi từng giá trị đã chọn
empty_value= ''
ComboField(Field)
fields=() # Sử dụng nhiều xác thực kết hợp, ví dụ: [CharField(max_length=20), EmailField()]
MultiValueField(Field) # Lớp trừu tượng, dùng để kết hợp nhiều trường thành một giá trị. Phối hợp với MultiWidget.
SplitDateTimeField(MultiValueField) # Tách DateTime thành trường ngày và trường giờ riêng biệt
input_date_formats=None,
input_time_formats=None
FilePathField(ChoiceField) # Cung cấp các lựa chọn từ các file trong một thư mục
path, # Đường dẫn thư mục
match=None, # Biểu thức chính quy để lọc file
recursive=False, # Có tìm kiếm trong các thư mục con không
allow_files=True, # Cho phép chọn file
allow_folders=False, # Cho phép chọn thư mục
GenericIPAddressField
protocol='both', # 'both', 'ipv4', 'ipv6'
unpack_ipv4=False # Giải nén địa chỉ IPv4 từ địa chỉ IPv6 (ví dụ: ::ffff:192.0.2.1 thành 192.0.2.1)
SlugField(CharField) # Chỉ cho phép chữ, số, dấu gạch dưới, dấu gạch ngang
UUIDField(CharField) # Xác thực định dạng UUID
Các Phương pháp Hiển thị Thẻ của Django Form
Django Forms cung cấp nhiều cách để hiển thị biểu mẫu trong template, từ tự động hoàn toàn đến tùy chỉnh thủ công từng phần tử.
Hiển thị mặc định toàn bộ Form
Django cung cấp các phương thức tiện lợi để hiển thị toàn bộ các trường của form một cách nhanh chóng, theo các định dạng HTML phổ biến. Điều này giúp thao tác đơn giản nhưng có thể thêm các thuộc tính HTML không cần thiết và khó kiểm soát bố cục toàn diện.
{{ form_data_display.as_p }}: Hiển thị mỗi trường trong một thẻ<p>.{{ form_data_display.as_ul }}: Hiển thị mỗi trường trong một thẻ<li>(bên trong thẻ<ul>).{{ form_data_display.as_table }}: Hiển thị mỗi trường trong một hàng bảng (<tr>).
Tự viết cấu trúc và hiển thị từng trường
Bạn có thể tự viết cấu trúc HTML cơ bản cho biểu mẫu của mình và sau đó sử dụng các biến của form để hiển thị từng trường. Cách này cung cấp khả năng kiểm soát bố cục tốt hơn nhưng vẫn tận dụng được các thuộc tính do Django Form cung cấp.
<!-- Ví dụ từ bieu_mau_dang_ky_form_django.html -->
<form action="/dang-ky-voi-form/" method="post" novalidate autocomplete="off">
{% csrf_token %}
<div>
<label for="{{ form_data.ten_dang_nhap.id_for_label }}">{{ form_data.ten_dang_nhap.label }}:</label>
{{ form_data.ten_dang_nhap }}
{% if form_data.ten_dang_nhap.errors %}
<ul class="error-list">
{% for error in form_data.ten_dang_nhap.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
<!-- Các trường khác tương tự -->
<div>
<input type="submit" class="btn-submit" value="Đăng Ký">
</div>
</form>
Hoàn toàn viết thủ công HTML và chỉ dùng Form để xác thực
Trong một số trường hợp, bạn muốn có toàn quyền kiểm soát cấu trúc HTML của mình. Khi đó, bạn có thể viết thủ công tất cả các thẻ HTML cho biểu mẫu và chỉ sử dụng đối tượng Django Form để xác thực dữ liệu gửi về.
Lưu ý quan trọng: Khi tự viết HTML, thuộc tính name của các thẻ <input> phải khớp chính xác với tên trường được định nghĩa trong lớp Django Form của bạn để quá trình xác thực hoạt động.
bieu_mau_them_sach.html (HTML tự viết)
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Thêm Sách Tùy Chỉnh</title>
</head>
<body>
<h1>Thêm Sách (Tùy Chỉnh HTML)</h1>
<form action="/them-sach-tu-do/" method="post">
{% csrf_token %}
<p>Tên Sách: <input type="text" name="ten_sach_form" id="id_ten_sach_form"></p>
<p>Giá Bán: <input type="number" name="gia_ban_form" id="id_gia_ban_form" step="0.01"></p>
<input type="submit" value="Lưu Sách">
{% if form_sach.errors %}
<div style="color: red;">
<p>Có lỗi trong biểu mẫu:</p>
<ul>
{% for field, errors in form_sach.errors.items %}
<li>{{ field }}: {{ errors|join:", " }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
</form>
</body>
</html>
views.py (Form xác thực HTML tự viết)
from django.forms import Form
from django import forms
from django.shortcuts import render
class BieuMauSachTuyChinh(Form):
ten_sach_form = forms.CharField(label="Tên sách", max_length=100,
error_messages={'required': 'Tên sách không được rỗng.'})
gia_ban_form = forms.FloatField(label="Giá bán", min_value=0.01,
error_messages={'required': 'Giá bán không được rỗng.',
'min_value': 'Giá bán phải lớn hơn 0.'})
def them_sach_bang_html_tu_do(request):
form_sach = BieuMauSachTuyChinh() # Khởi tạo form trống cho GET request
if request.method == "POST":
# Truyền request.POST trực tiếp vào form để nó xử lý
form_sach = BieuMauSachTuyChinh(request.POST)
if form_sach.is_valid():
# Dữ liệu hợp lệ đã nằm trong form_sach.cleaned_data
print("Dữ liệu đã xác thực:", form_sach.cleaned_data)
# Thực hiện lưu vào DB hoặc xử lý khác
return HttpResponse("Sách đã được thêm thành công!")
else:
# Nếu có lỗi, form_sach.errors sẽ chứa thông tin lỗi
print("Lỗi xác thực:", form_sach.errors)
return render(request, "bieu_mau_them_sach.html", {'form_sach': form_sach})
Khi xác thực, nếu lớp Form yêu cầu ít trường hơn so với dữ liệu POST thực tế, các trường thừa sẽ bị bỏ qua mà không gây lỗi (ví dụ: trường csrf_token). Ngược lại, nếu Form yêu cầu nhiều trường hơn dữ liệu POST, các trường thiếu sẽ bị báo lỗi thiếu dữ liệu.
Ghi đè Hàm Hook Xác thực
Django Forms cho phép bạn thêm logic xác thực tùy chỉnh bằng cách ghi đè các hàm "hook" (móc nối) nhất định.
from django import forms
from django.core.exceptions import ValidationError
class BieuMauDangKyNangCao(forms.Form):
ten_nguoi_dung_hook = forms.CharField(label="Tên Người Dùng", max_length=30)
mat_khau_hook = forms.CharField(label="Mật Khẩu", widget=forms.PasswordInput)
xac_nhan_mat_khau_hook = forms.CharField(label="Xác Nhận Mật Khẩu", widget=forms.PasswordInput)
ma_xac_nhan_tu_dong = forms.CharField(label="Mã Xác Nhận", max_length=6, min_length=6)
# Hook cục bộ: `clean_<field_name>`
# Hàm này được gọi sau khi các trình xác thực mặc định của trường đã chạy.
# Nó dùng để xác thực riêng cho trường đó hoặc điều chỉnh giá trị của nó.
def clean_ten_nguoi_dung_hook(self):
gia_tri_ten = self.cleaned_data.get("ten_nguoi_dung_hook")
# Ví dụ: cấm các từ nhạy cảm
if "admin" in gia_tri_ten.lower() or "root" in gia_tri_ten.lower():
raise ValidationError("Tên người dùng không được chứa các từ cấm như 'admin' hoặc 'root'!")
# Bạn có thể kiểm tra sự tồn tại của tên người dùng trong DB ở đây
# if User.objects.filter(username=gia_tri_ten).exists():
# raise ValidationError("Tên người dùng này đã tồn tại.")
return gia_tri_ten # Luôn phải trả về giá trị đã làm sạch
# Hook toàn cục: `clean()`
# Hàm này được gọi sau khi tất cả các trình xác thực trường và các hook cục bộ đã chạy.
# Nó dùng để xác thực các phụ thuộc giữa các trường hoặc các kiểm tra tổng thể.
def clean(self):
# Quan trọng: Gọi phương thức clean của lớp cha để đảm bảo các kiểm tra cơ bản đã được thực hiện
super().clean()
mat_khau = self.cleaned_data.get("mat_khau_hook")
xac_nhan_mat_khau = self.cleaned_data.get("xac_nhan_mat_khau_hook")
ma_xac_nhan = self.cleaned_data.get("ma_xac_nhan_tu_dong")
# Kiểm tra trùng khớp mật khẩu
if mat_khau and xac_nhan_mat_khau and mat_khau != xac_nhan_mat_khau:
# add_error() được dùng để thêm lỗi vào một trường cụ thể
self.add_error("xac_nhan_mat_khau_hook", ValidationError("Mật khẩu xác nhận không khớp."))
# Ví dụ kiểm tra mã xác nhận đơn giản (trong thực tế sẽ phức tạp hơn)
# Giả định mã xác nhận đúng là "SECRET"
if ma_xac_nhan and ma_xac_nhan != "SECRET":
self.add_error("ma_xac_nhan_tu_dong", ValidationError("Mã xác nhận không đúng."))
# Luôn phải trả về self.cleaned_data
return self.cleaned_data
Trình tự thực thi của phương thức form.is_valid()
Khi bạn gọi form.is_valid(), Django thực hiện một chuỗi các bước xác thực nội bộ. Hiểu rõ trình tự này giúp bạn biết khi nào và ở đâu để can thiệp bằng các hàm hook tùy chỉnh.
- Kiểm tra ban đầu: Phương thức
is_valid()đầu tiên kiểm tra xem form đã được khởi tạo với dữ liệu (thường làrequest.POSThoặcrequest.FILES) và liệu nó có lỗi chưa được xử lý không. - Gọi
full_clean(): Đây là phương thức chính chịu trách nhiệm cho toàn bộ quá trình xác thực. - Khởi tạo đối tượng lưu trữ lỗi và dữ liệu:
- Một từ điển rỗng
self._errors(thuộc kiểuErrorDict) được tạo để lưu trữ tất cả các lỗi được tìm thấy. - Một từ điển rỗng
self.cleaned_datađược tạo để lưu trữ dữ liệu đã được xác thực thành công.
- Một từ điển rỗng
- Thực thi
_clean_fields():- Lặp qua từng trường được định nghĩa trong form (
self.fields.items()). - Với mỗi trường, nó chạy các trình xác thực tích hợp sẵn của trường đó (ví dụ:
min_length,max_length, định dạngEmailField). - Nếu có lỗi, một đối tượng
ValidationErrorsẽ được thêm vàoself._errors. - Nếu không có lỗi, giá trị đã được làm sạch của trường sẽ được thêm vào
self.cleaned_data. - Sau khi chạy trình xác thực mặc định, Django kiểm tra xem có phương thức hook cục bộ nào có tên
clean_<tên_trường>(ví dụ:clean_ten_nguoi_dung_hook) hay không và gọi nó. Lỗi từ hook này cũng được thêm vàoself._errors, và giá trị trả về của hook sẽ cập nhậtself.cleaned_datacho trường đó.
- Lặp qua từng trường được định nghĩa trong form (
- Thực thi
_clean_form():- Phương thức này gọi hook toàn cục
self.clean()(nếu bạn đã ghi đè nó). - Trong hook
clean(), bạn có thể thực hiện các kiểm tra xác thực liên quan đến nhiều trường (ví dụ: so sánh mật khẩu và xác nhận mật khẩu). - Bất kỳ lỗi nào được thêm bằng
self.add_error()trongclean()sẽ được lưu vàoself._errors. - Phương thức
clean()phải trả vềself.cleaned_data(có thể đã được sửa đổi).
- Phương thức này gọi hook toàn cục
- Thực thi
post_clean():- Đây là một hook cuối cùng được gọi sau tất cả các quá trình xác thực khác. Nó thường được sử dụng cho các xử lý sau xác thực mà không liên quan trực tiếp đến việc thêm lỗi mới vào form (ví dụ: loại bỏ các trường không cần thiết từ
cleaned_data, hoặc các tác vụ phụ khác).
- Đây là một hook cuối cùng được gọi sau tất cả các quá trình xác thực khác. Nó thường được sử dụng cho các xử lý sau xác thực mà không liên quan trực tiếp đến việc thêm lỗi mới vào form (ví dụ: loại bỏ các trường không cần thiết từ
- Kết quả:
- Nếu
self._errorsrỗng sau tất cả các bước này, form được coi là hợp lệ (is_valid()trả vềTrue). - Nếu
self._errorschứa bất kỳ lỗi nào, form không hợp lệ (is_valid()trả vềFalse).
- Nếu
Như vậy, cả hook cục bộ (clean_<tên_trường>) và hook toàn cục (clean()) đều thao tác trên dữ liệu đã được xác thực một phần hoặc hoàn chỉnh có trong self.cleaned_data. Điều này cho phép bạn kiểm tra và thay đổi dữ liệu sau khi các trình xác thực cơ bản đã hoạt động.