
حالا که یک پنل مدیریت کاملاً کاربردی برای مدیریت پستهای بلاگ داریم، زمان مناسبی است که بیاموزیم چگونه به صورت برنامهنویسیشده (Programmatically) دادهها را در پایگاه داده بخوانیم و بنویسیم.
ORM جنگو، یک API قدرتمند برای انتزاع پایگاه داده است که به شما امکان میدهد آبجکتها را به آسانی ایجاد، بازیابی، بهروزرسانی و حذف کنید. ORM به شما اجازه میدهد تا کوئریهای SQL را با استفاده از پارادایم شیءگرا در پایتون تولید کنید. میتوانید آن را روشی برای تعامل با پایگاه داده به سبک پایتونی (Pythonic) در نظر بگیرید، بدون اینکه نیاز باشد کوئریهای خام SQL بنویسید.
ORM مدلهای شما را به جداول پایگاه داده نگاشت میکند و یک رابط پایتونی ساده برای تعامل با دیتابیس در اختیارتان قرار میدهد. ORM کوئریهای SQL را تولید کرده و نتایج را به آبجکتهای مدل نگاشت میکند. این ابزار با MySQL، PostgreSQL، SQLite، Oracle و MariaDB سازگار است.
به یاد داشته باشید که میتوانید پایگاه داده پروژه خود را در تنظیمات DATABASES داخل فایل settings.py تعریف کنید. جنگو میتواند همزمان با چندین پایگاه داده کار کند و شما میتوانید Routerهای دیتابیس را برای ایجاد طرحهای مسیریابی داده اختصاصی برنامهنویسی کنید.
پس از ایجاد مدلهای داده، جنگو یک API رایگان برای تعامل با آنها به شما میدهد. میتوانید مستندات رسمی مربوط به API مدلها را در آدرس https://docs.djangoproject.com/en/5.0/ref/models/ مطالعه کنید.
ORM جنگو بر پایه QuerySetها بنا شده است. یک QuerySet مجموعهای از کوئریهای پایگاه داده برای بازیابی آبجکتها از دیتابیس است. شما میتوانید فیلترهایی را روی QuerySetها اعمال کنید تا نتایج کوئری را بر اساس پارامترهای مشخص محدود کنید. QuerySet معادل عبارت SELECT در SQL است و فیلترها شروط و محدودکنندههایی مانند WHERE یا LIMIT محسوب میشوند.
در ادامه میآموزید که چگونه QuerySetها را بسازید و اجرا کنید.
برای باز کردن شل پایتون، دستور زیر را در ترمینال اجرا کنید:
python manage.py shell
سپس خطوط زیر را وارد کنید:
>>> from django.contrib.auth.models import User >>> from blog.models import Post >>> user = User.objects.get(username='admin') >>> post = Post(title='Another post', slug='another-post', body='Post body.', author=user) >>> post.save()
بیایید عملکرد این کد را تحلیل کنیم:
ابتدا، آبجکت کاربر با نام کاربری admin را بازیابی میکنیم:
user = User.objects.get(username='admin')
متد get() به ما امکان میدهد یک آبجکت تکی را از پایگاه داده بازیابی کنیم. این متد در پشت صحنه یک دستور SELECT اجرا میکند. توجه داشته باشید که این متد انتظار دارد دقیقاً یک نتیجه منطبق با کوئری پیدا کند. اگر دیتابیس هیچ نتیجهای برنگرداند، این متد استثنای DoesNotExist و اگر بیش از یک نتیجه برگرداند، استثنای MultipleObjectsReturned را پرتاب میکند. هر دوی این استثناها ویژگیهای کلاس مدلی هستند که کوئری روی آن انجام میشود.
سپس یک نمونه از Post با عنوان، اسلاگ و بدنه سفارشی ایجاد میکنیم و کاربری که قبلاً بازیابی کردیم را به عنوان نویسنده پست قرار میدهیم. این آبجکت در حافظه (RAM) قرار دارد و هنوز در دیتابیس ذخیره نشده است.
در نهایت، با استفاده از متد save()، آبجکت Post را در دیتابیس ذخیره میکنیم. این کار در پشت صحنه دستور INSERT را اجرا میکند.
شما میتوانید ایجاد و ذخیره آبجکت را در یک مرحله با استفاده از متد create() نیز انجام دهید:
>>> Post.objects.create(title='One more post', slug='one-more-post', body='Post body.', author=user)
در شرایطی که نیاز دارید آبجکتی را از دیتابیس بگیرید یا اگر وجود نداشت آن را بسازید، متد get_or_create() کارآمد است. این متد یک تاپل شامل آبجکت بازیابیشده (یا ایجاد شده) و یک مقدار بولی (Boolean) که نشان میدهد آیا آبجکت جدید ساخته شده است یا خیر، برمیگرداند.
کافی است ویژگیهای آبجکت را تغییر داده و دوباره save() را فراخوانی کنید:
>>> post.title = 'New title' >>> post.save()
این بار متد save() دستور UPDATE را در SQL اجرا میکند.
هر مدل جنگو حداقل یک Manager دارد و مدیر پیشفرض objects نامیده میشود. برای دریافت تمام آبجکتها از یک جدول، از متد all() استفاده میکنیم:
all_posts = Post.objects.all()
نکته کلیدی اینجاست: QuerySetها تنبل (Lazy) هستند. یعنی تا زمانی که مجبور به اجرا نشوند، کوئری به دیتابیس ارسال نمیکنند. این ویژگی باعث بهرهوری بالای ORM میشود.
برای فیلتر کردن یک QuerySet، از متد filter() استفاده میکنیم که به شما اجازه میدهد محتوای عبارت WHERE در SQL را مشخص کنید. (در شل وارد کنید):
Post.objects.filter(title='Who was Django Reinhardt?')
نکته: تغییراتی که روی یک مدل اعمال میکنید تا زمانی که متد save() را فراخوانی نکنید، در دیتابیس ثبت نمیشوند.
با چاپ کردن ویژگی query از یک QuerySet، میتوانید دستور SQL تولید شده توسط جنگو را مشاهده کنید:
خروجی SQL نشان میدهد که یک جستجوی دقیق (Exact match) روی ستون title انجام شده و به دلیل اینکه ترتیب خاصی مشخص نکردیم، از گزینه پیشفرض ordering در تنظیمات Meta مدل استفاده شده است.
>>> posts = Post.objects.filter(title='Who was Django Reinhardt?') >>> print(posts.query) # <- query اتریبیوت
با چاپ اتریبیوت query از کوئری-ست دریافتی، میتوان کد SQL تولید شده توسط جنگو را مشاهده کرد که در ادامه خواهید دید:
SELECT "blog_post"."id", "blog_post"."title", "blog_post"."slug", "blog_ post"."author_id", "blog_post"."body", "blog_post"."publish", "blog_ post"."created", "blog_post"."updated", "blog_post"."status" FROM "blog_post" WHERE "blog_post"."title" = Who was Django Reinhardt? ORDER BY "blog_ post"."publish" DESC
در این بخش از پست به بررسی QuerySetها در جنگو (Django) و انواع مختلف lookup یا الگوهای جستجو میپردازیم.
مثال پیشینِ QuerySet شامل یک فیلترِ جستجو (Lookup) با تطابق دقیق (Exact match) بود. رابطِ QuerySet انواع مختلفی از جستجوها را در اختیار شما قرار میدهد. برای تعریف نوع جستجو از دو آندرلاین استفاده میشود، که فرمت آن به صورت field__lookup است. برای نمونه، دستور زیر یک تطابق دقیق ایجاد میکند:
برای جستجوی دقیق از __exact استفاده میشود:
Post.objects.filter(id__exact=1)
اگر نوع جستجو را مشخص نکنید، جنگو بهصورت پیشفرض exact را فرض میکند:
Post.objects.filter(id=1)
برای جستجوی دقیق بدون حساسیت به حروف کوچک و بزرگ از __iexact استفاده میشود:
Post.objects.filter(title__iexact='who was django reinhardt?')
برای بررسی اینکه یک مقدار داخل مقدار فیلد وجود دارد از contains__ استفاده میشود. این lookup به SQL LIKE ترجمه میشود:
WHERE title LIKE '%Django%'
نسخه بدون حساسیت به حروف آن نیز با icontains__ در دسترس است.
برای بررسی وجود یک مقدار در یک iterable مانند لیست، tuple یا حتی یک QuerySet دیگر از __in استفاده میشود:
Post.objects.filter(id__in=[1, 3])
این lookupها برای مقایسه عددی و ترتیبی استفاده میشوند:
بزرگتر از: gt__
Post.objects.filter(id__gt=3)
معادل:
... WHERE id > 3
بزرگتر یا مساوی: gte__
کوچکتر از: lt__
کوچکتر یا مساوی: lte__
برای تطبیق ابتدای یا انتهای رشته:
startswith__
istartswith__
endswith__
iendswith__
میتوانید بر اساس اجزای مختلف یک DateTimeField یا DateField جستجو کنید:
Post.objects.filter(publish__date=date(2024, 1, 31)) Post.objects.filter(publish__year=2024) Post.objects.filter(publish__month=1) Post.objects.filter(publish__day=1) Post.objects.filter(publish__date__gt=date(2024, 1, 1))
با استفاده از دو آندرلاین (__) میتوان روی فیلدهای مدلهای مرتبط هم فیلتر اعمال کرد. (مثل کلید خارجی یا 1:M):
Post.objects.filter(author__username='admin')
همچنین میتوان جستجو در مدل مرتبط را زنجیرهای کرد:
Post.objects.filter(author__username__istartswith='ad')
و حتی چند شرط را همزمان اعمال کرد:
Post.objects.filter(publish__year=2024, author__username='admin')
نتیجهی filter() خودش یک QuerySet دیگر است، بنابراین میتوان فیلترها را زنجیرهای کرد:
Post.objects.filter(publish__year=2024) \ .filter(author__username='admin')
()excludeبرای حذف برخی نتایج از QuerySet از ()exclude استفاده میشود:
Post.objects.filter(publish__year=2024) \ .exclude(title__startswith='Why')
مرتبسازی پیشفرض در گزینه ordering در کلاس Meta مدل تعریف میشود.
برای تغییر ترتیب از ()order_by استفاده میکنیم:
صعودی:
Post.objects.order_by('title')
نزولی:
Post.objects.order_by('-title')
بر اساس چند فیلد:
Post.objects.order_by('author', 'title')
ترتیب تصادفی:
Post.objects.order_by('?')
میتوان با syntax برش آرایهای (slicing) تعداد نتایج را محدود کرد:
Post.objects.all()[:5]
این معادل SQL LIMIT 5 است.
مثال دیگر:
Post.objects.all()[3:6]
این مورد معادل دریافت اشیای چهارم تا ششم است و از نظر SQL به OFFSET و LIMIT ترجمه میشود.
برای گرفتن یک شیء منفرد نیز میتوان از index استفاده کرد:
Post.objects.order_by('?')[0]
متد count() تعداد کل اشیای منطبق با QuerySet را برمیگرداند:
Post.objects.filter(id__lt=3).count()
این معادل SELECT COUNT(*) در SQL است.
برای بررسی اینکه آیا QuerySet نتیجهای دارد یا نه از ()exists استفاده میشود:
Post.objects.filter(title__startswith='Why').exists()
خروجی این متد True یا False است.
برای حذف یک نمونه از متد ()delete استفاده میشود:
post = Post.objects.get(id=1) post.delete()
توجه داشته باشید که حذف یک آبجکت میتواند آبجکتهای وابسته را هم حذف کند، بهخصوص وقتی فیلدهای ForeignKey با on_delete=models.CASCADE تعریف شده باشند.
Q objectفیلترهای ()filter بهصورت پیشفرض با AND در SQL ترکیب میشوند.
برای ساخت شرطهای پیچیدهتر، مثل OR، از Q object استفاده میشود.
Q object مجموعهای از جستجوها | lookup را کپسوله میکند و میتوان آنها را با عملگرهای زیر ترکیب کرد:
& برای AND
| برای OR
^ برای XOR
مثال:
pythonfrom django.db.models import Q starts_who = Q(title__istartswith='who') starts_why = Q(title__istartswith='why') Post.objects.filter(starts_who | starts_why)
برای اطلاعات بیشتر:
https://docs.djangoproject.com/en/5.0/topics/db/queries/#complex-lookups-with-q-objects
ساختن یک QuerySet بهتنهایی هیچ کوئریای به دیتابیس ارسال نمیکند تا زمانی که evaluate شود.
ویژگی مهم QuerySetها این است که lazy هستند؛ یعنی تا زمان نیاز واقعی اجرا نمیشوند. این باعث میشود بتوانید چندین ()filter را زنجیرهای کنید بدون اینکه هنوز دیتابیس را hit کنید.
QuerySetها معمولاً در موارد زیر ارزیابی میشوند:
اولین بار که روی آنها iterate میکنید
هنگام slicing مثل:
Post.objects.all()[:3]
هنگام pickle یا cache کردن
هنگام فراخوانی ()repr یا ()len
هنگام تبدیل صریح به ()list
هنگام استفاده در شرطهای بولی مانند ()bool، or، and یا if
پست قبلی: (۱.۱۱: اضافه کردن آیکون شمارنده به فیلترها)
پست بعدی: (۱.۱۳: یادگیری ModelManager و شخصی سازی آن)