~/icsd.ir — bash
SYSTEM_ONLINE

📝 فصل ۵: فرم‌ها

فرم‌ها قلب تعامل کاربر با اپلیکیشن هستن. جنگو خیلی هوشمندانه باهاشون برخورد می‌کنه!

فرم‌ها قلب تعامل کاربر با اپلیکیشن هستن. جنگو خیلی هوشمندانه باهاشون برخورد می‌کنه!

🤔 چرا Form جنگو؟

می‌تونی فرم رو به‌صورت دستی توی HTML بنویسی و توی View هم با request.POST کار کنی. ولی فرم‌های جنگو خیلی چیزا رو خودکار می‌کنن:

  • تولید HTML خودکار – دیگه نیاز به نوشتن دستی input ها نیست
  • اعتبارسنجی قدرتمند – چک کردن داده‌ها قبل از ذخیره
  • پیام‌های خطا – نمایش خودکار خطاها
  • امنیت – محافظت در برابر XSS
  • تمیز کردن داده – تبدیل داده به نوع درست

🏗️ ساخت اولین فرم

یه فایل جدید به اسم forms.py داخل اپلیکیشن می‌سازیم:

contact/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

contact/views.py
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

contact/form.html
<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>

نمایش دستی فرم (کنترل بیشتر)

contact/form.html
<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

مثال جامع از همه فیلدها

forms.py
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

forms.py
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

وقتی نیاز داری چند فیلد رو با هم چک کنی:

forms.py
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)

forms.py
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 – فرم از روی مدل

این عالی‌ترین قابلیت فرم‌های جنگوست! اگه فرمت رو از روی مدل می‌خوای، نیازی نیست همه فیلدها رو دستی بنویسی.

products/forms.py
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

products/views.py
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})

قدرت ModelForm

با چند خط کد، فرم کاملی برای ساخت و ویرایش مدل داری! اعتبارسنجی، ذخیره در دیتابیس، همه‌چیز خودکار.

📎 آپلود فایل

تنظیمات

myproject/settings.py
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
myproject/urls.py
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)

مدل با فیلد فایل

products/models.py
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)

🎯 مثال‌های کاربردی

آسان

مثال ۱: فرم ساده تماس با ما

forms.py
class ContactForm(forms.Form):
    name = forms.CharField(max_length=100, label='نام')
    email = forms.EmailField(label='ایمیل')
    message = forms.CharField(widget=forms.Textarea, label='پیام')

متوسط

مثال ۲: فرم نظرات با ModelForm

comments/models.py
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)
comments/forms.py
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
            }),
        }
comments/views.py
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
    })

پیشرفته

مثال ۳: فرم با اعتبارسنجی پیشرفته – ثبت‌نام

accounts/forms.py
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 داشته باشه. هر ردیف شامل محصول و تعداد. اعتبارسنجی کن که کل سفارش بیشتر از ۱۰ آیتم نباشه.

نمایش سایت

رنگ سایت
حالت نمایش
اندازهٔ متن
خوانایی

این تنظیمات فقط روی مرورگر شما ذخیره می‌شود.