آموزش جنگو : جلسه سی و سه | آشنایی با تمامی متد های جنگو | پارت اول

در این جلسه انواع متد ها و نحوه عملکرد آنها را در جنگو بررسی خواهیم کرد . با ما همراه باشید .

آموزش جنگو : جلسه سی و سه | آشنایی با متد های Queryset | پارت اول
آموزش جنگو : جلسه سی و سه | آشنایی با متد های Queryset | پارت اول


بررسی متد هایی که QuerySet جدید بازمیگردانند

متد ها از نظر انواع به دو دسته تقسیم می شوند . متد هایی که یک QuerySet جدید بازمیگردانند و متد هایی که نتایج دیتابیس را به صورت مستقیم بازمیگردانند . جنگو طیف وسیعی از روش های بازگرداندن QuerySet را ارائه می دهد . در بخش های پیشین گفتیم که می خواهیم در قسمت های بعدی به طور کامل درباره متد هایی که یک QuerySet جدید بازمیگردانند صحبت کنیم . دانستن این متد ها به شما کمک می کند تا بفهمید جنگو چطور کار می کند و چطور بهتر از آن استفاده کنید .

این متد ها سریعا در دیتابیس اجرا نمی شوند و برای استفاده در محیط asynchronous ایمن هستند .


متد filter :درباره این متد صحبت هایی کرده بودیم . این متد یک QuerySet جدید را بازمیگرداند که حاوی اشیایی است که با پارامتر های داده شده مطابقت دارند . پارامتر ها با دستور AND به هم اتصال داده می شوند و کد SQL مورد نظر را می سازند . همچنین برای اجرای جستجوی های پیچیده تر می توانید از Q (قبلا صحبت کرده ایم) در پارامتر ها استفاده کنید .


متد exclude :این متد یک QuerySet جدید را بازمیگرداند که حاوی اشیایی است که با پارامتر های داده شده مطابقت ندارند . دستور SQL آن به صورتی تولید می شود که پارامتر ها با AND اتصال داده شدند و کل دستور در ()NOT محصور شده است .

به عنوان مثال , کد زیر تمام اشیایی را پیدا می کند که date_pub آنها برای سال 2005-1-3 و بعد از آن نیست و headline آنها برابر با Hello نیست .

Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3), headline='Hello')

کد SQL تولید شده برای کد بالا به صورت زیر است :

SELECT ... WHERE NOT (pub_date > '2005-1-3' AND headline = 'Hello')

مثال زیر ابتدا تمام اشیایی را پیدا می کند که date_pub آنها بزرگتر از تاریخ 2005-1-3 نباشد . سپس از میان آنها اشیایی را بازمیگرداند که headline آنها برابر با Hello نباشد .

Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3)).exclude(headline='Hello')

در SQL به صورت زیر تبدیل خواهد شد :

SELECT ... WHERE NOT pub_date > '2005-1-3' AND NOT headline = 'Hello

همانطور که مشخص است , مثال دوم قرار است تعداد محدود تری از نتایج را به ما نشان دهد .


متد annotate : یک لیست حاوی از اشیا را به شکل QuerySet بازمیگرداند که بر اساس آرگومان ارسالی شما مرتب شده اند . آرگومان شما برای مقایسه می تواند یک مقدار ساده , ارجاع به یک فیلد از همان مدل یا روابط و یا یک عبارت مقایسه ای (مانند averages یا sums) باشد .

هر آرگومان در این متد یک مقایسه است که به هر شی در QuerySet بازگشت داده شده به شما اضافه شده است .

متدهایی که برای میانگین گیری و aggregation استفاده می شوند , در مثال های بعدی توضیح خواهند داده شد .

برای استفاده از Annonation خود , باید از کلمات کلیدی ای به عنوان نام مستعار (نامی که آن را صدا می زنید) استفاده کنید . بطور پیش فرض و برای زمانی که نمی خواهید نامی را به متد خود اختصاص دهید , از ترکیب نام تابع و فیلد مورد استفاده , بهره می برید . البته به یاد داشته باشید این کار را فقط برای متد هایی می توانید انجام دهید که به یک فیلد منفرد اشاره کنند و برای هر چیز دیگری باید یک آرگومان کلمه کلیدی را اختصاص دهید .

برای مثال فرض کنید که می خواهید به یک فهرست از وبالگ های خود دسترسی پیدا کنید تا مشخص کنید در هر blog چند entry ساخته شده است : (در اینجا از آرگومان کلمه کلیدی استفاده نکردیم زیرا به یک فیلد منفرد اشاره کرده ایم)

>>> from django.db.models import Count 
>>> q = Blog.objects.annotate(Count('entry')) 
# The name of the first blog 
>>> q[0].name 'Blogasaurus' 
# The number of entries on the first blog 
>>> q[0].entry__count 
42

اما در صورتی که فیلد های شما در ورودی منفرد نبودند , باید یک آرگومان کلمه کلیدی را تعریف کنید و سپس به وسیله آن , کنترل کردن عملیات ها را انجام بدهید .

>>> q = Blog.objects.annotate(number_of_entries=Count('entry')) 
# The number of entries on the first blog, using the name provided 
>>> q[0].number_of_entries
42


متد alias : کارایی یکسانی با متد annotate دارد . اما بجای اینکه اشیا را در QuerySet مرتب کند , آنها را در QuerySet با متد های مختلف در خودش با ترتیب ذخیره می کند . این برای زمانی مفید است که بخواهید نتایج را بجای استفاده کردن , فیلتر کنید یا به عنوان بخشی از یک جستجو از آن استفاده کنید همچنین استفاده از این متد باعث می شود کار دیتابیس سبک تر بوده و برنامه بهتری را ارائه دهید .

برای مثال فرض کنید شما می خواهید شی های Blog را پیدا کنید که بیشتر از 5 عدد entry را درون خود جای داده اند (یعنی با 5 entry رابطه دارند) ولی به جزییات و تعداد دقیق entry نیاز ندارید . کد زیر را می توانید برای این کار بنویسید :

>>> from django.db.models import Count 
>>> blogs = Blog.objects.alias(entries=Count('entry')).filter(entries__gt=5)

متد ()alias را می توان همراه با ()annotate،() exclude،() filter،() by_order و ()update استفاده کرد. برای استفاده از عبارت پیچیده با متد های دیگر باید آن را با یک Annonation ترکیب کنید .

Blog.objects.alias(entries=Count('entry')).annotate( 
    entries=F('entries'),
).aggregate(Sum('entries'))


متد order_by :به صورت پیش فرض ,مرتب کردن اشیا در جنگو با استفاده از تاپلی که درون گزینه های متا (Meta) تعریف شده است ,صورت می گیرد . گرچه شما می توانید این فرایند را در یک QuerySet با متد order_by بازنویسی کنید . یعنی گزینه های متا را نادیده بگیرید و بر اساس آرگومان های درون این متد مرتب سازی اشیا را انجام دهید .

مثال :

Entry.objects.filter(pub_date__year=2005).order_by('-pub_date', 'headline')

نتایج بالا بر اساس فیلد pub_date بر اساس نزولی و سپس بر اساس فیلد headline با ترتیب صعودی مرتب می شوند. علامت منفی پشت فیلد (به عنوان مثال pub_date-) نشان دهنده نزولی بودن ترتیب فیلد است . همچنین برای تصادفی کردن مرتب سازی از ? استفاده می شود .

Entry.objects.order_by('?')
نکته : فرایند استفاده از ? وابسته به دیتابیسی که استفاده می کنید ممکن است سنگین باشد .

برای مرتب سازی بر اساس یک فیلد از یک مدل متفاوت ,از همان روشی استفاده کنید که در جست و جو ها استفاده می کنید . یعنی نام فیلد مرتبط و به دنبال آن یک زیر خط دوجفتی __ و به دنبال آن نام فیلد در مدل جدید . برای مثال :

Entry.objects.order_by('blog__name', 'headline')

اگر بخواهید بر اساس فیلدی که مربوط به مدل دیگری است مرتب سازی را انجام دهید , جنگو از ترتیب پیش فرض تعیین شده در Meta در آن مدل استفاده می کند . اگر این گزینه مشخص نشده باشد , از طریق primary key در آن مدل مرتب سازی را انجام می دهد . پس از آنجایی که مدل Blog هیچ گزینه ordering خاصی را در متای خود مشخص نکرده است :

Entry.objects.order_by('blog')

مثال بالا ,مساوی است با مثال زیر :

Entry.objects.order_by('blog__id')

اگر مدل Blog دارای ordering=[“name”] در گزینه های متای خود بود آنگاه مثال بالا به شکل زیر تغییر میافت :

Entry.objects.order_by('blog__name')

شما همچنین می توانید با استفاده از متد های desc و asc ,مرتب سازی را با query expressions نیز انجام دهید :

Entry.objects.order_by(Coalesce('summary', 'headline').desc())

