فصل ۷: فرم و اعتبارسنجی در جنگو

تا حالا یاد گرفتیم چطوری اطلاعات رو به کاربر نشون بدیم. حالا میخوایم از کاربر اطلاعات بگیریم! مثلاً اسمش، ایمیلش، یا یه نظر و پیام. برای این کار از فرم (Form) استفاده میکنیم.
فرم چیه؟
همون چیزایی که تو سایتها میبینی و توش اطلاعات وارد میکنی:
- یه کادر برای نوشتن اسم
- یه کادر برای ایمیل
- یه دکمه که روش نوشته “ارسال” یا “ثبت نام”
چرا فرم جنگو؟ مگه HTML خودش فرم نداره؟
چرا، با HTML خالی هم میشه فرم ساخت. ولی فرم جنگو مثل یه دستیار خیلی باهوش و کاردرسته که چند تا کار مهم رو برات انجام میده:
- ساختن خودکار HTML فرم: دیگه لازم نیست کلی کد HTML برای هر فیلد (کادر ورودی) بنویسی. جنگو خودش برات میسازه.
- اعتبارسنجی (Validation) راحت: این خیلی مهمه! جنگو چک میکنه کاربر اطلاعات رو درست وارد کرده یا نه.
- مثلاً اگه گفتی “اینجا باید ایمیل وارد بشه”، جنگو چک میکنه چیزی که کاربر نوشته شبیه ایمیل هست یا نه.
- اگه گفتی “این فیلد نباید خالی باشه”، چک میکنه کاربر حتماً یه چیزی توش نوشته باشه.
- تمیزکاری دادهها: اطلاعاتی که کاربر میده رو مرتب و استاندارد میکنه.
- امنیت: جلوی بعضی حملات امنیتی رایج روی فرمها (مثل CSRF) رو میگیره.
خب، چطوری کار میکنه؟ (خیلی ساده در ۳ مرحله)
- مرحله ۱: نقشه فرم رو میکشی (توی یه فایل به اسم
forms.py)- یه فایل جدید به اسم
forms.pyتوی پوشه اپلیکیشنت (مثلاًmyapp/forms.py) درست میکنی. - داخل این فایل، به جنگو میگی فرمت قراره چه چیزهایی از کاربر بپرسه (مثلاً اسم، ایمیل، پیام) و هرکدوم چه نوعی هستن (متن معمولی، آدرس ایمیل، متن طولانی و…).
- یه فایل جدید به اسم
- مرحله ۲: فرم رو به کاربر نشون میدی (توی تمپلیت و ویو)
- توی فایل
views.py، اون نقشهای که کشیدی رو صدا میزنی و میدی به تمپلیت. - توی فایل تمپلیت HTML، به جنگو میگی “این فرم رو اینجا نشون بده”. جنگو هم خودش کدهای HTML لازم رو تولید میکنه.
- توی فایل
- مرحله ۳: اطلاعات کاربر رو میگیری و بررسی میکنی (دوباره توی ویو)
- وقتی کاربر فرم رو پر کرد و دکمه “ارسال” رو زد، اطلاعاتش برمیگرده به ویوی تو.
- جنگو با همون نقشهای که قبلاً کشیده بودی، اطلاعات رو چک میکنه (همون اعتبارسنجی).
- اگه همه چی اوکی بود، میتونی از دادههای تمیز و مرتب شده استفاده کنی (مثلاً ذخیرشون کنی یا باهاشون یه کاری انجام بدی).
- اگه مشکلی داشت (مثلاً ایمیل اشتباه بود)، جنگو خودش به کاربر میگه کجای کارش ایراد داره.
بریم یه مثال خیلی کوچولو ببینیم: یه فرم تماس ساده
فرض کن میخوایم یه فرم تماس داشته باشیم که اسم، ایمیل و پیام کاربر رو بگیره.
۱. اول نقشه فرم رو میکشیم (myapp/forms.py):
این فایل رو خودت باید بسازی.
# myapp/forms.py
from django import forms
class ContactForm(forms.Form):
# یه فیلد برای اسم، از نوع متن معمولی (CharField)
name = forms.CharField(label='اسمت چیه؟', max_length=100, required=True)
# یه فیلد برای ایمیل، از نوع ایمیل (EmailField)
# خودش چک میکنه فرمت ایمیل درست باشه
email = forms.EmailField(label='ایمیلت چیه؟', required=True)
# یه فیلد برای پیام، از نوع متن طولانی (CharField با widget مخصوص)
message = forms.CharField(label='پیامت چیه؟', widget=forms.Textarea, required=False)
forms.CharField: برای متنهای کوتاه.forms.EmailField: برای ایمیل (خودش چک میکنه شبیه ایمیل باشه).forms.Textarea: یه جعبه بزرگتر برای نوشتن متنهای طولانی.label: متنی که کنار فیلد به کاربر نشون داده میشه.max_length: حداکثر طول مجاز متن.required=True: یعنی این فیلد حتما باید پر بشه. اگهFalseباشه، کاربر میتونه خالی بذارتش.
۲. حالا فرم رو توی ویو آماده میکنیم و به تمپلیت میدیم (myapp/views.py):
# myapp/views.py
from django.shortcuts import render
from .forms import ContactForm # فرمی که ساختیم رو وارد میکنیم
def contact_page_view(request):
# یه نمونه از فرممون میسازیم
form = ContactForm()
# فرم رو به تمپلیت پاس میدیم
return render(request, 'myapp/contact_page.html', {'form_in_template': form})
۳. فرم رو توی تمپلیت نشون میدیم (myapp/templates/myapp/contact_page.html):
این فایل HTML رو هم باید بسازی.
<!-- myapp/templates/myapp/contact_page.html -->
<h1>تماس با ما</h1>
<form method="POST"> <!-- متد رو POST میذاریم چون میخوایم اطلاعات بفرستیم -->
{% csrf_token %} <!-- این خیلی مهمه! برای امنیت حتماً بذارش -->
{{ form_in_template.as_p }} <!-- این جادوی جنگو! خودش فیلدها رو با برچسبهاشون میسازه -->
<!-- .as_p یعنی هر فیلد توی یه تگ <p> بره -->
<!-- میتونی از .as_table (توی جدول) یا .as_ul (لیست) هم استفاده کنی -->
<button type="submit">بفرست!</button>
</form>
{% csrf_token %}: این یه کد امنیتیه که جنگو اضافه میکنه تا مطمئن بشه درخواست از سایت خودت اومده. همیشه یادت باشه بذاریش.{{ form_in_template.as_p }}: جنگو میاد و تمام فیلدهایی که تویContactFormتعریف کردی (اسم، ایمیل، پیام) رو اینجا به صورت HTML قشنگ میچینه.
۴. حالا اطلاعات ارسالی کاربر رو توی ویو میگیریم و بررسی میکنیم (myapp/views.py رو کاملتر میکنیم):
# myapp/views.py
from django.shortcuts import render
from django.http import HttpResponse # برای نشون دادن یه پیام ساده
from .forms import ContactForm
def contact_page_view(request):
if request.method == 'POST': # اگه کاربر فرم رو پر کرده و دکمه "بفرست!" رو زده
form = ContactForm(request.POST) # فرم رو با اطلاعاتی که کاربر فرستاده پر میکنیم
if form.is_valid(): # آیا اطلاعاتی که کاربر وارد کرده، طبق نقشهمون درسته؟
# اگه درسته، اطلاعات تمیز شده رو از form.cleaned_data برمیداریم
name = form.cleaned_data['name']
email = form.cleaned_data['email']
message = form.cleaned_data['message']
# اینجا میتونی با اطلاعات یه کاری بکنی
# مثلا ایمیل بفرستی یا تو پایگاه داده ذخیره کنی
print(f"اسم: {name}, ایمیل: {email}, پیام: {message}") # فعلا فقط چاپشون میکنیم
return HttpResponse(f"<h2>مرسی {name}! پیامت رو گرفتیم.</h2>")
# اگه فرم معتبر نبود (مثلا ایمیل اشتباه بود یا فیلد ضروری خالی بود)،
# جنگو خودش خطاها رو به فرم اضافه میکنه و همون فرم با خطاها به کاربر نشون داده میشه (خط پایین)
else: # اگه درخواست GET بود (یعنی کاربر تازه صفحه رو باز کرده و هنوز فرمی نفرستاده)
form = ContactForm() # یه فرم خالی بهش نشون میدیم
# در هر صورت (چه بار اول باشه، چه فرم نامعتبر باشه)، فرم رو به تمپلیت پاس میدیم
return render(request, 'myapp/contact_page.html', {'form_in_template': form})
اعتبارسنجی چی شد پس؟
دیدی چقدر راحت بود؟
- وقتی توی
forms.pyبرای فیلد ایمیل ازforms.EmailFieldاستفاده کردی، جنگو خودش چک میکنه که کاربر یه چیزی شبیهuser@example.comوارد کرده باشه. - وقتی
required=Trueگذاشتی، اگه کاربر اون فیلد رو خالی بفرسته،form.is_valid()مقدارFalse(نادرست) برمیگردونه. - اگه
form.is_valid()نادرست باشه و تو دوباره همون فرم رو به تمپلیت بدی (render(request, '...', {'form_in_template': form}))، جنگو خودش پیامهای خطا رو کنار فیلدهای مربوطه نشون میده! مثلاً زیر فیلد ایمیل مینویسه “آدرس ایمیل معتبر وارد کنید.”
خلاصه و نکتههای خیلی مهم:
- فرم جنگو خیلی کار راه اندازه: ساختن فرم، چک کردن اطلاعات، امنیت.
- همیشه یه فایل
forms.pyتو اپلیکیشنت درست کن و کلاسهای فرمت رو اونجا تعریف کن. - توی ویو، اول چک کن درخواست
POSTهست یاGET.- اگه
GETبود (کاربر تازه اومده): یه فرم خالی (form = MyForm()) بساز و بده به تمپلیت. - اگه
POSTبود (کاربر اطلاعات فرستاده): اطلاعات رو بده به فرم (form = MyForm(request.POST)) و باform.is_valid()چکش کن.
- اگه
- اگه
form.is_valid()درست بود، ازform.cleaned_dataاستفاده کن تا به اطلاعات تمیز شده دسترسی داشته باشی. - حتماً
{% csrf_token %}رو توی تگ<form>در تمپلیت بذار. فراموشش نکن!
فرم مدلدار (ModelForm): فرمی که از روی مدل ساخته میشه!
یادته گفتیم توی myapp/models.py مدلهامون رو تعریف میکنیم؟ مثلاً یه مدل Article (مقاله) داریم که عنوان، محتوا، تاریخ انتشار و… داره.
# myapp/models.py
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=200, verbose_name="عنوان مقاله")
content = models.TextField(verbose_name="محتوای مقاله")
pub_date = models.DateTimeField(auto_now_add=True, verbose_name="تاریخ انتشار")
author_email = models.EmailField(blank=True, null=True, verbose_name="ایمیل نویسنده (اختیاری)")
def __str__(self):
return self.title
حالا فرض کن میخوای یه فرم بسازی که کاربر بتونه باهاش یه مقاله جدید ایجاد کنه یا یه مقاله موجود رو ویرایش کنه.
با فرم معمولی (forms.Form) که توضیح دادم، باید دونه دونه فیلدها رو دوباره توی forms.py تعریف کنی: یه CharField برای عنوان، یه TextField (یا CharField با widget=Textarea) برای محتوا و…
این یه کم تکراریه، نه؟ چون تو قبلاً همین اطلاعات (نوع فیلد، حداکثر طول و…) رو توی models.py گفتی!
اینجا ModelForm وارد میشه!
ModelForm مثل یه دستیار زرنگه که میگه: “هی، تو که قبلاً توی models.py گفتی مقاله چه شکلیه، دیگه چرا دوباره بهم میگی؟ بده مدل Article رو، خودم از روش یه فرم خوشگل برات میسازم!”
مزایای ModelForm:
- کد کمتر (DRY – Don’t Repeat Yourself): لازم نیست فیلدهایی که توی مدل تعریف کردی رو دوباره توی فرم تعریف کنی. جنگو خودش این کارو میکنه.
- اتصال خودکار به مدل: فیلدهای فرم دقیقاً با فیلدهای مدل مپ میشن.
- اعتبارسنجی خودکار از مدل: خیلی از قواعد اعتبارسنجی که توی مدل گذاشتی (مثلاً
max_length، یا اینکه یه فیلد میتونه خالی باشه (blank=True) یا نه) خود به خود توی فرم هم اعمال میشه. - ذخیره سازی راحت: مهمتر از همه،
ModelFormیه متد جادویی به اسمsave()داره که اطلاعات فرم رو خیلی راحت توی پایگاه داده (برای مدل مربوطه) ذخیره یا بهروزرسانی میکنه.
چطوری از ModelForm استفاده کنیم؟
۱. تعریف ModelForm (توی myapp/forms.py):
فرض کنیم همون مدل Article بالا رو داریم. حالا توی forms.py اینطوری یه فرم براش میسازیم:
# myapp/forms.py
from django import forms
from .models import Article # مدل Article رو وارد میکنیم
class ArticleForm(forms.ModelForm): # به جای forms.Form از forms.ModelForm ارثبری میکنه
class Meta:
model = Article # به جنگو میگیم این فرم برای کدوم مدل هست
fields = ['title', 'content', 'author_email'] # کدوم فیلدهای مدل Article رو میخوایم توی فرم داشته باشیم
# یا میتونیم بگیم همه فیلدها رو بیار (به جز اونایی که خودکار پر میشن مثل id یا pub_date اگه auto_now_add باشه)
# fields = '__all__'
# یا میتونیم بگیم همه رو بیار به جز چندتا:
# exclude = ['pub_date'] # مثلاً pub_date رو نمیخوایم کاربر وارد کنه چون خودکار پر میشه
class Meta: یه کلاس داخلیه که تنظیماتModelFormرو توش میذاریم.model = Article: خیلی مهم! به جنگو میگه این فرم به مدلArticleوصله.fields = ['title', 'content', 'author_email']: یه لیست از اسم فیلدهایی که میخوایم توی فرم باشن. جنگو میره توی مدلArticleو برای این فیلدها، فیلد فرم مناسب رو میسازه.- مثلاً برای
title(که توی مدلCharFieldبود)، یهforms.CharFieldمیسازه. - برای
content(که توی مدلTextFieldبود)، یهforms.CharFieldباwidget=forms.Textareaمیسازه. verbose_nameهایی هم که توی مدل تعریف کردی (مثل “عنوان مقاله”) خود به خود به عنوانlabelفیلدهای فرم استفاده میشن!
- مثلاً برای
۲. استفاده از ModelForm توی ویو (myapp/views.py):
حالا استفاده از این فرم توی ویو خیلی شبیه فرم معمولیه، با یه فرق خیلی باحال موقع ذخیره کردن.
الف) برای ایجاد یه مقاله جدید:
# myapp/views.py
from django.shortcuts import render, redirect
# from .models import Article # دیگه لازم نیست اینجا مستقیم مدل رو برای ذخیره بیاریم، فرم خودش بلده
from .forms import ArticleForm # فرم مدلدارمون
def create_article_view(request):
if request.method == 'POST':
form = ArticleForm(request.POST, request.FILES or None) # اگه فایل هم آپلود میکنیم request.FILES رو هم میدیم
if form.is_valid():
form.save() # <<<<----- جادو اینجاست!
# خودش یه Article جدید میسازه و تو پایگاه داده ذخیره میکنه
# میتونیم قبل از ذخیره نهایی، یه سری تغییرات بدیم:
# instance = form.save(commit=False) # هنوز تو دیتابیس ذخیره نشده
# instance.some_other_field = 'some_value' # مثلا فیلدی که تو فرم نبوده رو پر کنیم
# instance.save() # حالا ذخیره نهایی
return redirect('some_success_url_name') # مثلاً به لیست مقالات ریدایرکت میکنیم
else: # درخواست GET
form = ArticleForm()
return render(request, 'myapp/article_form_template.html', {'form_key': form})
form.save(): این متد کار اصلی رو انجام میده. اگه فرم برای ایجاد یه رکورد جدید باشه، یه نمونه جدید از مدلArticleمیسازه، اطلاعات فرم رو توش میریزه و بعدش توی پایگاه داده ذخیره میکنه. همه اینا فقط با یه خطform.save()!form.save(commit=False): گاهی وقتا میخوای قبل از اینکه اطلاعات واقعاً توی پایگاه داده ذخیره بشه، یه سری کارهای دیگه انجام بدی (مثلاً یه فیلدی که توی فرم نبوده رو بر اساس کاربر لاگین کرده پر کنی). در این حالت ازcommit=Falseاستفاده میکنی. این یه نمونه از مدل رو بهت برمیگردونه ولی هنوز تو دیتابیس ذخیرهاش نکرده. بعد از اینکه تغییراتت رو اعمال کردی، باید خودت متدsave()اون نمونه رو صدا بزنی (مثلاًinstance.save()).
ب) برای ویرایش یه مقاله موجود:
# myapp/views.py
from django.shortcuts import render, redirect, get_object_or_404
from .models import Article # اینجا مدل رو برای گرفتن نمونه اولیه لازم داریم
from .forms import ArticleForm
def update_article_view(request, article_id):
article_instance = get_object_or_404(Article, pk=article_id) # مقالهای که میخوایم ویرایش کنیم رو پیدا میکنیم
if request.method == 'POST':
form = ArticleForm(request.POST, request.FILES or None, instance=article_instance) # <<--- مهم! instance رو پاس میدیم
if form.is_valid():
form.save() # خودش میفهمه که باید این instance رو آپدیت کنه، نه یکی جدید بسازه
return redirect('article_detail_url_name', pk=article_instance.id) # مثلا به صفحه جزئیات همون مقاله
else: # درخواست GET
form = ArticleForm(instance=article_instance) # فرم رو با اطلاعات مقاله موجود پر میکنیم
return render(request, 'myapp/article_form_template.html', {'form_key': form, 'article': article_instance})
- وقتی میخوای یه رکورد موجود رو ویرایش کنی، باید اون نمونه (instance) از مدل رو به
ModelFormپاس بدی:ArticleForm(instance=article_instance)برای نمایش اولیه فرم با اطلاعات قبلی، وArticleForm(request.POST, instance=article_instance)موقع پردازش اطلاعات ارسالی. - اینطوری
form.save()میفهمه که نباید یه رکورد جدید بسازه، بلکه باید همونarticle_instanceرو با اطلاعات جدید آپدیت کنه.
۳. تمپلیت (myapp/templates/myapp/article_form_template.html):
تمپلیت دقیقاً مثل فرم معمولی (forms.Form) کار میکنه. هیچ فرقی نداره:
<!-- myapp/templates/myapp/article_form_template.html -->
{% if article %} <!-- اگر در حالت ویرایش هستیم -->
<h1>ویرایش مقاله: {{ article.title }}</h1>
{% else %}
<h1>ایجاد مقاله جدید</h1>
{% endif %}
<form method="POST" enctype="multipart/form-data"> <!-- اگه فایل آپلود میکنید enctype رو بذارید -->
{% csrf_token %}
{{ form_key.as_p }} <!-- یا .as_table یا .as_ul -->
<button type="submit">ذخیره</button>
</form>
شخصیسازی فیلدهای ModelForm:
حتی با اینکه ModelForm فیلدها رو خودکار میسازه، تو هنوز هم میتونی اونها رو شخصیسازی کنی، دقیقاً مثل forms.Form.
مثلاً اگه میخوای برای فیلد content یه widget خاص بذاری یا label پیشفرضش رو عوض کنی:
شروع کد:
myapp/forms.py
from django import forms
from .models import Article
class ArticleForm(forms.ModelForm):
# میتونی یه فیلد رو مستقیماً اینجا بازنویسی کنی
# title = forms.CharField(label=”عنوان اصلی مقاله”, widget=forms.TextInput(attrs={‘class’: ‘my-custom-input’}))
class Meta:
model = Article
fields = ['title', 'content', 'author_email']
widgets = {
'content': forms.Textarea(attrs={'rows': 15, 'cols': 70, 'placeholder': 'محتوای مقاله خود را اینجا بنویسید...'}),
'author_email': forms.EmailInput(attrs={'placeholder': 'test@example.com'})
}
labels = {
'author_email': 'ایمیل شما (برای تماس، اختیاری)'
}
help_texts = {
'title': 'یک عنوان جذاب برای مقالهتان انتخاب کنید.'
}
# error_messages هم میشه تعریف کرد
پایان کد
- میتونی مستقیم یه فیلد رو توی کلاس
ArticleForm(خارج ازMeta) تعریف کنی تا رفتار پیشفرضش رو بازنویسی کنی. - یا از دیکشنریهای
widgets،labels،help_textsوerror_messagesتویMetaبرای تغییر خصوصیات فیلدهای خودکار ساخته شده استفاده کنی.
چه زمانی از forms.Form و چه زمانی از forms.ModelForm استفاده کنیم؟
- از
ModelFormاستفاده کن: هر وقت فرمت مستقیماً برای ایجاد یا ویرایش یه نمونه از مدلهای پایگاه دادهات (همونایی که توmodels.pyهستن) به کار میره. این حالت خیلی رایجه. - از
forms.Form(فرم معمولی) استفاده کن:- وقتی فرمت به هیچ مدل خاصی توی دیتابیس ربط نداره. مثلاً یه فرم جستجو، یه فرم تماس که فقط ایمیل میفرسته، یا یه فرمی که اطلاعات پیچیدهای از کاربر میگیره ولی قرار نیست مستقیم توی یه مدل ذخیره بشه.
- وقتی میخوای کنترل خیلی خیلی دقیقی روی تک تک جزئیات فرم داشته باشی و خودکارسازی
ModelFormیه کم دست و پات رو میبنده (که البته کمتر پیش میاد).