راهنمای کامل ساخت، ذخیره‌سازی و اجرای Query در Django ORM

این مقاله نحوه ساخت، ذخیره، بازیابی، فیلتر کردن و مدیریت داده‌ها را با استفاده از ORM جنگو توضیح می‌دهد. موضوعاتی مانند ساخت آبجکت‌ها، ذخیره‌سازی تغییرات، کار با ForeignKey و ManyToManyField، ایجاد QuerySet، فیلتر کردن داده‌ها، زنجیره‌سازی فیلترها و رفتار مستقل QuerySetها پوشش داده شده‌اند. این مفاهیم پایه‌ای‌ترین بخش کار با داده‌ها در Django هستند.

Django ORM، QuerySet، ساخت آبجکت، save()filter()، exclude()، ForeignKeyManyToManyField، زنجیره‌سازی فیلترها

~15 دقیقه مطالعه • بروزرسانی ۱۹ اسفند ۱۴۰۴

۱. مقدمه‌ای بر Query در Django

پس از تعریف مدل‌ها، Django یک API قدرتمند ORM در اختیار شما قرار می‌دهد که امکان ساخت، بازیابی، به‌روزرسانی و حذف داده‌ها را بدون نوشتن SQL فراهم می‌کند. هر کلاس مدل نماینده یک جدول پایگاه‌داده و هر نمونه از آن نماینده یک رکورد است.

در این مقاله از مدل‌های زیر (یک اپلیکیشن وبلاگ) استفاده می‌کنیم:


class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

class Author(models.Model):
    name = models.CharField(max_length=200)
    email = models.EmailField()

class Entry(models.Model):
    blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
    headline = models.CharField(max_length=255)
    body_text = models.TextField()
    pub_date = models.DateField()
    mod_date = models.DateField(default=date.today)
    authors = models.ManyToManyField(Author)
    number_of_comments = models.IntegerField(default=0)
    number_of_pingbacks = models.IntegerField(default=0)
    rating = models.IntegerField(default=5)

۲. ساخت آبجکت‌ها

برای ساخت یک آبجکت جدید، کافی است مدل را با آرگومان‌های کلیدی مقداردهی کنید و سپس save() را فراخوانی کنید:


b = Blog(name="Beatles Blog", tagline="All the latest Beatles news.")
b.save()

این کار یک دستور SQL INSERT اجرا می‌کند. Django تا زمانی که save() را صدا نزنید به پایگاه‌داده دست نمی‌زند.

برای ساخت و ذخیره هم‌زمان:


Blog.objects.create(name="My Blog", tagline="Hello world")

۳. ذخیره‌سازی تغییرات

برای به‌روزرسانی یک آبجکت موجود، مقدار فیلدها را تغییر دهید و دوباره save() را فراخوانی کنید:


b5.name = "New name"
b5.save()

این کار یک دستور SQL UPDATE اجرا می‌کند.

۴. ذخیره‌سازی ForeignKey و ManyToManyField

به‌روزرسانی ForeignKey

کافی است یک نمونه از مدل مرتبط را به فیلد نسبت دهید:


entry = Entry.objects.get(pk=1)
cheese_blog = Blog.objects.get(name="Cheddar Talk")
entry.blog = cheese_blog
entry.save()

به‌روزرسانی ManyToManyField

برای افزودن رابطه، از add() استفاده کنید:


joe = Author.objects.create(name="Joe")
entry.authors.add(joe)

افزودن چند نویسنده به‌صورت هم‌زمان:


entry.authors.add(john, paul, george, ringo)

۵. بازیابی آبجکت‌ها

برای بازیابی داده‌ها از QuerySet استفاده می‌شود که از طریق Manager مدل (به‌طور پیش‌فرض objects) در دسترس است:


Blog.objects.all()

Manager فقط از طریق کلاس مدل قابل دسترسی است، نه از طریق نمونه‌ها.

۶. بازیابی همه آبجکت‌ها

ساده‌ترین Query:


all_entries = Entry.objects.all()

۷. فیلتر کردن QuerySet

