فصل ۲: مدل‌ها و ORM — طراحی داده‌ی درست

فیلدها و گزینه‌ها: null در برابر blank، TextChoices، unique و db_index

مدل، قرارداد شما با پایگاه داده است

هر کلاس مدل یک جدول و هر فیلد یک ستون است. انتخاب نوع فیلد و گزینه‌هایش فقط سلیقه نیست: روی درستی داده، سرعت کوئری و اعتبارسنجی فرم‌ها و ادمین اثر مستقیم دارد. مدل طرح فرش یک کارخانه‌ی کاشانی را ببینید:

# apps/catalog/models.py
from django.db import models
from django.db.models.functions import Now


class Carpet(models.Model):
    class Density(models.IntegerChoices):
        D700 = 700, "۷۰۰ شانه"
        D1000 = 1000, "۱۰۰۰ شانه"
        D1200 = 1200, "۱۲۰۰ شانه"

    class Status(models.TextChoices):
        DRAFT = "draft", "پیش‌نویس"
        ACTIVE = "active", "فعال"
        ARCHIVED = "archived", "بایگانی"

    code = models.CharField("کد طرح", max_length=20, unique=True)
    name = models.CharField("نام طرح", max_length=100, db_index=True)
    density = models.PositiveSmallIntegerField("تراکم", choices=Density)
    status = models.CharField("وضعیت", max_length=10, choices=Status, default=Status.DRAFT)
    price = models.PositiveBigIntegerField("قیمت هر متر (ریال)")
    stock = models.PositiveIntegerField("موجودی", default=0)
    weight_kg = models.DecimalField("وزن", max_digits=6, decimal_places=2, null=True, blank=True)
    description = models.TextField("توضیحات", blank=True)
    created_at = models.DateTimeField(db_default=Now())
    updated_at = models.DateTimeField(auto_now=True)

از Django 5.0 می‌توانید خود کلاس TextChoices را مستقیم به choices بدهید و دیگر لازم نیست .choices بنویسید. db_default هم (باز از 5.0) مقدار پیش‌فرض را در خود پایگاه داده تعریف می‌کند؛ حتی INSERTهای خارج از جنگو هم آن را می‌گیرند.

null در برابر blank

گزینهسطحمعنی
null=Trueپایگاه دادهستون می‌تواند NULL باشد
blank=Trueاعتبارسنجی (فرم/ادمین)فیلد در فرم می‌تواند خالی بماند

قاعده‌ی عملی: برای فیلدهای متنی (CharField، TextField) فقط blank=True بگذارید و «خالی» را با رشته‌ی خالی نشان دهید. برای عدد، تاریخ و ForeignKey اختیاری، هر دو را با هم بگذارید؛ چون عدد «خالی» معنایی جز NULL ندارد.

پول را چطور ذخیره کنیم؟

هرگز با FloatField. ریال را به‌صورت عدد صحیح (PositiveBigIntegerField) نگه دارید و تبدیل به تومان را فقط در نمایش انجام دهید. اگر اعشار واقعی دارید (نرخ ارز، وزن) از DecimalField استفاده کنید.

نکته‌هایی که کمتر کسی می‌داند

  • CharField با null=True دو نوع «خالی» می‌سازد (NULL و رشته‌ی خالی) و کوئری‌ها را پیچیده می‌کند؛ تنها استثنا وقتی است که unique=True دارید و چند رکورد خالی مجاز است.
  • unique=True خودش ایندکس می‌سازد؛ اضافه کردن db_index=True کنارش فقط یک ایندکس تکراری بی‌فایده است.
  • برای هر فیلد choices، متد get_status_display() خودکار ساخته می‌شود و برچسب فارسی را برمی‌گرداند؛ در قالب بدون پرانتز: {{ carpet.get_status_display }}.
  • auto_now فقط در save() به‌روز می‌شود؛ QuerySet.update() آن را دست نمی‌زند و باید خودتان updated_at=timezone.now() را بفرستید.
  • choices فقط در اعتبارسنجی اعمال می‌شود، نه در پایگاه داده؛ برای ضمانت واقعی یک CheckConstraint اضافه کنید (درس ۸).

برای ذخیره‌ی پیشرفت و شرکت در آزمون، وارد شوید — رایگان است.