کدستانِ من

۲ مطلب با کلمه‌ی کلیدی «django-orm» ثبت شده است

  • ۰
  • ۰

مدل های جنگو مانند هر کلاس دیگری در پایتون می توانند از یکدیگر ارث بری کنند. برای مدل های جنگو سه نوع وراثت وجود دارد. یک نوع وراثت به صورت ابسترکت (abstract) است و یک نوع به صورت غیر ابسترکت که به صورت وراثت چند جدولی (multi-table inheritance) است. نوع سوم وراثت نیز حالتی است که هدف تغییر فیلدهای مدل نیست بلکه می خواهیم رفتار یک مدل را تغییر دهیم و به آن مدل پراکسی (proxy) گفته می شود. در این پست در مورد وراثت غیرابسترکت صحبت می کنیم.


اگر مدل های جنگو به صورت معمولی از یکدیگر ارث بری کنند وراثت چندجدولی خواهیم داشت. علت نامگذاری هم این است که به ازای هر کلاس یک جدول جدا در دیتابیس تشکیل می شود.
در این نوع ارث بری ویژگی های مشترک در یک کلاس یا جدول
parent گذاشته می شوند و بعد هر کلاس یا جدول child فیلدهای مختص خودش را خواهد داشت که با یک کلید خارجی به کلاس یا جدول parent اشاره می کند. البته توجه کنید که در ORM جنگو این کلید خارجی به صورت فیلد OneToOneField پیاده سازی شده است


from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=225)
    price = models.PositiveIntegerField()

class DigitalProduct(Product):
    screen_size = models.PositiveSmallIntegerField()
    os = models.CharField(max_length=100, null=True)

class ElectronicProduct(Product):
    energy = models.PositiveSmallIntegerField()

class Mobile(DigitalProduct):
    camera_resolution = models.PositiveIntegerField()
    sim_cards = models.PositiveIntegerField()

class Laptop(DigitalProduct):
    cpu = models.CharField(max_length=100)
    ram_type = models.CharField(max_length=100)

همانطور که گفته شد در مثال بالا به ازای هر کلاس یک جدول ساخته می شود. یعنی در دیتابیس اکنون جداول test_product و test_digitialproduct و test_electronicproduct و test_mobile و test_laptop وجود دارند (اسم اپ test است) که هر جدول در صورت وجود با کلید خارجی به parent خود اشاره می کند. به دلیل این نوع ذخیره سازی، امکان جستجو در کلیه ی نمونه های Product وجود دارد بدون در نظر گرفتن اینکه این نمونه ها DigitalProduct باشند یا ElectronicProduct یا نمونه های فرزند دیگر. به این ترتیب سه کوئری


>>> Product.objects.all()
>>> ElectronicProduct.objects.all()
>>> Laptop.objects.all()

به ترتیب

  • همه ی اشیای Product (شامل نمونه های Mobile, DigitalProduct, ElectronicProduct, Product و Laptop)
  • اشیای ElectronicProduct
  • اشیای Laptop

را جستجو می کنند.

 

با این تفاسیر وراثت چند جدولی چنین ویژگی هایی دارد:

  • جستجو در همه ی اشیاء یک نوع
  • ارتباط همه ی اشیای یک نوع (با کلید خارجی)

این نوع وراثت همچنین این معایب را دارد:

  • چون به ازای هر کوئری، عملیات افزودن، حذف یا به روزرسانی باید در همه ی جدول های زنجیره ی وراثت انجام شود کوئری های بیش تری نیاز است.
  • در عمل به ازای کوئری های select میان جدول ها پیوست (join) صورت می گیرد! بنابراین به ازای هر کوئری باید میان جداول زنجیره ی وراثت پیوست های زیادی انجام شود. هرچه این زنجیره ی وراثت طولانی تر باشد تعداد پیوست ها بیش تر است.

 

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

  • سارا الف
  • ۰
  • ۰