برای محدود کردن نتایج از filter() و exclude() استفاده می‌شود:

  • filter(**kwargs) → انتخاب رکوردهای مطابق
  • exclude(**kwargs) → حذف رکوردهای مطابق

مثال: ورودی‌های سال ۲۰۰۶


Entry.objects.filter(pub_date__year=2006)

۸. زنجیره‌سازی فیلترها

هر بار که یک QuerySet را refine می‌کنید، یک QuerySet جدید ساخته می‌شود. بنابراین می‌توانید فیلترها را زنجیره کنید:


Entry.objects.filter(headline__startswith="What")
    .exclude(pub_date__gte=datetime.date.today())
    .filter(pub_date__gte=datetime.date(2005, 1, 30))

این QuerySet ورودی‌هایی را برمی‌گرداند که:

  • عنوانشان با "What" شروع می‌شود،
  • بین ۳۰ ژانویه ۲۰۰۵ تا امروز منتشر شده‌اند.

۹. استقلال QuerySetها

هر QuerySet مستقل از دیگری است:


q1 = Entry.objects.filter(headline__startswith="What")
q2 = q1.exclude(pub_date__gte=datetime.date.today())
q3 = q1.filter(pub_date__gte=datetime.date.today())

q1 بدون تغییر باقی می‌ماند.

جمع‌بندی

ORM جنگو یک API قدرتمند و شهودی برای تعامل با پایگاه‌داده ارائه می‌دهد. با درک نحوه ساخت، ذخیره، فیلتر و زنجیره‌سازی QuerySetها می‌توانید اپلیکیشن‌هایی پویا و داده‌محور بسازید بدون اینکه نیازی به نوشتن SQL داشته باشید.

۱. QuerySetها تنبل هستند (Lazy Evaluation)

QuerySet در Django تنبل است؛ یعنی تا زمانی که واقعاً به نتیجه نیاز نداشته باشید، هیچ کوئری SQL اجرا نمی‌شود.

مثال:


q = Entry.objects.filter(headline__startswith="What")
q = q.filter(pub_date__lte=datetime.date.today())
q = q.exclude(body_text__icontains="food")
print(q)

اگرچه سه فیلتر اعمال شده، اما تنها زمانی که print(q) اجرا شود، یک کوئری SQL به پایگاه‌داده ارسال می‌شود.

QuerySet تنها زمانی evaluate می‌شود که:

  • نتایج را چاپ کنید،
  • روی آن iteration انجام دهید،
  • به list تبدیلش کنید،
  • یا به مقدار خاصی نیاز داشته باشید.

۲. بازیابی یک آبجکت با get()

متد filter() همیشه یک QuerySet برمی‌گرداند—even اگر فقط یک نتیجه وجود داشته باشد.

اما get() مستقیماً یک آبجکت برمی‌گرداند:


one_entry = Entry.objects.get(pk=1)

تفاوت get() با filter()[0]:

  • اگر نتیجه‌ای وجود نداشته باشد:
    • get() → خطای DoesNotExist
  • اگر بیش از یک نتیجه وجود داشته باشد:
    • get() → خطای MultipleObjectsReturned
  • filter()[0] → در صورت نبود نتیجه، IndexError

۳. محدودسازی QuerySet با slicing

می‌توانید از slicing پایتون برای محدود کردن نتایج استفاده کنید. این معادل LIMIT و OFFSET در SQL است.

اولین ۵ رکورد:


Entry.objects.all()[:5]

رکوردهای ۶ تا ۱۰:


Entry.objects.all()[5:10]

نکته مهم: slicing معمولاً QuerySet جدید می‌سازد و کوئری را اجرا نمی‌کند—مگر اینکه از step استفاده کنید:


Entry.objects.all()[:10:2]

این مورد کوئری را اجرا می‌کند چون step نیازمند پردازش نتایج است.

بازیابی یک آبجکت با index:


Entry.objects.order_by("headline")[0]

این معادل LIMIT 1 است.

۴. Field Lookupها

