مدلهای چاق، Viewهای لاغر — با احتیاط
منطقی که به «خود داده» مربوط است (کد رهگیری، محاسبهی جمع، قابلپرداخت بودن) جایش در مدل است، نه در ده View مختلف. جنگو چند نقطهی استاندارد برای این کار دارد:
import secrets
from django.core.exceptions import ValidationError
from django.db import models
from django.urls import reverse
from django.utils import timezone
class Order(models.Model):
# ... فیلدهای درس قبل ...
delivery_date = models.DateField(null=True, blank=True)
def __str__(self):
return f"سفارش {self.tracking_code or self.pk}"
def get_absolute_url(self):
return reverse("orders:detail", kwargs={"pk": self.pk})
@property
def is_payable(self):
return self.status == self.Status.PENDING and self.total > 0
def clean(self):
if self.delivery_date and self.delivery_date < timezone.localdate():
raise ValidationError({"delivery_date": "تاریخ تحویل نمیتواند در گذشته باشد."})
def save(self, *args, **kwargs):
if not self.tracking_code:
self.tracking_code = "KSH" + secrets.token_hex(4).upper()
update_fields = kwargs.get("update_fields")
if update_fields is not None:
kwargs["update_fields"] = {"tracking_code", *update_fields}
super().save(*args, **kwargs)
| نقطه | کِی اجرا میشود | مناسب برای |
|---|---|---|
__str__ | نمایش در ادمین، shell، قالب | متن خوانا و کوتاه |
get_absolute_url | redirect(obj)، دکمهی «View on site» ادمین | آدرس صفحهی جزئیات |
clean() | فقط در full_clean()؛ یعنی ModelForm و ادمین | اعتبارسنجی بین چند فیلد |
save() | هر ذخیرهی تکی | مقداردهی خودکار فیلدها |
| سیگنال | قبل/بعد از save و delete | واکنش اپ «دیگر» به یک رویداد |
سیگنالها و وقتی نباید سراغشان رفت
# apps/notifications/signals.py
from django.db import transaction
from django.db.models.signals import post_save
from django.dispatch import receiver
from apps.orders.models import Order
from .tasks import send_order_sms
@receiver(post_save, sender=Order, dispatch_uid="order_created_sms")
def order_created(sender, instance, created, **kwargs):
if created:
transaction.on_commit(lambda: send_order_sms.delay(instance.pk))
# apps/notifications/apps.py
class NotificationsConfig(AppConfig):
name = "apps.notifications"
def ready(self):
from . import signals # noqa: F401 ثبت گیرندهها
سیگنال منطق را «پنهان» میکند: کسی که order.save() را میخواند نمیفهمد پیامکی هم فرستاده میشود. قاعده: اگر منطق در همان اپ است، یک تابع سرویس صریح (مثل place_order()) بنویسید. سیگنال را برای جداسازی اپها نگه دارید؛ مثلاً اپ اعلانها که نباید اپ سفارش از وجودش باخبر باشد.
نکتههایی که کمتر کسی میداند
save()بهطور خودکارfull_clean()را صدا نمیزند؛ پسclean()شما در shell، API و اسکریپتها اجرا نمیشود مگر خودتان صدایش کنید.QuerySet.update()وbulk_create()نهsave()را صدا میزنند نه سیگنالهای pre_save/post_save را؛ اماQuerySet.delete()سیگنالهای حذف را برای تکتک اشیا میفرستد.- اگر در save فیلدی را خودکار مقدار میدهید، حتماً آن را به
update_fieldsاضافه کنید (مثل کد بالا)؛ وگرنه فراخوانیsave(update_fields=[...])مقدار جدید را ذخیره نمیکند. - بدون
dispatch_uid، اگر ماژول سیگنال دو بار import شود گیرنده دو بار ثبت میشود و مشتری دو پیامک میگیرد. - کار خارجی (پیامک، ایمیل، وبهوک) را همیشه در
transaction.on_commitبگذارید؛ وگرنه ممکن است پیامک برود و تراکنش rollback شود.