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

در این جلسه در رابطه با مفهوم manager در جنگو صحبت می کنیم . با ما همراه باشید . آموزش جنگو : جلسه چهل و سه | بررسی Manager در جنگومفهوم Managerدر واقع Manager(در بعضی بخش ها آن را به عنوان مدیر خواندید) یک رابط میان مدل و کوئری های شماست . در واقع کاری که انجام می دهد این است که کوئری (query) را بررسی می کند و پس از اینکه فهمید این کوئری چکاری انجام می دهد , آن را روی مدل پیاده سازی می کند . حداقل یک manager برای هر مدل وجود دارد .همچنین managerیک مدل ,دسترسی به تمام متد های قابل اجرا برای دیتابیس را فراهم می کند . برای مثال برای فیلتر کردن رکورد های جدولی ,از یک کلاس manager استفاده می کنیم و می گوییم : model.manager_name.filterنحوه کار managerها را قبلا بررسی کردیم . در این بخش درباره شخصی سازی کلاس های manager است . (احتمالا تمام آنچه می دانید این است که manager یک کلاس قابل بازنویسی شدن است)نام گذاری managerبه طور پیش فرض ,هر manager یک نام دارد . جنگو به طور پیش فرض برای هر مدل یک manager را ایجاد می کند و نام آن را objects می گذارد . گرچه ,اگر می خواهید نام manager را تغییر بدهید و یا نام یکی از فیلد ها را objectsبگذارید ,کافی است آن را در همان کلاس مدل تغییر دهید . برای تغییر نام یک manager برای یک کلاس یا مدل مشخص یک ویژگی در همان کلاس تعریف کنید و مقدار آن را برابر با models.Manager قرار دهید . برای مثال :from django.db import models class Person(models.Model): # ... people = models.Managerبا استفاده از این مثال ,استفاده از Person.objects برای دسترسی به manager باعث ایجاد AttributeError می شود. شما برای دسترسی به manager باید از Person.people استفاده کنید . برای مثال Person.people.allشخصی سازی managerشما می توانید به وسیله ارث بری از کلاس Manager و گسترش آن ,یک manager سفارشی را ایجاد کنید و آن را در مدل خود به شکلی که بالا گفتیم ,معرفی کنید .دو دلیل وجود دارد که بخواهید یک manager سفارشی را ایجاد کنید . اضافه کردن متد های سفارشی به manager و یا تغییر آنچه QuerySet اولیه یا پیش فرض می تواند بازگرداند . حال بیایید به معرفی هر کدام از این موقعیت ها بپردازیم .اضافه کردن متد سفارشی به managerافزودن متد های سفارشی به یک کلاس manager ,راهی است که می توانید توابعی را برای اجرا در سطح جدول از دیتابیس ,تعریف کنید . برای ساخت توابعی که در سطرها تاثیر بگذارند (برای مثال توابعی که فقط روی یک رکورد از جدول تاثیر بگذارند) از متد های Model استفاده کنید و کلاس manager را تغییر ندهید .برای مثال ,در کد زیر یک تابع (متد نیز می توان گفت) با نام with_counts را به یک manager اختصاص دادیم و از آن در یک مدل استفاده کردیم :from django.db import models from django.db.models.functions import Coalesce class PollManager(models.Manager): def with_counts(self): return self.annotate(num_responses=Coalesce(models.Count(&quotresponse&quot), 0)) class OpinionPoll(models.Model): question = models.CharField(max_length=200) objects = PollManager class Response(models.Model): poll = models.ForeignKey(OpinionPoll, on_delete=models.CASCADE) # ...در مثال بالا ,می توانید از کد OpinionPoll.objects.with_counts ,برای دریافت یک QuerySet از اشیای مدل OpinionPoll که یک ویژگی num_responses در خود داشته باشند,استفاده کنید .یک تابع سفارشی در یک manager می تواند هر چیزی را بازگرداند و لازم نیست حتما یک QuerySet باشد .نکته دیگری که باید به آن توجه داشت این است که متد های Manager می توانند به self.model دسترسی داشته باشند . این یعنی دسترسی مستقیم به مدلی که از آن کلاس به عنوان manager استفاده کند .تغییر آنچه QuerySet بازمی گرداندمعمولا managerها برای مقدار QuerySet اولیه خود ,تمام اشیا مدل را بازمیگردانند . یعنی آنها روی تمامی اشیا عملیاتی را انجام می دهند و شما می توانید آنها را محدودتر کنید تا روی تعداد کمتری از اشیا عملیات ها را انجام بدهند . برای ادامه بخش ,مثال زیر را در نظر داشته باشید :from django.db import models class Book(models.Model): title = models.CharField(max_length=100) author = models.CharField(max_length=50)کد Book.objects.all تمامی اشیا درون مدل Book را بازخواهد گرداند . در واقع از زاویه ای دیگر می توان گفت تمامی رکورد های درون جدول Book را بازمیگرداند . می توان با بازنویسی (override) متد Manager.get_queryset در کلاس مربوط به manager ,آن QuerySet اولیه را بازنویسی کرد تا یک QuerySet با ویژگی های موردنظر شما را بازگرداند .به عنوان مثال ,مدل زیر دارای دو manager است . یکی که به صورت پیش فرض همه ی اشیا را بازمیگرداند و دیگری که به صورت پیش فرض فقط اشیایی را بازمیگرداند که مربوط به شخصی با نام Roald Dahl(نویسنده کتاب هایی که اکثر آنها یک فیلنامه شدند مانند چارلی و کارخانه شکلات سازی!) باشد .class DahlBookManager(models.Manager): def get_queryset(self): return super.get_queryset.filter(author=&quotRoald Dahl&quot) class Book(models.Model): title = models.CharField(max_length=100) author = models.CharField(max_length=50) objects = models.Manager # The default manager. dahl_objects = DahlBookManager # The Dahl-specific manager.نکته : منظور از QuerySet پیش فرض یک manager ,در واقع اشیایی است که جنگو آنها را به صورت معمول پیدا می کند یا روی آنها عملیاتی انجام می دهد . برای مثال برای managerهای عادی تمام اشیا است و به این معنی است که متد filter در آن manager روی تمامی اشیا عملیات جستجوی خود را انجام می دهد . ولی برای manager شماره دو مثال بالا ,جستجو روی اشیایی انجام می شود که نویسنده آنها همان شخص نامبرده است .با مدل های بالا ,کد Book.objects.all تمامی اشیا درون مدل را بازمیگرداند و Book.dahl_objects.all فقط اشیایی را بازمیگرداند که نویسنده آنها آقای Dahl باشد .از آنجایی که متد get_queryset یک QuerySet را بازمیگرداند ,پس می توانید از تمامی متد های exclude یا filter روی آن استفاده کنید . بنابراین کدهای زیر ,تمامی کار خواهند کرد :Book.dahl_objects.all Book.dahl_objects.filter(title=&quotMatilda&quot) Book.dahl_objects.countالبته قبل از هر چیزی ,این مثال یک نکته کاربردی دیگر نیز داشت . اینکه می توانید از چند manager در یک مدل استفاده کنید . شما می توانید هر تعداد manager که می خواهید در یک مدل تعریف کنید . برای مثال :class AuthorManager(models.Manager): def get_queryset(self): return super.get_queryset.filter(role=&quotA&quot) class EditorManager(models.Manager): def get_queryset(self): return super.get_queryset.filter(role=&quotE&quot) class Person(models.Model): first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) role = models.CharField( max_length=1, choices=[(&quotA&quot, _(&quotAuthor&quot)), (&quotE&quot, _(&quotEditor&quot))]) people = models.Manager authors = AuthorManager editors = EditorManagerدر مثال بالا می توانید هر بار کد های Person.authors.all , Person.editors.all و Person.people.all را فراخوانی کنید و نتایج مختلف را که می خواهید ,داشته باشید .مفهوم default managerاگر چند ویژگی manager را درون مدل خود تعریف کردید ,به یاد داشته باشید که ترتیب تعریف آنها مهم است . معمولا اولین ویژگی manager تعریف شده در مدل به عنوان یک پیش فرض در نظر گرفته می شود و چندین بخش جنگو (مانند dumpdata) از همان manager استفاده می کنند . بعدا بیشتر درباره بخش هایی که از manager پیش فرض استفاده می کنند ,خواهید فهمید . در نتیجه کافی است در تعریف اولین ویژگی ,مراقب باشید .البته در کلاس متا مدل می توانید پیش فرض را با استفاده Meta.default_manager_name تغییر دهید . نام manager که می خواهید پیش فرض باشد را به عنوان مقدار به این ویژگی بدهید .اگر در حال نوشتن کدی هستید که باید یک مدل ناشناخته را مدیریت کند (به عنوان مثال در اپ third-party یا شخص ثالث که یک generic view را پیاده سازی می کند) , از همین manager استفاده کنید . البته می توانید از base_manager_ نیز استفاده کنید که جلوتر توضیح می دهیم .مفهوم base manager و کاربرد آن در روابط دیتابیسیجنگو برای دسترسی به اشیایی که در روابط (مانند one-to-one) وجود دارند ,به طور پیش فرض از base_manager_ مدل شما استفاده می کند . نام آن را به اختصار base manager می گویند .نکته : base_manager_ یک مدل با manager پیش فرض آن متفاوت است .می توانید در صورت نیاز با استفاده از ویژگی Meta.base_manager_name در کلاس متای مدل و ارسال مقدار نام manager مد نظر خودتان , base manager یک مدل را تغییر دهید .معمولا base manager در هنگام اجرای کوئری در مدل هایی که رابطه ای دارند ,یا هنگام دسترسی به یک رابطه one-to-many و many-to-many استفاده نمی شود . برای مثال ,اگر مدل Question(در آموزش های قبلی) یک فیلد حذف شده و یک base manager داشت که اشیا را به صورت deleted=True فیلتر می کند ,یک QuerySet مانند Choice.objects.filter(question__name__startswith='What') شامل اشیایی از Choice خواهد بود که رابطه ای با اشیا Question که حذف شدند ,داشته اند .فیلتر کردن متد get_querysetاین manager برای دسترسی به اشیایی استفاده می شود که از مدل های دیگر با آن شی رابطه ای ایجاد شده است . بنابراین جنگو باید به تمامی اشیا کاملا دسترسی داشته باشد تا هر چیزی را که بخواهد را از دیتابیس دریافت کند .پس ,اصلا نباید متد get_queryset بازنویسی شود تا سطرها را فیلتر یا محدود کند . اگر این کار را انجام بدهید ,جنگو نتایج را ناقص بازمیگرداند .فراخوانی متد های سفارشی QuerySet در managerدر حالی که اکثر متد های یک manager به صورت استاندارد از خودش قابل دسترسی هستند ,می توانید متد های اضافی نیز برای QuerySet مربوط به مدل تعریف کنید تا از آنها استفاده کنید . شکل تعریف آنها به صورت زیر است :class PersonQuerySet(models.QuerySet): def authors(self): return self.filter(role=&quotA&quot) def editors(self): return self.filter(role=&quotE&quot) class PersonManager(models.Manager): def get_queryset(self): return PersonQuerySet(self.model, using=self._db) def authors(self): return self.get_queryset.authors def editors(self): return self.get_queryset.editors class Person(models.Model): first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) role = models.CharField( max_length=1, choices=[(&quotA&quot, _(&quotAuthor&quot)), (&quotE&quot, _(&quotEditor&quot))]) people = PersonManagerدر این مثال شما می توانید هم authors و هم editors را از Person.people فراخوانی کنید و اشیایی را دریافت کنید .ساخت manager با as_managerبه جای عملکرد بالا که نیاز دارد تا دوبار متدها در کلاس های manager و QuerySet تکرار شوند ,می توانید از QuerySet.as_manager استفاده کنید تا یک شی از Manager با کپی ای از متد های درون آن QuerySet ایجاد شود و از آن استفاده کنید . برای مثال :class Person(models.Model): ... people = PersonQuerySet.as_managerشی ای که با QuerySet.as_manager به عنوان manager تولید می شود تقریبا با PersonManager در مثال قبلی یکسان است .به یاد داشته باشید که نیاز نیست تا همه ی متد های QuerySet در manager وجود داشته باشند . برای مثال ممکن است شما عمدا بخواهید از کپی شدن متد QuerySet.delete جلوگیری کنید .کپی شدن متد ها قوانین خاص خود را دارد :متد های عمومی یا Public به صورت پیش فرض کپی خواهند شد .متد های شخصی یا Private(همان هایی که با _شروع می شوند) به طور پیش فرض کپی نمی شوند .متد هایی که ویژگی queryset_only=False دارند ,همیشه کپی می شوند .متد هایی که ویژگی queryset_only=True دارند ,هرگز کپی نمی شوند .برای مثال :class CustomQuerySet(models.QuerySet): # Available on both Manager and QuerySet. def public_method(self): return # Available only on QuerySet. def _private_method(self): return # Available only on QuerySet. def opted_out_public_method(self): return opted_out_public_method.queryset_only = True # Available on both Manager and QuerySet. def _opted_in_private_method(self): return _opted_in_private_method.queryset_only = Falseمتد from_querysetجنگو یک متد در کلاس های خود با عنوان from_queryset نیز دارد . سینتکس آن به شکل from_queryset(queryset_class) است . برای موارد پیشرفته ممکن است هم یک manager شخصی سازی شده و هم یک QuerySet شخصی سازی شده بخواهید . شما می توانید این کار را به وسیله Manager.from_queryset انجام بدهید که یک زیرکلاس از manager شما را با کپی ای از متد های QuerySet شخصی سازی شده شما بازمیگرداند:class CustomManager(models.Manager): def manager_only_method(self): return class CustomQuerySet(models.QuerySet): def manager_and_queryset_method(self): return class MyModel(models.Model): objects = CustomManager.from_queryset(CustomQuerySet)شما می توانید کلاس تولید شده را داخل یک متغییر نیز به شکل زیر ذخیره کنید :MyManager = CustomManager.from_queryset(CustomQuerySet) class MyModel(models.Model): objects = MyManagerارث بری یک managerدر این بخش قرار است بررسی کنیم که جنگو چگونه با ارث بری مدل ها و manager آنها برخورد می کند .معمولا کلاس فرزند همیشه از manager تعریف شده در کلاس پدر ارث بری می کند . همچنین قواعد پایتون در این ارث بری رعایت می شوند . یعنی می توانید manager به ارث رسیده را در کلاس فرزند دوباره نامگذاری کنید و آن را بازنویسی کنید . سپس managerهای کلاس والد به ارث خواهند رسید .اگر کلاس والد هیچ ویژگی manager تعریف شده ای نداشته باشد ,به صورت اتوماتیک کلاس فرزند , objects را که پیش فرض جنگو است برای manager برمی گزیند .در جنگو , manager پیش فرض مدل اولین manager است که در مدل تعریف می شود یا Meta.default_manager_name آن را مشخص کرده است . در بحث ارث بری نیز manager پیش فرض اولین مدل والد به ارث خواهد رسید .مواردی که گفته شد ,اصول اولیه در بحث ارث بری است . اما ممکن است کمی در بحث ارث بری از کلاس های abstract تفاوت وجود داشته باشد . برای مثال ,فرض کنید این کلاس را دارید :(کلاس دارای ویژگی abstract=True)class AbstractBase(models.Model): # ... objects = CustomManager class Meta: abstract = Trueچون هیچ manager خاصی در کلاس والد تعریف نشده است ,اگر یک کلاس فرزند از این کلاس ارث بری کند , manager پیش فرض آن برابر با objects خواهد شد :class ChildA(AbstractBase): # ... # This class has CustomManager as the default manager. passاگر می خواهید همچنان از AbstractBase ارث بری کنید ولی یک manager پیش فرض دیگر را برای کلاس فرزند انتخاب کنید ,می توانید آن را در همان کلاس فرزند تعیین کنید :class ChildB(AbstractBase): # ... # An explicit default manager. default_manager = OtherManagerدر اینجا default_manager به عنوان پیش فرض مدل انتخاب می شود . در اصل objects نیز هنوز در دسترس است زیرا به ارث رسیده است ولی manager پیش فرض شما تغییر پیدا کرده است .در نهایت فرض کنید برای این مثال می خواهید یک سری از کلاس های manager را نیز به کلاس فرزند اضافه کنید ,اما همچنان می خواهید از پیش فرض AbstractBase که به ارث رسیده است ,استفاده کنید . نمی توانید به صورت مستقیم یک manager دیگر را در کلاس فرزند معرفی کنید ,زیرا این امر باعث می شود تا manager پیش فرض شما به ارث نرسد و بازنویسی شود . راه حل این است که manager اضافی را در یک کلاس دیگر قرار دهید و سپس آن را بعد از کلاس AbstractBase ,به ارث ببرید . برای مثال :class ExtraManager(models.Model): extra_manager = OtherManager class Meta: abstract = True class ChildC(AbstractBase, ExtraManager): # ... # Default manager is CustomManager, but OtherManager is # also available via the &quotextra_manager&quot attribute. passتوجه داشته باشید که شما اجازه دارید تا یک متد شخصی سازی شده را در کلاس والد (کلاس های abstract البته) بنویسید ولی نمی توانید آنها را از همان کلاس والد فراخوانی کنید . برای مثال :ClassA.objects.do_somethingکد بالا کار خواهد کرد اما :AbstractBase.objects.do_somethingاین کد باعث ایجاد یک ارور خواهد شد . دلیل آن این است که manager برای این طراحی شده است تا مجموعه ای از اشیا را مدیریت کند . در مدل های abstract نمی توانید مجموعه ای از اشیا داشته باشید ,پس مدیریت آنها منطقی نیست .مشکلات احتمالی در ساخت یک managerهر قابلیتی که به manager خود اضافه می کنید ,باید امکان این را در manager ایجاد کند که یک کپی از همان manager بتوانید بسازید . برای مثال کد زیر کار می کند :>>> import copy >>> manager = MyManager >>> my_copy = copy.copy(manager)جنگو در اینجا می تواند در حین اجرای یک کوئری ,کپی هایی از آن manager ایجاد کند . اگر اینکار قابل انجام نباشد ,کوئری ها تماما با شکست مواجه می شوند .البته این مشکل برای اکثر کلاس های manager شخصی سازی شده وجود ندارد . اگر در حال اضافه کردن متد های ساده ای به manager هستید ,بعید است که سهوا آن را غیرقابل کپی کنید . با این حال اگر شما در حال بازنویسی متد های private کلاس manager خود مانند __getattr__ هستید ,باید اطمینان حاصل کنید که manager همچنان بتواند کپی شود .در این جلسه به بررسی نحوه کار با یک manager در جنگو پرداختیم . در جلسه بعدی به بررسی مفهوم Raw SQL در جنگو خواهیم پرداخت .