Các thành phần biểu mẫu Django
Đường dẫn- Các thành phần biểu mẫu Django
- Giới thiệu thành phần Form
- Tạo chức năng đăng ký thủ công
- Sử dụng thành phần Form để đăng ký
- login2.html
- Trường và plugin thường dùng
- Các trường cơ bản
- Tham số trường
- Xác thực tích hợp
- Kiểm tra tùy chỉnh
- Hàm gắp (hook)
- Quy trình is_valid
- Lớp Form: Trường và plugin
- Giá trị ban đầu
- Ghi đè thông báo lỗi
- Mật khẩu (input type='password')
- Radio đơn
- Select đơn
- Select đa
- Checkbox đơn
- Checkbox đa
- Lưu ý với lựa chọn:
- Cập nhật thời gian thực phương pháp 1
- Cập nhật thời gian thực phương pháp 2
- Tất cả trường Django Form
- Xác thực (biểu thức chính quy)
- Xác thực tùy chỉnh
- Áp dụng kiểu Bootstrap
- Thêm kiểu hàng loạt
- ModelForm
- Tham số trong class Meta:
- Ví dụ sử dụng ModelForm
Giới thiệu thành phần Form
"""
Khi gửi dữ liệu từ biểu mẫu HTML đến backend, chúng ta thường tạo các thẻ nhập liệu và bọc chúng bằng thẻ form.
Tuy nhiên, trong nhiều trường hợp cần kiểm tra đầu vào của người dùng như độ dài, định dạng,...
Thông báo lỗi tương ứng sẽ được hiển thị nếu đầu vào không hợp lệ.
"""
Thành phần Form của Django thực hiện các chức năng trên.
Tóm lại, các chức năng chính của Form bao gồm:
- Tạo thẻ HTML có thể sử dụng
- Kiểm tra dữ liệu người dùng gửi lên
- Giữ lại giá trị nhập trước đó
Tạo chức năng đăng ký thủ công
- views.py
# Đăng ký
def tai_khoan_moi(request):
thong_bao_loi = ""
if request.method == "POST":
ten_dang_nhap = request.POST.get("ten")
mk = request.POST.get("mk")
# Kiểm tra thông tin đăng ký
if len(ten_dang_nhap) < 6:
# Độ dài tên dưới 6 ký tự
thong_bao_loi = "Tên đăng nhập phải có ít nhất 6 ký tự"
else:
# Lưu tên và mật khẩu vào DB
return HttpResponse("Đăng ký thành công")
return render(request, "dangky.html", {"thong_bao_loi": thong_bao_loi})
- login.html
<html lang="vi">
<head>
<meta charset="UTF-8">
<title>Trang đăng ký</title>
</head>
<body>
<form action="/dk/" method="post">
{% csrf_token %}
<p>
Tên đăng nhập:
<input type="text" name="ten">
</p>
<p>
Mật khẩu:
<input type="password" name="mk">
</p>
<p>
<input type="submit" value="Đăng ký">
<p style="color: red">{{ thong_bao_loi }}</p>
</p>
</form>
</body>
</html>
Sử dụng thành phần Form để đăng ký
#Chức năng thành phần Form
1. Tạo thẻ input
2. Kiểm tra dữ liệu gửi lên
3. Hiển thị thông báo lỗi
- views.py
- Định nghĩa lớp DangKyForm:
#Cấu trúc cơ bản
from django import forms #Nhập module thành phần
#Tạo lớp theo yêu cầu của Django Form
class DangKyForm(forms.Form):
ten = forms.CharField(label="Tên đăng nhập")
mk = forms.CharField(label="Mật khẩu")
- Viết hàm xem:
# Sử dụng thành phần Form đăng ký
def dang_ky_moi(request):
form = DangKyForm()
if request.method == "POST":
# Khởi tạo form với dữ liệu POST
form = DangKyForm(request.POST)
# Gọi phương thức kiểm tra dữ liệu
if form.is_valid():
return HttpResponse("Đăng ký thành công")
return render(request, "dangky2.html", {"form": form})
-
login2.html
<html lang="vi">
<head>
<meta charset="UTF-8">
<title>Đăng ký 2</title>
</head>
<body>
<form action="/dk2/" method="post" novalidate autocomplete="off">
{% csrf_token %}
<div>
<label for="{{ form.ten.id_for_label }}">{{ form.ten.label }}</label>
{{ form.ten }} {{ form.ten.errors.0 }}
</div>
<div>
<label for="{{ form.mk.id_for_label }}">{{ form.mk.label }}</label>
{{ form.mk }} {{ form.mk.errors.0 }}
</div>
<div>
<input type="submit" class="btn btn-success" value="Đăng ký">
</div>
</form>
</body>
</html>
Hiệu ứng trang web xác minh chức năng Form:
• Giao diện được tạo bởi đối tượng Form -->Tạo thẻ HTML
• Khi tên/mật khẩu rỗng hoặc sai → hiển thị lỗi -->Kiểm tra đầu vào
• Sau khi nhập sai → giữ giá trị trước đó -->Giữ lại dữ liệu
Trường và plugin thường dùng
Các trường cơ bản
CharField #Chuỗi
ChoiceField #Lựa chọn
MultipleChoiceField#Lựa chọn đa
Tham số trường
required=True, Cho phép rỗng
widget=None, Plugin HTML
label=None, Tạo nhãn
initial=None, Giá trị mặc định
error_messages=None, {'required': 'Không được rỗng', 'invalid': 'Định dạng sai'}
validators=[], Quy tắc kiểm tra
disabled=False, Cho phép chỉnh sửa
Xác thực tích hợp
required=True #Bắt buộc điền
min_length #Độ dài tối thiểu
max_length #Độ dài tối đa
Kiểm tra tùy chỉnh
#Khi xác thực tích hợp không đủ
#Tạo hàm
def kiem_tra_ten(gia_tri):
# Quy tắc hợp lệ → không xử lý
# Không hợp lệ → ném ngoại lệ
if 'từ cấm' in gia_tri:
raise ValidationError('Vi phạm nội quy')
Hàm gắp (hook)
- Gắp cục bộ (kiểm tra trường cụ thể)
def clean_truong(self):
# Gắp cục bộ
# Hợp lệ → trả về giá trị
# Không hợp lệ → ném ngoại lệ
gia_tri = self.cleaned_data.get('trường')
if 'từ cấm' in gia_tri:
raise ValidationError('Nội dung không phù hợp')
else:
return gia_tri
- Gắp toàn cục
def clean(self):
# Gắp toàn cục
# Hợp lệ → trả về dữ liệu
# Không hợp lệ → ném ngoại lệ '__all__'
mk = self.cleaned_data.get('mk')
xac_nhan = self.cleaned_data.get('xac_nhan')
if mk == xac_nhan:
return self.cleaned_data
else:
#self.add_error("xac_nhan","Mật khẩu không khớp") #Tùy chỉnh thông báo
raise ValidationError('Mật khẩu không khớp')
Quy trình is_valid
- 1 Thực hiện full_clean():
- Tạo từ điển lỗi
- Tạo từ điển dữ liệu đã làm sạch
- 2 Thực hiện _clean_fields:
- Lặp tất cả các trường
- Lấy giá trị kiểm tra (xác thực tích hợp, kiểm tra tùy chỉnh)
- Hợp lệ
- self.cleaned_data[name] = value
- Nếu có gắp cục bộ → kiểm tra:
- Hợp lệ → self.cleaned_data[name] = value
- Không hợp lệ → self._errors thêm lỗi, xóa giá trị khỏi cleaned_data
- Không hợp lệ
- self._errors thêm lỗi
- 3 Thực hiện gắp toàn cục clean()
Tóm tắt quy trình:
#(1) is_valid() kiểm tra self.errors, có giá trị → false
#(2) Kiểm tra self._errors, rỗng → self.full_clean(), ngược lại → self._errors
#(3) full_clean() tạo self._errors và cleaned_data
#(4) self._clean_fields() → kiểm tra trường
#(5) field.clean(value) thực hiện kiểm tra
#(6) Thêm kết quả vào self._errors và cleaned_data
#(7) Gắp clean_XX nếu tồn tại → thêm lỗi vào self._errors
#(8) Hoàn thành quy trình
Lớp Form: Trường và plugin
- Tạo lớp Form cần sử dụng trường và plugin. Trường kiểm tra dữ liệu, plugin tạo HTML
-
Giá trị ban đầu
#Giá trị mặc định trong input
class DangNhapForm(forms.Form):
ten_dang_nhap = forms.CharField(
min_length=8,
label="Tên",
initial="NguyenVanA" # Thiết lập mặc định
)
mk = forms.CharField(min_length=6, label="Mật khẩu")
-
Ghi đè thông báo lỗi
class DangNhapForm(forms.Form):
ten_dang_nhap = forms.CharField(
min_length=8,
label="Tên",
initial="NguyenVanA",
error_messages={
"required": "Không được để trống",
"invalid": "Định dạng sai",
"min_length": "Tên tối thiểu 8 ký tự"
}
)
mk = forms.CharField(min_length=6, label="Mật khẩu")
-
Mật khẩu (input type='password')
class DangNhapForm(forms.Form):
...
mk = forms.CharField(
min_length=6,
label="Mật khẩu",
widget=forms.widgets.PasswordInput(attrs={'class': 'mk1'}, render_value=True)
)
-
Radio đơn
class DangNhapForm(forms.Form):
ten_dang_nhap = forms.CharField(
min_length=8,
label="Tên",
initial="NguyenVanA",
error_messages={
"required": "Không được để trống",
"invalid": "Định dạng sai",
"min_length": "Tên tối thiểu 8 ký tự"
}
)
mk = forms.CharField(min_length=6, label="Mật khẩu")
gioi_tinh = forms.fields.ChoiceField(
choices=((1, "Nam"), (2, "Nữ"), (3, "Bí mật")),
label="Giới tính",
initial=3,
widget=forms.widgets.RadioSelect()
)
-
Select đơn
class DangNhapForm(forms.Form):
...
thich = forms.fields.ChoiceField(
choices=((1, "Bóng rổ"), (2, "Bóng đá"), (3, "Xổ số"), ),
label="Sở thích",
initial=3,
widget=forms.widgets.Select()
)
-
Select đa
class DangNhapForm(forms.Form):
...
thich = forms.fields.MultipleChoiceField(
choices=((1, "Bóng rổ"), (2, "Bóng đá"), (3, "Xổ số"), ),
label="Sở thích",
initial=[1, 3],
widget=forms.widgets.SelectMultiple()
)
-
Checkbox đơn
class DangNhapForm(forms.Form):
...
luu = forms.fields.ChoiceField(
label="Ghi nhớ mật khẩu",
initial="checked",
widget=forms.widgets.CheckboxInput()
)
-
Checkbox đa
class DangNhapForm(forms.Form):
...
thich = forms.fields.MultipleChoiceField(
choices=((1, "Bóng rổ"), (2, "Bóng đá"), (3, "Xổ số"),),
label="Sở thích",
initial=[1, 3],
widget=forms.widgets.CheckboxSelectMultiple()
)
Lưu ý về lựa chọn:
Khi sử dụng thẻ lựa chọn, lưu ý rằng options có thể lấy từ DB nhưng là trường tĩnh nên không cập nhật theo thời gian thực.
Phải tùy chỉnh __init__ để đạt được điều này.
-
Cập nhật thời gian thực phương pháp 1
from django.forms import Form
from django.forms import widgets
from django.forms import fields
class FormMoi(Form):
nguoi_dung = fields.ChoiceField(
# choices=((1, 'Hà Nội'), (2, 'TP.HCM'),),
initial=2,
widget=widgets.Select
)
def __init__(self, *args, **kwargs):
super(FormMoi,self).__init__(*args, **kwargs)
# self.fields['nguoi_dung'].choices = ((1, 'Hà Nội'), (2, 'TP.HCM'),)
# Hoặc
self.fields['nguoi_dung'].choices = models.LopHoc.objects.all().values_list('id','ten')
-
Cập nhật thời gian thực phương pháp 2
from django import forms
from django.forms import fields
from django.forms import models as form_model
class FormThongTin(forms.Form):
tac_gia = form_model.ModelMultipleChoiceField(queryset=models.LoaiTin.objects.all()) # Đa chọn
# tac_gia = form_model.ModelChoiceField(queryset=models.LoaiTin.objects.all()) # Đơn chọn
Tất cả trường Django Form
Field
required=True, Cho phép rỗng
widget=None, Plugin HTML
label=None, Tạo nhãn (mặc định tên trường viết hoa chữ cái đầu)
initial=None, Giá trị mặc định
help_text='', Hướng dẫn (hiển thị bên cạnh nhãn)
error_messages=None, {'required': 'Không được rỗng', 'invalid': 'Định dạng sai'}
validators=[], Quy tắc kiểm tra
localize=False, Hỗ trợ địa phương hóa
disabled=False, Cho phép chỉnh sửa
label_suffix=None Ký hiệu sau nhãn
CharField(Field)
max_length=None, Độ dài tối đa
min_length=None, Độ dài tối thiểu
strip=True Xóa khoảng trắng đầu/cuối
IntegerField(Field)
max_value=None, Giá trị lớn nhất
min_value=None, Giá trị nhỏ nhất
FloatField(IntegerField)
...
DecimalField(IntegerField)
max_value=None, Giá trị lớn nhất
min_value=None, Giá trị nhỏ nhất
max_digits=None, Tổng số chữ số
decimal_places=None, Số chữ số thập phân
BaseTemporalField(Field)
input_formats=None Định dạng thời gian
DateField(BaseTemporalField) Định dạng: 2015-09-01
TimeField(BaseTemporalField) Định dạng: 11:12
DateTimeField(BaseTemporalField)Định dạng: 2015-09-01 11:12
DurationField(Field) Khoảng thời gian: %d %H:%M:%S.%f
...
RegexField(CharField)
regex, Biểu thức chính quy
max_length=None, Độ dài tối đa
min_length=None, Độ dài tối thiểu
error_message=None, Bỏ qua, dùng error_messages={'invalid': '...'}
EmailField(CharField)
...
FileField(Field)
allow_empty_file=False Cho phép file rỗng
ImageField(FileField)
...
Cần PIL/Pillow
Lưu ý:
- form có enctype="multipart/form-data"
- view có obj = FormMoi(request.POST, request.FILES)
URLField(Field)
...
BooleanField(Field)
...
NullBooleanField(BooleanField)
...
ChoiceField(Field)
...
choices=(), Lựa chọn, ví dụ: choices = ((0,'Hà Nội'),(1,'TP.HCM'),)
required=True, Bắt buộc
widget=None, Plugin, mặc định select
label=None, Nội dung nhãn
initial=None, Giá trị mặc định
help_text='', Hướng dẫn
ModelChoiceField(ChoiceField)
... django.forms.models.ModelChoiceField
queryset, Truy vấn dữ liệu DB
empty_label="---------", Nội dung rỗng
to_field_name=None, Trường đại diện cho value
limit_choices_to=None Lọc queryset lần hai
ModelMultipleChoiceField(ModelChoiceField)
... django.forms.models.ModelMultipleChoiceField
TypedChoiceField(ChoiceField)
coerce = lambda val: val Chuyển đổi giá trị
empty_value= '' Giá trị rỗng mặc định
MultipleChoiceField(ChoiceField)
...
TypedMultipleChoiceField(MultipleChoiceField)
coerce = lambda val: val Chuyển đổi từng giá trị
empty_value= '' Giá trị rỗng mặc định
ComboField(Field)
fields=() Nhiều quy tắc kiểm tra, ví dụ: độ dài 20 và định dạng email
fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])
MultiValueField(Field)
PS: Lớp trừu tượng, có thể kết hợp nhiều trường, cần MultiWidget
SplitDateTimeField(MultiValueField)
input_date_formats=None, Định dạng ngày: ['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y']
input_time_formats=None Định dạng giờ: ['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']
FilePathField(ChoiceField) Hiển thị file trong thư mục
path, Đường dẫn
match=None, Biểu thức chính quy
recursive=False, Đệ quy
allow_files=True, Cho phép file
allow_folders=False, Cho phép folder
required=True,
widget=None,
label=None,
initial=None,
help_text=''
GenericIPAddressField
protocol='both', Hỗ trợ IP
unpack_ipv4=False Giải mã IPv4, cần protocol=both
SlugField(CharField) Số, chữ, gạch dưới, gạch nối
...
UUIDField(CharField) Định dạng UUID
Xác thực (biểu thức chính quy)
from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.validators import RegexValidator
class FormMoi(Form):
nguoi_dung = fields.CharField(
validators=[RegexValidator(r'^[0-9]+$', 'Vui lòng nhập số'), RegexValidator(r'^159[0-9]+$', 'Số phải bắt đầu bằng 159')],
)
-
Xác thực tùy chỉnh
import re
from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.exceptions import ValidationError
# Quy tắc kiểm tra tùy chỉnh
def kiem_tra_so_dien_thoai(gia_tri):
regex = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
if not regex.match(gia_tri):
raise ValidationError('Số điện thoại không hợp lệ')
class FormDangKy(Form):
tieu_de = fields.CharField(max_length=20,
min_length=5,
error_messages={'required': 'Không được để trống tiêu đề',
'min_length': 'Tối thiểu 5 ký tự',
'max_length': 'Tối đa 20 ký tự'},
widget=widgets.TextInput(attrs={'class': "form-control",
'placeholder': 'Từ 5-20 ký tự'}))
# Sử dụng quy tắc kiểm tra tùy chỉnh
so_dt = fields.CharField(validators=[kiem_tra_so_dien_thoai, ],
error_messages={'required': 'Không được để trống số điện thoại'},
widget=widgets.TextInput(attrs={'class': "form-control",
'placeholder': 'Số điện thoại'}))
email = fields.EmailField(required=False,
error_messages={'required': 'Không được để trống email','invalid': 'Định dạng email sai'},
widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': 'Email'}))
Áp dụng kiểu Bootstrap
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
<title>Đăng nhập</title>
</head>
<body>
<div class="container">
<div class="row">
<form action="/login2/" method="post" novalidate class="form-horizontal">
{% csrf_token %}
<div class="form-group">
<label for="{{ form.ten_dang_nhap.id_for_label }}"
class="col-md-2 control-label">{{ form.ten_dang_nhap.label }}</label>
<div class="col-md-10">
{{ form.ten_dang_nhap }}
<span class="help-block">{{ form.ten_dang_nhap.errors.0 }}</span>
</div>
</div>
<div class="form-group">
<label for="{{ form.mk.id_for_label }}" class="col-md-2 control-label">{{ form.mk.label }}</label>
<div class="col-md-10">
{{ form.mk }}
<span class="help-block">{{ form.mk.errors.0 }}</span>
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">{{ form.gioi_tinh.label }}</label>
<div class="col-md-10">
<div class="radio">
{% for radio in form.gioi_tinh %}
<label for="{{ radio.id_for_label }}">
{{ radio.tag }}{{ radio.choice_label }}
</label>
{% endfor %}
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="submit" class="btn btn-default">Đăng ký</button>
</div>
</div>
</form>
</div>
</div>
<script src="/static/jquery-3.2.1.min.js"></script>
<script src="/static/bootstrap/js/bootstrap.min.js"></script>
</body>
</html>
Thêm kiểu hàng loạt
- Ghi đè init của lớp form
class DangNhapForm(forms.Form):
ten_dang_nhap = forms.CharField(
min_length=8,
label="Tên",
initial="NguyenVanA",
error_messages={
"required": "Không được để trống",
"invalid": "Định dạng sai",
"min_length": "Tên tối thiểu 8 ký tự"
}
...
def __init__(self, *args, **kwargs):
super(DangNhapForm, self).__init__(*args, **kwargs)
for truong in iter(self.fields):
self.fields[truong].widget.attrs.update({
'class': 'form-control'
})
ModelForm
- Kết hợp form và model
class FormSach(forms.ModelForm):
class Meta:
model = models.Sach
fields = "__all__"
labels = {
"ten": "Tên sách",
"gia": "Giá"
}
widgets = {
"mat_khau": forms.widgets.PasswordInput(attrs={"class": "mk1"}),
}
-
Tham số trong class Meta:
model = models.HocSinh # Lớp Model tương ứng
fields = "__all__" # Tất cả trường
exclude = None # Loại trừ
labels = None # Nhãn
help_texts = None # Hướng dẫn
widgets = None # Plugin
error_messages = None # Thông báo lỗi
Ví dụ sử dụng ModelForm
- Tạo lớp FormChinhSuaBlog
from blog import models
from django import forms
class FormChinhSuaBlog(forms.ModelForm):
class Meta:
model = models.Blog # Lớp tạo form
fields = '__all__' # Tất cả trường
exclude = ['ma_so',] # Loại trừ
error_messages = {
'ten': {'required': 'Không được để trống'}, # Thông báo lỗi
'tac_gia': {'required': 'Không được để trống'},
}
def __init__(self, *args, **kwargs): # Ghi đè __init__ để thêm class cho input
super().__init__(*args, **kwargs)
from multiselectfield.forms.fields import MultiSelectFormField
for ten_truong, truong in self.fields.items():
if not isinstance(truong, MultiSelectFormField):
truong.widget.attrs.update({'class': 'form-control'})
- views
from blog import models
from blog.forms import FormChinhSuaBlog
from django.shortcuts import render, redirect
def sua_blog(request, ma_so=None):
doi_tuong = models.Blog.objects.filter(id=ma_so).first() # Lấy đối tượng
tieu_de = 'Tạo mới blog' if not ma_so else 'Chỉnh sửa blog' # Tựa đề
form = FormChinhSuaBlog(instance=doi_tuong) # Truyền đối tượng
if request.method == 'POST': # Yêu cầu POST
form = FormChinhSuaBlog(data=request.POST, instance=doi_tuong) # Kiểm tra dữ liệu
if form.is_valid(): # Kiểm tra hợp lệ
form.save() # Lưu
return redirect('blog:danh_sach_blog') # Chuyển hướng
return render(request, 'sua_blog.html', {'form': form, 'tieu_de': tieu_de})
- html
{% extends 'base.html' %}
{% block title %}
<h1>
{{ tieu_de }} <!--Tựa đề-->
</h1>
{% endblock %}
{% block content %}
<div class="panel-body">
<form class="form-horizontal" action="" method="post" novalidate>
{% csrf_token %} <!--Kiểm tra csrf-->
{% for truong in form %}
<div class="form-group {% if truong.errors %} has-error {% endif %}"><!--Hiển thị lỗi màu đỏ-->
<label for="{{ truong.id_for_label }}" {% if not truong.field.required %} style="color: dimgrey" {% endif %}
class="col-sm-2 control-label">{{ truong.label }}</label>
<div class="col-sm-5">
{{ truong }}
<span class="help-block">{{ truong.errors.0}}</span><!--Thông báo lỗi-->
</div>
</div>
{% endfor %}
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">Gửi</button>
</div>
</div>
</form>
</div>
{% endblock %}