فصل ۱۰: کار با QuerySet و ORM جنگو — راهنمای جامع و پیشرفته

""

۱. ORM چیست و چرا استفاده می‌کنیم؟

ORM یا Object Relational Mapping یعنی این که به جای نوشتن دستورات SQL خام، ما از اشیاء پایتون برای کار با داده‌ها استفاده کنیم.

در Django:

  • هر Model معادل یک جدول در دیتابیسه.
  • هر شیء مدل معادل یک سطر (row) در جدول.
  • ORM جنگو این ارتباط رو ایجاد می‌کنه و طوری طراحی شده که بدون نیاز به دانستن SQL بتونی داده‌ها رو ایجاد، خواندن، بروزرسانی و حذف (CRUD) کنی.

✅ مزایا:

  • پورتابل بودن (روی SQLite، PostgreSQL، MySQL و غیره یکسان کار می‌کنه)
  • ایمن‌تر بودن (جلوگیری از SQL Injection)
  • راحت‌تر بودن و کمتر بودن خطا

۲. مدل نمونه برای کار

ما در این فصل با یک مثال فروشگاه آنلاین کار می‌کنیم. در models.py:

from django.db import models

class Category(models.Model):
    name = models.CharField(max_length=100, verbose_name="نام دسته")

    def __str__(self):
        return self.name


class Product(models.Model):
    name = models.CharField(max_length=200, verbose_name="نام محصول")
    price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="قیمت")
    stock = models.PositiveIntegerField(verbose_name="موجودی")
    category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name="products", verbose_name="دسته‌بندی")
    created_at = models.DateTimeField(auto_now_add=True, verbose_name="تاریخ ایجاد")

    def __str__(self):
        return self.name

حالا با:

python manage.py makemigrations
python manage.py migrate

دو جدول category و product ساخته میشن.


۳. ایجاد داده‌ها (INSERT)

با ORM میشه خیلی ساده رکورد جدید ساخت.

۳.۱. ساخت یک آبجکت و ذخیره

cat = Category(name="موبایل")
cat.save()

۳.۲. استفاده از create()

Category.objects.create(name="لپ‌تاپ")

نکته: متد create() هم آبجکت رو می‌سازه و هم بلافاصله save() می‌کنه.


۴. خواندن داده‌ها (SELECT)

۴.۱. همه رکوردها

categories = Category.objects.all()

خروجی → یک QuerySet که مثل لیست پایتون قابل پیمایشه.

۴.۲. یک رکورد خاص

cat = Category.objects.get(id=1)

اگر چنین رکوردی نبود:

  • DoesNotExist Exception داده میشه.

۴.۳. فیلتر کردن

mobiles = Product.objects.filter(category__name="موبایل")
cheap_products = Product.objects.filter(price__lt=2000000)

۴.۴. اولین یا آخرین رکورد

first_cat = Category.objects.first()
last_cat = Category.objects.last()

۴.۵. وجود داشتن داده

is_exist = Product.objects.filter(stock=0).exists()

۵. استفاده از Lookup ها

ORM جنگو lookupهای قدرتمندی داره که ما می‌تونیم در فیلترها ازشون استفاده کنیم:

Lookupتوضیحمثال
exactبرابری دقیقfilter(name__exact="iPhone")
iexactبرابری بدون حساسیت به حروفfilter(name__iexact="iphone")
containsشامل بودن متنfilter(name__contains="Pro")
icontainsشامل بودن متن، بدون حساسیت به حروفfilter(name__icontains="pro")
lt / lteکوچکتر از / کوچکتر یا مساویfilter(price__lt=1000000)
gt / gteبزرگتر از / بزرگتر مساویfilter(price__gte=5000000)
inداخل لیستfilter(id__in=[1,2,3])
startswithشروع شدن باfilter(name__startswith="Samsung")
endswithپایان یافتن باfilter(name__endswith="Ultra")

۶. مرتب‌سازی (ORDER BY)

products = Product.objects.all().order_by('price')    # صعودی
products = Product.objects.all().order_by('-price')   # نزولی

