در این جلسه به بررسی Field lookups در جنگو خواهیم پرداخت . با ما همراه باشید .
بسیار خب ,در قسمت پیشین بیشتر متد هایی که میشد از آنها استفاده کرد را معرفی کردیم . اکنون می خواهیم درباره lookup های یک فیلد صحبت کنیم . Lookup ها به زبانی ساده همان کلمات اضافه ای هستند که با __ به نام فیلد اضافه می شوند (برای مثال field__exact) و به شما امکان می دهند تا جستجوی پیشرفته تری را روی دیتابیس انجام دهید . در واقع آنها چگونگی ایجاد دستور WHERE در SQL را تغییر می دهند . درباره آنها قبلا مقداری صحبت کردیم . حال میخواهیم تک تک آنها را بررسی کنیم .
نکته : lookup ها به صورت پیش فرض روی exact هستند . یعنی اگر lookup را استفاده نکنید (Entry.objects.get(id=1))exact برای شما فعال می شود .
-لوکاپ exact : به معنای همخوانی دقیق است . یعنی دقیقا مقداری که وارد کرده اید برای مقایسه و فیلتر استفاده می شود . برای مثال اگر مقدار وارد شده ‘hello’ باشد ,دقیقا بدنبال ‘hello’ در دیتابیس خواهد گشت . به یاد داشته باشید که این lookup , Case-sensitive است . یعنی به حروف بزرگ و کوچک حساس است .
اگر مقدار None را برای آن تنظیم کنید بدنبال مقادیر NULL در دیتابیس خواهد بود . به مثال های زیر نگاه کنید . مثال های اول شکل پایتونی کد و مثال های دوم کد SQL تولید شده برای آن است .
Entry.objects.get(id__exact=14) Entry.objects.get(id__exact=None)
SELECT ... WHERE id =14; SELECT ... WHERE id IS NULL;
-لوکاپ iexact : همان exact است اما به حروف بزرگ و کوچک حساسیت ندارد . این یعنی مقادیری مانند Hello و یا heLlo نیز پذیرفته خواهند شد . اگر مقدار None را تنظیم کنید بدنبال مقادیر NULL در دیتابیس خواهد گشت . برای مثال :
Blog.objects.get(name__iexact='beatlesblog') Blog.objects.get(name__iexact=None)
SELECT ... WHERE name ILIKE 'beatlesblog'; SELECT ... WHERE name IS NULL;
نکته : در SQLite مراقب باشید . این دیتابیس تطبیق غیر حساس به حروف بزرگ و کوچک را برای رشته های غیر ASCII انجام نمی دهد .
-لوکاپ contains :مقادیری را بازمیگرداند که در آنها پارامتر وارد شده وجود داشته باشد . به حروف بزرگ و کوچک نیز حساس است . برای مثال :
Entry.objects.get(headline__contains='Lennon')
SELECT ... WHERE headline LIKE '%Lennon%';
برای مثال مقادیر Lennon is good یا Lennon is okay چون کلمه Lennon در آنها وجود دارد ,پذیرفته خواهند شد .
نکته : در SQLite , contains مانند icontains رفتار می کند (از لحاظ حساسیت به حروف بزرگ و کوچک) .
-لوکاپ icontains :همان contains است اما به حروف بزرگ و کوچک حساس نیست . برای مثال :
Entry.objects.get(headline__icontains='Lennon')
SELECT ... WHERE headline ILIKE '%Lennon%';
نکته : در SQLite مراقب باشید . این دیتابیس تطبیق غیر حساس به حروف بزرگ و کوچک را برای رشته های غیر ASCII انجام نمی دهد . (برای تمامی lookup های جلوتر نیز این قانون وجود دارد)
-لوکاپ in : in برای بدست آوردن مقادیری است که در آن مقدار فیلد یکی از مقادیر لیست , رشته ,تاپل یا QuerySet وارد شده است (هر چیزی که iterable باشد پذیرفته می شود) . برای مثال :
Entry.objects.filter(id__in=[1, 3, 4]) Entry.objects.filter(headline__in='abc')
SELECT ... WHERE id IN (1, 3, 4); SELECT ... WHERE headline IN ('a', 'b', 'c');
همچنین می توانید از یک QuerySet برای پارامتر استفاده کنید . به این صورت برنامه شما داینامیک خواهد بود و هر لحظه می توانند نتایج تغییرات داشته باشند . برای مثال :
inner_qs = Blog.objects.filter(name__contains='Cheddar') entries = Entry.objects.filter(blog__in=inner_qs)
SELECT ... WHERE blog.id IN (SELECT id FROM ... WHERE NAME LIKE '%Cheddar%')
توجه داشته باشید اگر یک QuerySet را که روی آن values یا list_values فراخوانی شده است , به __inارسال کنید ,باید مطمئن شوید که فقط و فقط یک فیلد از آن QuerySet خارج می شود . برای مثال :
inner_qs = Blog.objects.filter(name__contains='Ch').values('name') entries = Entry.objects.filter(blog__name__in=inner_qs)
برعکس کد بالا ,کد زیر بخاطر خارج شدن مقادیر دو فیلد از QuerySet دچار ارور خواهد شد .
# Bad code! Will raise a TypeError. inner_qs = Blog.objects.filter(name__contains='Ch').values('name', 'id') entries = Entry.objects.filter(blog__name__in=inner_qs)
برخی از دیتابیس ها (مانند MySQL) بدرستی از QuerySet های تودرتو پشتیبانی نمی کنند . کد های بالا مثال هایی از QuerySet های تودرتو هستند . در این دیتابیس ها ,استخراج یک لیست از QuerySet اول و ارسال آن لیست به QuerySet دوم کارآمد تر خواهد بود . یعنی به جای اجرای یک Query ,دو Query را اجرا کنید. برای مثال :
values = Blog.objects.filter( name__contains='Cheddar').values_list('pk', flat=True) entries = Entry.objects.filter(blog__in=list(values))
در کد بالا قبل از Blog , list را فراخوانی کردیم تا QuerySet اول را مجبور به اجرا در دیتابیس کنیم . بدون آن یک QuerySet تودرتو اجرا خواهد شد .
-لوکاپ gt : به معنای مقایسه برای بزرگتر از یا بیشتر از است . برای مثال :
Entry.objects.filter(id__gt=4)
SELECT ... WHERE id >4;
کد بالا یعنی مقادیری را پیدا کن که id آنها از 4 بیشتر باشند .
-لوکاپ gte : به معنای مقایسه برای بزرگتر یا مساوی است .
-لوکاپ lt : به معنای کوچک تر یا کمتر از است .
-لوکاپ lte : به معنای مقایسه برای کوچکتر یا مساوی است .
-لوکاپ startswith :مقادیری را بازمیگرداند که ابتدای آنها با پارامتر ارسالی شروع شده باشد . به حروف بزرگ و کوچک نیز حساس است . برای مثال :
Entry.objects.filter(headline__startswith='Lennon')
SELECT ... WHERE headline LIKE 'Lennon%';
کد بالا مقادیری که ابتدای آنها با Lennon شروع شده باشد را بازمیگرداند .
نکته : در SQLite , startswith مانند istartswith عمل خواهد کرد . زیرا این دیتابیس دستور های LIKE را نمی توانند به صورت Case-sentisive هندل کند .
-لوکاپ istartswith :همان startswith است ولی به حروف بزرگ و کوچک حساس نیست . برای مثال :
Entry.objects.filter(headline__istartswith='Lennon')
SELECT ... WHERE headline ILIKE 'Lennon%';
-لوکاپ endswith :مقادیری را بازمیگرداند که انتهای آنها با پارامتر ارسالی تمام شده باشد . به حروف بزرگ و کوچک نیز حساس است . برای مثال :
Entry.objects.filter(headline__endswith='Lennon')
SELECT ... WHERE headline LIKE '%Lennon';
کد بالا مقادیری که انتهای آنها با Lennon تمام شده باشد را بازمیگرداند .
نکته : در SQLite , endswith مانند iendswith عمل خواهد کرد . زیرا این دیتابیس دستور های LIKE را نمی توانند به صورت Case-sentisive هندل کند .
-لوکاپ iendswith :همان endswith است اما به حروف بزرگ و کوچک حساس نیست . برای مثال :
Entry.objects.get(headline__iendswith='Lennon')
SELECT ... WHERE headline ILIKE '%Lennon‘;
-لوکاپ range : این looup مقادیری را بازمیگرداند که میان دو چیز قرار دارند . مقادیر میتوانند عدد , حروف و یا تاریخ ها باشند . برای مثال :
import datetime start_date = datetime.date(2005, 1, 1) end_date = datetime.date(2005, 3, 31) Entry.objects.filter(pub_date__range=(start_date, end_date))
SELECT ... WHERE pub_date BETWEEN '2005-01-01' and '2005-03-31';
نکته : به طور کلی شما نمی توانید dates و datetimes را با هم ترکیب کنید . بنابر این اگر pub_date یک DateTimeField بود ,کد بالا به صورت SQL زیر درمیامد :
SELECT ... WHERE pub_date BETWEEN '2005-01-01 00:00:00' and '2005-03-31 00:00:00';
-لوکاپ date :مقدار داخل دیتابیس را به صورت تاریخ در میاورد . برای فیلد های datetime کار می کند و راهی است برای مقایسه تاریخ های درون یک شی .پارامتر ارسالی باید یک شی از datetime.date باشد . برای مثال :
Entry.objects.filter(pub_date__date=datetime.date(2005, 1, 1)) Entry.objects.filter(pub_date__date__gt=datetime.date(2005, 1, 1))
بدلیل متفاوت بودن کارکرد موتور های دیتابیسی در این مورد , هیچ کد SQL ای ارائه نشده است .
نکته : همچنین وقتی USE_TZ در settings.py روی Trueباشد ,مقادیر فیلد ها قبل از فیلتر شدن به منطقه زمانی کنونی تبدیل می شود . (برای تمامی lookupها این مطلب درست است)
-لوکاپ year : برای فیلد های datefield و datetimefield است . مقدار سال فیلد را با پارامتر ارسالی تطبیق می دهد و در صورت تطابق آن را بازمیگرداند . برای مثال :
Entry.objects.filter(pub_date__year=2005) Entry.objects.filter(pub_date__year__gte=2005)
SELECT ... WHERE pub_date BETWEEN '2005-01-01' AND '2005-12-31'; SELECT ... WHERE pub_date >= '2005-01-01';
البته سینتکس دقیق کد SQL بالا برای هر دیتابیس متفاوت خواهد بود .
-لوکاپ iso_year :برای فیلد های datefield و datetimefield است . مقدار سال فیلد را با پارامتر ارسالی تطبیق می دهد و در صورت تطابق آن را بازمیگرداند (البته به صورت استاندارد جهانی ISO 8601) . برای مثال :
Entry.objects.filter(pub_date__iso_year=2005) Entry.objects.filter(pub_date__iso_year__gte=2005)
-لوکاپ month : برای فیلد های datefield و datetimefield است . مقدار ماه فیلد را با پارامتر ارسالی تطبیق می دهد و در صورت تطابق آن را بازمیگرداند . پارامتر شما باید یک عدد از 1 تا 12 باشد (ماه های سال) . برای مثال :
Entry.objects.filter(pub_date__month=12) Entry.objects.filter(pub_date__month__gte=6)
SELECT ... WHERE EXTRACT('month' FROM pub_date) = '12'; SELECT ... WHERE EXTRACT('month' FROM pub_date) >= '6';
-لوکاپ day : برای فیلد های datefield و datetimefield است . مقدار روز فیلد را با پارامتر ارسالی تطبیق می دهد و در صورت تطابق آن را بازمیگرداند . برای مثال :
Entry.objects.filter(pub_date__day=3) Entry.objects.filter(pub_date__day__gte=3)
SELECT ... WHERE EXTRACT('day' FROM pub_date) = '3'; SELECT ... WHERE EXTRACT('day' FROM pub_date) >= '3';
توجه داشته باشید هر رکورد که تاریخ آن در روز سوم ماه باشد (برای مثال 3 ژانویه یا 3 ژوئیه) مطابقت پیدا خواهد کرد.
-لوکاپ week : بر اساس استاندارد ISO 8601 برای شما شماره هفته فیلد را (1-52 یا 53) با پارامتر وارد شده مقایسه می کند . به یاد داشته باشید هفته ها از روز دوشنبه شروع می شوند . برای مثال :
Entry.objects.filter(pub_date__week=52) Entry.objects.filter(pub_date__week__gte=32, pub_date__week__lte=38)
به یاد داشته باشید که اگر USE_TZ در settings.py برابر با True باشد ,زمان ها به زمان محلی شما تبدیل می شوند .
-لوکاپ week_day :برای شما روز هفته فیلد را (برای مثال دوشنبه) با پارامتر وارد شده مقایسه می کند . یک عدد را به عنوان پارامتر میگیرد که نشان دهنده روز هفته از 1(یکشنبه) تا 7(شنبه) است . برای مثال :
Entry.objects.filter(pub_date__week_day=2) Entry.objects.filter(pub_date__week_day__gte=2)
کد های بالا بدون توجه به ماه یا سال هر تاریخی که مربوط به روز دوشنبه باشد را بازمیگردانند .
-لوکاپ iso_week_day :در واقع همان week_day است . اما عملیات را بر اساس استاندارد ISO 8601 انجام می دهد . پارامتر های ارسالی باید یک عدد بین 1 (دوشنبه) تا 7(یکشنبه) باشند . برای مثال :
Entry.objects.filter(pub_date__iso_week_day=1) Entry.objects.filter(pub_date__iso_week_day__gte=1)
کد های بالا بدون توجه به ماه یا سال هر تاریخی که مربوط به روز دوشنبه باشد را بازمیگردانند .
-لوکاپ quarter :یک سال به 4 قسمت تبدیل می شود . در واقع هر سه ماه یک قسمت است . این lookup برای شما قسمتی که تاریخ فیلد در آن قرار دارد را با پارامتر ورودی مقایسه می کند . پارامتر ورودی باید یک عدد بین 1تا 4 باشد که برای مثال مقدار 1 نشان دهنده سه ماه اول سال است .
برای دریافت اشیا مدل Entry که در سه ماهه دوم سال هستند (از 1 آپریل تا 30 ژوئن) کد زیر را مینویسید .
Entry.objects.filter(pub_date__quarter=2)
-لوکاپ time : برای datetimefield بکار می رود . مقدار فیلد را با پارامتر وارد شده مقایسه می کند . پارامتر وارد شده باید یک شی از datetime.time باشد . برای مثال :
Entry.objects.filter(pub_date__time=datetime.time(14, 30)) Entry.objects.filter(pub_date__time__range=(datetime.time(8), datetime.time(17)))
-لوکاپ hour : برای شما ساعت فیلد را با پارامتر وارد شده مقایسه می کند . یک عدد را به عنوان پارامتر میگیرد که نشان دهنده ساعت از 0 تا 23 است . برای مثال :
Event.objects.filter(timestamp__hour=23) Event.objects.filter(time__hour=5) Event.objects.filter(timestamp__hour__gte=12)
SELECT ... WHERE EXTRACT('hour' FROM timestamp) = '23'; SELECT ... WHERE EXTRACT('hour' FROM time) = '5'; SELECT ... WHERE EXTRACT('hour' FROM timestamp) >= '12';
-لوکاپ minute : برای شما دقیقه فیلد را با پارامتر وارد شده مقایسه می کند . یک عدد را به عنوان پارامتر میگیرد که نشان دهنده دقیقه از 0 تا 59 است . برای مثال :
Event.objects.filter(timestamp__minute=29) Event.objects.filter(time__minute=46) Event.objects.filter(timestamp__minute__gte=29)
SELECT ... WHERE EXTRACT('minute' FROM timestamp) = '29'; SELECT ... WHERE EXTRACT('minute' FROM time) = '46'; SELECT ... WHERE EXTRACT('minute' FROM timestamp) >= '29';
-لوکاپ second : برای شما ثانیه فیلد را با پارامتر وارد شده مقایسه می کند . یک عدد را به عنوان پارامتر میگیرد که نشان دهنده ثانیه از 0 تا 59 است . برای مثال :
Event.objects.filter(timestamp__second=31) Event.objects.filter(time__second=2) Event.objects.filter(timestamp__second__gte=31)
SELECT ... WHERE EXTRACT('second' FROM timestamp) = '31'; SELECT ... WHERE EXTRACT('second' FROM time) = '2'; SELECT ... WHERE EXTRACT('second' FROM timestamp) >= '31';
-لوکاپ is_null :دو مقدار True و False را قبول می کند که به ترتیب کد های IS NULL و IS NOT NULL را ایجاد می کند . در واقع بررسی می کند که آیا یک مقدار Null است یا خیر . برای مثال :
Entry.objects.filter(pub_date__isnull=True)
SELECT ... WHERE pub_date IS NULL;
-لوکاپ regex : یک یا چند regular expression یا عبارات منظم را به عنوان پارامتر می پذیرد و مقدار فیلد را با آن عبارت منظم تطابق می دهد (در مورد آنها باید خودتان بخوانید زیرا سرفصل بزرگی است) .
در هر دیتابیس نحوه کارکرد عبارات منظم متفاوت است . در مورد SQLite که هیچ پشتیبانی داخلی ای از عبارات منظم ندارد ,این ویژگی توسط یک تابع REGEXP هندل می شود . بنابراین شبیه ماژول re در پایتون عمل می کند.
برای مثال :
Entry.objects.get(title__regex=r'^(An?|The) +')
SELECT ... WHERE title REGEXP BINARY '^(An?|The) +'; -- MySQL SELECT ... WHERE REGEXP_LIKE(title, '^(An?|The) +', 'c'); -- Oracle SELECT ... WHERE title ~ '^(An?|The) +'; -- PostgreSQL SELECT ... WHERE title REGEXP '^(An?|The) +'; -- SQLite
استفاده از رشته های خالی مانند r‘foo’ بجای ‘foo’ برای pass یا رد شدن از عبارت منظم توصیه می شود .
-لوکاپ iregex : همان regex است اما به حروف بزرگ و کوچک حساس نیست .برای مثال :
Entry.objects.get(title__iregex=r'^(an?|the) +')
SELECT ... WHERE title REGEXP '^(an?|the) +'; -- MySQL SELECT ... WHERE REGEXP_LIKE(title, '^(an?|the) +', 'i'); -- Oracle SELECT ... WHERE title ~* '^(an?|the) +'; -- PostgreSQL SELECT ... WHERE title REGEXP '(?i)^(an?|the) +'; -- SQLite
در این جلسه در رابطه با تمامی لوکاپ های قابل استفاده در جنگو صحبت کردیم . در جلسه بعدی درباره api مدل ها در جنگو صحبت می کنیم و مباحث تخصصی ما شروع خواهد شد .