آموزش جنگو (Basic) : جلسه بیست و هفت | ایجاد کوئری (Query)

در این جلسه به بررسی کوئری ها در جنگو خواهیم پرداخت . با ما همراه باشید . آموزش جنگو : جلسه بیست و هفت | ایجاد کوئری (Query)ایجاد queryهنگامی که یک مدل را ایجاد می کنید ,جنگو به صورت اتوماتیک به شما یک مجموعه از API ها را می دهد که به وسیله آنها می توانید جدول دیتابیس خود را کنترل کنید . برای مثال رکورد ایجاد کنید ,حذف کنید ,آپدیت کنید و یا دریافت کنید . در این بخش قرار است درباره چگونگی کار با این API صحبت کنیم .نکته :مدل های زیر را در نظر بگیرید . تمام این بخش قرار است با مدل های زیر کار کنیم (یعنی کد ها به این مدل ها اشاره دارند) مدل های زیر مدل های یک وبلاگ ساده هستند :from datetime import date from django.db import models class Blog(models.Model): name = models.CharField(max_length=100) tagline = models.TextField() def __str__(self): return self.name class Author(models.Model): name = models.CharField(max_length=200) email = models.EmailField() def __str__(self): return self.name 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) def __str__(self): return self.headlineایجاد رکورد (یا همان object یا شی)جنگو برای نمایش و ذخیره داده های شما در دیتابیس از یک سیستم ساده استفاده می کند . یک کلاس مدل ,یک جدول پایگاه داده را نشان می دهد و هر شی آن کلاس یک رکورد خاص را دیتابیس نشان می دهد .برای ایجاد یک شی (رکورد) ,آن را با استفاده از آرگومان های خود کلاس (ویژگی های آن) نمونه سازی کنید . سپس ()save را فراخوانی کنید تا در دیتابیس ذخیره شود .با فرض اینکه مدل های شما در mysute/blog/models.py وجود دارند ,مثال در اینجا آمده است .>>> from blog.models import Blog >>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.') >>> b.save()این کد در پشت صحنه یک دستور INSERT را روی دیتابیس اعمال می کند . توجه داشته باشید که تغییرات شما تا زمانی که متد ()save فراخوانی نشود ,بر روی دیتابیس اعمال نخواهند شد .نکته : متد ()save هیچ مقدار بازگشتی ای ندارد !همچنین می توانید از متد ()create استفاده کنید تا بدون نیاز به نمونه سازی و فراخوانی متد ()save تمام کار ها را انجام دهید . (خودش ()save را فراخوانی می کند)آپدیت رکوردبرای ذخیره تغییرات روی یک شی که از قبل در دیتابیس وجود دارد , از ()save استفاده کنید .برای مثال فرض کنید نمونه با نام b5 دارید که قبلا در دیتابیس وجود دارد . این مثال نام آن را تغییر داده و رکورد آن را در دیتابیس به روز رسانی می کند .>>> b5.name = 'New name' >>> b5.save()این مثال در پشت صحنه یک دستور UPDATE را اعمال می کند . جنگو تا زمانی که متد ()save را فراخوانی نکنید, تغییرات را روی دیتابیس اعمال نمی کند .ذخیره فیلد های روابطیبه روز رسانی یک ForeignKey دقیقا مانند ذخیره سازی یک فیلد معمولی عمل می کند . یک شی از نوع مناسب به فیلد موردنظر اختصاص می دهید و آن را ()save می کنید . مثال زیر ,مقدار فیلد blog در مدل Entry را به روز رسانی می کند و به ان یک شی از مدل Blog را اختصاص می دهد . (توجه داشته باشید رکورد های ما در دیتابیس از قبل ایجاد شده اند !)>>> from blog.models import Blog, Entry >>> entry = Entry.objects.get(pk=1) >>> cheese_blog = Blog.objects.get(name=&quotCheddar Talk&quot) >>> entry.blog = cheese_blog >>> entry.save()در مورد ManyToManyField کمی راهکار متفاوت است . در اینجا باید از متد ()add در فیلد برای افزودن رکورد به رابطه استفاده کنید . این مثال Joe را که نمونه ای از مدل Author است را به فیلد ManyToManyField ما اضافه می کند .>>> from blog.models import Author >>> joe = Author.objects.create(name=&quotJoe&quot) >>> entry.authors.add(joe)برای افزودن چندین رکورد به فیلد به صورت یکجا تمام آنها را در ()add وارد کنید .>>> john = Author.objects.create(name=&quotJohn&quot) >>> paul = Author.objects.create(name=&quotPaul&quot) >>> george = Author.objects.create(name=&quotGeorge&quot) >>> ringo = Author.objects.create(name=&quotRingo&quot) >>> entry.authors.add(john, paul, george, ringo)اگر از نوع غیر شی تلاش کنید به فیلد اضافه کنید ,جنگو ارور خواهد داد .دریافت و دسترسی به رکوردبرای دسترسی و دریافت اشیا یا رکورد ها باید یک QuerySet را با manager مدل خود ایجاد کنید .یک QuerySet در واقع مجموعه ای از اشیا یا رکورد های خاص هست . آنها می توانند یک شی یا بیشتر یا حتی صفر شی درون خود داشته باشند . آنها را با filter می توانید ایجاد کنید تا فقط به مجموعه خاصی از اشیا که با پارامتر های شما همخوانی داشته باشند دسترسی داشته باشد . این روش دستورات SELECT را اعمال می کند . علاوه بر آن ارسال پارامتر های filter دستورات WHERE و LIMIT را اجرا خواهد کرد .یک QuerySet را میتوانید به وسیله manager ایجاد کنید . هر مدل حداقل یک manager دارد که به صورت پیش فرض نام آن objects است . از مدل خود به صورت مستقیم می توانید به manager دسترسی پیدا کنید :>>> Blog.objects <django.db.models.manager.Manager object at ...> >>> b = Blog(name='Foo', tagline='Bar') >>> b.objects Traceback: ... AttributeError: &quotManager isn't accessible via Blog instances.&quotنکته : ارور بالا حاصل از آن است که manager فقط از طریق خود مدل قابل دسترسی هستند و از طریق نمونه های مدل نمی توان به آنها دسترسی یافت !متد های مختلفی در QuerySet ها وجود دارند که از طریق manager قابل اجرا هستند . برای مثال متد ()Blog.objects.all تمامی شی و رکورد ها را در مدل Blog به شما باز میگرداند .دریافت تمامی رکورد هابرای دریافت تمامی رکورد های یک جدول یا دریافت تمامی اشیا یک مدل فقط کافی است از متد ()all در manager استفاده کنید .>>> all_entries = Entry.objects.all()فیلتر کردن رکورد هامتد قبلی شما تمامی اشیا را در دیتابیس باز میگرداند . معمولا شما این را نمی خواهید و فقط رکورد هایی را نیاز دارید که بر اساس پارامتر های خاصی فیلتر شده باشند . در این زمان از ()filter استفاده می شود .فیلترگرچه filter معمول ترین متد مورد استفاده است اما در واقع دو متد برای انجام چنین کار هایی مورد استفاده است .شماره 1-filter(**kwargs) :تمام رکورد هایی که با آرگومان های داده شده هم خوانی داشته باشند را باز میگرداند .شماره 2-exclude(**kwargs) :تمام رکورد هایی را باز میگرداند که با آرگومان های داده شده هم خوانی نداشته باشد !تمامی **kwargs شما همان نام فیلد ها هستند که بر اساس مقادیر آنها , آنها را فیلتر می کنید . برای مثال برای دریافت تمامی رکورد هایی از Blog که فیلد pub_date آنها و قسمت سال یا year آنها برابر با 2006 باشد :Entry.objects.filter(pub_date__year=2006)شما همچنین می توانید از روش زیر نیز استفاده کنید . (گرچه توصیه نمی شود اما گاهی باید تمام اشیا را ابتدا بگیرید و سپس آنها را فیلتر کنید)Entry.objects.all().filter(pub_date__year=2006)فیلتر زنجیرینتیجه ای که جنگو از یک QuerySet بازمیگرداند خودش یک QuerySet است . پس این امکان دارد تا بتوانید آنها را به یکدیگر زنجیر کنید و فیلتر های دو الی سه مرحله ای ایجاد کنید :>>> Entry.objects.filter( ... headline__startswith='What‘ ... ).exclude( ... pub_date__gte=datetime.date.today() ... ).filter( ... pub_date__gte=datetime.date(2005, 1, 30) ... )این کد , QuerySet ابتدایی را می گیرد و روی آن فیلتر را انجام می دهد . سپس روی نتیجه اش یک exclude انجام می دهد و روی نتیجه آن دوباره یک فیلتر را انجام می دهد . این QuerySet حاوی تمام اشیایی است که فیلد headline آنها با what شروع شده باشد و محدوده تاریخ فیلد pub_date آنها از 30 ژوئن 2005 تا امروز باشد (ابتدا بررسی می کنیم که امروز نباشد و سپس بررسی می کنیم که از چه تاریخی بیشتر باشد) .نکته : هر QuerySet در واقع unique یا منحصر به فرد است . هر بار که شما یک QuerySet را مجددا استفاده می کنید , شما یک QuerySet کاملا جدید و جدا از قبلی دریافت خواهید کرد .هر بار استفاده مجدد از یک QuerySet یک QuerySet جدید ایجاد می کند که آن نیز می تواند دوباره استفاده بشود . برای مثال کد بالا را می توان به صورت زیر نوشت :>>> q1 = Entry.objects.filter(headline__startswith=&quotWhat&quot) >>> q2 = q1.exclude(pub_date__gte=datetime.date.today()) >>> q3 = q1.filter(pub_date__gte=datetime.date.today())برای مثال q1 در اینجا یک QuerySet حاوی تمام رکورد هایی است که با what شروع می شوند . بعد از آن از q2 وجود دارد که زیر مجموعه ای از q1 است که تمام رکورد هایی که pub_date آنها برابر با امروز یا آینده است را حذف می کند .بعد از آن نیز q3 وجود دارد که زیر مجموعه ای از q1است که تمام رکورد هایی که pub_date آنها برابر با امروز یا آینده است را فیلتر می کند و ذخیره می کند . توجه داشته باشید که در طی کل این فرایند ها q1 تغییر نخواهد کرد .یک نکته این است که QuerySet ها معمولا lazy هستند . این یعنی شما می توانید تمام روز QuerySet ایجاد کنید و جنگو تا زمانی که از آن استفاده نکنید , QuerySet شما را در دیتابیس اجرا نمی کند . به مثال زیر نگاه کنید.>>> q = Entry.objects.filter(headline__startswith=&quotWhat&quot) >>> q = q.filter(pub_date__lte=datetime.date.today()) >>> q = q.exclude(body_text__icontains=&quotfood&quot) >>> print(q)در کد بالا به نظر می رسد که جنگو سه بار در دیتابیس عملیات اجرا کرده است و QuerySet ها شما را ایجاد کرده است اما در حقیقت فقط یک بار جنگو QuerySet را اجرا کرده است . فقط زمانی که شما از آن استفاده کردید (در خط آخر و دستور print) . به طور کلی QuerySet شما فقط زمانی در دیتابیس اجرا خواهد شد که شما نتیجه آن را بخواهید .متد ()getبه یاد داشته باشید که متد filter حتی اگر فقط یک رکورد با پارامتر های شما همخوانی داشته باشد , آن را به صورت یک QuerySet بازمیگرداند . در اینجا , QuerySet شما فقط یک رکورد را خواهد داشت .اگر می دانید که فقط یک رکورد در دیتابیس شما با پارامتر های شما همخوانی دارد , می توانید از متد ()get از manager مدل خود استفاده کنید تا آن را به صورت مستقیم بگیرید . برای مثال :>>> one_entry = Entry.objects.get(pk=1)استفاده از این متد نیز مانند استفاده از ()filter ساده است . فقط کافی است تا پارامتر های خود را به همراه نام فیلد به آنها بدهید .توجه داشته باشید که اگر رکوردی با پارامتر های شما همخوانی نکند , متد ()filter فقط یک QuerySet خالی به شما باز میگرداند . اما متد ()get به شما یک ارور DoesNotExist باز خواهد گرداند . بنابر این در کد بالا اگر رکوردی با این مشخصات یافت نشود , شما ارور دریافت خواهید کرد .همچنین اگر بیشتر از یک رکورد با مشخصات شما در متد ()get همخوانی کند ,شما به ارور MultipleObejctReturned برخواهید خورد .متدهای queryدر جنگو معمولا از متد های filter , all و یا get و exclude استفاده می شود . اما جنگو متد های بیشتری نیز درون خود جاسازی کرده است . بعدها به بررسی آنها خواهیم پرداخت . جستجوی دیتابیس :محدود کردن QuerySetبرای محدود کردن نتایج بازگشتی از QuerySet (یعنی تعیین محدودیت تعداد رکورد های بازگشتی) فقط کافی است به روش list slice در پایتون ,محدوده را مشخص کنید . این معادل دستور LIMIT و OFFSE در SQL است.به عنوان مثال ,کد زیر 5 رکورد اول جدول را باز میگرداند . (LIMIT 5)>>> Entry.objects.all()[:5]کد زیر نیز رکورد های شماره 6 الی 10 ابتدایی را باز خواهد گرداند . (OFFSET 5 LIMIT 5)>>> Entry.objects.all()[5:10]ایندکس های منفی (یعنی Entry.objects.all()[-1]) پشتیبانی نمی شود .به طور کلی , slice کردن یک QuerySet باعث ایجاد یک QuerySet جدید می شود . این باعث نمی شود تا query شما ,روی دیتابیس اجرا شود . البته یک استثنا وجود دارد ;زمانی که شما از پارامتر step در سینتکس slice کردن خود استفاده کنید . برای مثال کد زیر ,لیستی از رکورد های شماره زوج یعنی هر دو رکورد یکی را از 10 مورد اولی باز میگرداند .>>> Entry.objects.all()[:10:2]نکته مهم این است که به دلیل مبهم بودن ماهیت کارکرد آن لیست , شما روی آن نمی توانید فیلتر یا دستورات QuerySet دیگری را اجرا کنید .اگر می خواهید برای مثال اولین رکورد را به جای یک لیست از رکورد ها دریافت کنید (SELECT foo FROM bar LIMIT 1) به جای برش لیست ,از یک index استفاده کنید . برای مثال کد زیر پس از مرتب کردن رکورد ها بر اساس حروف الفبا بر اساس فیلد headline ,اولین رکورد آن را باز میگرداند .>>> Entry.objects.order_by('headline')[0]این تقریبا معادل است با :>>> Entry.objects.order_by('headline')[0:1].get()البته به یاد داشته باشید که اولین کد اگر هیچ رکوردی با آن مطابقت نکند , ارور IndexError به شما باز خواهد گرداند ولی کد دوم اگر هیچ رکوردی را مطابق با پارامتر هایش پیدا نکند ,ارور DoesNotExist را باز خواهد گرداند.در این جلسه به بررسی چندین متد در جنگو پرداختیم که برای جستجو در دیتابیس به کار میرفتند . در جلسه بعدی درباره lookup صحبت خواهیم کرد .