~/icsd.ir — bash
SYSTEM_ONLINE

🗄️ فصل ۲: مدل‌ها و دیتابیس

تو این فصل یاد می‌گیری چطور با ORM جنگو کار کنی - بدون نوشتن حتی یک خط SQL!

تو این فصل یاد می‌گیری چطور با ORM جنگو کار کنی – بدون نوشتن حتی یک خط SQL!

🤔 ORM چیه و چرا مهمه؟

ORM مخفف Object-Relational Mapping هست. به زبون ساده: به جای اینکه با SQL با دیتابیس حرف بزنی، با کلاس‌ها و آبجکت‌های پایتون باهاش کار می‌کنی!

❌ بدون ORM (SQL خام)

SQL
SELECT * FROM products 
WHERE price > 1000 
ORDER BY created_at DESC 
LIMIT 10;

✅ با ORM جنگو

Python
Product.objects.filter(
    price__gt=1000
).order_by('-created_at')[:10]

💡

مزایای ORM

۱. تغییر دیتابیس راحته (MySQL به PostgreSQL و…)
۲. امن در برابر SQL Injection
۳. کد پایتونی، قابل خوندن و دیباگ راحت‌تر
۴. اعتبارسنجی خودکار داده‌ها

🏗️ ساخت اولین مدل

بیا یه مدل ساده برای محصولات بسازیم. توی فایل blog/models.py ولی این بار اپ products:

products/models.py
from django.db import models

class Product(models.Model):
    # نام محصول - حداکثر 200 کاراکتر
    name = models.CharField(max_length=200)
    
    # توضیحات - متن طولانی
    description = models.TextField()
    
    # قیمت - عدد اعشاری
    price = models.DecimalField(max_digits=10, decimal_places=2)
    
    # موجودی - عدد صحیح
    stock = models.IntegerField(default=0)
    
    # تاریخ ایجاد - خودکار پر میشه
    created_at = models.DateTimeField(auto_now_add=True)
    
    # تاریخ آخرین ویرایش
    updated_at = models.DateTimeField(auto_now=True)
    
    # وضعیت فعال/غیرفعال
    is_active = models.BooleanField(default=True)
    
    def __str__(self):
        return self.name

چی شد؟

یه کلاس ساده پایتون نوشتی، ولی جنگو این رو به یک جدول دیتابیس با ۷ ستون تبدیل می‌کنه! متد __str__ هم باعث میشه وقتی آبجکت محصول رو چاپ می‌کنی، اسمش نمایش داده بشه.

📋 انواع فیلدهای پرکاربرد

نوع فیلد کاربرد مثال
CharField متن کوتاه نام، عنوان
TextField متن طولانی توضیحات، محتوا
IntegerField عدد صحیح تعداد، سن
DecimalField عدد اعشاری دقیق قیمت، وزن
FloatField عدد اعشاری میانگین
BooleanField درست/نادرست فعال، تأیید شده
DateField تاریخ تاریخ تولد
DateTimeField تاریخ و زمان زمان ایجاد
EmailField ایمیل (با اعتبارسنجی) آدرس ایمیل
URLField آدرس وب لینک سایت
SlugField متن مناسب URL my-product-name
ImageField تصویر عکس محصول
FileField فایل PDF، اکسل

پارامترهای مهم فیلدها

پارامترهای رایج
# null=True : می‌تواند در دیتابیس NULL باشد (برای داده‌های غیر متنی)
# blank=True : می‌تواند در فرم خالی باشد (برای ولیدیشن)
# default : مقدار پیش‌فرض
# unique=True : یکتا باشد (مثل ایمیل)
# choices : از بین گزینه‌های خاص انتخاب
# verbose_name : نام نمایشی فارسی برای پنل ادمین
# help_text : متن راهنما

name = models.CharField(
    max_length=100,
    verbose_name='نام محصول',
    help_text='نام محصول را وارد کنید',
    unique=True
)

# مثال choices
STATUS_CHOICES = [
    ('draft', 'پیش‌نویس'),
    ('published', 'منتشر شده'),
    ('archived', 'بایگانی'),
]
status = models.CharField(
    max_length=10,
    choices=STATUS_CHOICES,
    default='draft'
)

🔄 Migrations – مدیریت تغییرات دیتابیس