Field lookupها نحوه ساخت شرط‌های WHERE در SQL هستند. این lookupها با دو زیرخط (__) مشخص می‌شوند.

مثال:


Entry.objects.filter(pub_date__lte="2006-01-01")

معادل SQL:


SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';

Lookupهای رایج:

  • exact → تطابق دقیق
  • iexact → تطابق دقیق بدون حساسیت به حروف
  • contains / icontains
  • lt / lte / gt / gte
  • startswith / istartswith
  • in

نکته مهم:

برای ForeignKey می‌توانید از field_id استفاده کنید:


Entry.objects.filter(blog_id=4)

جمع‌بندی

QuerySetها در Django تنبل هستند و تنها زمانی اجرا می‌شوند که به نتیجه نیاز داشته باشید. متد get() برای بازیابی یک آبجکت استفاده می‌شود و slicing امکان محدودسازی نتایج را فراهم می‌کند. Field lookupها نیز ابزار اصلی ساخت شرط‌های SQL در ORM جنگو هستند.

۱. Lookupهای حساس و غیرحساس به حروف

iexact

برای تطابق دقیق بدون حساسیت به حروف:


Blog.objects.get(name__iexact="beatles blog")

این مقدارهایی مثل “Beatles Blog” یا “BeAtlES blOG” را هم تطبیق می‌دهد.

contains / icontains

contains جستجوی زیررشته به‌صورت حساس به حروف است:


Entry.objects.get(headline__contains="Lennon")

معادل SQL:


SELECT ... WHERE headline LIKE '%Lennon%';

icontains نسخه غیرحساس به حروف است.

startswith / endswith

برای جستجوی شروع یا پایان رشته. نسخه‌های غیرحساس: istartswith و iendswith.

۲. Lookupهایی که روابط را طی می‌کنند

Django به‌صورت خودکار JOINهای لازم را هنگام استفاده از __ برای پیمایش روابط انجام می‌دهد.

مثال: یافتن Entryهایی که Blog آن‌ها “Beatles Blog” است:


Entry.objects.filter(blog__name="Beatles Blog")

جستجوی معکوس:


Blog.objects.filter(entry__headline__contains="Lennon")

رفتار هنگام نبود رابطه میانی:

Django خطا نمی‌دهد و آن را NULL در نظر می‌گیرد.


Blog.objects.filter(entry__authors__name__isnull=True)

۳. فیلتر کردن روی روابط چندارزشی (ManyToMany و reverse FK)

باید تصمیم بگیرید که آیا همه شرط‌ها باید روی یک آبجکت مرتبط صدق کنند یا روی آبجکت‌های مختلف.

هر دو شرط روی یک Entry اعمال شود:


Blog.objects.filter(
    entry__headline__contains="Lennon",
    entry__pub_date__year=2008,
)

شرط‌ها می‌توانند روی Entryهای مختلف صدق کنند:


Blog.objects.filter(entry__headline__contains="Lennon")
    .filter(entry__pub_date__year=2008)

این روش ممکن است نتایج تکراری ایجاد کند.

۴. رفتار متفاوت exclude()

exclude() مانند filter() عمل نمی‌کند و شرط‌ها لزوماً روی یک آبجکت مشترک اعمال نمی‌شوند.

روش اشتباه:


Blog.objects.exclude(
    entry__headline__contains="Lennon",
    entry__pub_date__year=2008,
)

روش صحیح برای حذف Entryهایی که هر دو شرط را دارند:


Blog.objects.exclude(
    entry__in=Entry.objects.filter(
        headline__contains="Lennon",
        pub_date__year=2008,
    )
)

۵. مقایسه فیلدها با F Expressions

F expressions امکان مقایسه فیلدها با یکدیگر یا انجام عملیات ریاضی در سطح پایگاه‌داده را فراهم می‌کنند.

کامنت‌ها بیشتر از پینگ‌بک‌ها:


Entry.objects.filter(number_of_comments__gt=F("number_of_pingbacks"))

بیش از دو برابر پینگ‌بک‌ها:


Entry.objects.filter(number_of_comments__gt=F("number_of_pingbacks") * 2)

rating کمتر از مجموع کامنت‌ها و پینگ‌بک‌ها:


Entry.objects.filter(
    rating__lt=F("number_of_comments") + F("number_of_pingbacks")
)

پیمایش روابط با F:


Entry.objects.filter(authors__name=F("blog__name"))

عملیات روی تاریخ‌ها:


Entry.objects.filter(mod_date__gt=F("pub_date") + timedelta(days=3))

عملیات بیتی:


F("somefield").bitand(16)

(Oracle از XOR پشتیبانی نمی‌کند.)

جمع‌بندی

Lookupهای پیشرفته، پیمایش روابط، فیلترهای چندارزشی و F expressions ابزارهای قدرتمندی برای ساخت کوئری‌های پیچیده و کارآمد در Django ORM هستند. با تسلط بر این مفاهیم می‌توانید بدون نوشتن SQL، کوئری‌هایی بسیار دقیق و حرفه‌ای بسازید.

۱. استفاده از Transforms در Expressions

Django اجازه می‌دهد در F expressions از transforms استفاده کنید.

مثال: یافتن Entryهایی که سال انتشار و سال آخرین ویرایش یکسان دارند:


Entry.objects.filter(pub_date__year=F("mod_date__year"))

مثال: یافتن اولین سال انتشار:


Entry.objects.aggregate(first_published_year=Min("pub_date__year"))

مثال: استفاده از Subquery و OuterRef:

یافتن بالاترین rating و مجموع کامنت‌ها برای هر سال:


Entry.objects.values("pub_date__year").annotate(
    top_rating=Subquery(
        Entry.objects.filter(
            pub_date__year=OuterRef("pub_date__year")
        ).order_by("-rating").values("rating")[:1]
    ),
    total_comments=Sum("number_of_comments"),
)

۲. میان‌بُر pk در جستجو

pk مخفف primary key است و معادل id__exact می‌باشد.

سه عبارت زیر یکسان‌اند:


Blog.objects.get(id__exact=14)
Blog.objects.get(id=14)
Blog.objects.get(pk=14)

مثال‌های دیگر:


Blog.objects.filter(pk__in=[1, 4, 7])
Blog.objects.filter(pk__gt=14)

در روابط نیز کار می‌کند:


Entry.objects.filter(blog__pk=3)

۳. Escape شدن % و _ در LIKE

Lookupهایی مثل contains، icontains، startswith و … که به LIKE تبدیل می‌شوند، به‌طور خودکار % و _ را Escape می‌کنند.

مثال:


Entry.objects.filter(headline__contains="%")

SQL تولیدشده:


WHERE headline LIKE '%\%%'

۴. کش QuerySet

هر QuerySet یک کش داخلی دارد. اولین بار که QuerySet evaluate شود، نتایج در کش ذخیره می‌شوند.

مثال اشتباه (دو بار کوئری اجرا می‌شود):


print([e.headline for e in Entry.objects.all()])
print([e.pub_date for e in Entry.objects.all()])

روش صحیح:


queryset = Entry.objects.all()
print([e.headline for e in queryset])
print([e.pub_date for e in queryset])

۵. زمان‌هایی که QuerySet کش نمی‌شود

اگر فقط بخشی از QuerySet را evaluate کنید (مثل slicing یا index)، کش پر نمی‌شود.

مثال:


queryset = Entry.objects.all()
print(queryset[5])  # هر بار کوئری جدید
print(queryset[5])

اما اگر کل QuerySet evaluate شده باشد:


queryset = Entry.objects.all()
list(queryset)  # کش پر می‌شود
print(queryset[5])  # از کش

۶. Queryهای Asynchronous

در async viewها نمی‌توان از ORM همگام استفاده کرد. Django نسخه async بسیاری از متدها را ارائه می‌دهد.

مثال:


async for entry in Authors.objects.filter(name__startswith="A"):
    ...

مثال ترکیب filter و afirst:


user = await User.objects.filter(username=my_input).afirst()