درباره asc و desc قبلا توضیح دادیم . آنها برای نزولی کردن یا صعودی کردن ترتیب به کار می روند . همچنین دارای آرگومان های null_last و nulls_first هست که نحوه مرتب سازی مقادیر null را کنترل می کند . (اولی null را در آخر مرتب می کند و دومی در ابتدای لیست خروجی)

نکته : شما می توانید همچنین یک فیلد multi-value یا فیلدی مانند یک ForeignKey یا ManyToManyField (که چند مقدار را درون خود دارند) را به عنوان آرگومان برای مرتب سازی به متد ارسال کنید . برای مثال :
class Event(Model):
    parent = models.ForeignKey(
        'self',
        on_delete=models.CASCADE,
        related_name='children',
    )
    date = models.DateField()
  
Event.objects.order_by('children__date')

شما باید در استفاده از اینگونه فیلد ها در order_by مراقب باشید . برای نمونه ,در مثال بالا هر شی از مدل Event با چند شی از همان مدل رابطه داشته باشد ,بنابراین بر اثر این تکرار ممکن است چرخه ای به وجود بیاید و QuerySet شما تعداد اشیایی بیشتر از حتی خود اشیا مدل بازگرداند ! این عملیات اشتباه است و اصلا فایده ای ندارد .

بنابراین فقط اطمینان حاصل کنید که هر شی یکبار در QuerySet تکرار شده است و رویکرد شما مشکلی ندارد .

شما هیچ راهی ندارید تا اشیا را بر اساس بزرگ و کوچکی حروف مرتب کنید . گرچه خود جنگو به کوچک یا بزرگ بودن حساسیت نشان می دهد و نتایج را صحیح بازمیگرداند .

می‌توانید بر اساس فیلدی که با Lower به حروف کوچک تبدیل شده است سفارش دهید که به ترتیب مطابق با حروف بزرگ می‌رسد :

Entry.objects.order_by(Lower('headline').desc())
نکته :اگر نمی خواهید روی QuerySet شما هیچ مرتب سازی ای صورت بگیرد ,حتی مرتب سازی پیش فرض (در ordering کلاس متا مدل) ,فقط کافی است تا متد order_by را بدون هیچ آرگومانی فراخوانی کنید . همچنین اگر می خواهید بفهمید که آیا یک QuerySet مرتب سازی شده است یا خیر از ویژگی QuerySet.ordered استفاده کنید که اگر برابر با True باشد ,یعنی QuerySet شما از قبل مرتب سازی شده است .

هر بار متد های order_by متد قبلی خود را نادیده می گیرند . یعنی در مثال زیر اشیا بر اساس pub_date مرتب می شوند و نه headline :

Entry.objects.order_by('headline').order_by('pub_date')
نکته : مرتب سازی یک عملیات ساده بر روی دیتابیس نیست و هر بار که اشیایی را مرتب می کنید ,فرایند سنگینی بر روی دیتابیس اعمال می شود .

علاوه بر این اگر یک ordering را مشخص نکنید ,اشیا با ترتیب مشخصی به شما بازگردانده نمی شوند و این ممکن است برای شما کمی دردسر ساز باشد . یک ترتیب خاص تنها زمانی تضمین می شود که یک مرتب سازی بر روی فیلدی منحصر به فرد از آن انجام پذیرد . (دلیل آن این است که اگر دو فیلد مقادیر یکسان داشته باشند ,متد order_by معلوم نیست که هر بار کدام یک از آنها را ابتدا مرتب می کند)


متد reverse :از متد reverse برای معکوس کردن ترتیب اشیای یک Query استفاده می شود . این یعنی ترتیب نتایج را دقیقا برعکس می کند . فراخوانی این متد برای بار دوم روی یک Query ترتیب را به حالت اولیه بازمیگرداند .

برای مثال ,برای دریافت 5 شی آخر در یک Query به شکل زیر اقدام کنید :

my_queryset.reverse()[:5]

توجه کنید که کد بالا دقیقا مانند یک برش لیست یا slice کردن یک لیست در پایتون نیست . در واقع مثال بالا ,ابتدا آخرین شی ,سپس شی ماقبل آن و در غیره را بازمیگرداند . اما اگر یک slice داشتیم ابتدا شی پنجم را میدیدیم . جنگو از slice به شکل reverse پشتیبانی نمی کند زیرا در SQL امکان پذیر نیست .

همچنین ,توجه داشته باشید که reverse باید بر روی QuerySet ای اجرا شود که دارای ترتیب است (ترتیب پیش فرض در کلاس متا یا استفاده از متد order_by) . در غیر این صورت فراخوانی متد reverse روی آن تاثیری نخواهد داشت .


متد distinct : یک QuerySet جدید را برمی‌گرداند که از SELECT DISTINCT در کد SQL خود استفاده می‌کند. این کار ردیف های (row) تکراری را از نتایج QuerySet حذف می کند .

به طور پیش فرض، یک QuerySet ردیف های تکراری را حذف نمی کند . در عمل ، این به ندرت مشکل ساز است، زیرا QuerySet های ساده مانند Blog.objects.all امکان تکرار ردیف های نتیجه را ندارند . با این حال، اگر QuerySet شما از چندین جدول استفاده می کند ، زمانی که یک QuerySet اجرا می شود، ممکن است نتایج تکراری دریافت کنید. در آن زمان است که از distinct استفاده می کنید.

نکته : در دیتابیس PostgreSQL میتوانید به متد distinct آرگومان هایی را ارسال کنید (نام فیلدها) . این آرگومان ها نام فیلد هایی هستند که دستور DISTINCT فقط برای آنها باید اعمال شود . در حالت عادی این دستور در SQL تمام سطر ها یا row را جستجو می کند و آیتم های تکراری را در هر کدام حذف می کند , اما با استفاده از آرگومان ها ,دستور SQL شما به شکل SELECT DISTICNT ON درخواهد آمد و روی فیلد های انتخاب شده شما اجرا خواهد شد . (یعنی فقط در همان سطر ها به دنبال آیتم های تکراری خواهد بود)
نکته : وقتی نام فیلد ها را در متد مشخص می کنید باید متد order_by را نیز برای QuerySet ارائه دهید . همچنین ترتیب شروع نام فیلد ها در هر دو متد باید یکسان باشند . برای مثال :
>>> Author.objects.distinct()
[...]
>>> Entry.objects.order_by('pub_date').distinct('pub_date')
[...]
>>> Entry.objects.order_by('blog').distinct('blog')
[...]
>>> Entry.objects.order_by('author', 'pub_date').distinct('author', 'pub_date')
[...]
>>> Entry.objects.order_by('blog__name', 'mod_date').distinct('blog__name', 'mod_date')
[...]
>>> Entry.objects.order_by('author', 'pub_date').distinct('author')
[...]

توجه داشته باشید که در فیلد های روابطی ,فراخوانی آنها در متد order_by باعث می شود تا از فیلدی برای آرگومان استفاده کنند که در گزینه های متا و ordering آنها تعریف شده است . پس ممکن است استفاده از دو متد order_by و distinct با هم باعث ایجاد مشکلاتی شود . این مشکلات زمانی ایجاد می شوند که ordering و نام فیلد مورداستفاده از مدل ارجاع داده شده ,یکسان نباشد . برای مثال اگر مدل Blog یک ordering را بر اساس فیلد name خود تعریف کند :

Entry.objects.order_by('blog').distinct('blog')

کد بالا با شرط بالا کار نخواهد کرد . زیرا در متد order_by شما از فیلد name برای مرتب سازی استفاده می کنید و در متد distinct با فیلد id کار می کنید . برای حل این مشکل می توانید به صورت زیر , خیلی صریح به فیلد های مدل ارجاع داده شده اشاره کنید .

Entry.objects.order_by('blog__id').distinct('blog__id')


متد values :یک QuerySet را در صورت استفاده به صورت یک iterable بازمیگرداند که به جای اشیا یک مدل ,دیکشنری هایی را دارا است که هر کدام یک شی از همان مدل هستند .

آنها به صورت Key-value هستند و هر Key یک فیلد را از شی مشخص می کند که مقدار آن نیز در value مشخص شده است .

مثال زیر تفاوت استفاده از values را در یک QuerySet با استفاده نکردن از آن نشان می دهد .