هر بار که مدل رو تغییر می‌دی (اضافه/حذف/ویرایش فیلد)، باید این تغییرات به دیتابیس اعمال بشن. این کار با Migration انجام میشه.

📟 ترمینال
# قدم ۱: ساخت فایل migration از روی تغییرات مدل
python manage.py makemigrations

# قدم ۲: اعمال migrations به دیتابیس
python manage.py migrate

# دیدن SQL تولید شده (اختیاری ولی مفید!)
python manage.py sqlmigrate products 0001
⚠️

قانون طلایی

هر تغییر در فایل models.py = اجرای دو دستور makemigrations و migrate. اگه یادت بره، اپلیکیشن خطا می‌ده!

🔍 کوئری‌نویسی با ORM

حالا می‌خوایم با مدل کار کنیم. برای تست از Django Shell استفاده می‌کنیم:

📟 ترمینال
python manage.py shell

۱. ساخت رکورد جدید (Create)

Django Shell
from products.models import Product

# روش ۱: ساخت و ذخیره با هم
product = Product.objects.create(
    name='فرش دستباف اصفهان',
    description='فرش ۶ متری با طرح لچک ترنج',
    price=15000000,
    stock=5
)

# روش ۲: ساخت و ذخیره جداگانه
product = Product(name='فرش ماشینی', price=2000000)
product.stock = 10
product.save()

۲. خواندن رکوردها (Read)

Django Shell
# همه محصولات
all_products = Product.objects.all()

# تعداد
count = Product.objects.count()

# یک محصول خاص با ID
product = Product.objects.get(id=1)

# اولین یا آخرین
first = Product.objects.first()
last = Product.objects.last()

# فیلتر کردن
expensive = Product.objects.filter(price__gt=1000000)
cheap = Product.objects.filter(price__lt=500000)
in_range = Product.objects.filter(price__range=(100000, 500000))

# جستجو در نام
results = Product.objects.filter(name__icontains='فرش')
# icontains یعنی حاوی (case-insensitive)

۳. عملگرهای فیلتر (Lookups)

عملگر معنی مثال
__exact دقیقاً برابر name__exact='فرش'
__iexact برابر بدون توجه به حروف name__iexact='فرش'
__contains حاوی name__contains='فرش'
__icontains حاوی – بدون حساسیت name__icontains='فرش'
__gt بزرگتر از price__gt=1000
__gte بزرگتر مساوی price__gte=1000
__lt کوچکتر از price__lt=500
__lte کوچکتر مساوی price__lte=500
__in در لیست id__in=[1,2,3]
__startswith شروع با name__startswith='فرش'
__endswith پایان با name__endswith='ابریشم'
__isnull NULL هست؟ image__isnull=True
__year سال تاریخ created_at__year=1403

۴. مرتب‌سازی (Order By)

Django Shell
# صعودی (پیش‌فرض)
Product.objects.order_by('price')

# نزولی (با علامت منها)
Product.objects.order_by('-price')

# چند فیلد
Product.objects.order_by('-created_at', 'name')

۵. ویرایش (Update)

Django Shell
# تک رکورد
product = Product.objects.get(id=1)
product.price = 20000000
product.save()

# آپدیت گروهی (سریع‌تر)
Product.objects.filter(stock=0).update(is_active=False)

۶. حذف (Delete)

Django Shell
# حذف یک رکورد
product = Product.objects.get(id=1)
product.delete()

# حذف گروهی
Product.objects.filter(is_active=False).delete()
🚨

احتیاط با delete()!

اگه Product.objects.all().delete() رو اجرا کنی، همه محصولات حذف میشن. حواست باشه!

🔗 روابط بین مدل‌ها

سه نوع رابطه اصلی در دیتابیس وجود داره:

۱. ForeignKey (یک‌به‌چند)

مثلاً هر محصول یک دسته‌بندی داره ولی هر دسته‌بندی چند محصول داره.

products/models.py
class Category(models.Model):
    name = models.CharField(max_length=100)
    
    def __str__(self):
        return self.name

class Product(models.Model):
    name = models.CharField(max_length=200)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    
    # رابطه با Category
    category = models.ForeignKey(
        Category,
        on_delete=models.CASCADE,
        related_name='products'
    )
    
    def __str__(self):
        return self.name
📚

on_delete چیه؟

