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

در این جلسه در رابطه با نحوه عملکرد مدل ها در جنگو صحبت می کنیم و api مربوط به آنها را بررسی می کنیم. با ما همراه باشید . آموزش جنگو : جلسه سی و شش | بررسی Model API در جنگوقبل از اینکه وارد این مبحث شویم , بیایید یک متد دیگر که در سری قبلی مقالات توضیحات کامل آن نبود , را بررسی کنیم . توابع Aggregationمتد aggregate را به یاد داشته باشید . سینتکس استفاده از آن به شکل زیر بود .>>> q = Blog.objects.aggregate(Count('entry'))این متد برای شما مقدارهایی از یک فیلد در آن مدل را جمع آوری می کرد و برای شما آن را محاسبه می کرد . این محاسبه میتواند میانگین آنها ,جمع آنها یا تعداد آنها (مانند مثال بالا) یا بزرگترین و کوچکترین مقادیر در میان آنها باشد . برای استفاده از هر کدام کافی است از توابع آن استفاده کنیم . در کد بالا از تابع مربوط به شمارش (Count) استفاده کردیم .نکته : در SQLite شما نمی توانید aggregate را روی فیلد های DateTimeField و هر فیلد مربوط به تاریخ انجام دهید . زیرا این دیتابیس اصلا از تاریخ پشتیبانی نمی کند و فیلد های مربوط به تاریخ توسط جنگو به متن تبدیل می شوند و سپس در دیتابیس ذخیره می شوند . تلاش برای استفاده از aggregate روی این نوع فیلد های منجر به ارور می شود .توجه داشته باشید که اگر یک QuerySet خالی باشد (درون آن شی ای وجود نداشته باشد) متد aggregate مقدار None را بازمیگرداند .اکنون توابع دیگر که می توانیم به شکل بالا از آنها برای محاسبات مقادیر استفاده کنیم را بررسی می کنیم .-مقدار Avg :مقدار میانگین از مقادیر فیلد ها را بازمیگرداند . خروجی به صورت float خواهد بود .-مقدار Count :تعداد اشیا و مقادیر را برای شما بازمیگرداند .-مقدار Max :بالاترین مقدار را پیدا می کند و برای شما بازمیگرداند .-مقدار Min :کمترین مقدار را پیدا می کند و برای شما بازمیگرداند .-مقدار Sum : جمع مقادیر را برای شما بازمیگرداند .-مقدار variance :اختلاف مقادیر را برای شما بازمیگرداند .نکته : برای مثال اگر شما از یک QuerySet خالی استفاده کنید ,تابع Sum به شما None را بازمیگرداند . ولی یک استثنا درباره تابع Count وجود دارد . برعکس دیگر توابع , Count عدد 0 را بجای None بازمیگرداند .البته اینجا مبحث aggregation را خلاصه باز کردیم و بعدا بیشتر به بحث درباره آن خواهیم پرداخت .اشیا در یک مدلدر این بخش ,به بررسی API مدل ها می پردازیم و توضیح می دهیم که جنگو کارهای خود را در دیتابیس چگونه پیش می برد . دانستن این رویکرد ها به شما کمک می کند تا جنگو را شخصی سازی کنید . تمام مثال های این بخش نیز مربوط به کد های بخش قبل است که نوشته ایم . (کد های مربوط به مدل های Entry و غیره)ساخت اشیابرای ساخت یا ایجاد یک شی در جنگو ,فقط کافی است کلاس مدل را نمونه سازی یا instantiate کنید . بعد از فراخوانی متد save از آن کلاس ,شی شما در دیتابیس ثبت خواهد شد و یک رکورد جدید ساخته خواهد شد .Class Model(**kwargs) :کد بالا را نگاه کنید , آرگومان ها و kwargs در هر مدل نام فیلد هایی است که در آن تعریف کردید . توجه داشته باشید نمونه سازی از مدل به تنهایی باعث ذخیره چیزی در دیتابیس نمی شود و این عملیات فقط بعد از save اتفاق میافتد .نکته :هر کلاس در پایتون یک تابع __init__ همراه خود دارد . شاید بخواهید تا برای شخصی سازی مدل , این تابع را override کنید . اگر اینکار را انجام دادید مراقب باشید تا آسیبی به مدل وارد نشود . تغییر در فرایند کارکرد مدل ممکن است باعث ذخیره نشدن نمونه ها در دیتابیس شود . به هر حال ,بهتر است بجای override کردن __init__ سعی کنید از روش های زیر استفاده کنید .1-یک classmethod به کلاس خود اضافه کنید . (cls همیشه باید به عنوان آرگومان ارسال شود)from django.db import models class Book(models.Model): title = models.CharField(max_length=100) @classmethod def create(cls, title): book = cls(title=title) # do something with the book return book book = Book.create(&quotPride and Prejudice&quot)در کد بالا ما یک تابع سفارشی (شخصی سازی) نوشتیم و از آن استفاده کردیم .2-تابع سفارشی را در یک manager بنویسید . (این روش توصیه می شود)class BookManager(models.Manager): def create_book(self, title): book = self.create(title=title) # do something with the book return book class Book(models.Model): title = models.CharField(max_length=100) objects = BookManager book = Book.objects.create_book(&quotPride and Prejudice&quot)البته که راه حل دوم نیاز به آشنایی با چگونگی ساخت manager ها دارد و در ادامه با آن بیشتر آشنا می شویم . به طور خلاصه کافی است یک کلاس که از models.Manager ارث بری می کند را به عنوان manager تعریف کنید و سپس در مدل اصلی objects را با کلاس manager مقداردهی کنید .لود شدن اشیا از دیتابیسجنگو در هر مدل متدی با نام from_db نیز دارد . در واقع هنگامی که جنگو داده ها را از دیتابیس دریافت می کند . داده ها به شکل خام هستند . بر فرض مثال که داده ها به این صورت باشند :رکورد اول : {'id': 14, 'name': 'foo'}رکورد دوم : {'id': 25, 'name': 'bar'}جنگو این داده های خام را به کمک متد from_db تبدیل به model obejcts می کند . در واقع اطلاعات خام به اشیا تبدیل می شوند و به شما تحویل داده می شوند . هر بار که یک QuerySet اجرا می شود این متد داده های دیتابیس را به اشیا تبدیل خواهد کرد . رکورد های بالا به شکل زیر به شما تحویل داده می شوند (همان نتیجه QuerySet ها) :SomeModel.from_db('db-alias', ['id', 'name'], [14, 'foo']) SomeModel.from_db('db-alias', ['id', 'name'], [25, 'bar'])این متد قابل شخصی سازی شدن است و میتواند override شود .متد from_db سه آرگومان دریافت می کند . آنها به ترتیب db , field_names و values نام دارند .آرگومان db همان نام مستعار دیتابیس یا db-alias است . دیتابیسی که قرار است داده ها از آن بارگیری شوند . آرگومان field_names شامل نام تمامی فیلد های بارگذاری شده است . در آخر آرگومان values نیز شامل مقادیر فیلد هایی است که در field_names وجود دارند . همه ی مقادیر به ترتیب قرار گرفتن فیلد ها در field_names قرار می گیرند . به همین علت تضمین می شود که مقادیر به ترتیبی باشند که __init__ آنها را لازم دارد . این دلیلی است که می توانید با cls(*values) یک نمونه ایجاد کرد (cls مخفف class است) .اگر روی هر کدام از فیلد ها defer فراخوانی شود ,در field_names نام آن فیلد قرار نخواهد گرفت . در آن صورت مقدار django.db.models.DEFERRED به هر کدام از آن فیلد ها اختصاص خواهد یافت .علاوه بر این متد from_db کارایی های دیگری نیز برای تنظیم state_ دارد . در صفحه بعد مثالی وجود دارد که نشان می دهد چگونه این متد مقادیر فیلد ها را از دیتابیس دریافت می کند و سپس تبدیل به مقادیر خروجی (model objects) می کند .from django.db.models import DEFERRED @classmethod def from_db(cls, db, field_names, values): # Default implementation of from_db(subject to change and could # be replaced with super). if len(values) != len(cls._meta.concrete_fields): values = list(values) values.reverse values = [ values.pop if f.attname in field_names else DEFERRED or f in cls._meta.concrete_fields ] instance = cls(*values) instance._state.adding = False instance._state.db = db # customization to store the original field values on the instance instance._loaded_values = dict( zip(field_names, (value for value in values if value is not DEFERRED)) ) return instance def save(self, *args, **kwargs): # Check how the current values differ from ._loaded_values. For example, # prevent changing the creator_id of the model. (This example doesn't # support cases where 'creator_id' is deferred). if not self._state.adding and ( self.creator_id != self._loaded_values['creator_id']): raise ValueError(&quotUpdating the value of creator isn't allowed&quot) super.save(*args, **kwargs)مثال بالا یک پیاده سازی کامل از متد from_db انجام می دهد تا چگونگی کارکرد آن را توضیح دهد . شما می توانید برای ساده سازی از super نیز استفاده کنید .ریلود شدن اشیااگر یک فیلد از یک شی را حذف کنید ,دسترسی مجدد به آن باعث می شود تا آن مقدار از دیتابیس دوباره بارگیری شود (ریلود شود) . برای مثال :>>> obj = MyModel.objects.first >>> del obj.field >>> obj.field # Loads the field from the databaseجنگو برای ریلود کردن مقادیر درون یک شی ,متدی با نام refresh_form_db دارد . البته این متد ها فقط درون کلاس وجود دارند و برای override کردن آنها ,این موضوعات را توضیح می دهیم . با تنظیم دوباره این متد ها میتوانید مدل خود را شخصی سازی کنید تا طبق انتظار شما کار کند .هنگامی که این متد از یک کلاس فراخوانی می شود ,فرایند های زیر اتفاق میافتد :1-تمام فیلد هایی که در defer قرار نگرفته باشند ,مقادیر خود را ریلود و بروزرسانی می کنند .2-هر رابطه (فیلد های روابطی) که کش شده باشد ,از شی پاک می شود .فقط فیلد های مدل شما ,از دیتابیس ریلود و بارگذاری مجدد می شوند . سایر مقادیر مانند فیلد هایی که در annotations هستند ,دوباره بارگذاری نمی شوند . در ضمن ,هیچ کدام از ویژگی و attribute های @cached_property نیز پاک نمی شود .این متد دو آرگومان را می پذیرد . آرگومان اولی using نام دارد . این آرگومان مقدار نام دیتابیس مورد استفاده برای بارگیری مجدد یا ریلود داده ها را دریافت می کند و باعث می شود ریلود شدن شی از آن دیتابیس اتفاق بیافتد . به طور پیش فرض بارگیری مجدد از دیتابیسی که شی از آن دریافت شده ,انجام می شود .آرگومان بعدی fields است . این آرگومان مجموعه ای از نام فیلد ها را دریافت می کند که فقط آنها را لازم است ریلود کند.به عنوان مثال برای نوشتن یک تست که صحیح بودن فراخوانی update را در کد تایید کند ,تست زیر را می توانید بنویسید . (فعلا به نحوه استفاده از refresh_from_db نگاه کنید . بعدا درباره تست نویسی مفصل صحبت می کنیم)def test_update_result(self): obj = MyModel.objects.create(val=1) MyModel.objects.filter(pk=obj.pk).update(val=F('val') +1) # At this point obj.val is still 1, but the value in the database # was updated to 2. The object's updated value needs to be reloaded # from the database. obj.refresh_from_db self.assertEqual(obj.val, 2)وقتی که به یک فیلد که در داخل متد defer قرار گرفته بود دسترسی پیدا کنید ,لود شدن مقدار آن فیلد از طریق همین متد انجام می شود . بنابراین امکان سفارشی کردن نحوه لود شدن مقادیر فیلد هایی که در متد defer قرار گرفته شده اند ,وجود دارد . برای مثال کد زیر نشان می دهد که چگونه تمام فیلدهای یک شی را هنگام بارگیری مجدد یک فیلد به تاخیر افتاده (فیلدی که در متد defer باشد) لود کنیم :class ExampleModel(models.Model): def refresh_from_db(self, using=None, fields=None, **kwargs): # fields contains the name of the deferred field to be # loaded. if fields is not None: fields = set(fields) deferred_fields = self.get_deferred_fields # If any deferred field is going to be loaded if fields.intersection(deferred_fields): # then load all of them fields = fields.union(deferred_fields) super.refresh_from_db(using, fields, **kwargs)در کد بالا از یک متد دیگر با نام get_deferred_fields استفاده کردیم . این متد یک متد کمکی است که یک ست حاوی تمام attribute های تمام فیلد های به تاخیر افتاده را بازمیگرداند .اعتبارسنجی اشیاچهار مرحله برای اعتبارسنجی یک مدل وجود دارد . اعتبارسنجی یعنی درستی و صحیح بودن مقدار های فیلد و مدل را بررسی می کنید .-متد Model.clean_fields تمام فیلد های مدل را اعتبارسنجی می کند .-متد Model.clean تمام یک مدل را اعتبارسنجی می کند .-متد Model.validate_unique , unique و یکتا بودن مقادیر در فیلد ها را اعتبارسنجی می کند .-متد Model.validate_constraints برای محدودیت های اعمال شده بر مدل ,مدل را اعتبارسنجی می کند .هر چهار متد زمانی با هم فراخوانی می شوند و کل مدل و فیلد ها را اعتبارسنجی می کنند که متد full_clean را از مدل فراخوانی کنید .به یاد داشته باشید که در هنگام استفاده از ModelForm و فراخوانی is_valid در آنها (قبلا در فصل اول این ها را بررسی کردیم) ,تمام این متد ها برای اعتبارسنجی داده ها و فیلد ها فراخوانی می شوند . فقط در صورتی از متد full_clean استفاده کنید که می خواهید خطاها و ارور های اعتبارسنجی فیلد ها را خودتان مدیریت کنید یا فیلد هایی را که به اعتبارسنجی نیاز دارند را از ModelForm حذف کردید (در واقع آنها را در exclude قرار دادید) .نکته : به یاد داشته باشید بعضی از فیلد ها مانند JSONField ممکن است هیچ اروری از طرف اعتبارسنجی جنگو به شما بازنگردانند . زیرا بعضی از ارور های مربوط به key , index و path transforms در خود دیتابیس اتفاق میافتند و فقط در لاگ ها (log =گزارش های ارورها) قابل مشاهده هستند .از این رو حتما بررسی کنید که هیچ پیام خطایی در logger جنگو (در مسیر django.db.models قرار دارد) وجود نداشته باشد .قبل تر درباره متدی با نام full_clean صحبت کردیم . قبل از بررسی آن به شکل کلی این متد نگاهی بیندازید:Model.full_clean(exclude=None, validate_unique=True, validate_constraints=True)این متد برای مدل شما تمامی متد های مربوط به اعتبار سنجی را (همه ی 4 مورد) اجرا می کند . به ترتیب clean_fields ,سپس clean و بعد validate_unique (اگر validate_unique برابر با True باشد) و در آخر validate_constraints (اگر validate_constraints برابر با Trueباشد) اجرا می شوند .در صورت فراخوانی تمامی متد ها و وجود ارور یک ValidationError بازگردانده می شود که حاوی یک message_dict است . این تمامی خطاها و ارور ها را در هر چهار مرحله درون خود ذخیره می کند .این متد و تابع یک آرگومان با نام exclude را نیز قبول می کند . البته این آرگومان اختیاری است . در واقع یک set حاوی نام فیلد ها را قبول می کند و آنها را exclude یا جدا می کند (آنها را اعتبارسنجی نمی کند) . از این ویژگی در ModelForm نیز استفاده می شود تا فیلد هایی که در فرم نیستند ,اعتبارسنجی نشوند . دلیل اعتبارسنجی نشدن آن فیلدها این است که اگر خطایی داشته باشند توسط کاربر نمی تواند اصلاح شود (اصلا آن فیلد ها در فرم نیستند که کاربر آنها را اصلاح کند!) .به یاد داشته باشید که با فراخوانی متد save هیچ اعتبارسنجی صورت نمی گیرد . در صورتی که نیاز داشتید باید خودتان مدل را اعتبارسنجی کنید . البته به شکل زیر :from django.core.exceptions import ValidationError try: article.full_clean except ValidationError as e: # Do something based on the errors contained in e.message_dict. # Display them to a user, or handle them programmatically. passالبته در ModelForm خود این مراحل اتوماتیک انجام می شود . بعدا با فرم ها بیشتر آشنا خواهید شد . حال بهتر است تک تک 4 متد فراخوانی شده را به صورت جداگانه مورد بررسی قرار دهیم .-متد clean_fields(exclude=None) :این متد تمامی فیلد های مدل را (به جز آنهایی که exclude یا جداسازی شده اند) اعتبارسنجی می کند .در اعتبارسنجی فیلد ها بررسی می شود که مقادیر مخرب یا خارج از فرمت فیلد (int , str , …) ,وارد فیلد نشده باشد .-متد Model.clean :این متد را فقط زمانی بازنویسی کنید که نیاز دارید اعتبارسنجی بر مدل شما انجام شود . این یعنی برای مثال یک فیلد را در هنگام اعتبارسنجی قبل از save بطور اتوماتیک مقداردهی کنید . یا برای مثال چندین فیلد را بر اساس مقادیر یکدیگر اعتبارسنجی کنید . به مثال زیر نگاه کنید که چگونه این کار را انجام دادیم:import datetime from django.core.exceptions import ValidationError from django.db import models from django.utils.translation import gettext_lazy as _ class Article(models.Model): ... def clean(self): # Don't allow draft entries to have a pub_date. if self.status == 'draft' and self.pub_date is not None: raise ValidationError(_('Draft entries may not have a publication date.')) # Set the pub_date for published items if it hasn't been set already. if self.status == 'published' and self.pub_date is None: self.pub_date = datetime.date.todayبا این حال توجه داشته باشید که قبل از save باید خودتان به صورت دستی متد clean را فراخوانی کنید و با فراخوانی save به تنهایی متد clean اجرا نمی شود .به یاد دارید که گفتیم هر ارور به شکل یک دیکشنری به شما نشان داده می شود ؟ در کد بالا نیز اگر مقادیر اشتباه وارد شود ارور ValidationError به شما بازگشت داده می شود که درون یک str ذخیره شده است . در دیکشنری , key آن ارور برابر با NON_FIELD_RRORS است . شما نیز می توانید از آن استفاده کنید . برای مثال :from django.core.exceptions import NON_FIELD_ERRORS, ValidationError try: article.full_clean except ValidationError as e: non_field_errors = e.message_dict[NON_FIELD_ERRORS]کد بالا از مدل شما ایراد خواهد گرفت و به هیچ فیلدی اشاره نخواهد کرد . برای حل این مشکل کافی است ValidationError را به شکل یک دیکشنری بسازید و key را در آن برابر با اسم فیلد ها قرار دهید . می توانیم مثال بالا را بازسازی کنیم تا ارور به فیلد pub_date اختصاص داده شود . (یعنی در پیغام خروجی به فیلد pub_date اشاره کند)class Article(models.Model): ... def clean(self): # Don't allow draft entries to have a pub_date. if self.status == 'draft' and self.pub_date is not None: raise ValidationError({'pub_date': _('Draft entries may not have a publication date.')}) ...اگر ارور ها مربوط به چند فیلد باشند می توانید از ValidationError به شکل یک دیکشنری استفاده کنید . Keyها در آن نام فیلد و value در آن ارور مربوط به آن فیلد هستند . برای مثال :raise ValidationError({ 'title': ValidationError(_('Missing title.'), code='required'), 'pub_date': ValidationError(_('Invalid date.'), code='invalid'), })بعد از این متد ,متد full_clean برای شما محدودیت های unique را بررسی می کند . قبل از آن ,این نکته را بخوانید.نکته : به نظر شما اگر فیلد هایی دچار ارور شده باشند که در ModelForm نمایش داده نمی شوند ,چگونه باید آنها را نشان دهیم؟در واقع در متد Model.clean شما نمی توانید ارورها را برای فیلد هایی که قابل نمایش نیستند , raise کنید (فرم ممکن است فیلد های خود را به وسیله Meta.fields و یا Meta.exclude محدود کند) . انجام این کار یک ValueError ایجاد می کند .برای حل این مشکل ,کافی است متد Model.clean_fields را بازنویسی کنید و ارور ها را در آن ایجاد کنید . زیرا این متد حتی به فیلد هایی که exclude (جدا شده اند و نمایش داده نمی شوند) شده اند نیز دسترسی دارد . برای مثال :class Article(models.Model): ... def clean_fields(self, exclude=None): super.clean_fields(exclude=exclude) if self.status == 'draft' and self.pub_date is not None: if exclude and 'status' in exclude: raise ValidationError( _('Draft entries may not have a publication date.') ) else: raise ValidationError({ 'status': _( 'Set status to draft if there is not a ' 'publication date.' ), })-متد Model.validate_unique(exclude=None) :این متد شباهت زیادی به متد clean_fields دارد . اما در واقع محدودیت های فیلد ها را در زمینه یکتا یا unique بودن مقادیر آنها بررسی می کند . شما می توانید از طریق Field,unique , Field.unique_for_date , Field.unique_for_month و Field.unique_for_year یا Meta.unique_together یک مدل یا فیلد را unqiue کنید . این یعنی مقادیر تکراری در آن نمی تواند ثبت شود .این متد نیز بررسی می کند و اجازه ثبت مقادیر تکراری در یک جدول در دیتابیس را به شما نمی دهد . هر مقدار باید یکبار در ردیف تکرار شود .آرگومان exclude نیز می تواند تعدادی نام فیلد را قبول کند . فیلد های قرار گرفته در این آرگومان , مورد بررسی قرار نخواهند گرفت .اگر هر کدام از فیلد ها نیز در اعتبار سنجی با ارور مواجه شود (برای مثال مقدار تکراری در آن وارد شود) ,ارور ValidationError ایجاد می شود .البته توجه داشته باشید که فقط محدودیت های مربوط به unqiue بودن در این متد بررسی می شود و محدودیت های دیگر مروبط به مدل در متد دیگری با نام Model.validate_constraints بررسی خواهد شد .در نهایت متد full_clean ,متد Model_validate_constraints را برای بررسی محدودیت های دیگر مدل اجرا خواهد کرد .-متد Model.validate_constraints(exclude=None) : این متد به تازگی در جنگو 4.1 اضافه شد . وظیفه این متد بررسی محدودیت های مدل است که در گزینه های متا و در متغییر constraints تعریف شده است . آرگومان exclude نیز نام فیلد هایی را قبول می کند و روی آنها بررسی برای محدودیت را انجام نمی دهد . در آخر ,اگر یکی از محدودیت ها رعایت نشده باشد ,دچار ارور ValidationError می شوید .ذخیره سازی (سیو کردن) اشیابرای ذخیره کردن یک شی در دیتابیس کافی است متد save را از شی فراخوانی کنید . سینتکس کلی دستور save به شکل زیر است :Model.save(force_insert=False, force_update=False, using=DEFAULT_DB_ALIAS, update_fields=None)کمی جلوتر درباره force_insert و دیگر آرگومان ها بیشتر صحبت می کنیم . همچنین این متد می تواند بازنویسی شود تا طبق انتظار شما رفتار کند . در بخش زیر درباره ریزه کاری های کار با save صحبت خواهیم کرد .تعیین خودکار primarykeyیکی از کارهایی که متد save وظیفه انجام آن را بر عهده دارد ,تعیین مقدار primarykey به صورت اتوماتیک است (id) .در واقع اگر مدل شما یک AutoField داشته باشد (مانند فیلد id) ,مقدار آن به صورت خودکار محاسبه خواهد شد و اولین باری که متد save را روی یک شی فراخوانی می کنید ,آن مقدار را در فیلد مربوطه وارد خواهد کرد . مقادیر همیشه افزایش پیدا خواهند کرد . برای مثال 1 و 2 و 3 و 4 و ...برای مثال :>>> b2 = Blog(name='Cheddar Talk', tagline='Thoughts on cheese.') >>> b2.id # Returns None, because b2 doesn't have an ID yet. >>> b2.save >>> b2.id # Returns the ID of your new object.به صورت پیش فرض در یک مدل همیشه فیلد id وجود دارد . نوع این فیلد یک AutoField است . تنها در صورتی این فیلد ایجاد نخواهد شد که در یکی از فیلد ها primary_key را برابر با True قرار دهید .نکته : راهی برای تعیین مقدار یک id هنگام فراخوانی save وجود ندارد . زیرا مقدار آن توسط دیتابیس محاسبه می شود و نه توسط جنگو .همچنین یک ویژگی با نام pk برای primarykey در جنگو ایجاد شده است . فرقی ندارد که فیلد primarykey را خودتان بسازید (به یک فیلد primary_key=True اختصاص دهید) یا بگذارید جنگو اتوماتیک آن را ایجاد کند (فیلد id) , در هر دو حالت شما به یک مقدار با نام pk دسترسی خواهید داشت که به primarykey آن مدل (هر فیلدی که باشد) اشاره می کند . این مقدار در حالت پیش فرض برابر با فیلد id خواهد بود .تعیین مقادیر primarykey به صورت دستیاگر یک مدل دارای AutoField باشد ,می توانید به صورت دستی id را در هنگام ساخت شی ,تعیین کنید . برای مثال :>>> b3 = Blog(id=3, name='Cheddar Talk', tagline='Thoughts on cheese.') >>> b3.id # Returns 3. >>> b3.save >>> b3.id # Returns 3.اگر مقادیر primarykey را به صورت دستی (مانند بالا) تعیین کنید ,باید مطمئن باشید مقداری که می خواهید به آن اختصاص دهید ,تکراری نباشد . در واقع تمامی فیلد های primarykey ,ویژگی unique=True دارند . اگر شما تلاش کنید یک مقدار قبلا استفاده شده را به فیلد اختصاص دهید , جنگو فکر می کند که می خواهید رکورد حاوی مقدار را (رکورد قدیمی) بازنویسی کنید .با توجه به مثال Cheddar Talk در بالا , مثال زیر رکورد قبلی را در دیتابیس بازنویسی می کند :b4 = Blog(id=3, name='Not Cheddar', tagline='Anything but cheese.') b4.save # Overrides the previous blog with ID=3!تعیین دستی مقدار primarykey زمانی مفید است که بخواهید اشیایی را به صورت انبوه بسازید . در آن صورت مطمئن خواهید بود که primarykey تکراری نخواهند بود .فرایند عملکرد متد saveدر این بخش درباره این صحبت خواهیم کرد که وقتی متد save را روی یک شی فراخوانی می کنید ,دقیقا چه مراحلی طی می شود تا شی در دیتابیس ذخیره شود . 5 مرحله در هنگام فراخوانی این مت اتفاق میافتد :1-یک سیگنال pre-save ارسال می شود . درباره سیگنال ها بعدا صحبت خواهیم کرد اما در این حد بدانید که می توانید برای مثال با استفاده از سیگنال یک تابع را در زمان ذخیره شدن یک شی , اجرا کند .2-سپس داده ها پردازش می شوند . در این مرحله متد pre_save روی هر فیلد فراخوانی می شود . وظیفه این متد تغییر داده های درون فیلدها در صورت نیاز است . برای مثال اگر فیلد شما datefield باشد و auto_now_add = True باشد ,این متد برای شما مقدار کنونی زمان را در فیلد وارد می کند .3-در این مرحله داده ها برای دیتابیس آماده می شوند . متد get_db_prep_svae در این مرحله برای هر فیلد اجرا می شود . این متد از هر فیلد می خواهد تا مقدار فعلی خود را در نوع داده ای (int , str , json , …) که می تواند در دیتابیس ذخیره شود ,ارائه دهد .اکثر فیلد ها به این مرحله نیازی ندارند . انواع داده ای مانند str یا int آماده ثبت شدن در دیتابیس هستند . با این حال برخی از انواع داده های پیچیده تر , نیاز به این مرحله ویژه خواهند داشت .به عنوان مثال ,فیلد های DateField از شی datetime در پایتون برای ذخیره سازی تاریخ در خود استفاده می کنند . دیتابیس ها نمی توانند اشیا پایتونی را در خود ذخیره کنند پس باید تاریخ شما تبدیل به یک رشته مطابق با ISO بشود . این مرحله برای شما اینکار را انجام می دهد .4-داده های شما در قالب یک دستور SQL به دیتابیس وارد می شوند و ذخیره می شوند .5-یک سیگنال post-save ارسال می شود .نکته : سیگنال های مربوط به save دو دسته هستند . Pre-save (قبل از ذخیره شدن شی) و post-save (بعد از ذخیره شدن شی) که هر بار در زمان خود ارسال می شوند . با توجه به این سیگنال ها می توانید در بعد یا قبل از ذخیره شدن یک شی یک عملیات را با استفاده از یک تابع انجام دهید . برای مثال قبل از ذخیره شدن یک شی , یک شی دیگر در دیتابیس را به صورت اتوماتیک حذف کنید ! در جلوتر آن را بیشتر بررسی می کنیم .نکته : متد های get_db_prep_save و pre_save مربوط به فیلد ها هستند و از خود هر فیلد فراخوانی می شوند.تشخیص اتوماتیک تفاوت UPDATE و INSERT در جنگوتا الان باید متوجه شده باشید که جنگو چه برای آپدیت و چه برای ایجاد شی از یک متد استفاده می کند (متد save) . در واقع جنگو دستورات INSERT و UPDATE در SQL را در یک متد خلاصه کرده است . به طور کلی زمانی که یک defaultبرای primary key شی تعیین نشده باشد و متد saveفراخوانی شود ,جنگو از الگوریتم زیر پیروی می کند :اگر primary key مقداردهی شود و مقدار آن None یا رشته خالی نباشد ,جنگو یک UPDATE را اعمال می کند .اگر primary key مقداردهی نشود و یا UPDATE شی ای را آپدیت نکند (برای مثال primary key روی مقداری تنظیم شود که در دیتابیس نیست) ,جنگو دستور INSERT را اجرا می کند .در صورتی که یک default برای primary key در متد save تعیین شود ,جنگو همین فرایند بالا را انجام می دهد و در صورتی که مقدار primary key در دیتابیس وجود داشته باشد ,دستور UPDATE و در غیر این صورت دستور INSERT را اعمال می کند .یک نکته مهم این است که حتما باید primary key یک مقدار بگیرد که در دیتابیس استفاده نشده است . در صورتی که از این بابت مطمئن نیستید ,اجازه بدهید جنگو خودش برای شما primary key را اتوماتیک مقدار دهی کند (مثل فیلد id) .در نسخه های 1.5 و قدیمی تر جنگو ,ابتدا یک دستور SELECT روی primary key انجام می شد . اگر این عملیات یک ردیف را پیدا می کرد , جنگو یک UPDATE انجام می داد و در غیر این صورت یک INSERT انجام می داد . الگوریتم قدیمی دارای خطاهایی بود و علاوه بر آن باعث می شد تا یک Query اضافه نیز تولید شود . موارد نادری باعث ایجاد خطا می شدند و دیتابیس ممکن بود در آنها اصلا گزارش ندهد که یک ردیف آپدیت شده است (برای مثالPostgreSQL ON UPDATE trigger) ! گرچه استفاده از این الگوریتم توصیه نمی شود اما برای استفاده از آن کافی است select_on_save را روی True قرار دهید . بعدا درباره این صحبت خواهیم کرد .بررسی force_update و force_insertدر برخی از شرایط بسیار نادر ,لازم است متد save را مجبور کنیم که حتما یک دستور INSERT یا حتما یک دستور UPDATE انجام دهد (در واقع نمی خواهیم جنگو تصمیم بگیرد از کدام استفاده کند) . برای مجبور کردن آن برای دستور INSERT از force_insert=True و برای UPDATE از force_update=True استفاده می کنیم . هر دو پارامتر را با هم نمی توانید استفاده کنید , زیرا نمی توان همزمان هم آپدیت کرد و هم یک شی را به دیتابیس وارد کرد .این پارامتر ها فقط برای استفاده پیشرفته مناسب است . همچنین عملکرد متد update_fields و پارامتر force_update تقریبا شبیه یکدیگر است .آپدیت کردن یک فیلد بر اساس فیلدی دیگرقبل از بررسی عنوان این بخش به این مورد توجه داشته باشید . گاهی از وقت ها نیاز دارید تا یک عملیات حسابی (مانند حمع یا تفریق یا ضرب) روی یکی از فیلد ها انجام دهید و مقدار جدید را در فیلد وارد کنید (آپدیت کنید) . این کار را با سینتکس ساده پایتون انجام می دهید :>>> product = Product.objects.get(name='Venezuelan Beaver Cheese') >>> product.number_sold +=1 >>> product.saveاگر مقدار قبلی number_sold برابر با 10 باشد ,تبدیل به عدد 11 می شود و مقدار 11 در دیتابیس ذخیره می شود .مثال بالا می تواند سریعتر و قوی تر نیز نوشته شود . کافی است برای این کار از عبارات F استفاده کنید . با استفاده از F می توانید مثال بالا را به شکل زیر بنویسید (توصیه می شود) :>>> from django.db.models import F >>> product = Product.objects.get(name='Venezuelan Beaver Cheese') >>> product.number_sold = F('number_sold') +1 >>> product.saveتعیین فیلد ها برای سیو شدندر این بخش می خواهیم درباره این صحبت کنیم که چگونه به متد save بفهمانیم که فقط کدام فیلد ها را سیو کند (عملیات سیو را روی آنها انجام دهد) . برای مثال ,اگر در متد save آرگومان update_fields را مقداردهی کنید و به آن یک لیست از نام فیلد هایی خاص را بدهید , فقط همان فیلد ها آپدیت خواهند شد .در مواردی که بخواهید فقط چند فیلد بخصوص آپدیت شوند ,این آرگومان مهم است . با جلوگیری از بروزرسانی تمامی فیلد های یک مدل و استفاده از update_fields یک عملکرد جزئی بهتر دریافت می کنید :product.name = 'Name changed again' product.save(update_fields=['name'])مقداری که به این آرگومان ارسال می کنید باید یک iterable حاوی رشته ها (همان نام فیلدها) باشد . یک iterable خالی باعث می شود مرحله سیو شدن رد شود و ارسال مقدار None به این آرگومان به معنی آپدیت همه فیلد ها است .همچنین استفاده از این آرگومان به این معنی است که آپدیت اجباری انجام خواهد شد و دستور INSERT قرار نیست مورد استفاده قرار بگیرد . (مانند force_update)نکته دیگر این است که اگر یک فیلد در defer قرار گرفته باشد یا به اصطلاح به تعویق افتاده باشد و شما مقدار آن را تغییر دهید یا بروز کنید ,آن فیلد به update_fields در پشت صحنه اضافه می شود . در اصل اگر تغییری در مقدار فیلد انجام ندهید , فقط فیلد هایی که قبلا در defer قرار نگرفته اند ,آپدیت می شوند (یعنی اگر هیچ کاری با فیلد های به تعویق افتاده نداشته باشید) .نکته :به یاد داشته باشید اگر از آرگومان update_fields استفاده کنید ,جنگو به صورت اتوماتیک متد pre_save را فقط برای فیلد های نام برده درون آرگومان فراخوانی می کند . این یعنی اگر یک فیلد DateField داشته باشید که auto_now=True باشد ,آپدیت نمی شود (auto_now کار نمی کند) مگر اینکه نام فیلد درون آرگومان update_fields باشد .حذف اشیابرای حذف اشیا یا رکورد ها از دیتابیس کافی است از متد delete روی شی استفاده کنید . ساختار کلی متد به صورت زیر است :Model.delete(using=DEFAULT_DB_ALIAS, keep_parents=False)به مثال زیر نگاه کنید که چگونه یک شی را پیدا می کنیم و آن را حذف می کنیم :instance = SomeModel.objects.get(id=id) instance.deleteهمچنین می توانید آن را به صورت مستقیم حذف کنید .SomeModel.objects.filter(id=id).deleteدر اصل یک دستور DELETE در SQL ایجاد می کند و شی را حذف می کند . این متد فقط شی (رکورد) را از دیتابیس حذف می کند و این یعنی شی ای که در پایتون وجود دارد (در حافظه است) , هنوز وجود دارد ! شما می توانید به فیلد های آن دسترسی پیدا کنید . گرچه فیلد primary key آن روی None تنظیم خواهد شد .پس از استفاده از این متد تعداد اشیا حذف شده و تعداد حذف ها در هر نوع شی در قالب یک دیکشنری به شما بازگردانده می شود . که البته در قبل تر راجب آن مفصل صحبت کردیم .اگر می خواهید رفتار حذف شدن یک رکورد یا شی را توسط این متد تغییر دهید , کافی است آن را در مدل مربوطه بازنویسی کنید .آرگومان using در این متد تعیین می کند که از کدام دیتابیس استفاده کند . (درباره چند دیتابیسی و این آرگومان بعدا نیز توضیح می دهیم)آرگومان دیگر keep_parents است که اگر برابر با True قرار داده شود ,در ارث بری های multi-table یا چند جدولی باعث می شود فقط داده های مدل فرزند حذف شود و داده های مدل والد حفظ شود .متد های دیگر اشیامتد های دیگری نیز در یک شی وجود دارند که باید درباره آنها صحبتی داشته باشیم . متد های پیشین باعث می شدند تا با فراخوانی آنها عملیاتی روی شی انجام شود . حال این متد ها را نگاه کنید :1-متد __str__ :این متد زمانی اجرا می شود که شما str را روی یک شی فراخوانی کنید . در واقع کاری که انجام می دهد تعیین یک نام برای همان شی است . (در ادمین پنل نام شی ها در لیست آنها به وسیله همین متد نمایش داده می شوند) به علت اینکه جنگو از این متد در نمایش نام اشیا در ادمین پنل استفاده می کند ,همیشه برای آن یک مقدار قابل خواندن برای انسان و قابل فهم ایجاد کنید .برای مثال :(باید معمولا این متد در هر مدل بازنویسی شود)from django.db import models class Person(models.Model): first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) def __str__(self): return '%s %s' % (self.first_name, self.last_name)نکته : در حالت عادی این متد مقدار نام مدل + object + یک عدد (مانند id آن) بازمیگرداند . مثلا Person object12-متد __eq__ :این متد در اشیا عادی پایتون نیز وجود دارد . وظیفه آن overload کردن عملگر == برای اشیا است تا بتونید بررسی کنید که آیا دو شی با یکدیگر مساوی هستند یا خیر . در جنگو نیز این متد وجود دارد . در جنگو این متد اشیایی که مقدار primary key آنها برابر باشد و از یک کلاس اصلی باشند ,برابر در نظر می گیرد . با این تفاوت که اگر primary key برابر با None باشد ,به جز خودش برابر با شی دیگری نیست .برای مدل هایی که ارث بری نوع proxy دارند ,کلاس اصلی برابر با اولین والد غیر پروکسی است . در بقیه مدل ها , کلاس اصلی برابر با کلاس مدل است . در کد زیر نحوه کارکرد این متد را می بینید :(نکته مهم اینکه این متد و چند متد دیگر تنها برای درک کردن چگونگی کار جنگو توضیح داده می شوند و لازم به بازنویسی ندارند)from django.db import models class MyModel(models.Model): id = models.AutoField(primary_key=True) class MyProxyModel(MyModel): class Meta: proxy = True class MultitableInherited(MyModel): pass # Primary keys compared MyModel(id=1) == MyModel(id=1) MyModel(id=1) != MyModel(id=2) # Primary keys are None MyModel(id=None) != MyModel(id=None) # Same instance instance = MyModel(id=None) instance == instance # Proxy model MyModel(id=1) == MyProxyModel(id=1) # Multi-table inheritance MyModel(id=1) != MultitableInherited(id=1)3-متد __hash__ :در اشیا عادی پایتون نیز این متد وجود دارد . وظیفه آن در فرایند هش شدن شی تاثیر دارد . در واقع تعیین می کند کدام ویژگی از شی باید هش بشود . در جنگو نیز این متد قرار داده شده است . به صورت عادی مقدار فیلدی که primary key باشد در هش شدن استفاده خواهد شد (یعنی hash(obj.pk)) . اگر primary key مقدار دهی نشده باشد یک TypeError بازگردانده می شود .متد __hash__ مقادیر مختلفی را قبل و بعد از سیو شدن شی برمی گرداند، اما تغییر مقدار __hash__ یک شی در پایتون ممنوع است.4-متد get_absolute_url :این متد در اشیا عادی پایتون وجود ندارد و مخصوص به جنگو است . همچنین بسیار پرکاربرد است و بهتر است آن را خوب یاد بگیرید . فرض کنید یک مدل مربوط به پست های یک وبلاگ را ساختید . هر شی در این مدل در واقع یک پست از وبلاگ شماست . این متد استفاده می شود تا هر شی از مدل (در اینجا هر پست در وبلاگ) یک url داشته باشد . یعنی وقتی این متد را فراخوانی کردید url یا آدرس صفحه مربوط به آن شی بازگردانده شود.منظور ما از url آن شی ,در واقع آدرس مربوط به ویویی است که می خواهید . بر فرض مثال اگر این متد را بر اساس id اشیا تنظیم کرده باشید و در آدرسی با نام post ,ویو مربوط به آن را نوشته باشید , url مربوط به شی با id 12 برابر با post/12/ خواهد بود .با نوشتن این متد به جنگو خواهید فهماند که چطور url مربوط به هر شی محاسبه شود . هنگامی که رشته ای که حاوی url است بازگردانده می شود ,از طریق HTTP می تواند کاربر را به یک ویو یا صفحه از جنگو هدایت کند . برای مثال :def get_absolute_url(self): return &quot/people/%i/&quot % self.idاگر چه کد بالا بسیار واضح و ساده است و کار می کند ,اما بهترین راه استفاده از تابع reverse است .5-متد Reverse :این تابع برای شما url تولید می کند . در واقع name مربوط به url شما را (در فایل urls.py) دریافت می کند و در ادامه kwargs ارسال شده را درون url جایگذاری می کند . (بعدا به بررسی کامل آن می پردازیم)برای مثال :def get_absolute_url(self): from django.urls import reverse return reverse('people-detail', kwargs={'pk' : self.pk})از کاربرد های متعدد این متد می توان به ادمین پنل اشاره کرد . اگر این متد برای یک مدل تعریف شود ,در صفحه لیست اشیا آن مدل (در ادمین پنل) شما یک لینک View on site را مشاهده خواهید کرد . این لینک همان url تولید شده در این متد است و شما را مستقیما به آن آدرس هدایت خواهد کرد .همچنین بسیاری از کاربرد های دیگر نیز برای این متد وجود دارد . برای مثال در هنگام استفاده از syndication feed framework باید متد get_absolute_url تعریف شود . اگر منطقی است که نمونه های مدل شما هر کدام یک url منحصر به فرد داشته باشند، باید get_absolute_url را تعریف کنید .نکته : شما باید از ساخت url در این متد بر اساس ورودی کاربر خودداری کنید . در این صورت یک هکر می تواند به راحتی url های مخرب در سایت شما ایجاد کند . برای مثال اگر متد زیر را نوشته باشید :def get_absolute_url(self): return '/%s/' % self.nameاگر self.name برابر با /example.com باشد (یعنی برای مثال این مقدار را از کاربر بگیرد) , این متد مقدار //example.com را بازمیگرداند که باز هم به نوبه خود معتبر است اما اگر برای مثال مقدار /%2Fexample.com/ بازگردانده شود ,اصلا آدرس معتبری نیست .همچنین استفاده از get_absolute_url در تمپلیت ها (templates) توصیه می شود . برای مثال کد زیر یک کد بد در تمپلیت است :<!-- BAD template code. Avoid! --> <a href=&quot/people/{{ object.id }}/&quot>{{ object.name }}</a>برای اصلاح این کد می توانید از متد get_absolute_url استفاده کنید :<a href=&quot{{ object.get_absolute_url }}&quot>{{ object.name }}</a>دلیل بهینه بودن این روش این است که اگر بخواهید یک تغییر کوچک در url خود بدهید ,می توانید بدون اینکه انجام تغییر در همه تمپلیت هایی که از آن استفاده کردید , آن را تغییر دهید . یعنی یکبار url را ایجاد می کنید و هر چندبار که خواستید از آن استفاده می کنید .نکته : در رشته ای که متد get_absolute_url بازمیگرداند فقط کاراکتر های ASCII مجاز هستند . یا اینکه اگر نیاز است می توانند encode شوند . (در این لینک درباره کاراکتر های مجاز در url بیشتر توضیح داده شده است : https://datatracker.ietf.org/doc/html/rfc2396.html#section-2)کدهایی که متد get_absolute_url را فراخوانی می کنند باید بدون نیاز به پردازش شدن ,بتوانند از نتیجه استفاده کنند . اگر نیز در urlشما کاراکتر هایی به غیر از محدوده ASCII بود ,شاید نیاز باشد از تابع django.utils.encoding.iri_to_uri استفاده کنید . (در اینترنت درباره آن بیشتر جستجو کنید)6-متد get_FOO_display :این متد فقط برای اشیایی است که در آنها یک فیلد حاوی choices تعریف شده باشد . در این متد FOO با نام فیلد حاوی choices جایگذاری می شود . قسمت خوانا توسط کاربر یا human-readable مجموعه ی choices با استفاده از این متد بازگردانده می شود . به مثال زیر نگاه کنید .from django.db import models class Person(models.Model): SHIRT_SIZES = ( ('S', 'Small'), ('M', 'Medium'), ('L', 'Large'), ) name = models.CharField(max_length=60) shirt_size = models.CharField(max_length=2, choices=SHIRT_SIZES)>>> p = Person(name=&quotFred Flintstone&quot, shirt_size=&quotL&quot) >>> p.save >>> p.shirt_size 'L' >>> p.get_shirt_size_display 'Large'7-متد get_next_by_FOO(**kwargs) : این متد یک ورژن دیگر با نام get_previous_by_FOO نیز دارد . همچنین فقط برای مدل هایی کار می کند که فیلد DateField یا DateTimeField در آنها تعریف شده است . توجه کنید که نباید null=True در آن نوع از فیلد ها وجود داشته باشد . FOO برابر با نام فیلد در نظر گرفته می شود (فیلد DateField یا DateTimeField ) . همانطور که از نام آن مشخص است متد get_next_by_FOO شی بعدی و get_previous_by_FOO شی قبلی را (که بر اساس تاریخ مرتب شده اند) بازمیگرداند . در صورت خطا یک ارور DoesNotExist بازگردانده می شود .هر دوی این متد ها عملیات هایشان را با manager پیش فرض انجام می دهند . اگر نیاز داشته باشید می توانید از lookup در kwargs** استفاده کنید تا جستجو های خود را دقیق تر کنید .اگر دو شی با تاریخ برابر وجود داشته باشند ,متد ها از primary key آنها برای مقایسه استفاده می کنند . این یعنی هیچ شی ای رد یا تکراری نمی شود . همچنین این یعنی نمی توانید این متد ها را برای اشیایی که هنوز save روی آنها فراخوانی نشده استفاده کنید .نکته : در بیشتر موارد بازنویسی متد های get_FOO_display , get_next_by_FOO , get_previous_by_FOO مشکلی ندارد و کار می کند . ولی از آنجایی که این متد ها توسط کلاس Meta اضافه می شوند ,باید در بعضی از شرایط متد Field.contribute_to_class بازنویسی شود تا بتوانید به متد های دیگر دسترسی داشته باشید .8مقدار state_ : این در واقع یک ویژگی در شی شماست . به یک شی ModelState (یک کلاس در جنگو است که اشیا آن وضعیت مدل را زیر نظر می گیرند) اشاره می کند که وضعیت کنونی شی را نشان می دهد .هر شی ModelState دو ویژگی درون خود دارد . یک flag با نام adding که اگر هنوز شی شما در دیتابیس ذخیره نشده باشد برابر با True خواهد بود . و یک db که یک رشته حاوی database alias یا نام مستعار دیتابیسی است که شی از آن بارگیری می شود .برای مثال اشیایی که تازه ساخته شده اند دارای adding=True و db=None هستند . در صورتی که اشیایی که ذخیره شده باشند دارای adding=False و db که مقدار آن database alias است ,هستند .در این جلسه نحوه مدیریت مدل ها و عملیات های آنها توسط جنگو را بررسی کردیم . در جلسه بعدی api مربوط به فیلد ها را بررسی می کنیم و نحوه کارکردن آنها را نیز بررسی می کنیم .