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

در این جلسه به بررسی برخی دیگر از متد های قابل استفاده (نوع دوم) خواهیم پرداخت . بهتر است مقاله قبلی را قبل از این مقاله مطالعه کرده باشید . با ما همراه باشید .آموزش جنگو : جلسه سی و چهار | آشنایی با تمامی متد های جنگو | پارت دوممتد هایی که QuerySet جدید بازنمیگردانند !در جلسه قبلی گفتیم متدها در جنگو به دو نوع تقسیم می شوند . در این جلسه به بررسی نوع دوم می پردازیم . در نوع دوم شما Queryset جدیدی را به عنوان پاسخ دریافت نمی کنید و مستقیما خود شی یا اشیا را دریافت خواهید کرد . متد هایی که در ادامه بررسی می کنیم ,لیست متد هایی است که QuerySet را بازنمیگردانند و بجای آن یک چیز دیگر مانند یک شی یا نمونه از دیتابیس یا یک کلاس یا حتی یک عدد را بازمیگردانند .این متد ها از کش استفاده نمی کنند (درباره کش ها قبلا صحبت کرده ایم) . آنها هر بار که فراخوانی می شوند ,روی دیتابیس عملیاتی انجام می دهند .این متد ها به صورت عادی در نسخه ی synchronous برنامه استفاده می شوند . اگر شما یک برنامه از نوع asynchronous دارید ,باید ورژن دیگری از این متدها را فراخوانی کنید . برای مثال بجای استفاده از ()get باید از ()await get استفاده کنید . هر دو ورژن متدها (synchronousو asynchronous) را برای هر کدام از آنها در لیست زیر نوشته ایم .معمولا ورژن های این متدها با یکدیگر تفاوتی در عملکرد ندارند , اما اگر تفاوتی وجود داشته باشد ,آن را در توضیحات متد بررسی می کنیم .نکته : جنگو 4.1 فقط دارای ورژن های asynchronous متد های زیر است . تنها تفاوت در میان آنها پیشوند a برای متد های استفاده شده در asynchronous است .متد get | aget : یک شی را بازمیگرداند که با پارامتر های ورودی و lookup شما همخوانی داشته باشد . شما قبلا با این متد آشنا شده اید . همچنین می توانید آن را به صورت ترکیبی نیز استفاده کنید . (برای مثال استفاده از شی Q)Entry.objects.get(id=1) Entry.objects.get(Q(blog=blog) & Q(entry_number=1))اگر مطمئن هستید که QuerySet شما فقط یک شی را قرار است بازگرداند ,میتوانید برای دسترسی مستقیم به شی از get بدون ارسال هیچ پارامتری استفاده کنید .Entry.objects.filter(pk=1).get()در مثال بالا ما مطمئن هستیم که در سطر مورد نظر فقط یک شی با pk=1 قرار دارد (PrimaryKey نمی تواند دو مقدار یکسان را در یک جدول داشته باشد) . بنابراین از get برای دسترسی مستقیم به همان شی استفاده می کنیم .اگر هیچ شی ای با پارامتر یا lookup های شما همخوانی نداشته باشد ,این متد ارور DoesNotExsist را بازمیگرداند .Entry.objects.get(id=-999) # raises Entry.DoesNotExistمتد get فقط و فقط در زمانی استفاده می شود که مطمئن باشید یک شی با مشخصات شما وجود دارد . اگر این متد چند شی را که با پارامتر های شما همخوانی دارند , پیدا کند باعث ایجاد ارور MultipleObjectsReturned می شود . (در مواقعی که چندین شی دارید باید از filter استفاده کنید)Entry.objects.get(name='A Duplicated Name') # raises Entry.MultipleObjectsReturnedگرچه اگر خواستید یا نیاز داشتید می توانید ارور ها را وسیله try و except هندل کنید . ابتدا کافی است ارور های بالا را از Django.core.exceptions وارد کنید . هر ارور در واقع کلاس مربوط به خود را در این بخش دارد . سپس می توانید ارور ها را مدیریت کنید . برای مثال در کد زیر ارور DoesNotExsist را هندل کرده ایم . در واقع برای این ارور از کلاس ObjectDoesNotExsist استفاده کردیم .from django.core.exceptions import ObjectDoesNotExist try: blog = Blog.objects.get(id=1) entry = Entry.objects.get(blog=blog, entry_number=1) except ObjectDoesNotExist: print(&quotEither the blog or entry doesn't exist.&quot)متد create | acreate :همانطور که قبلا با این متد آشنا شده اید ,این متد برای شما یک شی یا رکورد را درون دیتابیس ایجاد می کند و آن را سیو می کند . در استفاده از آن نیازی به فراخوانی save ندارید . همچنین پارامتر های آن فیلد های مدل هستند که باید به هر کدام از آنها یک مقدار اختصاص داد . (استثنای آنهایی که null,blank=True هستند)p = Person.objects.create(first_name=&quotBruce&quot, last_name=&quotSpringsteen&quot)کد زیر نیز می تواند برای ساخت اشیا استفاده شود . البته که create بهتر از روش زیر (نمونه سازی از مدل) است.p = Person(first_name=&quotBruce&quot, last_name=&quotSpringsteen&quot) p.save(force_insert=True)نکته : اگر شما یک primarykey دستی برای مدل تنظیم کرده باشید و از قبل آن را مقداردهی کرده باشید , وارد کردن دوباره مقدار آن در create باعث ارور IntegrityError می شود . دلیل آن این است که primarykey باید منحصر به فرد باشد . (همچنین force_insert را بعدها توضیح خواهیم داد)متد get_or_create | aget_or_create : یک متد که ابتدا بررسی می کند که آیا شی یا رکوردی با پارامتر و lookup های شما همخوانی دارد یا خیر و سپس اگر آن را پیدا کند ,شی را بازمیگرداند و اگر نه ,آن را ایجاد می کند. (برای ایجاد یک شی به مقداردهی همه فیلد ها نیاز دارد)نتیجه این متد به صورت یک تاپل است . شکل این تاپل به صورت (object , created) است . در این تاپل object شی یافته شده و یا ایجاد شده است و created یک Boolean است که نشان می دهد آیا یک شی جدید ایجاد شده است یا خیر .این متد به کار می رود تا از ایجاد اشیا یکسان در یک لحظه توسط چندین درخواست (request) جلوگیری شود . یعنی فرض کنید که چندین درخواست به صورت همزمان و موازی درخواست ایجاد یک شی یکسان داشته باشند . این متد از وقوع این اتفاق جلوگیری می کند . برای مثال :try: obj = Person.objects.get(first_name='John', last_name='Lennon') except Person.DoesNotExist: obj = Person(first_name='John', last_name='Lennon', birthday=date(1940, 10, 9)) obj.save()در کد بالا ممکن است با چند درخواست همزمان ,چندین تلاش برای ذخیره یک Person با پارامتر های یکسان صورت بگیرد . برای حل این مشکل باید از get_or_create استفاده کرد .obj, created = Person.objects.get_or_create( first_name='John', last_name='Lennon', defaults={'birthday': date(1940, 10, 9)}, )کد بالا نتیجه را به صورت یک تاپل حاوی object که شی ایجاد شده یا یافت شده است و created باز میگرداند .نکته : این روش نیاز دارد تا فیلد های دیتابیس شما unique باشند . این به نفع خودتان است زیرا فراخوانی همزمان این متد ممکن است باعث ایجاد چندین شی با پارامتر های یکسان شود .همچنین شما می توانید شرایط پیچیده تر را با زنجیره ای کردن get_or_create با متدهای filter و یا شی Q مدیریت کنید . برای مثال ,برای دریافت Robert یا Bob Marley در صورتی که اولی وجود داشته باشد و دومی ساخته بشود :from django.db.models import Q obj, created = Person.objects.filter( Q(first_name='Bob') | Q(first_name='Robert'), ).get_or_create(last_name='Marley', defaults={'first_name': 'Bob'})اگر چندین شی را این متد شناسایی کند (که با پارامتر های شما همخوانی داشته باشند) ارور MultipleObjectsReturned به شما بازگردانده می شود . اگر هم شی ای پیدا نشود این متد آن را برای شما ایجاد می کند . وبسایت رسمی جنگو الگوریتم ایجاد شی توسط این متد را به شکل زیر توصیف می کند :params ={k: v for k, v in kwargs.items() if '__' not in k} params.update({k: v() if callable(v) else v for k, v in defaults.items()}) obj = self.model(**params) obj.save()به زبانی ساده این متد ابتدا هر آرگومان که defaults نباشد و دارای ‘__’ نباشد را پیدا می کند (که نشان دهنده non-exact بودن فیلد است) . سپس محتویات defaults را نیز اضافه می کند . در صورتی که آرگومان ها و محتویات آرگومان defaultsمفایرت داشته باشند آنها را بازنویسی یا override می کند و در آخر از نتیجه برای یافتن شی استفاده می کند . در آخر نیز اگر وجود نداشته باشد آن را ایجاد خواهند کرد . این ساده شده الگوریتم اصلی است .اگر یک فیلد با نام defaults دارید که می خواهید از آن در get_or_create استفاده کنید ,از defaults__exact استفاده کنید . برای مثال :Foo.objects.get_or_create(defaults__exact='bar', defaults={'defaults': 'baz'})متد get_or_create یک ارور مشابه با متد create دارد . زمانی که شما از primarykey های دستی استفاده کنید . اگر یک شی در حال ایجاد شدن باشد و Key در دیتابیس از قبل مشخص شده باشد ,یک ارور IntegrityError به شما بازگردانده می شود .در نهایت جنگو پیشنهاد می کند که از این متد فقط در ویو هایی که با درخواست های POST (POST requests) کار می کنند استفاده کنید . درخواست های GET نباید روی دیتا و داده های شما تاثیری ایجاد کنند . در عوض از POST استفاده کنید تا داده ها و دیتاها بتوانند اثر جانبی را پذیرش کنند . درباره دلیل آن بیشتر صحبت خواهیم کرد .نکته : شما می توانید از فیلد ManyToManyField و روابط معکوس یا reverse نیز در get_or_create استفاده کنید.البته فقط به صورت محدود و از خود فیلد ManyToManyField و رابطه میتوانید از متد استفاده کنید . در صورت های دیگر دچار ارور خواهید شد .برای فهم بیشتر فرض کنید مدل زیر را داریم .class Chapter(models.Model): title = models.CharField(max_length=255, unique=True) class Book(models.Model): title = models.CharField(max_length=256) chapters = models.ManyToManyField(Chapter)شما فقط میتوانید از get_or_create در فیلد chapters مدل Book استفاده کنید اما فقط در درون محتوای Book متد شما کار می کند . برای مثال :>>> book = Book.objects.create(title=&quotUlysses&quot) >>> book.chapters.get_or_create(title=&quotTelemachus&quot) (<Chapter: Telemachus>, True) >>> book.chapters.get_or_create(title=&quotTelemachus&quot) (<Chapter: Telemachus>, False) >>> Chapter.objects.create(title=&quotChapter 1&quot) <Chapter: Chapter 1> >>> book.chapters.get_or_create(title=&quotChapter 1&quot) # Raises IntegrityErrorارور خط آخر به این دلیل رخ داد که جنگو تلاش کرد تا get_or_create را روی Chapter1 فراخوانی کند (البته از طریق شی book که title آن برابر با Ulysses است) . ولی نمی تواند اینکار را انجام دهد . این رابطه نمی تواند chapter را دریافت کند زیرا به book مربوط نیست . برای ایجاد آن نیز باید فیلد title , unique باشد.متد update_or_create | aupdate_or_create : این متد همانطور که از نام آن مشخص است ,روش راحتی است برای آپدیت مقادیر یک شی و یا ایجاد یک شی جدید در صورت نیاز .مقادیری که باید آپدیت شوند در یک دیکشنری با نام defaults به صورت (field , value) قرار می گیرند .مقدار Field همان فیلد انتخابی شما و value مقدار برای آن فیلد است که باید آپدیت شود . همچنین می توانید بجای value یک callable نیز قرار دهید (callable مانند توابع) .نتیجه این متد به صورت یک تاپل به شکل (object , created) است . در این تاپل object شی آپدیت شده و یا ایجاد شده است و created یک Boolean است که نشان می دهد آیا یک شی جدید ایجاد شده است یا خیر .متد update_or_create در واقع سعی می کند که شی را از دیتابیس بر اساس پارامتر و lookup های داده شده پیدا کند . وقتی آن را پیدا کرد فیلد های درون defaults را بر اساس مقادیر آنها آپدیت می کند .به کد صفحه بعد نگاه کنید که بدون استفاده از این متد ,عملکرد مشابه آن را نوشته ایم .defaults = {'first_name': 'Bob'} try: obj = Person.objects.get(first_name='John', last_name='Lennon') for key, value in defaults.items(): setattr(obj, key, value) obj.save() except Person.DoesNotExist: new_values = {'first_name': 'John', 'last_name': 'Lennon'} new_values.update(defaults) obj = Person(**new_values) obj.save()اگر تعداد فیلد هایی که باید آپدیت شوند زیاد شود این روش ناکارآمد است . برای حل این مشکل از متد update_or_create استفاده می کنیم :obj, created = Person.objects.update_or_create( first_name='John', last_name='Lennon', defaults={'first_name': 'Bob'}, )نکته : این متد نیز نیاز به unique بودن فیلد و مقدار ها دارد . اگر این شرایط رعایت نشود , ممکن است در دیتابیس چندین شی به وسیله درخواست های همزمان با پارامتر های یکسان آپدیت یا ساخته شوند .مانند متد های get_or_create و متد create ,زمانی که شما از primarykey های دستی استفاده کنید و یک شی در حال ایجاد شدن باشد و Key در دیتابیس از قبل مشخص شده باشد ,یک ارور IntegrityError به شما بازگردانده می شود .متد bulk_create | abulk_create : این متد لیستی از اشیا را دریافت می کند و آنها را در دیتابیس وارد می کند. برتری این متد این است که همه ی اشیا را فقط در یک عملیات و Query در دیتابیس وارد می کند . سپس به عنوان نتیجه اشیا ایجاد شده را در یک لیست به همان ترتیب داده شده بازمیگرداند . برای مثال :>>> objs = Entry.objects.bulk_create([ ... Entry(headline='This is a test'), ... Entry(headline='This is only a test'), ... ])اگرچه روی این متد یک سری از محدودیت ها اعمال می شود :-متد save مدل شما فراخوانی نمی شود و سیگنال های pre_save و post_save ارسال نمی شوند .-با مدل های فرزند در ارث بری نوع multi-table inheritance کار نمی کند .-اگر primarykey شما یک AutoField باشد ,فقط روی دیتابیس های PostgreSQL و MariaDB 10.5 و SQLite 3.35 کار می کند و میتوانید آن را دریافت کنید . در باقی آنها فیلد تنظیم و مقداردهی نمی شود .-با روابط ManyToManyFields کار نمی کند .در دیتابیس هایی که پشتیبانی می کنند (همه آنها به جز Oracle) یک پارامتر با نام ignore_conflicts دارید . دو مقدار True و False را قبول می کند . تنظیم آن روی Trueبه جنگو می گوید که ارور هایی که در سطح ردیف ها به وجود میایند (مانند ارور تکرار مقدار یکسان در فیلد unique) را نادیده بگیرد .نکته :در MySQL و MariaDB تنظیم پارامتر ignore_conflicts روی True انواع خاصی از خطاها را میتواند به هشدار یا warning تبدیل کند . برای مثال : invalid values و یا non-nullable violationsمتد bulk_update | abulk_update: این متد به صورت کارآمد فیلد های داده شده برای یک شی از مدل را آپدیت می کند . برتری این متد این است که همه ی این عملیات را برای تمام اشیا در یک Query انجام می دهد . در آخر نیز تعداد اشیا آپدیت شده بازگردانده می شود .>>> objs = [ ... Entry.objects.create(headline='Entry 1'), ... Entry.objects.create(headline='Entry 2'), ... ] >>> objs[0].headline = 'This is entry 1' >>> objs[1].headline = 'This is entry 2' >>> Entry.objects.bulk_update(objs, ['headline']) 2استفاده از متد دیگر که QuerySet.update نامیده می شود روش دیگری برای آپدیت اشیا است . این متد اتوماتیک شی را آپدیت می کند و سیو می کند . این کار بهتر از پیمایش روی اشیا و فراخوانی save برای هرکدام از آنها است . اما نکاتی وجود دارند که باید در استفاده از اینها توجه داشته باشید . برای مثال شما نمی توانید مقدار primarykey را آپدیت کنید .-متد save مدل شما فراخوانی نمی شود و سیگنال های pre_save و post_save ارسال نمی شوند .-اگر شما در حال آپدیت تعداد زیادی از اشیا باشید ,کد SQL تولید شده بسیار بزرگ و طولانی خواهد شد . برای جلوگیری از آن کافیست یک batch_size را تعریف کنید . (batch_size یک پارامتر در متد است که یک عدد را به عنوان محدودیت تعداد اشیا آپدیت شده می پذیرد . به زبانی دیگر تعداد اشیا آپدیت شده را در هر بار فراخوانی متد محدود می کند . برای مثال 100 شی)-تعدادی که این متد به شما بازمیگرداند ممکن است کمتر از تعداد اشیایی باشد که ارسال کرده اید . دلیل آن این است که ممکن است بعضی از اشیا به صورت تکراری ارسال شده باشند .نکته : SQLite و Oracle محدودیت هایی در تعداد متغییر مورد استفاده برای Query دارند . پس در آنها مراقب به استفاده از batch_size باشید .متد count | acount : یک عدد (integer) را بازمیگرداند که نشان دهنده تعداد اشیا در یک QuerySet است . برای مثال :# Returns the total number of entries in the database. Entry.objects.count() # Returns the number of entries whose headline contains 'Lennon' Entry.objects.filter(headline__contains='Lennon').count()فراخوانی count یک کد (*) SELECT COUNT را ایجاد می کند . بنابراین همیشه از count باید بجای لود کردن تمام اشیا و فراخوانی len روی آنها استفاده کنید . (گرچه اگر قرار است تمام اشیا را نیاز داشته باشید و بخواهید آنها را در حافظه لود کنید , متد len سریعتر خواهد بود)اگر قبلا Query به طور کامل از دیتابیس بارگذاری یا لود شده باشد , count از طول آن استفاده می کند تا Query اضافی روی دیتابیس اعمال نکند .متد in_bulk | ain_bulk : این متد یک لیست از مقادیر فیلد (برای مثال لیست idها) و نام فیلد ها را برای آن مقادیر دریافت می کند . سپس یک دیکشنری را که اشیا با هر value مطابقت داده شده اند را بازمیگرداند . برای مثال :>>> Blog.objects.in_bulk([1]) {1: <Blog: Beatles Blog>} >>> Blog.objects.in_bulk([1, 2]) {1: <Blog: Beatles Blog>, 2: <Blog: Cheddar Talk>} >>> Blog.objects.in_bulk([]) {} >>> Blog.objects.in_bulk() {1: <Blog: Beatles Blog>, 2: <Blog: Cheddar Talk>, 3: <Blog: Django Weblog>} >>> Blog.objects.in_bulk(['beatles_blog'], field_name='slug') {'beatles_blog': <Blog: Beatles Blog>} >>> Blog.objects.distinct('name').in_bulk(field_name='name') {'Beatles Blog': <Blog: Beatles Blog>, 'Cheddar Talk': <Blog: Cheddar Talk>, 'Django Weblog': <Blog: Django Weblog>}این متد هیچگاه ارور django.core.exceptions.ObjectDoesNotExist را به شما بازنمیگرداند . یعنی هر id که با هیچ نمونه ای مطابقت نداشته باشد به سادگی نادیده گرفته می شود . اگر هیچ value ای به متد ارسال نشود ,تمام اشیا بازگردانده می شوند . همچنین نام فیلد ها باید unique باشد . به صورت پیش فرض نیز نام فیلد ها روی primarykey تنظیم می شود (id) .اگر یک لیست خالی به این متد ارسال کنید یک دیکشنری خالی دریافت خواهید کرد .متد iterator | aiterator:یک QuerySet را در دیتابیس اجرا می کند و یک iterator روی نتایج بازمیگرداند . همچنین اگر از aiterator استفاده کنید یک asynchronous iterator بازگشت داده می شود .یک QuerySet معمولا نتایج خود را در کش ذخیره می کند . اما استفاده از این متد باعث می شود تا هیچ فرایندی برای کش کردن QuerySet صورت نگیرد . Iterator نتایج را مستقیما به شما می دهد . این فرایند زمانی مفید است که تعداد اشیا زیادی دارید که فقط یکبار به آنها نیاز دارید . استفاده از این متد در آن زمان باعث افزایش عملکرد حافظه می شود .توجه داشته باشید فراخوانی iterator روی یک QuerySet که قبلا در دیتابیس اجرا شده است ,باعث می شود تا دوباره در دیتابیس اجرا شود .یک Iterator با فراخوانی های متد prefetch_related در قبل از آن فقط تا زمانی سازگار است که پارامتر chunk_size تعیین شود .نکته : aiterator هیچگاه با فراخوانی متد prefetch_related در قبل از آن سازگار نیست .در برخی از دیتابیس ها مانند Oracle و SQLite حداکثر تعداد عبارات در یک SQL IN محدود است . از این رو باید مراقب باشید تا مقادیر و تعداد عبارات زیر حد مشخص شده باشند . (البته بعدا با chunk_size آشنا می شوید که به شما در این مورد کمک می کند)نکته : تا زمانی که QuerySet به هیچ شی ارجاع داده شده یا رابطه ای برنخورد , chunk_size برابر با 2000 در نظر گرفته می شود .وابسته به دیتابیس بک اندی که استفاده می کنید , Query ممکن است کل نتایج را به یکباره لود و دریافت کند و یا ممکن است نتایج را با server-side cursors , استریم کند . در قسمت زیر آنها را بررسی می کنیم .نوع اول دیتابیس های دارای server-side cursors هستند . آنها شامل PostgreSQL و Orcale می شوند . این نوع از دیتابیس ها نتایج را در حافظه لازم نیست لود کنند و بجای آن نتایج را استریم (stream) خواهند کرد .درایور های Oracle همیشه از این ویژگی استفاده می کنند . با server-side cursors پارامتر chunk_size تعداد نتایجی را که باید در حافظه (در سطح درایور) ذخیره شوند را مشخص می کند . هرچقدر این عدد بیشتر باشد تعداد رفت و برگشت داده میان درایور و دیتابیس کاهش پیدا می کند و باعث از دست دادن مقدار بیشتری از رم یا حافظه می شود .در PostgreSQL نیز فقط زمانی از این ویژگی استفاده می شود که متغییر DISABLE_SERVER_SIDE_CURSORS در settings.py برابر با False باشد .نوع دوم دیتابیس های بدون server-side cursors هستند . آنها شامل MySQL و SQLite هستند . در آنها درایور نتایج را تماما در حافظه بارگذاری می کند . سپس نتایج به وسیله database adapter (و متد fethcmany) به اشیا پایتون تبدیل می شوند . (البته این رفتار برای MySQL است ,در SQLite فقط از متد fetchmany استفاده می شود)پارامتر chunk_size در این نوع از دیتابیس ها اندازه دسته های داده که جنگو از درایور دریافت می کند را کنترل می کنند . هر چقدر بیشتر باشد عملیات دیتابیس سنگین تر ولی مصرف حافظه کمتر خواهد بود .تا زمانی که QuerySet هیچ شی ارتباطی (related object) را دریافت نکند , chunk_size به صورت پیش فرض روی 2000 قرار خواهد گرفت .نکته :با فرض اینکه شما 10 الی 20 row یا سطر در جدول داشته باشید عدد 2000 داده ای کمتر از 100 کیلوبایت را میتواند جا به جا کند .متد latest | alatest :آخرین شی را در جدول بر اساس فیلد داده شده بازمیگرداند .این مثال آخرین شی Entry در جدول را با توجه به فیلد pub_date بازمیگرداند . (قدیمی ترین تاریخ)Entry.objects.latest('pub_date')همچنین می توانید بر اساس چندین فیلد آخرین شی را انتخاب کنید . به عنوان مثال برای دریافت شی Entry با آخرین زمان فیلد expire_date در زمانی که دو شی دارای pub_date یکسان هستند .Entry.objects.latest('pub_date', '-expire_date')علامت منفی (-) در پشت expire_date به معنای ترتیب نزولی expire_date است . از آنجایی که متد latest آخرین شی را دریافت می کند ,نتیجه شی ای از مدل Entry با جدیدترین تاریخ در فیلد expire_date است .اگر در کلاس متای مدل خود get_latest_by را مشخص کنید ,می توانید آرگومان های درون متد را حذف کنید . فیلد های مشخص شده در آن گزینه به صورت پیش فرض استفاده خواهند شد .اگر نیز شی ای با مشخصات داده شده پیدا نشود (فیلد نامعتبر) , ارور DoesNotExist را دریافت می کنید .متد earliest| aearliest : جدیدترین شی را در جدول بر اساس فیلد داده شده بازمیگرداند .قوانین برای متد قبلی برای این متد نیز اعمال می شوند .متد first | afirst : این متد اولین شی از QuerySet را به شما بازمیگرداند . اگر شی ای را نیز پیدا نکند ,مقدار None را بازمیگرداند . به یاد داشته باشید اگر ordering مشخص نشده باشد اشیا بر اساس primarykey مرتب می شوند .برای مثال :p = Article.objects.order_by('title', 'pub_date').first()توجه داشته باشید این متد برای ساده سازی کد به کار می رود . مثال زیر برابر با کد بالا است . (الگوریتم مثال بالا به شکل زیر خواهد بود)try: p = Article.objects.order_by('title', 'pub_date')[0] except IndexError: p = Noneمتد last | alast :این متد آخرین شی از QuerySet را به شما بازمیگرداند .همچنین قوانین مشابه با متد first دارد .متد aggregate | aaggregate : این متد برای محاسبه مقادیر در کل یک QuerySet به کار می رود . برای مثال مقادیری مانند جمع یا میانگین مقادیر یک فیلد در کل QuerySet . همچنین می توانید این متد را با متد های دیگر نیز به کار ببرید . نتیجه این متد به صورت یک دیکشنری به شما بازگشت داده خواهد شد .آرگومانی که در این متد ارسال می کنید می تواند به دو صورت باشد . هر دو آنها را بررسی خواهیم کرد .برای مثال برای به دست آوردن تعداد اشیا Author که در Blog__entry مشارکت دارند ,کد زیر را مینویسیم . در این نوع از آرگومان نویسی , Key در دیکشنری خروجی خودکار تولید می شود .>>> from django.db.models import Count >>> q = Blog.objects.aggregate(Count('entry')) {'entry__count': 16}در مثال دوم همان عملیات بالا را انجام می دهیم ,اما اینبار آرگومان ارسال شده را به شکل زیر مینویسیم . در این نوع Key در دیکشنری خروجی نام مشخص شده ای خواهد داشت .>>> q = Blog.objects.aggregate(number_of_entries=Count('entry')) {'number_of_entries': 16}گرچه بعدا در مورد این متد و فرایند Aggregation بیشتر صحبت خواهیم کرد . در واقع یک تاپیک کامل درباره این موضوع وجود دارد !متد exists | aexists : این متد اگر QuerySet دارای شی باشد (نتیجه داشته باشد) True و اگر شی ای درون آن نباشد مقدار False را بازمیگرداند . این متد تلاش می کند این کار را به سریعترین روش ممکن انجام دهد اما تقریبا QuerySet را به طور کامل در دیتابیس اجرا می کند (باعث ایجاد فرایند در دیتابیس می شود).برای مثال ,برای اینکه بفهمید که آیا یک QuerySet نتیجه ای داشته یا خیر :if some_queryset.exists(): print(&quotThere is at least one object in some_queryset&quot)که بخاطر استفاده از این متد سریعتر از روش زیر است :if some_queryset: print(&quotThere is at least one object in some_queryset&quot)البته لازم است بگوییم سرعت آن در QuerySet های بزرگتر چشم گیر است .علاوه بر این اگر some_queryset هنوز در دیتابیس اجرا نشده باشد ,اما قرار باشد زمانی اجرا بشود , some_queryset.exists مجبور به انجام فرایند ها و Query های بیشتر می شود . پس بهتر است در آن زمان از روش دوم استفاده کنید .متد contains | acontains : شما به این متد یک obj یا شی ارسال می کنید و برای شما بررسی می کند که آیا این شی درون QuerySet وجود دارد یا خیر . اگر وجود داشته باشد به شما True و اگر نه False را بازمیگرداند . این متد این کار را به سریعترین شکل ممکن انجام می دهد .برای مثال برای اینکه بفهمید آیا یک شی درون QuerySet وجود دارد از متد contains استفاده کنید (با فرض اینکه شی درون یک متغییر با نام obj قرار گرفته باشد) :if some_queryset.contains(obj): print('Entry contained in queryset')کد بالا سریعتر از کد زیر خواهد بود . زیرا کد زیر مجبور است یکبار کل QuerySet را اجرا کند و سپس روی آن پیمایش کند .if obj in some_queryset: print('Entry contained in queryset')مانند متد exists این متد نیز اگر some_queryset هنوز در دیتابیس اجرا نشده باشد ,اما قرار باشد زمانی اجرا بشود , some_queryset.contains مجبور به انجام فرایند ها و Query های بیشتر می شود . پس بهتر است در آن زمان از روش دوم استفاده کنید .متد update | aupdate : این متد برای بروزرسانی مقادیر فیلد های مشخص شده بکار می رود. در واقع یک دستور UPDATE در SQL ایجاد می‌کند. در آخر برای نتیجه تعداد ردیف هایی که با فیلد های شما مطابقت داشتند را بازمی‌گرداند. ممکن است این عدد با ردیف هایی که آپدیت شده اند مساوی نباشد، زیرا ممکن است برخی از آنها در آن زمان مقادیر جدید داشته باشند. برای مثال برای قرار دادن مقدار False برای فیلد comments_on در اشیا Entry که مربوط به سال 2010 هستند باید کد زیر را بنویسید.>>> Entry.objects.filter(pub_date__year=2010).update(comments_on=False)البته در مثال بالا فرض میکنیم مدل Entry دارای فیلد های comments_on و pub_date است.همچنین شما می توانید چندین فیلد را آپدیت کنید. جنگو محدودیتی برای تعداد فیلد ها اعمال نمی کند. برای مثال در کد زیر ما فیلد های headline و comments_on را آپدیت کردیم.>>> Entry.objects.filter(pub_date__year=2010).update(comments_on=False, headline='This is old')به یاد داشته باشید این متد فورا تغییرات را اعمال می‌کند و تنها یک محدودیت دارد. این متد نمی‌تواند روی مدل های مرتبط یا مدل های ارجاع داده شده تغییری اعمال کند. برای مثال شما نمی‌توانید این کد را بنویسید :>>> Entry.objects.update(blog__name='foo') # Won't work!هرچند فرایند فیلتر کردن بر اساس مدل های ارجاع داده شده یا مرتبط هنوز امکان پذیر است. برای مثال :>>> Entry.objects.filter(blog__id=1).update(comments_on=True)البته توجه کنید که شما نمی‌توانید متد update را روی QuerySet هایی که slice داده شده اند و یا نمی‌توانند فیلتر شوند، فراخوانی کنید. متد update تحت اشیایی که تحت تاثیر قرار گرفته شده اند را بازمی‌گرداند که ممکن است با تعداد اشیا آپدیت شده برابر نباشند.>>> Entry.objects.filter(id=64).update(comments_on=True) 1 >>> Entry.objects.filter(slug='nonexistent-slug').update(comments_on=True) 0 >>> Entry.objects.filter(pub_date__year=2010).update(comments_on=False) 132البته توجه داشته باشید متد update مخصوص زمانی است که فقط می خواهید یک شی را آپدیت کنید و نمی خواهید به داده های آن شی دسترسی داشته باشید (برای مثال مقدار یک فیلد از آن را دریافت کنید) . پس برای مثال بجای نوشتن کد زیر که ابتدا شی را در حافظه بارگذاری می کند .e = Entry.objects.get(id=10)e.comments_on = Falsee.save()می توانید کد زیر را بنویسید که لازم به بارگذاری شی در حافظه ندارد .Entry.objects.filter(id=10).update(comments_on=False)همچنین استفاده از این متد باعث می شود تا از امکان تغییر در داده های دیتابیس در مدت زمان میان بارگذاری شی و فراخوانی save جلوگیری شود .در نهایت باید بدانید که update در سطح SQL تغییرات را اعمال می کند و متد save دیتابیس شما را یا سیگنال های pre_save و post_save را فراخوانی نمی کند . اگر می خواهید تعداد زیادی از رکورد ها را برای مدلی که متد save سفارشی دارد ,آپدیت کنید ,می توانید به شکل زیر اینکار را انجام دهید :for e in Entry.objects.filter(pub_date__year=2010): e.comments_on = False e.save()نکته خاص این است که زنجیر کردن یا اتصال متد های order_by و update فقط در دیتابیس های MariaDB و MySQL پشتیبانی می شود (برای مثال ()Entry.objects.oreder_by(‘title’).update) . در دیتابیس های دیگر این اتصال نادیده گرفته می شود . کد زیر را نگاه کنید :Entry.objects.order_by('-number').update(number=F('number') +1)نکته : البته متد order_by در صورتی که حاوی annotations و یا فیلد های وراثتی و یا related lookups باشد ,نادیده گرفته می شود .متد delete| adelete : این متد برای شما اشیا را از دیتابیس حذف می کند و اشیا حذف شده را به شکل یک دیکشنری به همراه تعداد اشیا حذف شده و تعداد حذف شده ها در هر نوع اشیا را بازمیگرداند . در ادامه بهتر این موضوع را درک می کنید .این متد سریعا بر دیتابیس اعمال می شود و روی QuerySet هایی که slice شده اند یا نمی توانید متد filter را روی آنها اعمال کنید ,کار نمی کند .برای مثال برای حذف تمام اشیا مدل Entry که فیلد blog آنها یک شی خاص است ,کد زیر را مینویسید . (نتیجه به شکل یک دیکشنری که تعداد حذف ها را نشان می دهد خواهد بود . برای مثال در اینجا دو شی از blog و دو شی از authors حذف شده است)>>> b = Blog.objects.get(pk=1) # Delete all the entries belonging to this Blog. >>> Entry.objects.filter(blog=b).delete() (4, {'blog.Entry': 2, 'blog.Entry_authors': 2})به طور پیش فرض ,فیلد ForeignKey نوع ON DELETE CASCADE را تقیلد می کند . یعنی اگر شی ای با ForeignKeyب ه شی دیگری اشاره کند (رابطه داشته باشد) و شی دوم حذف شود , شی اول نیز حذف می شود . برای مثال :>>> blogs = Blog.objects.all() # This will delete all Blogs and all of their Entry objects. >>> blogs.delete() (5, {'blog.Blog': 1, 'blog.Entry': 2, 'blog.Entry_authors': 2})البته از طریق on_delete در فیلد ForeignKey مربوطه میتوانید این رفتار را تغییر دهید . درباره تمام آنها قبلا صحبت کرده ایم .متد delete یک حذف انبوه انجام می دهد و متد delete را از مدل شما فراخوانی نمی کند . اما با این حال سیگنال های pre_delete و post_delete را ارسال می کند .نکته :اگر نمی دانید سیگنال های چه هستند ,نگران نباشید . بعدا درباره آنها صحبت می کنیم . ابتدا روی مطالبی که میفهمید تمرکز کنید .جنگو برای ارسال سیگنال ها و مدیریت CASCADE نیاز به بارگذاری اشیا در حافطه خواهد داشت . در شرایطی که به سیگنال ها و on_delete نوع CASCADE نیازی ندارید ,جنگو ممکن است بدون بارگذاری اشیا در حافظه اقدام به حذف آنها کند . در حذف اشیا به تعداد بالا این فرایند می تواند به افزایش سرعت حافظه کمک کند . یعنی باید on_delete را روی چیزی غیر از CASCADE و سیگنال ها را غیرفعال کنید . (البته نه همیشه)مقدار DO_NOTHING در on_delete فیلد های ForeignKey مانع در افزایش سرعت حافظه نمی شوند .متد explain | aexplain : رشته ای را بازمیگرداند که نحوه اجرای QuerySet را توضیح می دهد . جزئیات آن شامل join و index هایی است که در SQL آن QuerySet به کار رفته است . دانستن این جزئیات ممکن است برای بهبود عملکرد برنامه به شما کمک کند . (تغییری را روی QuerySet انجام نمی دهد)به عنوان مثال با فرض اینکه دیتابیس شما PostgreSQL باشد :>>> print(Blog.objects.filter(title='My Blog').explain()) SeqScan on blog (cost=0.00..35.50 rows=10 width=12) Filter: (title = 'My Blog'::bpchar)خروجی در میان هر دیتابیس متفاوت خواهد بود . در واقع هر دیتابیس نحوه اجرای کد را به شکل های مختلف ارائه می کند .متد explain در تمامی دیتابیس ها به جز Oracle پشتیبانی می شود .این متد همچنین یک پارامتر با نام format نیز دریافت می کند . در واقع این پارامتر فرمت متن خروجی است . تنوع فرمت ها وابسته به دیتابیس و پشتبیبانی آن متفاوت است . به صورت پیش فرض نیز همه ی آنها به صورت text هستند.دیتابیس PostgreSQL :فرمت های XML و TEXT و JSON و YAML را پشتیبانی می کند .دیتابیس MariaDB و MySQL :فرمت TEXT و JSON را پشتیبانی می کند .دیتابیس MySQL +8.0.16 :علاوه بر TEXTو JSON از TREE نیز پشتیبانی می کند که مشابه خروجی TEXT در PostgreSQL است .برای استفاده از هر فرمت کافی است آن را در پارامتر وارد کنید (format=‘TEXT’) .در بعضی از دیتابیس ها مانند PostgreSQL می توانید از flag استفاده کنید . آنها باعث می شوند تا اطلاعات بیشتری درباره QuerySet به شما نمایش داده شود . برای مثال :>>> print(Blog.objects.filter(title='My Blog').explain(verbose=True, analyze=True)) SeqScan on public.blog(cost=0.00..35.50 rows=10 width=12) (actual time=0.004..0.004 rows=10 loops=1) Output: id, title Filter: (blog.title = 'My Blog'::bpchar) Planning time: 0.064 ms Execution time: 0.058 msدر برخی از دیتابیس ها استفاده از بعضی flagباعث ایجاد اثرات مخرب روی دیتابیس می شود . برای مثال ANALYZE که در MariaDB و MySQL 8.0.18 و PostgreSQL پشتیبانی می شود ,در صورتی که توابعی در QuerySet وجود داشته باشند باعث تغییرات در داده های دیتابیس شود .در این جلسه برخی دیگر از متد های جنگو را بررسی کردیم و این مبحث را در همین نقطه خواهیم بست . در جلسه بعدی به بررسی کامل تمامی لوکاپ ها (field lookup) خواهیم پرداخت .