فصل ۵: فرم‌ها و اعتبارسنجی — ورودی امن و فارسی

Form و ModelForm: چرخه‌ی اعتبارسنجی، cleaned_data و widgetها

فرم جنگو فقط 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() را صدا می‌زنید، این مراحل اجرا می‌شود:

  1. برای هر فیلد: to_python() (تبدیل نوع)، validate() (الزامی بودن و…)، run_validators() (max_length و validatorهای اضافه).
  2. برای هر فیلد: متد clean_<field>() فرم، اگر تعریف شده باشد.
  3. متد clean() کل فرم، برای قواعدی که به چند فیلد وابسته‌اند.
  4. در 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) بگذارید و مقدار را قبل از اعتبارسنجی به میلادی برگردانید.

برای ذخیره‌ی پیشرفت و شرکت در آزمون، وارد شوید — رایگان است.