# This list contains a Blog object.
>>> Blog.objects.filter(name__startswith='Beatles')
<QuerySet[<Blog: Beatles Blog>]>
# This list contains a dictionary.
>>> Blog.objects.filter(name__startswith='Beatles').values()
<QuerySet[{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]>

همچنین این متد آرگومان هایی را تحت عنوان fields* دریافت می کند که نام فیلد هایی از مدل هستند و تعیین میکنند که دستور SELECT روی کدام فیلد ها اجرا شود . در واقع پس از تعیین فیلد ها , در دیکشنری های بازگشت داده شده فقط فیلد هایی که انتخاب کردید , وجود دارند (و مقدارهایشان) . اگر این آرگومان را مشخص نکنید , جنگو تمام فیلد ها را در خروجی نمایش می دهد .

برای مثال :

>>> Blog.objects.values()
<QuerySet[{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]>
>>> Blog.objects.values('id', 'name')
<QuerySet[{'id': 1, 'name': 'Beatles Blog'}]>

همچنین شما می توانید مقادیر keyword argument را برای متد مشخص کنید که به عنوان annotate در متد اجرا می شوند . برای مثال :

>>> from django.db.models.functions import Lower
>>> Blog.objects.values(lower_name=Lower('name'))
<QuerySet[{'lower_name': 'beatlesblog'}]>

نکته :اگر شما بر فرض مثال یک فیلد ForeignKey داشته باشید که نام آن foo باشد ,استفاده از متد values برای آن باعث می شود تا مقدار foo_id در خروجی نشان داده شود . این در صورتی قابل تغییر است که در ارگومان fields* ذکر کنید دقیقا کدام فیلد از مدل ارجاع داده شده را نیاز دارید . (مثال foo__name)

به مثال صفحه بعد خوب دقت کنید . می بینید که استفاده از نام فیلد بدون id_ دوباره به شما فیلد id را از مدل ارجاع داده شده بازمیگرداند این یک پیش فرض برای جنگو است :

>>> Entry.objects.values()
<QuerySet[{'blog_id': 1, 'headline': 'First Entry', ...}, ...]>
>>> Entry.objects.values('blog')
<QuerySet[{'blog': 1}, ...]>
>>> Entry.objects.values('blog_id')
<QuerySet[{'blog_id': 1}, ...]>

نکته :به یاد داشته باشید شما همچنان می توانید بعد از فراخوانی values زنجیره ای را ایجاد کنید . (یعنی از متد هایی مانند order_by یا filter استفاده کنید) . برای مثال دو کد زیر با هم نتایج یکسانی دارند :

Blog.objects.values().order_by('id')
Blog.objects.order_by('id').values()
نکته : همچنین به این نکته توجه داشته باشید که در دیتابیس SQLite و فیلد های JSONField شما به علت دلایلی مانند نحوه عملکرد JSON_TYPE و یا JSON_EXTRACT (دستور های SQL) ,مقادیر True و False و None را بجای “true” , “false”و “null”در صورت استفاده از values دریافت خواهید کرد .


متد values_list :همان values است که به جای بازگرداندن دیکشنری ,به جای هر شی یک تاپل حاوی مقادیر فیلد های انتخاب شده توسط شما بازمیگرداند . نکته این است که مقادیر داخل تاپل به ترتیب فیلد های انتخاب شده شما هستند که در آرگومان متد مشخص می کنید . برای مثال :

>>> Entry.objects.values_list('id', 'headline')
<QuerySet[(1, 'First entry'), ...]>
>>> from django.db.models.functions import Lower
>>> Entry.objects.values_list('id', Lower('headline'))
<QuerySet[(1, 'first entry'), ...]>
نکته : در صورتی که یک فیلد را در آرگومان ها استفاده کرده اید , می توانید مقدار flat را در آرگومان مشخص کنید . دو مقدار False و True را قبول می کند که اگر برابر با True باشد مقادیر را بجای ریختن درون تاپل به صورت اصلی خودشان بازمیگرداند . هر مقدار در واقع اشاره به فیلد یک شی دارد . برای مثال :
>>> Entry.objects.values_list('id').order_by('id')
<QuerySet[(1,), (2,), (3,), ...]>
>>> Entry.objects.values_list('id', flat=True).order_by('id')
<QuerySet[1, 2, 3, ...]>

اگر هنگام ارسال چند فیلد آرگومان flat را مشخص کنید ,با ارور مواجه خواهید شد .

شما همچنین می توانید آرگومان named=True را مشخص کنید و نتایج را به صورت namedtuple (مباحث پایتون 3.11) دریافت کنید . برای مثال :

>>> Entry.objects.values_list('id', 'headline', named=True)
<QuerySet[Row(id=1, headline='First entry'), ...]>

استفاده از namedtuple می تواند خواندن اطلاعات را از دیتابیس برای شما آسان تر کند . همچنین این فرایند کمتر دیتابیس را درگیر می کند .

اگر هیچ مقداری را درون values_list ارسال نکنید ,این متد تمام فیلد های مدل را به ترتیب تعریف شدن آنها در خود مدل به شما بازمیگرداند .

یک عملیات متداول این است که مقادیر را دریافت کنید و مقداری را از یک فیلد بخواهید که از یک شی خاص برگرفته شده است . برای پیدا کردن این شی و مقدار فیلد آن به صورت زیر از متد get پس از متد values_list استفاده کنید :

>>> Entry.objects.values_list('headline', flat=True).get(pk=1)
'First entry'

سازندگان جنگو می گویند که هدف اصلی از طراحی متد های values و values_list این بوده که بدون فراخوانی و ایجاد یک نمونه از کلاس مدل ,بتوانید به اطلاعات درون آن دسترسی پیدا کنید . اما این هدف و قانون برای استفاده از فیلد های روابطی در جنگو نقض می شود زیرا قانونی با نام اینکه در هر ردیف یک شی باشد در این شرایط وجود ندارد .

برای مثال ,در کد زیر ما فیلد ManyToManyField را به صورت آرگومان فرا خوانده ایم و مقدار فیلد headline را از آن برداشته ایم :

>>> Author.objects.values_list('name', 'entry__headline')
<QuerySet[('Noam Chomsky', 'Impressions of Gaza'),
    ('George Orwell', 'Why Socialists Do Not Believe in Fun'),
    ('George Orwell', 'In Defenceof English Cooking'),
    ('Don Quixote', None)]>

اشیا مدل Author که با چندین شی از entry در رابطه هستند ,چندین بار نمایش داده می شوند ولی آنهایی که با هیچ کدام در رابطه نیستند ,مقدار None را دارند .

به همین ترتیب هنگام query زدن برای یک ForeignKey به صورت معکوس ,شی های entry که با هیچ Author در ارتباط نیستند ,مقدار None را دارند .

برای مثال :

>>> Entry.objects.values_list('authors')
 <QuerySet[('Noam Chomsky',), ('George Orwell',), (None,)]>
نکته : همچنین به این نکته توجه داشته باشید که در دیتابیس SQLite و فیلد های JSONField شما به علت دلایلی مانند نحوه عملکرد JSON_TYPE و یا JSON_EXTRACT (دستور های SQL) ,مقادیر Trueو False و None را بجای “true” , “false” و “null” در صورت استفاده از values_list دریافت خواهید کرد .


متد dates :یک QuerySet بازمی گرداند که حاوی فهرستی از اشیا است که تاریخ های موجود در محتویات QuerySet را به بخش های خاص (سال و ماه و روز و هفته) تقسیم می کند و شما تعیین می کنید که کدام یک از بخش ها به شما نمایش داده شود .

فیلد ارسال شده در آرگومان این متد باید یک DateField باشد و نوعی که تعیین می کنید باید یکی از year (سال) , month (ماه) , week (هفته) , day (روز) باشد . هر کدام را که تعیین کنید ,شی ها همان بخش ها را از تاریخ بازمیگردانند . البته توجه داشته باشید که فرمت های متفاوتی برای هر نوع در نظر گرفته شده است .

نوع Year :یک لیست از اشیایی تهیه می کند که سال های آن متفاوت است . (برای مثال اگر 2 شی با سال 2005 داشته باشید ,فقط یکبار سال 2005 را نمایش می دهد)

نوع Month : یک لیست از اشیایی تهیه می کند که ماه های آن متفاوت است . (برای مثال اگر 2 شی با ماه 2 داشته باشید , فقط یکبار ماه 2 را نمایش می دهد)

نوع Week :به شکل بالایی است ولی برای هفته . این را در نظر بگیرید که هفته های آنها بر اساس روز دوشنبه تعیین می شود.

نوع Day :یک لیست از اشیایی که روز های آنها متفاوت است . به زبانی دیگر تمام تاریخ های درون اشیا مدل .

در آخر شما می توانید order یا ترتیب آنها را بر اساس ASC یا DESC مرتب کنید . (نزولی و صعودی)

برای مثال :

>>> Entry.objects.dates('pub_date', 'year')
[datetime.date(2005, 1, 1)]
>>> Entry.objects.dates('pub_date', 'month')
[datetime.date(2005, 2, 1), datetime.date(2005, 3, 1)]
>>> Entry.objects.dates('pub_date', 'week')
[datetime.date(2005, 2, 14), datetime.date(2005, 3, 14)]
>>> Entry.objects.dates('pub_date', 'day')
[datetime.date(2005, 2, 20), datetime.date(2005, 3, 20)]
>>> Entry.objects.dates('pub_date', 'day', order='DESC')
[datetime.date(2005, 3, 20), datetime.date(2005, 2, 20)]
>>> Entry.objects.filter(headline__contains='Lennon').dates('pub_date', 'day')
[datetime.date(2005, 3, 20)]


متد datetimes :یک QuerySet را بازمیگرداند که حاوی اشیایی از کلاس datetime.datetime است و همه تاریخ های موجود از یک نوع خاص را در یک مدل نمایش می دهد .

یک آرگومان اجباری field_name دارد که باید نام یک DateTimeField از مدل شما باشد .

نوع انتخابی نیز باید یکی از year (سال) , month (ماه) , week (هفته) , day (روز) ,ساعت (hour) ,دقیقه (minute) , ثانیه (second) باشد . هر شی و تاریخ آن با توجه به نوع انتخابی شما ,تفسیر و بازگردانده می شود .

همچنین ترتیب آن به طور پیش فرض روی ASC است . دو مقدار ASC و DESC برای این QuerySet موجود هستند . (نزولی و صعودی)


متد none : این متد زمانی استفاده می شود که می خواهید یک QuerySet خالی داشته باشید . در واقع این QuerySet هیچ شی ای را به شما بازنمیگرداند و هنگام دسترسی به آن روی دیتابیس اجرا نمی شود . همچنین QuerySet شما و یا qs.none یک شی از کلاس EmptyQuerySet خواهد شد .

برای مثال :

>>> Entry.objects.none()
<QuerySet[]>
>>> from django.db.models.query import EmptyQuerySet
>>> isinstance(Entry.objects.none(), EmptyQuerySet)
True


متد all : یک کپی از QuerySet فعلی را بازمیگرداند . همچنین تمام اشیا یک مدل را به شما بازمیگرداند . این زمانی مفید است که یا همه آنها را نیاز داشته باشید و یا بخواهید در یک کپی از QuerySet اصلی به وسیله manager فیلتر کردن اشیا را انجام بدهید .

نکته :هنگامی که یک QuerySet در دیتابیس اجرا می شود ,معمولا نتایج خود را در کش ذخیره خواهد کرد . اگر اطلاعات دیتابیس از زمان ذخیره سازی کش تغییر کرده باشند ,می توانید با فراخوانی all آنها رو دوباره به روز کنید.


متد union :از دستور UNION در SQL استفاده می کند تا دو QuerySet را با هم ترکیب کند . برای مثال اگر نام دو QuerySet ما qs2 و qs3 باشد ,داریم :

>>> qs1.union(qs2, qs3)

عملگر UNION به طور پیش فرض فقط اشیا متمایز را بازمیگرداند (یعنی اشیا تکراری را حذف می کند) . برای بازگرداندن مقادیر تکراری از آرگومان all=True استفاده کنید .


متد intersection : از INTERSECT در SQL استفاده می کند تا اشیا مشترک بین دو QuerySet را بازگرداند . برای مثال :

>>> qs1.intersection(qs2, qs3)


متد difference :از دستور EXCEPT در SQL استفاده می کند تا اشیایی که در QuerySet شما وجود دارند ولی در QuerySet هایی که در آرگومان ارسال شده اند وجود ندارند را بازگرداند . برای مثال :

>>> qs1.difference(qs2, qs3)

در کد بالا qs1 , QuerySet اولی حساب می شود و اشیایی که در qs1 وجود دارند ولی در qs2 و qs3 وجود ندارند را بازمیگرداند .


متد select_related : فیلدی را از نوع فیلد های روابطی به آن می دهید (مثال ForeignKey) و سپس اطلاعات داخل شی های مدل ارجاع داده شده را ذخیره می کند . کاربرد این متد این است که در حالت ساده استفاده از روابط دیتابیسی ,احتمالا چندین بار روی دیتابیس عملیاتی انجام بدهد و کار دیتابیس را سنگین کند . اما در صورت استفاده از این متد فقط یکبار دیتابیس عملیات جستجو را انجام می دهد و تمام روابط درون جنگو ذخیره می شوند .

در اینجا تفاوت حالت ساده و متد select_related را می بینید . ابتدا نگاهی به استفاده ساده بیاندازید . در حالت اول دو بار روی دیتابیس عملیات صورت میگیرد :

# Hits the database.
e = Entry.objects.get(id=5)
# Hits the database again to get the related Blog object.
b = e.blog

در حالت دوم از متد select_related استفاده می کنیم . در اینجا فقط یکبار دیتابیس عملیات جستجو را انجام می دهد :

# Hits the database.
e = Entry.objects.select_related('blog').get(id=5)
# Doesn't hit the database, because e.bloghas been prepopulated
# in the previous query.
b = e.blog

همچنین شما می توانید روی هر نوع QuerySet این متد را استفاده کنید . برای مثال در کد زیر ,یک set را با نام blogs تعریف کرده ایم . در این متغییر قرار است اطلاعات شی blog آنها در صورتی که pub_date آنها مربوط به آینده باشد ,قرار بگیرد :

from django.utils import timezone
# Find all the blogs with entries scheduled to be published in the future.
blogs = set()

for e in Entry.objects.filter(pub_date__gt=timezone.now()).select_related('blog'):
    # Without select_related(), this would make a database query for each
    # loop iteration in order to fetch the related blog for each entry.
    blogs.add(e.blog)

همچنین تفاوتی ندارد که ابتدا کدام را قرار می دهید (filter یا select_related) بنابراین دو کد زیر با هم نتیجه یکسانی دارند :

Entry.objects.filter(pub_date__gt=timezone.now()).select_related('blog')
Entry.objects.select_related('blog').filter(pub_date__gt=timezone.now())

البته نکته ای قابل توجه باقی مانده است . فرض کنید که مدل های زیر را نوشته اید :

from django.db import models

class City(models.Model):
    # ...
    pass

class Person(models.Model):
    # ...
    hometown = models.ForeignKey(
        City,
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
    )

class Book(models.Model):
    # ...
    author = models.ForeignKey(Person, on_delete=models.CASCADE)

حال کد Book.objects.select_related(‘author__hometown’).get(id=4) را بنویسید . این کد برای شما اشیایی از مدل های Person و City را که با آنها رابطه دارید ,در حافظه کش ذخیره می کند . بنابراین شما می توانید به هر دوی آنها دسترسی داشته باشید و آنها روی دیتابیس دوباره عملیاتی را انجام نمی دهند . (منظور این است که تودرتو بودن باعث نمی شود تا بعضی از بخش ها را نتواند کش کند)

# Hits the database with joins to the author and hometown tables.
b = Book.objects.select_related('author__hometown').get(id=4)
p = b.author # Doesn't hit the database.
c = p.hometown # Doesn't hit the database.

# Without select_related()...
b = Book.objects.get(id=4) # Hits the database.
p = b.author # Hits the database.
c = p.hometown # Hits the database.

شما می توانید به هر فیلد ForeignKey یا OneToOneField که در لیست پارامتر های ارسالی به متد select_related است ,مراجعه کنید (یعنی از آن استفاده گنید مانند فیلد hometown و author) .

همچنین می توانید به شکل reverse یا بالعکس به یک OneToOneField دسترسی داشته باشید (OneToOneField که در لیست پارامتر های متد باشد) . یعنی می توانید از شی ارجاع داده شده به شی ای که فیلد OneToOneField در آن تعریف شده است ,دسترسی پیدا کنید . برای این کار ,به جای تعیین نام فیلد ,از related_name برای فیلد شی ارجاع داده شده استفاده کنید . (فقط OneToOneField)

البته در بعضی از شرایط ممکن است بخواهید select_related را برای اشیا زیادی فراخوانی کنید یا همه روابط مورد نیاز را ندانید . در این شرایط میتوانید select_related را بدون هیچ پارامتری فراخوانی کنید . این کار تمام ForeignKey هایی که null نباشند را پیدا می کند و آنها را ذخیره می کند . البته این کار توصیه نمی شود زیرا در هنگام استفاده از اطلاعات پیچیدگی زیادی دارد و داده های بیشتری را از آنچه مورد نیاز است ممکن است بازگرداند .

اگر می خواهید اطلاعات کش شده را که با Query قبلی در QuerySet ذخیره شده است ,پاک کنید فقط کافی است None را به عنوان پارامتر به متد ارسال کنید :

>>> without_relations = queryset.select_related(None)


متد prefetch_related : این متد نیز همان کار select_related را انجام می دهد اما به روشی متفاوت . متد select_related جداول دیتابیس که با هم رابطه دارند را به وسیله SQL به هم متصل نگه می دارد و روی ManyToManyField(بخاطر سنگینی و پیچیده شدن روابط) کار نمی کند . اما متد prefetch_related جداول را با پایتون به یکدیگر اتصال می دهد و همین باعث می شود تا روی ManyToManyField کارایی داشته باشد .

بسیار خب , فرض کنید مدل های زیر را نوشته اید .

from django.db import models

class Topping(models.Model):
    name = models.CharField(max_length=30)

class Pizza(models.Model):
    name = models.CharField(max_length=50)
    toppings = models.ManyToManyField(Topping)

    def __str__(self):
        return &quot%s (%s)&quot %(
            self.name,
            &quot, &quot.join(topping.name for topping in self.toppings.all()),
        )

حال اگر کد زیر را برای مدل های بالا اجرا کنیم :

>>> Pizza.objects.all()
[&quotHawaiian (ham, pineapple)&quot, &quotSeafood (prawns, smoked salmon)&quot...

مشکل این است که هر بار که ()__Pizza.__str برای بازگرداندن رشته اش کد self.toppings.all را اجرا می کند ,پایگاه داده مورد استفاده قرار می گیرد . این باعث می شود تا هر شی در Pizza.objects.all یکبار روی دیتابیس عملیات اضافه انجام بدهد . (کار دیتابیس سنگین می شود)

شما می توانید با استفاده از prefetch_related این عملیات را به دو query کاهش بدهید :

>>> Pizza.objects.prefetch_related('toppings')

این کد برای شما مقدار self.toppings.all را و تمام روابط آن را کش می کند و هر بار که کد self.toppings.all اجرا می شود ,بجای انجام عملیات روی دیتابیس ,اشیا را از کش حافظه خود پیدا می کند .

همچنین هر Query اضافه برای این متد ,بعد از اینکه Query در دیتابیس اجرا شد و نتایج آن بازگشت اجرا می شود .

توجه کنید که استفاده از این متد باعث می شود تا کش QuerySet به طور کامل در حافظه کامپیوتر بارگذاری شود . این رفتار برای یک QuerySet عادی صورت نمی گیرد زیرا آنها سعی میکنند تا از بارگذاری نتایج قبل از نیاز به استفاده در حافظه جلوگیری کنند .

اجرا کردن یک QuerySet با متد prefetch_related باعث نمی شود تا QuerySet های بعدی که Query متفاوتی دارند ,نتایج خود را از کش بارگذاری کنند . در واقع اگر شما دو خط کد زیر را در نظر بگیرید :

>>> pizzas = Pizza.objects.prefetch_related('toppings')
>>> [list(pizza.toppings.filter(spicy=True)) for pizza in pizzas]

در اینجا Pizza.objects.prefetch_related در واقع برای شما نتایج را کش می کند ولی در خط بعدی pizza.toppings.filter شما یک Query جدید ساخته اید و این باعث می شود تا نتواند از نتایج کش شده استفاده کند. به همین علت دوباره روی دیتابیس عملیات جستجو انجام می دهد .

همچنین استفاده از متد های add , remove , clear و set و از این قبیل باعث می شود تا کش حافظه prefetch_related خالی شود . (زیرا نتایج باید آپدیت بشوند)

شما همچنین می توانید از سینتکس های ساده Join نیز برای دسترسی به related objects از طریق related objects انجام بدهید . برای مثال فرض کنید در مدل های بالا , مدل زیر نیز اضافه شده است :

class Restaurant(models.Model):
    pizzas = models.ManyToManyField(Pizza, related_name='restaurants')
    best_pizza = models.ForeignKey(Pizza, related_name='championed_by' 
    on_delete=models.CASCADE)

کد زیر را می توانید برای آن بنویسید :

>>> Restaurant.objects.prefetch_related('pizzas__toppings')

در کد بالا تمام اشیا pizza که متعلق به مدل Restaurant هستند (با آن رابطه دارند) + تمام فیلد های topping از تک تک اشیا pizza را کش خواهد کرد . در واقع نتایج حاصل سه Query هستند ,یکی برای Restaurant ,یکی برای pizza و یکی برای تمام فیلد های topping .

حال کد زیر را ببینید :

>>> Restaurant.objects.prefetch_related('best_pizza__toppings')

کد بالا نیز تمام best_pizza و تمام فیلد های topping برای آن در هر شی از Restuarant را کش می کند . این فرایند در سه Query انجام خواهد شد . یکی برای اشیا Restuarant و یکی برای اشیا best_pizza و دیگری برای فیلد های topping .

همچنین برای سبک تر کردن کار دیتابیس و رساندن Query به تعداد دو Query ,میتوانید برای best_pizza از select_related به شکل زیر استفاده کنید .

>>>Restaurant.objects.select_related('best_pizza').prefetch_related('best_pizza__toppings')

از آنجایی که ما قبل از prefetch_related از select_reated استفاده کرده ایم , prefetch_related می تواند تشخیص بدهد که اشیا best_pizza از قبل کش شده اند و دوباره آنها را کش نخواهد کرد .

اگر شما این متد ها را زنجیره کرده باشید و بخواهید آثار متد های قبلی را پاک کنید , فقط کافی است تا None را به عنوان پارامتر برای prefetch ارسال کنید .

>>> non_prefetched = qs.prefetch_related(None)
نکته : یکی از نکاتی که هنگام استفاده از prefetch_related باید به آن توجه کرد این است که اشیا یک مدل ممکن است میان اشیایی که با آنها رابطه دارند ,تکرار بشوند . در واقع این متد از تکرار اشیا پرهیز نمی کند و اشیا ممکن است دو بار در میان QuerySet بازگشتی دیده شوند .
نکته : همچنین کد prefetch_related در SQL با عملگر IN در واقع ترکیب می شود و عملیات جستجو را انجام می دهد . این به این معنی است که می توان برای یک QuerySet بزرگ یک عبارت IN بزرگ نیز تعریف کرد که ممکن است این کار باعث ایجاد مشکلات در عملکرد متد شود .

می توانید از شی Prefetch برای کنترل بیشتر این متد استفاده کنید (Django.db.models.Prefetch) . در ساده ترین حالت استفاده خالی از Prefetch به معنای جستجو مبنا بر یک رشته است . (همان نام فیلد ها)

>>> from django.db.models import Prefetch
>>> Restaurant.objects.prefetch_related(Prefetch('pizzas__toppings'))

شما می توانید یک QuerySet را به عنوان پارامتر به شی Pretetch بدهید . برای مثال می توانید از این روش برای تغییر ترتیب پیش فرض QuerySet اصلی استفاده کنید . (یعنی روی QuerySet اصلی شما اعمال می شود و باید با آن هماهنگ باشد)

>>> Restaurant.objects.prefetch_related(
...                       Prefetch('pizzas__toppings', queryset=Toppings.objects.order_by('name')))

یا می توانید select_related را به عنوان پارامتر ارسال کنید تا جستجو را روی QuerySet اصلی محدود تر کنید .

>>> Pizza.objects.prefetch_related(
...            Prefetch('restaurants', queryset=Restaurant.objects.select_related('best_pizza')))

شما می توانید همچنین از یک آرگومان با نام to_attr استفاده کنید . این آرگومان اختیاری است و باعث می شود تا نتایج QuerySet همان لحظه در لیستی با نام متغییری که به عنوان پارامتر برای این ارگومان ارسال کرده اید ,ذخیره شوند .

این به شما اجازه می دهد تا چندین بار یک QuerySet را به اصطلاح fetch کنید و هر بار پارامتر های ارسالی را تغییر دهید . برای مثال :

>>> vegetarian_pizzas = Pizza.objects.filter(vegetarian=True)
>>> Restaurant.objects.prefetch_related(
...                    Prefetch('pizzas', to_attr='menu'),
...                    Prefetch('pizzas', queryset=vegetarian_pizzas, to_attr='vegetarian_menu'))

علاوه بر این توصیه جنگو این است که استفاده از to_attr بهتر از تبدیل نتایج فیلترشده به لیست و پیمایش آنها است . زیرا عملیات دوم باعث می شود تا کش QuerySet کمی درهم ریخته شود . برای مثال :

>>> queryset = Pizza.objects.filter(vegetarian=True)

>>> # Recommended:
>>> restaurants = Restaurant.objects.prefetch_related(
...                 Prefetch('pizzas', queryset=queryset, to_attr='vegetarian_pizzas'))
>>> vegetarian_pizzas = restaurants[0].vegetarian_pizzas

>>> # Not recommended:
>>> restaurants = Restaurant.objects.prefetch_related(
...                   Prefetch('pizzas', queryset=queryset))
>>> vegetarian_pizzas = restaurants[0].pizzas.all()

معمولا برای فیلد های OneToOneField و ForeignKey از select_related استفاده می شود . اما شرایطی وجود دارد که باعث می شود بخواهید از prefetch_related برای این فیلد ها استفاده کنید :

  • شما می خواهید به فیلد های روابطی بیشتری دسترسی داشته باشید .
  • شما می خواهید فقط به زیرمجموعه ای محدود از روابط در نتایج دسترسی داشته باشید .
  • شما می خواهید از تکنیک های بهینه سازی مانند deferred fields (بعدا درباره این صحبت خواهیم کرد) استفاده کنید.

متد prefetch_related از چندین دیتابیسی در جنگو پشتیبانی می کند و در صورتی که Query شما تعیین نکند که از کدام دیتابیس باید استفاده شود ,از دیتابیس تعیین شده در بیرون از Query استفاده می کند . به همین علت تمام مثال های زیر معتبر هستند و کار می کنند :(مبحث چند دیتابیسی در بعد ها اشاره شده است و اکنون زمان مناسبی برای شرح آن نیست)

>>> # Both inner and outer queries will use the 'replica' database
>>> Restaurant.objects.prefetch_related('pizzas__toppings').using('replica')
>>> Restaurant.objects.prefetch_related(
...              Prefetch('pizzas__toppings'),
...              ).using('replica')

>>> # Inner will use the 'replica' database; outer will use 'default' database
>>> Restaurant.objects.prefetch_related(
...            Prefetch('pizzas__toppings', queryset=Toppings.objects.using('replica')),
...            )

>>> # Inner will use 'replica' database; outer will use 'cold-storage' database
>>> Restaurant.objects.prefetch_related(
...           Prefetch('pizzas__toppings', queryset=Toppings.objects.using('replica')),
...           ).using('cold-storage')

ترتیب lookup شما نیز مهم است . برای مثال کد های زیر را نگاه کنید :

>>> prefetch_related('pizzas__toppings', 'pizzas')

این کد حتی اگر ترتیبی برای آن تعریف نشود باز نیز کار می کند . زیرا آرگومان اول pizzas__toppings از قبل حاوی تمام اطلاعات مورد نیاز است . بنابراین آرگومان دوم اضافی است .

>>> prefetch_related('pizzas__toppings', Prefetch('pizzas',
queryset=Pizza.objects.all()))

کد بالا یک valueError را ایجاد می کند که بخاطر تلاش برای تعریف دوباره query از lookup قبلی است . توجه داشته باشید که این QuerySet برای این ساخته شده است تا اشیا pizzas را در جستجوی قبلی خود pizzas__toppings پیدا کند .

>>> prefetch_related('pizza_list__toppings', Prefetch('pizzas', to_attr='pizza_list'))

کد بالا خطای AttributeError را خواهد داد زیرا pizza_list هنوز وجود ندارد (یعنی هنوز pizza_list__toppings در حال پردازش و اجرا است)

راه حل برای تمام این مشکلات استفاده درست و بجا از Prefetch است . همچنین باید عملیات ها را به طوری ترتیب داد که باعث ایجاد Query اضافه نشوند .

متد extra : گاهی اوقات سینتکس های جنگو برای Query زدن ,نمی تواند یک Query پیچیده را که از دستور WHERE استفاده می کند را روی دیتابیس اجرا کند . (نمی تواند آن را هندل کند) در این مواقع شما از extra برای اضافه کردن کد های SQL خودتان به کد های SQL تولید شده توسط QuerySet استفاده خواهید کرد . (به کدهای SQL جستجوی خود ,مواردی را دستی اضافه می کنید)

نکته امنیتی : در هنگام استفاده از extra بسیار مراقب باشید . در واقع هر بار که از آن استفاده می کنید ممکن است بخاطر تغییر دادن سینتکس SQL به صورت دستی باگ SQL Injection را ایجاد کنید . (درباره این باگ در اینترنت بخوانید)

همچنین نباید از از متغییر ها در این متد استفاده کنید . برای مثال کد SQL زیر آسیب پذیر است زیرا از ‘در اطراف %s استفاده کرده است :

SELECT col FROM sometable WHERE othercol = '%s' # unsafe!
نکته : لازمه یادگیری این متد مسلط بودن به SQL است !

همچنین اینکه چند دیتابیس داشته باشید و از این متد استفاده کنید ممکن است قابل انجام نباشد (زیرا شما به صراحت در حال نوشتن کد SQL هستید) . به همین علت باید تا حداکثر توان خود از نوشتن آنها اجتناب کنید .

در این متد باید حداقل یکی از params , select , where یا tables را استفاده کنید و مقداردهی کنید . در ادامه به بررسی هر کدام از اینها خواهیم پرداخت . (یادتان باشد می توانید همه آنها را با هم نیز استفاده کنید)

استفاده از Select :این دستور برای اضافه کردن فیلد های اضافی به سینتکس SELECT در SQL به کار می رود . مقدار آن باید یک دیکشنری باشد . دیکشنری ای ک حاوی نام فیلد و مقدار مورد نیاز برای آن است . برای مثال :

Entry.objects.extra(select={'is_recent': &quotpub_date > '2006-01-01'&quot})

نتیجه کد بالا این خواهد بود که به هر شی Entry یک attribute یا ویژگی اضافه می شود . نام آن is_recent است . این ویژگی در واقع یک Boolean است که در صورتی که pub_date شی , از 1 ژانویه 2006 بیشتر باشد , True خواهد بود.

جنگو قطعه کد SQL ورودی را مستقیما در دستور SELECT وارد خواهد کرد . بنابراین کد بالا به شکل زیر ترجمه می شود :

SELECT blog_entry.*, (pub_date > '2006-01-01') AS is_recent
FROM blog_entry;

کد بعدی کمی پیچیده تر خواهد بود ,کد زیر یک جستجوی فرعی (subquery) انجام می دهد که به هر شی Blog یک ویژگی با نام entry_count اضافه می کند . این ویژگی یک int است که تعداد روابط شی با اشیا Entry را نشان می دهد .

Blog.objects.extra(
    select={
        'entry_count': 'SELECT COUNT(*) FROM blog_entry WHERE blog_entry.blog_id =
        blog_blog.id'
    },
)

در این مورد خاص , Query شما از قبل حاوی جدول blog_blog در دستور FROM خود است (یعنی دستوری که خود جنگو ایجاد کرده است این ویژگی را دارد ) . کد بالا به صورت زیر در SQL ترجمه می شود :

SELECT blog_blog.*, (SELECT COUNT(*) FROM blog_entry WHERE blog_entry.blog_id = blog_blog.id) AS entry_count
FROM blog_blog;
نکته : در مورد subquery در SQL خودتان تحقیق کنید . این مبحث از مباحث SQL است و در آموزش های جنگو نمیگنجد .

توجه داشته باشید که پرانتز های یک subquery برای بعضی از موتور های دیتابیسی لازم نیست (در مثال بالا دستور دوم SELECT را نگاه کنید ,این بخش می گوید که لازم نیست پرانتز های این قسمت حتما گذاشته شوند). همچنین توجه داشته باشید که در بعضی از موتور های دیتابیسی مانند چند نسخه از MySql از subquery پشتیبانی نمی شود .

در برخی موارد نادر نیز ممکن است بخواهید پارامتر ها را به قطعات SQL به extra ارسال کنید . برای این کار از پارامتر select_params استفاده کنید . برای مثال :

Blog.objects.extra(
    select={'a': '%s', 'b': '%s'},
    select_params=('one', 'two'),
)

در صورتی که می خواهید از خود %s در دستور SELECT خود استفاده کنید ,از %%s بهره ببرید .

استفاده از Where / tables :با استفاده از این دو تا می توانید دستور های WHERE و FROM را به صورت مستقیم در کد وارد کنید (به ترتیب) . همچنین هر دوی آنها لیستی از str را دریافت می کنند که به صورت مستقیم وارد کد می شود .

نکته بعدی این است که تمام پارامتر های where با AND به یکدیگر متصل هستند . برای مثال :

Entry.objects.extra(where=[&quotfoo='a' OR bar = 'a'&quot, &quotbaz = 'a'&quot])

کد بالا به صورت کد SQL زیر ترجمه می شود .

SELECT * FROM blog_entry WHERE (foo='a' OR bar='a') AND (baz='a')

هنگامی که می خواهید از پارامتر tables استفاده کنید ,مراقب باشید . گاهی اوقات ممکن است جدول هایی را اضافه کنید که از قبل در Query استفاده شده اند . هنگامی که این کار را انجام بدهید و از قبل جدول شما در Query استفاده شده باشد ,جنگو برای آن جدول یک نام مستعار در نظر می گیرد . چون در SQL اگر یک جدول دو بار تکرار شود ,جستجو های دوم و سوم آن باید از همان نام مستعار استفاده کنند . این در بعضی از مواقع باعث ایجاد خطا می شود .

معمولا باید از جداولی استفاده کنید که در Query استفاده نشده اند . اما اگر مشکل بالا برای شما رخ داد ,چندین راه حل وجود دارد . ابتدا بررسی کنید که می توانید بدون استفاده از جدول تکراری این مشکل را حل کنید یا نه . اگر جواب نداد فقط کافی است در کد داخل extra از نام مستعار جدول استفاده کنید . (نام مستعار جدول هرگر تغییر نخواهد کرد )

نکته : درباره نام مستعار جداول در SQL بخوانید .

استفاده از order_by : اگر می خواهید تا نتایج QuerySet را بر اساس یک فیلد یا جدول جدید که از طریق extra اضافه کرده اید مرتب کنید ,از order_by استفاده کنید و رشته ای از مقادیر را به آن پاس دهید (ارسال کنید) . این مقادیر باید نام فیلد های مدل باشند . می توانند در فرمت table_name.column_name باشند یا نام مستعار ستونی که در extra ارسال کرده اید .

مثال :

q = Entry.objects.extra(select={'is_recent': &quotpub_date > '2006-01-01'&quot})
q = q.extra(order_by = ['-is_recent'])

کد بالا تمام اشیا را که شرط is_recent آنها برابر True است (pub_date آنها از 2006 بیشتر باشد) را در ابتدای نتایج مرتب می کند . (مرتب سازی True قبل از False صورت می گیرد و ترتیب آن نزولی است)

همچنین این کد نشان می دهد که می توانید روی یک QuerySet چندین بار extra را فراخوانی کنید و هر بار محدودیت های جدید را اضافه کنید .

استفاده از Params : پارامتر Where توضیح داده شده در بالا ممکن است از متغییر های استاندارد پایتون برای دیتابیس استفاده کند . برای مثال %s .آرگومان params لیستی از پارامتر های اضافی است که باید جایگزین شوند .

برای مثال :

Entry.objects.extra(where=['headline=%s'], params=['Lennon'])

همیشه به جای جاگذاری مستقیم در دستور where از params استفاده کنید . زیرا این کار باعث می شود تا جنگو اطمینان حاصل کند که مقادیر به درستی در متغییر ها جایگذاری می شوند . به عنوان مثال کد زیر جالب نیست و ممکن است خطا ایجاد کند .

Entry.objects.extra(where=[&quotheadline='Lennon'&quot])

بهتر است به جای کد بالا از کد زیر استفاده کنید .

Entry.objects.extra(where=['headline=%s'], params=['Lennon'])

متد defer :برخی مواقع ممکن است یک مدل حاوی فیلد های زیادی باشد . دریافت اطلاعات و مقادیر این فیلد ها ممکن است کار دیتابیس را سنگین کند و شما ممکن است اصلا نیازی به همه ی آن فیلد ها نداشته باشید . در این مواقع از متد defer استفاده می کنید و به آن نام فیلد ها را ارسال می کنید . این کار باعث می شود آن فیلد های به خصوص از دیتابیس بارگذاری نشوند .

برای مثال در کد زیر فیلد های headline و body در هر شی بارگذاری نخواهند شد و کار دیتابیس سبک تر می شود .

Entry.objects.defer(&quotheadline&quot, &quotbody&quot)
نکته : در برنامه نویسی synchronous شما می توانید به شکل lazy-load فیلد هایی که دریافت نشده اند (deferred fields) را دریافت کنید (البته یکی از آنها در هر زمان و نه همه ی آنها با هم) . در برنامه نویسی asynchronous این کار امکان پذیر نیست و در صورت تلاش برای انجام آن با ارور SynchronousOnlyOperation مواجه خواهید شد .

همچنین شما می توانید این متد را به صورت زنجیره ای استفاده کنید یا فیلد های جدیدی را به این متد اضافه کنید .

# Defers both the body and headline fields.
Entry.objects.defer(&quotbody&quot).filter(rating=5).defer(&quotheadline&quot)

به یاد داشته باشید که ترتیب استفاده از فیلد ها در defer اهمیت خاصی ندارد و حتی اگر یک فیلد دو بار در این متد استفاده شود هیچ اروری را ایجاد نمی کند .

این نیز مهم است که در هر صورت استفاده از select_related یا فیلد های روابطی و برای اشاره به فیلد های ارجاع داده شده از آنها کافی است مانند روال عادی جنگو از __ استفاده کنید . برای مثال :

Blog.objects.select_related().defer(&quotentry__headline&quot, &quotentry__body&quot)

اگر می خواهید تا سابقه فیلد های استفاده شده در این متد را پاک کنید نیز کافی است مقدار None را به عنوان پارامتر به این متد بدهید .

# Load all fields immediately.
my_queryset.defer(None)

برخی از فیلد ها در یک مدل حتی اگر بخواهید هم defer روی آنها تاثیری نخواهد داشت . مثال های آن primarykey هستند . هنگامی که از select_related استفاده می کنید نیز نباید defer را روی فیلدی استفاده کنید که مدل ها را به یکدیگر اتصال داده است (فیلد رابطه ای). این کار موجب ایجاد ارور می شود .

نکته : متد defer و only که در ادامه آن را خواهید خواند ,فقط برای زمانی مفید هستند که نیاز به چند فیلد ندارید و تمام جستجو های خود در دیتابیس را به طور دقیق بررسی کردید .

حتی اگر در حالت بالا هستید و بررسی را به طور دقیق انجام داده اید ,پیشنهاد می شود تا از متد defer زمانی استفاده کنید که مطمئن هستید به فیلد های اضافی نیازی ندارید . اگر پروژه شما آنقدر پیشرفته نیست ,فقط کافی است اطلاعات مورد نیاز در یک مدل و اطلاعات اضافی را در یک مدل جداگانه قرار بدهید (اگر یادتان باشد درباره ارث بری ها صحبت کرده ایم) . اگر ستون های دیتابیس باید در یک جدول قرار بگیرند ,مدلی با Meta.managed = False ایجاد کنید و فیلد های موردنیاز را در آن قرار بدهید . این باعث می شود تا دیتابیس سریع تر باشد و حافظه کمتری نسبت به استفاده از defer اشغال بشود .

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

class CommonlyUsedModel(models.Model):
    f1 = models.CharField(max_length=10)

    class Meta:
        managed = False
        db_table = 'app_largetable'

class ManagedModel(models.Model):
    f1 = models.CharField(max_length=10)
    f2 = models.CharField(max_length=10)
    
    class Meta:
        db_table = 'app_largetable'

# Two equivalent QuerySets:
CommonlyUsedModel.objects.all()
ManagedModel.objects.defer('f2')

اگر تعداد فیلد هایی که نیاز دارید نیز زیاد است ,فقط کافی است تا یک مدل abstract ایجاد کنید که آن را در مباحث ارث بری به طور کامل توضیح دادیم و سپس مدل managed=False را و مدل دیگر را فرزندان آن قرار بدهید .

نکته : هنگامی که از save بعد از defer استفاده کنید ,فقط فیلد هایی که از آنها استفاده کرده اید سیو می شوند.

متد only : متد only اساسا برعکس متد defer است. فیلد هایی که از قبل در متد defer قرار نگرفته باشند و در متد only مورد استفاده قرار بگیرند، از دیتابیس بارگذاری خواهند شد. اگر مدلی دارید که اکثریت فیلد ها باید defer شوند، می‌توانید از only استفاده کنید و فقط فیلد هایی که نیاز دارید را دریافت کنید. فرض کنید یک مدل با نام Person دارید که دارای فیلد های name ، age و biography است . دو QuerySet زیر از نظر عملکرد و نتیجه یکسان هستند :

Person.objects.defer(&quotage&quot, &quotbiography&quot)
Person.objects.only(&quotname&quot)

همچنین فراخوانی پی در پی only باعث می شود تا فقط فیلدی که در آخرین متد فراخوانی شده استفاده کرده اید، از دیتابیس دریافت شود. برای مثال، در کد زیر فقط فیلد headline از دیتابیس دریافت می شود.

# This will defer all fields except the headline.
Entry.objects.only(&quotbody&quot, &quotrating&quot).only(&quotheadline&quot)

همچنین می‌توانید از defer و only همزمان استفاده کنید تا رفتار های منطقی ای را شکل بدهید. کد زیر را نگاه کنید که در کامنت های آن نتیجه هر QuerySet نوشته شده است.

# Final result is that everything except &quotheadline&quot is deferred.
Entry.objects.only(&quotheadline&quot, &quotbody&quot).defer(&quotbody&quot)
# Final result loads headline immediately.
Entry.objects.defer(&quotbody&quot).only(&quotheadline&quot, &quotbody&quot)
نکته: تمام نکاتی که برای متد defer نوشته شده بود برای متد only نیز کار می کند و باید با احتیاط زیاد از این متد استفاده شود.

همچنین به یاد داشته باشید مانند متد defer نمی‌توانید انتظار داشته باشید که در کد asynchronous فیلد های لود نشده، لود شوند و شما به آنها دسترسی داشته باشید.

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

متد using : این متد برای زمانی استفاده می شود که بیش از چند دیتابیس دارید. این متد باعث می شود تا QuerySet را کنترل کنید که با کدام دیتابیس اجرا بشود. تنها آرگومانی که دریافت می‌کند نیز نام دیتابیس مورد استفاده است. همان نامی که در متغییر DATABASES در settings.py تعریف کرده اید. در مورد چند دیتابیسی بعدا صحبت خواهیم کرد.

به مثال زیر نگاه کنید :

# queries the database with the 'default' alias.
>>> Entry.objects.all()
# queries the database with the 'backup' alias
>>> Entry.objects.using('backup')

متد select_for_update: یک دستور SELECT … FOR UPDATE در SQL ایجاد می کند و ردیف های انتخاب شده را تا پایان انجام شدن transactions یا عملیات ها قفل می کند . (باعث می شود تا نتوانید آنها را تغییر دهید یا دریافت کنید)

برای مثال :

from django.db import transaction
entries = Entry.objects.select_for_update().filter(author=request.user)

with transaction.atomic():
    for entry in entries:
        ...
نکته : به یاد داشته باشید که این متد فقط در دیتابیس هایی که از آن پشتیبانی کنند کار می کند.

در کد بالا هنگامی که QuerySet شما روی دیتابیس اجرا می شود، تمام ردیف های مطابق با Query شما قفل می شوند. در اینجا ردیف های قفل شده همان entry in entries هستند. به این معنی که این ردیف ها تا پایان بلوک تراکنش یا عملیات شما قابل تغییر و دستکاری نیستند.

نکته مهم این است که اگر تراکنش یا عملیات دیگری در حال انجام روی ردیف های قفل شده باشد ، QuerySet شما مسدود یا block می شود و به شما اجازه دریافت اطلاعات را تا زمانی که قفل آزاد نشود ، نمی‌دهد. اگر این رفتار را نمی‌خواهید فقط کافیست بنویسید select_for_update(nowait=True) . این باعث می شود تا QuerySet مسدود نشود. اگر یک قفل متضاد نیز بر روی ردیف ها اعمال شود شما دچار DatabaseError میشوید. برای نادیده گرفتن قفل ردیف ها و حل این ارور میتوانید از کد select_for_update(skip_locked=True) استفاده کنید . به یاد داشته باشید که نمی‌توانید هر دوی skip_locked و nowait را با هم استفاده کنید. این کار موجب ایجاد ValueError می شود.

نکته : اگر شما از وراثت multi-table inheritance استفاده می‌کنید، برای قفل کردن مدل های والد فقط کافی است تا فیلد های parent link را به عنوان آرگومان برای of ارسال کنید. به طور پیش فرض آنها به صورت <parent_model_name_<ptr نامگذاری شده اند.

برای مثال :

Restaurant.objects.select_for_update(of=('self', 'place_ptr'))

فقط در دیتابیس PostgreSQL میتوانید آرگومان no_key=True را ارسال کنید تا قفل شدن ردیف ها را ضعیف تر کنید. این ویژگی اجازه می دهد تا ردیف هایی را ایجاد کنید که می‌توانند به ردیف های قفل شده ارجاع پیدا کنند یا با آن برای مثال با یک ForeignKey مرتبط شوند.

همچنین نمی‌توانید از این متد برای روابط nullable استفاده کنید (دچار ارور خواهید شد) :

>>> Person.objects.select_related('hometown').select_for_update()
Traceback(most recent call last):
     ...
    django.db.utils.NotSupportedError: FOR UPDATE cannot be applied to the nullableside
    of an outer join

برای حل این مشکل فقط کافی است اگر نیازی به مقادیر null ندارید، آنها را جدا کنید.

>>>Person.objects.select_related('hometown').select_for_update().exclude(hometown=None)<QuerySet[<Person: ...)>, ...]>

دیتابیس های PostgreSQL ،oracle و mysql از آرگومان ها برای این متد پشتیبانی می‌کنند. گرچه Mariadb فقط از آرگومان nowait پشتیبانی می‌کند. نسخه 10.6Mariadb از skip_locked نیز پشتیبانی می‌کند .Mysql 8.0.1 نیز از آرگومان های skip_locked و nowait و of پشتیبانی می‌کند . no_key فقط در PostgreSQL پشتیبانی می شود.

استفاده از این آرگومان ها در هر دیتابیسی که از آن پشتیبانی نکند باعث ارور می شود.

استفاده از select_for_update در حالت autocommit mode و در دیتابیس هایی که ازکد خاصی مانند SELECT ... FOR UPDATE پشتیبانی می‌کنند باعث ارور TransactionManagementError می‌شود زیرا ردیف ها در آن حالت قفل نمی شوند . اگر اینکار مجاز باشد باعث می شود تا داده ها به راحتی دچار خرابی شوند.

استفاده از select_for_update در یک backend که از SELECT ... FOR UPDATE پشتیبانی نمی کنند (مانند SQLite) بیهوده است و عمل نمی کند .

متد raw : در آن یک Query به زبان SQL را به صورت خام می نویسید و آن را برای شما اجرا می کند . نتیجه این Query یک شی از کلاس dajngo.db.models.query.RawQuerySet است . این شی RawQuerySet می تواند درست مانند QuerySet معمولی پیمایش شود (iterate) . بعدا در این باره بیشتر صحبت خواهیم کرد . ابتدا به مثال زیر نگاه کنید که چگونه یک کد SQL خام را به صورت دستی با این متد اجرا کرده ایم .

>>> from Django.db.models import RawQuery
>>> Person.objects.raw(‘SELECT * FROM Person’)

همچنین به یاد داشته باشید raw تمام فیلتر های زنجیره ای قبل از خودش را نادیده میگیرد . به همین علت معمولا آن را از یک Manager یا یک QuerySet جدید دست نخورده فراخوانی می کنند .


عملگر هایی که QuerySet جدید بازمیگردانند !

در این بخش درباره عملگر یا operator که یک QuerySet جدید را بازمیگردانند صحبت می کنیم . به یاد داشته باشید که Query استفاده شده باید از یک مدل باشد (متعلق به یک مدل باشند) . عملگر ها نیز همان علاماتی هستند که می توانید به وسیله آنها دو Query را با یکدیگر ترکیب کنید (عملگر AND , ORو XOR) .

عملگر AND(&) :دو QuerySet را با یکدیگر ترکیب می کند . در واقع از دستور AND در SQL استفاده می کند .

دو کد زیر با یکدیگر نتایج یکسان دارند .

Model.objects.filter(x=1) & Model.objects.filter(y=2)
Model.objects.filter(x=1, y=2)

from django.db.models import Q
Model.objects.filter(Q(x=1) & Q(y=2))

هر دو کد بالا ,کد SQL زیر را تولید می کنند :

SELECT ... WHERE x=1 AND y=2

عملگر OR(|) : دو QuerySet را با یکدیگر ترکیب می کند . در واقع از دستور OR در SQL استفاده می کند . این دستور به معنی “یا” است و باعث می شود یکی از QuerySetها اجرا شود و نتیجه ای نمایش دهد .

دو کد زیر نتایج یکسان تولید می کنند :

Model.objects.filter(x=1) | Model.objects.filter(y=2)

from django.db.models import Q
Model.objects.filter(Q(x=1) | Q(y=2))

کد SQL زیر برای مثال های بالا تولید می شود :

SELECT ... WHERE x=1 OR y=2
نکته :این عملگر ممکن است Query های مساوی اما متفاوت در هر بار تولید کند . (یعنی نتایج یکسان است اما کد متفاوت خواهد بود)

عملگر XOR(^) : دو QuerySet را با استفاده از دستور XOR ترکیب می کند . (این دستور در جنگو 4.1 پشتیبانی میشود)

مثال های زیر نتایج یکسانی را دارند :

Model.objects.filter(x=1) ^ Model.objects.filter(y=2)
Model.objects.filter(Q(x=1) ^ Q(y=2))

کد SQL زیر برای مثال های بالا تولید خواهد شد :(درباره عملگر XOR کمی تحقیق کنید)

SELECT ... WHERE x=1 XOR y=2

عملگر XOR به صورت native در MariaDB و MySQL پشتیبانی می شود . در دیتابیس های دیگر x ^ y ^ … ^ z به صورت یک معادل درمیاید . برای مثال :

(x OR y OR ... OR z) AND
1=(
    (CASE WHEN x THEN 1 ELSE 0 END) +
    (CASE WHEN y THEN 1 ELSE 0 END) +
    ...
    (CASE WHEN z THEN 1 ELSE 0 END) +
)


در این جلسه در رابطه با تمامی متد هایی که یک queryset جدید را بازمیگردانند صحبت شد . در جلسه بعدی در رابطه با متد هایی صحبت خواهیم کرد که queryset جدیدی را به ما بازنمیگردانند . در جلسات آینده تمامی متد های دیتابیس ها به طور کامل بررسی خواهد شد و این مبحث تمام می شود .