فرم جنگو فقط HTML نیست
فرم جنگو سه کار را با هم انجام میدهد: HTML فیلدها را میسازد، دادهی ورودی را از رشته به نوع درست پایتون تبدیل میکند (رشتهی «1200» به عدد 1200، رشتهی تاریخ به date) و آن را اعتبارسنجی میکند. هرگز مستقیماً از request.POST داده برندارید و ذخیره نکنید؛ همیشه از form.cleaned_data بخوانید.
فرم ساده و ModelForm
# apps/orders/forms.py
from django import forms
from .models import Order
class ContactForm(forms.Form):
name = forms.CharField(label="نام و نام خانوادگی", max_length=80)
mobile = forms.CharField(label="موبایل", max_length=15)
message = forms.CharField(label="پیام", widget=forms.Textarea(attrs={"rows": 4}))
class OrderForm(forms.ModelForm):
class Meta:
model = Order
fields = ["delivery_date", "address", "note"] # فهرست صریح؛ هرگز "__all__"
labels = {"note": "توضیحات برای کارگاه"}
help_texts = {"delivery_date": "حداقل ۱۰ روز کاری بعد از ثبت"}
widgets = {
"note": forms.Textarea(attrs={"rows": 3}),
"address": forms.TextInput(attrs={"autocomplete": "street-address"}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields.values():
field.widget.attrs.setdefault("class", "form-control")
الگوی استاندارد View
def contact(request):
if request.method == "POST":
form = ContactForm(request.POST) # فرم «bound»
if form.is_valid():
send_contact_email(**form.cleaned_data)
messages.success(request, "پیام شما دریافت شد.")
return redirect("pages:contact")
else:
form = ContactForm() # فرم «unbound»
return render(request, "pages/contact.html", {"form": form})
ترتیب اعتبارسنجی
وقتی is_valid() را صدا میزنید، این مراحل اجرا میشود:
- برای هر فیلد:
to_python()(تبدیل نوع)،validate()(الزامی بودن و…)،run_validators()(max_length و validatorهای اضافه). - برای هر فیلد: متد
clean_<field>()فرم، اگر تعریف شده باشد. - متد
clean()کل فرم، برای قواعدی که به چند فیلد وابستهاند. - در ModelForm: ساختن instance و اجرای
full_clean()مدل، یعنیclean()مدل و قیدهای unique و constraints.
در قالب سادهترین حالت {{ form }} است که از Django 5.0 خروجی مبتنی بر div تولید میکند. برای کنترل بیشتر هر فیلد را جدا رندر کنید: {{ form.mobile.as_field_group }} برچسب، ویجت، راهنما و خطا را با هم میآورد.
نکتههایی که کمتر کسی میداند
fields = "__all__"یاexcludeدر ModelForm یعنی اگر فردا فیلدis_approvedیاdiscountبه مدل اضافه شود، کاربر میتواند با دستکاری HTML آن را هم بفرستد (mass assignment). همیشه فهرست صریح بنویسید.form.save(commit=False)شیء را بدون ذخیره برمیگرداند تا فیلدهای سمت سرور (مثل customer) را پر کنید؛ اگر فرم فیلد ManyToMany دارد، بعد از save خودتانform.save_m2m()را صدا بزنید.initialفقط مقدار نمایشی فرم unbound است؛ اگر فرم را با دادهی POST بسازید، initial نادیده گرفته میشود و فیلدی که ارسال نشده خالی حساب میشود.form.has_changed()وform.changed_dataمیگویند کاربر دقیقاً کدام فیلدها را عوض کرده است؛ برای ثبت تاریخچهی تغییرات یا ارسال اعلان عالی است.- ویجت
DateInput(attrs={"type": "date"})تقویم میلادی مرورگر را نشان میدهد؛ برای کاربر ایرانی یک datepicker شمسی محلی (بدون CDN) بگذارید و مقدار را قبل از اعتبارسنجی به میلادی برگردانید.