همانطور که می دانید Django ORM ابزاری برای ایجاد، حذف، بازیابی و به روز رسانی داده ها در دیتابیس را در اختیار ما قرار داده است. گاهی در ارتباط با دیتابیس به عملیات پیچیده تری نیاز داریم. یکی از ابزارهای در دسترس aggregate و annotate هستند که برای متراکم کردن یا خلاصه سازی اطلاعات به کار می روند. اگر درخواست های ما به دیتابیس شامل عملیاتی مثل شمردن، میانگین گیری، جمع کردن، یافتن ماکزیمم و مینیمم باشد می توان از aggregate و annotate استفاده کرد.

 

در مثال های این بخش از مدل های زیر استفاده شده است:


class Brand(models.Model):
    name = models.CharField(max_length=50)

class Product(models.Model):
    name = models.CharField(max_length=100)
    brand = models.ForeignKey(Brand, on_delete=models.CASCADE)
    price = models.PositiveIntegerField()
    amount = models.PositiveSmallIntegerField()

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


from django.db.models import Sum

Product.objects.aggregate(Sum('amount'))
>>>{'amount__sum': 114}

همانطور که می بینید aggregate یک دیکشنری پایتون برمی گرداند. در نتیجه نمی توان روی نتیجه ی آن از متدهای کوئری ست مثل filter، values و ... استفاده کرد.

حال می خواهیم تعداد محصولات را در هر برند پیدا کنیم. در این حالت دیگر نمی خواهیم نتیجه را از کل داده ها پیدا کنیم بلکه هدف پیدا کردن نتیجه در هر گروه برندها است. در این صورت دیگر نمی توان از aggregate استفاده کرد و به جای آن باید از annotate استفاده کرد.
متد
annotate در واقع GROUP BY دیتابیس های SQL را پیاده سازی می کند. این متد برخلاف aggregate لیستی از کوئری ست ها را بر می گرداند در نتیجه می توان روی آن عملیاتی مثل filter، values، order_by و ... را انجام داد.
پیدا کردن تعداد محصولات هر برند با
SQL به شکل زیر انجام می شود:

SELECT Brand.name, SUM(Product.amount) AS Brand_sum
FROM Brand LEFT OUTER JOIN Product ON Brand.id=Product.brand
GROUP BY Brand.name;

همین دستور SQL را می توان با annotate به شکل زیر پیاده سازی کرد:
 


from django.db.models import Sum

brands = Brand.objects.annotate(brand_sum=Sum('product__amount'))
for brand in brands:
    print(brand.name, ' : ', brand.brand_sum)

که کوئری متناظر با آن در جنگو ORM به صورت زیر است:

SELECT "test_group_brand"."id", "test_group_brand"."name", SUM("test_group_product"."amount") AS "brand_sum"
FROM "test_group_brand" LEFT OUTER JOIN "test_group_product" ON ("test_group_brand"."id" = "test_group_product"."brand_id")
GROUP BY "test_group_brand"."id", "test_group_brand"."name"

 

البته مسئله ی بالا را می توان به شکل زیر هم نوشت:


from django.db.models import Sum

brands = Product.objects.values('brand__name').annotate(brand_sum=Sum('amount'))


که کوئری متناظر با آن به این صورت است:
 

SELECT "test_group_brand"."name", SUM("test_group_product"."amount") AS "brand_sum"
FROM "test_group_product" INNER JOIN "test_group_brand" ON ("test_group_product"."brand_id" = "test_group_brand"."id")
GROUP BY "test_group_brand"."name"

همانطور که گفته شد می توان متدهای کوئری ست را روی نتیجه ی annotate اعمال کرد. برای مثال اگر بخواهیم نام برندهایی که بیش از ۷۰۰ عدد موجودی دارند را مشخص کنیم از قطعه کد زیر استفاده می کنیم:

 


from django.db.models import Sum

brands = Brand.objects.annotate(brand_sum=Sum('product__amount')).filter(brand_sum__gt=700).values('name','brand_sum')

که کوئری آن برابر است با:

SELECT "test_group_brand"."name", SUM("test_group_product"."amount") AS "brand_sum"
FROM "test_group_brand" LEFT OUTER JOIN "test_group_product" ON ("test_group_brand"."id" = "test_group_product"."brand_id")
GROUP BY "test_group_brand"."id", "test_group_brand"."name"
HAVING SUM("test_group_product"."amount") > 400

  • سارا الف