اگر await را فراموش کنید، با خطاهایی مثل “coroutine object has no attribute …” مواجه می‌شوید.

۷. تشخیص متدهای async و sync

متدهایی که QuerySet جدید برمی‌گردانند (مثل filter، exclude) غیرمسدودکننده‌اند و async لازم ندارند.

متدهایی که QuerySet را evaluate می‌کنند (مثل get، first) نسخه async دارند: aget()، afirst().

جمع‌بندی

Transforms، Subquery، pk lookup، رفتار LIKE، کش QuerySet و Queryهای async ابزارهای قدرتمندی برای نوشتن کوئری‌های پیچیده، بهینه و مقیاس‌پذیر در Django ORM هستند. با تسلط بر این مفاهیم می‌توانید اپلیکیشن‌هایی سریع، تمیز و حرفه‌ای بسازید.

۱. تراکنش‌ها در Queryهای Asynchronous

Django در حال حاضر از تراکنش‌ها در Queryهای async پشتیبانی نمی‌کند. تلاش برای استفاده از تراکنش در async باعث خطای SynchronousOnlyOperation می‌شود.

برای استفاده از تراکنش‌ها، باید کد ORM را در یک تابع synchronous قرار دهید و آن را با sync_to_async فراخوانی کنید.

۲. Query گرفتن از JSONField

JSONField رفتار lookup متفاوتی دارد، زیرا از key، index و path transforms پشتیبانی می‌کند.

مدل نمونه:


class Dog(models.Model):
    name = models.CharField(max_length=200)
    data = models.JSONField(null=True)

۳. ذخیره و Query گرفتن از None

ذخیره None معمولاً SQL NULL ایجاد می‌کند. برای ذخیره JSON null باید از:


Value(None, JSONField())

هر دو مقدار هنگام بازیابی به None تبدیل می‌شوند، بنابراین تشخیص آن‌ها دشوار است.

رفتار Query:

  • data=None → JSON null
  • data__isnull=True → SQL NULL

مثال:


Dog.objects.filter(data=None)  # JSON null
Dog.objects.filter(data__isnull=True)  # SQL NULL

بهتر است null=False تنظیم شود و مقدار پیش‌فرض مثل default=dict استفاده شود.

۴. Key، Index و Path Transformها

Query با کلید:


Dog.objects.filter(data__breed="collie")

چند کلید پشت سر هم:


Dog.objects.filter(data__owner__name="Bob")

ایندکس در آرایه:


Dog.objects.filter(data__owner__other_pets__0__name="Fishy")

ایندکس منفی:


Dog.objects.filter(**{"data__owner__other_pets__-1__name": "Fishy"})

MySQL، MariaDB و Oracle از ایندکس منفی پشتیبانی نمی‌کنند.

کلیدهای موجود/ناموجود:


Dog.objects.filter(data__owner__isnull=True)

۵. KT Expressions

KT() مقدار متنی یک key/index/path را استخراج می‌کند و می‌توان آن را annotate یا filter کرد.

مثال:


Dog.objects.annotate(
    first_breed=KT("data__breed__1"),
    owner_name=KT("data__owner__name")
).filter(first_breed__startswith="lhasa", owner_name="Bob")

هشدار: هر lookup ناشناخته به‌عنوان key lookup تفسیر می‌شود. اشتباه تایپی خطا نمی‌دهد.

۶. تفاوت رفتار دیتابیس‌ها

  • MariaDB و Oracle: مرتب‌سازی JSON بر اساس رشته انجام می‌شود.
  • Oracle: در exclude با None رفتار متفاوت دارد.
  • PostgreSQL: از -> و #> استفاده می‌کند.
  • SQLite: رشته‌های "true"، "false"، "null" را JSON boolean/null تفسیر می‌کند.

۷. Lookupهای Containment و Key

contains

بررسی می‌کند که آیا JSON شامل key-valueهای داده‌شده هست یا نه.


Dog.objects.filter(data__contains={"owner": "Bob"})

در Oracle و SQLite پشتیبانی نمی‌شود.

contained_by