CASCADE: اگه دسته حذف شد، محصولاتش هم حذف بشن
PROTECT: نذاره دسته‌ای حذف بشه که محصول داره
SET_NULL: دسته محصول رو null کنه (نیاز به null=True)
SET_DEFAULT: مقدار پیش‌فرض رو بذاره

۲. ManyToManyField (چند‌به‌چند)

مثلاً هر محصول چند تگ داره و هر تگ هم به چند محصول وصله.

products/models.py
class Tag(models.Model):
    name = models.CharField(max_length=50)
    
    def __str__(self):
        return self.name

class Product(models.Model):
    name = models.CharField(max_length=200)
    tags = models.ManyToManyField(Tag, related_name='products', blank=True)

۳. OneToOneField (یک‌به‌یک)

مثلاً هر کاربر فقط یک پروفایل داره.

users/models.py
from django.contrib.auth.models import User

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    bio = models.TextField(blank=True)
    avatar = models.ImageField(upload_to='avatars/', blank=True)

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

آسان

مثال ۱: مدل ساده برای پست وبلاگ

blog/models.py
from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=200, verbose_name='عنوان')
    slug = models.SlugField(unique=True, verbose_name='نامک')
    content = models.TextField(verbose_name='محتوا')
    published_at = models.DateTimeField(auto_now_add=True)
    is_published = models.BooleanField(default=False)
    
    class Meta:
        ordering = ['-published_at']
        verbose_name = 'پست'
        verbose_name_plural = 'پست‌ها'
    
    def __str__(self):
        return self.title

متوسط

مثال ۲: سیستم سفارش با چند مدل

orders/models.py
from django.db import models
from django.contrib.auth.models import User

class Order(models.Model):
    STATUS_CHOICES = [
        ('pending', 'در انتظار پرداخت'),
        ('paid', 'پرداخت شده'),
        ('shipped', 'ارسال شده'),
        ('delivered', 'تحویل داده شده'),
        ('cancelled', 'لغو شده'),
    ]
    
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='orders')
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending')
    total_price = models.DecimalField(max_digits=12, decimal_places=2)
    address = models.TextField(verbose_name='آدرس')
    created_at = models.DateTimeField(auto_now_add=True)
    
    def __str__(self):
        return f'سفارش #{self.id} - {self.user.username}'

class OrderItem(models.Model):
    order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='items')
    product = models.ForeignKey('products.Product', on_delete=models.PROTECT)
    quantity = models.PositiveIntegerField(default=1)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    
    def get_total(self):
        return self.quantity * self.price

پیشرفته

مثال ۳: کوئری پیشرفته با Aggregation

Django Shell
from django.db.models import Count, Sum, Avg, Max, Min, Q, F
from products.models import Product, Category

# ۱. تعداد محصولات هر دسته
Category.objects.annotate(
    product_count=Count('products')
).order_by('-product_count')

# ۲. مجموع موجودی همه محصولات
total = Product.objects.aggregate(total_stock=Sum('stock'))
print(total['total_stock'])

# ۳. میانگین قیمت
avg_price = Product.objects.aggregate(avg=Avg('price'))

# ۴. کوئری OR با Q
from django.db.models import Q
results = Product.objects.filter(
    Q(name__icontains='فرش') | Q(description__icontains='فرش')
)

# ۵. مقایسه دو فیلد با F
# محصولاتی که قیمتشون بیشتر از قیمت اصلیه
Product.objects.filter(price__gt=F('original_price'))

# ۶. کوئری پیچیده: محصولات گرون‌تر از میانگین
from django.db.models import Avg
avg = Product.objects.aggregate(Avg('price'))['price__avg']
expensive = Product.objects.filter(price__gt=avg)

📝 تمرین‌های فصل

آسان

تمرین ۱

یه مدل برای کتاب بساز با فیلدهای: عنوان، نویسنده، تعداد صفحات، قیمت، تاریخ انتشار. بعد ۵ کتاب از طریق Shell اضافه کن.

متوسط

تمرین ۲

دو مدل Author و Book بساز که با ForeignKey بهم وصلن. سپس کوئری بنویس که “همه کتاب‌های یک نویسنده خاص” رو بگیره.

پیشرفته

تمرین ۳

یه کوئری بنویس که نویسنده‌هایی رو پیدا کنه که بیشتر از ۳ کتاب دارن، به ترتیب نزولی تعداد کتاب. (راهنمایی: annotate + filter)

نمایش سایت

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

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