🗄️ فصل ۲: مدلها و دیتابیس
تو این فصل یاد میگیری چطور با ORM جنگو کار کنی - بدون نوشتن حتی یک خط SQL!
تو این فصل یاد میگیری چطور با ORM جنگو کار کنی – بدون نوشتن حتی یک خط SQL!
📑 فهرست این فصل
🤔 ORM چیه و چرا مهمه؟
ORM مخفف Object-Relational Mapping هست. به زبون ساده: به جای اینکه با SQL با دیتابیس حرف بزنی، با کلاسها و آبجکتهای پایتون باهاش کار میکنی!
❌ بدون ORM (SQL خام)
SELECT * FROM products
WHERE price > 1000
ORDER BY created_at DESC
LIMIT 10;
✅ با ORM جنگو
Product.objects.filter(
price__gt=1000
).order_by('-created_at')[:10]
۱. تغییر دیتابیس راحته (MySQL به PostgreSQL و…)
۲. امن در برابر SQL Injection
۳. کد پایتونی، قابل خوندن و دیباگ راحتتر
۴. اعتبارسنجی خودکار دادهها
🏗️ ساخت اولین مدل
بیا یه مدل ساده برای محصولات بسازیم. توی فایل blog/models.py ولی این بار اپ products:
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)
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)
# همه محصولات
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)
# صعودی (پیشفرض)
Product.objects.order_by('price')
# نزولی (با علامت منها)
Product.objects.order_by('-price')
# چند فیلد
Product.objects.order_by('-created_at', 'name')
۵. ویرایش (Update)
# تک رکورد
product = Product.objects.get(id=1)
product.price = 20000000
product.save()
# آپدیت گروهی (سریعتر)
Product.objects.filter(stock=0).update(is_active=False)
۶. حذف (Delete)
# حذف یک رکورد
product = Product.objects.get(id=1)
product.delete()
# حذف گروهی
Product.objects.filter(is_active=False).delete()
اگه Product.objects.all().delete() رو اجرا کنی، همه محصولات حذف میشن. حواست باشه!
🔗 روابط بین مدلها
سه نوع رابطه اصلی در دیتابیس وجود داره:
۱. ForeignKey (یکبهچند)
مثلاً هر محصول یک دستهبندی داره ولی هر دستهبندی چند محصول داره.
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
CASCADE: اگه دسته حذف شد، محصولاتش هم حذف بشن
PROTECT: نذاره دستهای حذف بشه که محصول داره
SET_NULL: دسته محصول رو null کنه (نیاز به null=True)
SET_DEFAULT: مقدار پیشفرض رو بذاره
۲. ManyToManyField (چندبهچند)
مثلاً هر محصول چند تگ داره و هر تگ هم به چند محصول وصله.
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 (یکبهیک)
مثلاً هر کاربر فقط یک پروفایل داره.
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)
🎯 مثالهای واقعی و کاربردی
مثال ۱: مدل ساده برای پست وبلاگ
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
مثال ۲: سیستم سفارش با چند مدل
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
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)