بررسی می‌کند که JSON موجود زیرمجموعه مقدار داده‌شده باشد.


Dog.objects.filter(data__contained_by={"breed": "collie", "owner": "Bob"})

has_key


Dog.objects.filter(data__has_key="owner")

has_keys


Dog.objects.filter(data__has_keys=["breed", "owner"])

has_any_keys


Dog.objects.filter(data__has_any_keys=["owner", "breed"])

جمع‌بندی

Query گرفتن از JSONField در Django بسیار قدرتمند است و از key، index، path، KT expressions و lookupهای متنوع پشتیبانی می‌کند. درک تفاوت SQL NULL و JSON null، رفتار دیتابیس‌ها و نحوه استفاده از transformها برای نوشتن Queryهای دقیق و بهینه ضروری است.

۱. ساخت کوئری‌های پیچیده با Q Objects

در Django، آرگومان‌های filter و exclude به‌صورت پیش‌فرض با AND ترکیب می‌شوند. برای ساخت کوئری‌های OR یا ترکیبی، از Q استفاده می‌کنیم.

مثال ساده:


Q(question__startswith="What")

OR بین دو شرط:


Q(question__startswith="Who") | Q(question__startswith="What")

NOT (نقیض):


Q(question__startswith="Who") | ~Q(pub_date__year=2005)

استفاده در filter/get:


Poll.objects.get(
    Q(question__startswith="Who"),
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
)

نکته مهم:

Q objects باید قبل از keyword arguments قرار بگیرند.

۲. مقایسه آبجکت‌های مدل

برای مقایسه دو آبجکت مدل، از == استفاده می‌شود. Django پشت‌صحنه primary key را مقایسه می‌کند.

مثال:


some_entry == other_entry
# معادل:
some_entry.id == other_entry.id

اگر primary key نام دیگری داشته باشد، باز هم همین رفتار برقرار است.

۳. حذف آبجکت‌ها

حذف یک آبجکت:


e.delete()
# خروجی:
(1, {'blog.Entry': 1})

حذف گروهی:


Entry.objects.filter(pub_date__year=2005).delete()

نکته: در حذف گروهی، متد delete مدل‌ها اجرا نمی‌شود. اگر delete سفارشی دارید، باید تک‌تک حذف کنید.

حذف cascade:

حذف یک آبجکت، تمام آبجکت‌های مرتبط با ForeignKey (با on_delete پیش‌فرض) را نیز حذف می‌کند.

۴. کپی‌کردن آبجکت‌ها

Django متد copy ندارد، اما می‌توان با تغییر pk یک آبجکت جدید ساخت.

کپی ساده:


blog.pk = None
blog._state.adding = True
blog.save()

کپی در وراثت:

در مدل‌های ارث‌بری، باید هم pk و هم id را None کنید:


django_blog.pk = None
django_blog.id = None
django_blog._state.adding = True
django_blog.save()

کپی ManyToMany:


old_authors = entry.authors.all()
entry.pk = None
entry._state.adding = True
entry.save()
entry.authors.set(old_authors)

کپی OneToOne:

باید آبجکت مرتبط را نیز کپی کنید تا constraint نقض نشود.

۵. به‌روزرسانی گروهی با update()

برای تغییر مقدار یک فیلد در تمام آبجکت‌های یک QuerySet از update() استفاده می‌شود.

مثال:


Entry.objects.filter(pub_date__year=2007).update(headline="Everything is the same")

به‌روزرسانی ForeignKey:


Entry.objects.update(blog=b)

نکات مهم update:

  • مستقیماً به SQL تبدیل می‌شود.
  • متد save اجرا نمی‌شود.
  • سیگنال‌های pre_save و post_save اجرا نمی‌شوند.
  • auto_now به‌روزرسانی نمی‌شود.
  • فقط فیلدهای جدول اصلی مدل قابل تغییر هستند.

استفاده از F expressions در update:


Entry.objects.update(number_of_pingbacks=F("number_of_pingbacks") + 1)

محدودیت: در update نمی‌توان join ایجاد کرد:


Entry.objects.update(headline=F("blog__name"))  # خطا

جمع‌بندی

Q objects امکان ساخت کوئری‌های پیچیده را فراهم می‌کنند. حذف و کپی مدل‌ها نیازمند دقت در روابط است. update() برای به‌روزرسانی گروهی بسیار سریع است اما محدودیت‌هایی دارد. با درک این مفاهیم می‌توانید ORM جنگو را در سطح حرفه‌ای استفاده کنید.

۱. مقدمه: آبجکت‌های مرتبط در Django

هر زمان در یک مدل رابطه‌ای تعریف کنید (ForeignKey، OneToOneField یا ManyToManyField)، Django به‌طور خودکار APIهایی برای دسترسی به آبجکت‌های مرتبط ایجاد می‌کند.

مثلاً یک Entry می‌تواند با e.blog به Blog مرتبط دسترسی پیدا کند، و یک Blog می‌تواند با b.entry_set.all() تمام Entryهای مرتبط را دریافت کند.

۲. روابط یک‌به‌چند (ForeignKey)

دسترسی forward


e = Entry.objects.get(id=2)
e.blog

می‌توانید مقدار ForeignKey را تغییر دهید و سپس ذخیره کنید:


e.blog = some_blog
e.save()

اگر null=True باشد، می‌توانید None قرار دهید.

کش شدن دسترسی forward

اولین بار که FK را می‌خوانید، Query اجرا می‌شود؛ دفعات بعد از کش استفاده می‌شود:


print(e.blog)  # کوئری
print(e.blog)  # کش

select_related() کش را از قبل پر می‌کند:


e = Entry.objects.select_related().get(id=2)
print(e.blog)  # بدون کوئری

۳. دسترسی backward

برای دسترسی از مدل مقصد به مدل مبدا، Django یک Manager به نام FOO_set ایجاد می‌کند:


b = Blog.objects.get(id=1)
b.entry_set.all()
b.entry_set.filter(headline__contains="Lennon")
b.entry_set.count()

استفاده از related_name

می‌توانید نام پیش‌فرض را تغییر دهید:


blog = ForeignKey(Blog, related_name="entries")

سپس:


b.entries.all()

۴. استفاده از Reverse Manager سفارشی

می‌توانید مشخص کنید که برای رابطه معکوس از کدام Manager استفاده شود:


b.entry_set(manager="entries").all()

این امکان استفاده از متدهای سفارشی را فراهم می‌کند:


b.entry_set(manager="entries").is_published()

Prefetch با مدیر سفارشی


prefetch = Prefetch("entry_set", queryset=Entry.entries.all())
Blog.objects.prefetch_related(prefetch)

۵. متدهای مدیریت رابطه‌ها

Manager رابطه‌ها متدهای زیر را ارائه می‌دهد:

  • add(obj1, obj2, ...) – افزودن آبجکت‌ها
  • create(**kwargs) – ساخت و افزودن آبجکت جدید
  • remove(obj1, obj2, ...) – حذف آبجکت‌ها
  • clear() – حذف همه آبجکت‌های مرتبط
  • set(objs) – جایگزینی کل مجموعه

مثال:


b.entry_set.set([e1, e2])

تمام این عملیات‌ها فوراً در دیتابیس ذخیره می‌شوند.

۶. روابط چندبه‌چند (ManyToMany)

دسترسی از هر دو طرف:


e = Entry.objects.get(id=3)
e.authors.all()

a = Author.objects.get(id=5)
a.entry_set.all()

استفاده از related_name

اگر در Entry بنویسید:


authors = ManyToManyField(Author, related_name="entries")

آنگاه:


a.entries.all()

پشتیبانی از primary key در add/set/remove


a.entry_set.set([e1, e2])
a.entry_set.set([e1.pk, e2.pk])

جمع‌بندی

Django ORM یک API قدرتمند برای مدیریت روابط ارائه می‌دهد. دسترسی forward و backward، استفاده از related_name، مدیرهای سفارشی و متدهای add/remove/set/clear کار با داده‌های مرتبط را بسیار ساده و قابل‌اعتماد می‌کند. درک این مفاهیم برای ساخت اپلیکیشن‌های حرفه‌ای ضروری است.