۷. محدود کردن تعداد نتایج (LIMIT)

top3 = Product.objects.all().order_by('-price')[:3]

۸. بروزرسانی داده‌ها (UPDATE)

Product.objects.filter(id=1).update(price=2500000)

یا:

p = Product.objects.get(id=1)
p.stock = 20
p.save()

۹. حذف داده‌ها (DELETE)

Product.objects.get(id=2).delete()

یا چندتایی:

Product.objects.filter(stock=0).delete()

۱۰. استفاده از Q برای شرط‌های پیچیده

from django.db.models import Q

# محصولاتی که یا قیمت کمتر از 2 میلیون یا موجودی صفر دارن
products = Product.objects.filter(Q(price__lt=2000000) | Q(stock=0))

۱۱. استفاده از F برای ارجاع به فیلدها

from django.db.models import F

# قیمت رو ۱۰٪ افزایش بده
Product.objects.update(price=F('price') * 1.1)

۱۲. توابع تجمعی (Aggregate Functions)

from django.db.models import Count, Avg, Max, Min, Sum

# آمار محصولات
Product.objects.aggregate(
    total_products=Count('id'),
    avg_price=Avg('price'),
    max_price=Max('price')
)

۱۳. گروه‌بندی (annotate)

مثال: تعداد محصولات هر دسته

Category.objects.annotate(
    product_count=Count('products')
)

۱۴. انتخاب فقط برخی فیلدها

# خروجی: لیست دیکشنری
Product.objects.values('name', 'price')

# خروجی: لیست Tuple
Product.objects.values_list('name', 'price')

۱۵. بهینه‌سازی کوئری‌ها

۱۵.۱. استفاده از select_related

برای رابطه ForeignKey — یک JOIN می‌زنه:

products = Product.objects.select_related('category').all()

۱۵.۲. استفاده از prefetch_related

برای روابط Many-to-Many یا reverse ForeignKey:

categories = Category.objects.prefetch_related('products').all()

۱۶. Raw SQL وقتی ORM کافی نیست

Product.objects.raw("SELECT * FROM myapp_product WHERE price > %s", [2000000])

۱۷. نکات حرفه‌ای و Best Practices

  1. همیشه از QuerySetها استفاده کن، نه لیست‌های تبدیل‌شده — چون QuerySet تنبل (lazy) هست و فقط وقتی لازم باشه از دیتابیس می‌خونه.
  2. از exists() برای چک کردن وجود داده استفاده کنید چون سبک‌تر از count() یا len() هست.
  3. از Lookups مناسب استفاده کن تا کوئری دقیق‌تر و سریع‌تر باشه.
  4. بهینه‌سازی صبح اول پروژه خیلی مهمه، مخصوصاً وقتی داده‌ها زیاد شدن.

۱۸. مثال عملی — صفحه محصولات فروشگاه

views.py

from django.shortcuts import render
from .models import Product

def product_list(request):
    products = Product.objects.filter(stock__gt=0).order_by('price')
    return render(request, 'product_list.html', {'products': products})

product_list.html

<h1>لیست محصولات</h1>
<ul>
    {% for product in products %}
        <li>{{ product.name }} - {{ product.price }} تومان</li>
    {% empty %}
        <li>هیچ محصولی موجود نیست</li>
    {% endfor %}
</ul>

۱۹. خطاهای رایج

خطاعلتراه‌حل
DoesNotExistاستفاده از get برای رکوردی که وجود ندارهاستفاده از filter().first()
MultipleObjectsReturnedget و بیش از یک نتیجهاستفاده از filter()
افت سرعتکوئری اضافی یا N+1 Queryselect_related / prefetch_related

۲۰. جمع‌بندی

در این فصل یاد گرفتیم:

  • CRUD کامل با ORM
  • فیلترها و Lookups
  • مرتب‌سازی، گروه‌بندی و Aggregate
  • شرط‌های پیچیده با Q
  • بهینه‌سازی QuerySetها
  • مثال عملی فروشگاه آنلاین
محمد وب‌سایت

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *