📝 فصل ۵: فرمها
فرمها قلب تعامل کاربر با اپلیکیشن هستن. جنگو خیلی هوشمندانه باهاشون برخورد میکنه!
فرمها قلب تعامل کاربر با اپلیکیشن هستن. جنگو خیلی هوشمندانه باهاشون برخورد میکنه!
📑 فهرست این فصل
🤔 چرا Form جنگو؟
میتونی فرم رو بهصورت دستی توی HTML بنویسی و توی View هم با request.POST کار کنی. ولی فرمهای جنگو خیلی چیزا رو خودکار میکنن:
- تولید HTML خودکار – دیگه نیاز به نوشتن دستی input ها نیست
- اعتبارسنجی قدرتمند – چک کردن دادهها قبل از ذخیره
- پیامهای خطا – نمایش خودکار خطاها
- امنیت – محافظت در برابر XSS
- تمیز کردن داده – تبدیل داده به نوع درست
🏗️ ساخت اولین فرم
یه فایل جدید به اسم forms.py داخل اپلیکیشن میسازیم:
from django import forms
class ContactForm(forms.Form):
name = forms.CharField(
max_length=100,
label='نام و نام خانوادگی',
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'نام خود را وارد کنید'
})
)
email = forms.EmailField(
label='ایمیل',
widget=forms.EmailInput(attrs={'class': 'form-control'})
)
subject = forms.CharField(
max_length=200,
label='موضوع'
)
message = forms.CharField(
label='پیام',
widget=forms.Textarea(attrs={
'class': 'form-control',
'rows': 5
})
)
استفاده در View
from django.shortcuts import render, redirect
from django.contrib import messages
from .forms import ContactForm
def contact(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
# دسترسی به دادههای تمیز شده
name = form.cleaned_data['name']
email = form.cleaned_data['email']
subject = form.cleaned_data['subject']
message = form.cleaned_data['message']
# کارهای مورد نظر (ذخیره، ارسال ایمیل و...)
messages.success(request, 'پیام شما با موفقیت ارسال شد!')
return redirect('contact')
else:
form = ContactForm() # فرم خالی
return render(request, 'contact/form.html', {'form': form})
نمایش در Template
<form method="POST">
{% csrf_token %}
{# روش ۱: نمایش کامل خودکار #}
{{ form.as_p }} {# هر فیلد در یک <p> #}
{{ form.as_table }} {# در جدول #}
{{ form.as_div }} {# در div - جدید #}
<button type="submit" class="btn btn-primary">ارسال</button>
</form>
نمایش دستی فرم (کنترل بیشتر)
<form method="POST" class="row g-3">
{% csrf_token %}
{% for field in form %}
<div class="col-md-6">
<label for="{{ field.id_for_label }}" class="form-label">
{{ field.label }}
{% if field.field.required %}
<span class="text-danger">*</span>
{% endif %}
</label>
{{ field }}
{% if field.help_text %}
<small class="form-text text-muted">{{ field.help_text }}</small>
{% endif %}
{% if field.errors %}
<div class="invalid-feedback d-block">
{{ field.errors|join:", " }}
</div>
{% endif %}
</div>
{% endfor %}
<div class="col-12">
<button type="submit" class="btn btn-primary">ارسال</button>
</div>
</form>
📋 انواع فیلدهای فرم
| فیلد | کاربرد |
|---|---|
CharField |
متن کوتاه |
EmailField |
ایمیل (با اعتبارسنجی) |
IntegerField |
عدد صحیح |
FloatField |
عدد اعشاری |
BooleanField |
چکباکس |
DateField |
تاریخ |
DateTimeField |
تاریخ و زمان |
FileField |
آپلود فایل |
ImageField |
آپلود تصویر |
ChoiceField |
انتخاب از لیست |
MultipleChoiceField |
چند انتخاب |
URLField |
آدرس URL |
مثال جامع از همه فیلدها
from django import forms
class RegisterForm(forms.Form):
GENDER_CHOICES = [
('male', 'مرد'),
('female', 'زن'),
]
INTEREST_CHOICES = [
('tech', 'تکنولوژی'),
('art', 'هنر'),
('sport', 'ورزش'),
]
full_name = forms.CharField(max_length=100, label='نام کامل')
email = forms.EmailField(label='ایمیل')
password = forms.CharField(
widget=forms.PasswordInput, # ← input از نوع password
label='رمز عبور',
min_length=8
)
age = forms.IntegerField(min_value=18, max_value=100, label='سن')
birth_date = forms.DateField(
widget=forms.DateInput(attrs={'type': 'date'}),
label='تاریخ تولد'
)
gender = forms.ChoiceField(
choices=GENDER_CHOICES,
widget=forms.RadioSelect, # ← دکمههای رادیویی
label='جنسیت'
)
interests = forms.MultipleChoiceField(
choices=INTEREST_CHOICES,
widget=forms.CheckboxSelectMultiple, # ← چندتا چکباکس
label='علایق'
)
avatar = forms.ImageField(required=False, label='تصویر پروفایل')
bio = forms.CharField(
widget=forms.Textarea(attrs={'rows': 4}),
required=False,
label='بیوگرافی'
)
accept_terms = forms.BooleanField(label='شرایط را میپذیرم')
✅ اعتبارسنجی
جنگو سه روش برای اعتبارسنجی دادهها داره:
۱. اعتبارسنجی پیشفرض
هر فیلد خودش چکهای پایه رو انجام میده (مثلاً EmailField فرمت ایمیل رو چک میکنه).
۲. اعتبارسنجی فیلد – متد clean_FIELDNAME
class RegisterForm(forms.Form):
username = forms.CharField(max_length=30)
email = forms.EmailField()
age = forms.IntegerField()
def clean_username(self):
username = self.cleaned_data['username']
# نباید فقط عدد باشه
if username.isdigit():
raise forms.ValidationError('نام کاربری نمیتواند فقط عدد باشد')
# یکتا بودن (مثال - بهتره از ModelForm استفاده شه)
from django.contrib.auth.models import User
if User.objects.filter(username=username).exists():
raise forms.ValidationError('این نام کاربری قبلاً ثبت شده')
return username
def clean_age(self):
age = self.cleaned_data['age']
if age < 18:
raise forms.ValidationError('سن باید بیشتر از ۱۸ باشد')
return age
۳. اعتبارسنجی کلی – متد clean
وقتی نیاز داری چند فیلد رو با هم چک کنی:
class RegisterForm(forms.Form):
password = forms.CharField(widget=forms.PasswordInput)
confirm_password = forms.CharField(widget=forms.PasswordInput)
def clean(self):
cleaned_data = super().clean()
password = cleaned_data.get('password')
confirm_password = cleaned_data.get('confirm_password')
if password and confirm_password:
if password != confirm_password:
raise forms.ValidationError(
'رمز عبور و تکرار آن یکسان نیستند'
)
return cleaned_data
اعتبارسنجهای آماده (Validators)
from django import forms
from django.core.validators import (
MinLengthValidator,
MaxLengthValidator,
RegexValidator,
MinValueValidator,
MaxValueValidator
)
class MyForm(forms.Form):
# شماره موبایل ایرانی
phone = forms.CharField(
validators=[
RegexValidator(
regex=r'^09d{9}$',
message='شماره موبایل معتبر نیست (مثال: 09121234567)'
)
]
)
# کد ملی ۱۰ رقمی
national_code = forms.CharField(
validators=[
RegexValidator(r'^d{10}$', message='کد ملی باید ۱۰ رقم باشد')
]
)
# عدد بین ۱ تا ۱۰۰
rating = forms.IntegerField(
validators=[MinValueValidator(1), MaxValueValidator(100)]
)
🎁 ModelForm – فرم از روی مدل
این عالیترین قابلیت فرمهای جنگوست! اگه فرمت رو از روی مدل میخوای، نیازی نیست همه فیلدها رو دستی بنویسی.
from django import forms
from .models import Product
class ProductForm(forms.ModelForm):
class Meta:
model = Product
fields = ['name', 'description', 'price', 'stock', 'category']
# یا
# fields = '__all__' # همه فیلدها
# exclude = ['created_at'] # همه بهجز
labels = {
'name': 'نام محصول',
'description': 'توضیحات',
'price': 'قیمت (تومان)',
'stock': 'موجودی',
'category': 'دستهبندی',
}
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control'}),
'description': forms.Textarea(attrs={
'class': 'form-control',
'rows': 5
}),
'price': forms.NumberInput(attrs={'class': 'form-control'}),
'stock': forms.NumberInput(attrs={'class': 'form-control'}),
'category': forms.Select(attrs={'class': 'form-select'}),
}
help_texts = {
'name': 'حداکثر ۲۰۰ کاراکتر',
}
error_messages = {
'name': {
'required': 'نام محصول الزامی است',
'max_length': 'نام نباید بیشتر از ۲۰۰ کاراکتر باشد',
},
}
استفاده در View
from django.shortcuts import render, redirect, get_object_or_404
from .models import Product
from .forms import ProductForm
# ساخت محصول جدید
def product_create(request):
if request.method == 'POST':
form = ProductForm(request.POST, request.FILES)
if form.is_valid():
product = form.save() # خودکار ذخیره میشه!
return redirect('products:detail', pk=product.id)
else:
form = ProductForm()
return render(request, 'products/form.html', {'form': form})
# ویرایش محصول
def product_update(request, pk):
product = get_object_or_404(Product, pk=pk)
if request.method == 'POST':
# ← instance رو پاس میدی تا دادههای قبلی پر بشن
form = ProductForm(request.POST, request.FILES, instance=product)
if form.is_valid():
form.save()
return redirect('products:detail', pk=product.id)
else:
form = ProductForm(instance=product)
return render(request, 'products/form.html', {'form': form})
با چند خط کد، فرم کاملی برای ساخت و ویرایش مدل داری! اعتبارسنجی، ذخیره در دیتابیس، همهچیز خودکار.
📎 آپلود فایل
تنظیمات
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
# ...
]
# فقط در حالت توسعه (DEBUG=True)
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
مدل با فیلد فایل
class Product(models.Model):
name = models.CharField(max_length=200)
image = models.ImageField(upload_to='products/%Y/%m/', blank=True)
pdf_catalog = models.FileField(upload_to='catalogs/', blank=True)
فرم HTML با فایل
<!-- enctype برای فایل ضروریه! -->
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">ذخیره</button>
</form>
در View هم باید request.FILES رو به فرم پاس بدی:
form = ProductForm(request.POST, request.FILES)
🎯 مثالهای کاربردی
مثال ۱: فرم ساده تماس با ما
class ContactForm(forms.Form):
name = forms.CharField(max_length=100, label='نام')
email = forms.EmailField(label='ایمیل')
message = forms.CharField(widget=forms.Textarea, label='پیام')
مثال ۲: فرم نظرات با ModelForm
class Comment(models.Model):
product = models.ForeignKey('products.Product', on_delete=models.CASCADE)
name = models.CharField(max_length=100)
email = models.EmailField()
text = models.TextField()
rating = models.IntegerField(default=5)
created_at = models.DateTimeField(auto_now_add=True)
is_approved = models.BooleanField(default=False)
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['name', 'email', 'text', 'rating']
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control'}),
'email': forms.EmailInput(attrs={'class': 'form-control'}),
'text': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}),
'rating': forms.NumberInput(attrs={
'class': 'form-control',
'min': 1,
'max': 5
}),
}
def add_comment(request, product_id):
product = get_object_or_404(Product, id=product_id)
if request.method == 'POST':
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False) # هنوز ذخیره نکن
comment.product = product # محصول رو ست کن
comment.save() # حالا ذخیره
messages.success(request, 'نظر شما ثبت شد')
return redirect('products:detail', pk=product.id)
else:
form = CommentForm()
return render(request, 'comments/form.html', {
'form': form,
'product': product
})
مثال ۳: فرم با اعتبارسنجی پیشرفته – ثبتنام
from django import forms
from django.contrib.auth.models import User
from django.core.validators import RegexValidator
import re
class RegisterForm(forms.Form):
username = forms.CharField(
max_length=30,
label='نام کاربری',
validators=[
RegexValidator(
regex=r'^[a-zA-Z0-9_]+$',
message='فقط حروف انگلیسی، اعداد و _'
)
]
)
email = forms.EmailField(label='ایمیل')
phone = forms.CharField(
max_length=11,
label='موبایل',
validators=[
RegexValidator(r'^09d{9}$', message='موبایل معتبر نیست')
]
)
password = forms.CharField(
widget=forms.PasswordInput,
min_length=8,
label='رمز عبور'
)
confirm_password = forms.CharField(
widget=forms.PasswordInput,
label='تکرار رمز عبور'
)
def clean_username(self):
username = self.cleaned_data['username']
if User.objects.filter(username=username).exists():
raise forms.ValidationError('این نام کاربری قبلاً ثبت شده')
if len(username) < 4:
raise forms.ValidationError('حداقل ۴ کاراکتر')
return username
def clean_email(self):
email = self.cleaned_data['email']
if User.objects.filter(email=email).exists():
raise forms.ValidationError('این ایمیل قبلاً ثبت شده')
return email
def clean_password(self):
password = self.cleaned_data['password']
# حداقل یک حرف بزرگ، یک عدد
if not re.search(r'[A-Z]', password):
raise forms.ValidationError('باید حداقل یک حرف بزرگ داشته باشد')
if not re.search(r'd', password):
raise forms.ValidationError('باید حداقل یک عدد داشته باشد')
return password
def clean(self):
cleaned = super().clean()
password = cleaned.get('password')
confirm = cleaned.get('confirm_password')
if password and confirm and password != confirm:
raise forms.ValidationError('رمز و تکرارش یکی نیستند')
return cleaned
def save(self):
# متد سفارشی برای ذخیره
user = User.objects.create_user(
username=self.cleaned_data['username'],
email=self.cleaned_data['email'],
password=self.cleaned_data['password'],
)
return user
📝 تمرینها
تمرین ۱
یه فرم خبرنامه با یک فیلد ایمیل بساز. وقتی ثبت شد، پیام موفقیت نمایش بده.
تمرین ۲
یه ModelForm برای پست وبلاگ بساز که شامل عنوان، محتوا و تصویر باشه. آپلود تصویر هم پشتیبانی شه.
تمرین ۳
یه فرم خرید بساز که چندین محصول از یک FormSet داشته باشه. هر ردیف شامل محصول و تعداد. اعتبارسنجی کن که کل سفارش بیشتر از ۱۰ آیتم نباشه.