۱. فیلتر کردن روی روابط چندبه‌چند (Many-to-Many)

هنگام استفاده از filter() روی روابط many-to-many باید دقت کنید، زیرا Django فقط یک بار join بین مدل اصلی و جدول واسط را انجام می‌دهد. این باعث ایجاد یک رفتار محدودکننده یا «sticky» می‌شود.

مثال:

فرض کنید یک Entry توسط دو نویسنده نوشته شده است:


anna.entry_set.filter(authors__name="Gloria")

انتظار داریم Entry مشترک برگردد، اما QuerySet خالی است. دلیل:

  • join فقط یک بار انجام می‌شود،
  • هیچ ردیفی در جدول واسط وجود ندارد که همزمان نویسنده Anna و Gloria باشد.

راه‌حل: دو فیلتر جداگانه


anna.entry_set.filter().filter(authors__name="Gloria")

این کار دو join جداگانه ایجاد می‌کند و نتیجه صحیح را برمی‌گرداند.

رفتار exclude()

exclude() نیز در این حالت sticky است:


anna.entry_set.exclude(authors__name="Gloria")

نتیجه اشتباه است و Entry را حذف نمی‌کند. اما با دو exclude پشت سر هم:


anna.entry_set.exclude().exclude(authors__name="Gloria")

نتیجه درست می‌شود.

۲. روابط یک‌به‌یک (One-to-One)

روابط یک‌به‌یک مشابه ForeignKey هستند، اما هر طرف فقط یک آبجکت مرتبط دارد.

مثال:


class EntryDetail(models.Model):
    entry = models.OneToOneField(Entry, on_delete=models.CASCADE)
    details = models.TextField()

دسترسی forward:


ed.entry

دسترسی backward:


e.entrydetail

اگر آبجکت مرتبط وجود نداشته باشد، خطای DoesNotExist رخ می‌دهد.

۳. چگونه روابط backward ایجاد می‌شوند؟

Django فقط نیاز دارد رابطه را در یک سمت تعریف کنید. سمت دیگر به‌طور خودکار ساخته می‌شود.

این کار توسط app registry انجام می‌شود:

  • Django تمام اپ‌های INSTALLED_APPS را بارگذاری می‌کند،
  • مدل‌ها را می‌خواند،
  • برای هر رابطه، دسترسی معکوس را ایجاد می‌کند،
  • اگر مدل هنوز بارگذاری نشده باشد، رابطه را بعداً اضافه می‌کند.

بنابراین تمام مدل‌ها باید در INSTALLED_APPS باشند.

۴. Query گرفتن از آبجکت‌های مرتبط

برای فیلتر کردن بر اساس رابطه، می‌توانید از خود آبجکت یا primary key آن استفاده کنید.

سه Query یکسان:


Entry.objects.filter(blog=b)
Entry.objects.filter(blog=b.id)
Entry.objects.filter(blog=5)

۵. استفاده از SQL خام

اگر Query شما بسیار پیچیده باشد و ORM نتواند آن را بسازد، Django اجازه استفاده از SQL خام را می‌دهد.

برای این کار از قابلیت‌های raw SQL استفاده کنید.

به یاد داشته باشید: Django فقط یک رابط است؛ دیتابیس شما مستقل است و می‌توانید با ابزارهای دیگر نیز به آن دسترسی داشته باشید.

جمع‌بندی

فیلتر کردن روی روابط many-to-many نیازمند دقت است، زیرا join فقط یک بار انجام می‌شود و ممکن است نتایج غیرمنتظره ایجاد کند. روابط یک‌به‌یک مشابه ForeignKey هستند اما فقط یک آبجکت مرتبط دارند. Django با استفاده از app registry روابط backward را ایجاد می‌کند. همچنین می‌توانید از SQL خام برای Queryهای پیچیده استفاده کنید.

نوشته و پژوهش شده توسط دکتر شاهین صیامی