Oruji.org
Oruji.orgPersian Tutorials
ورود
ویرایش: 1396/11/13 19:19

آموزش مدل های پیشرفته در جنگو (Django)

در آموزش مدل از این کتاب، یک مقدمه از لایه ی پایگاه داده ی جنگو ارائه شد – نحوه ی تعریف مدل ها و نحوه ی استفاده از API پایگاه داده جهت ساخت، به روز رسانی، و حذف رکوردها. در این آموزش، برخی از ویژگی های پیشرفته تر این بخش از فریم ورک یا چارچوب جنگو (Django) را معرفی خواهیم کرد.

شیء های مرتبط

مدل های book در آموزش مدل جنگو را بخاطر بیاورید:

from django.db import models class Publisher(models.Model): name = models.CharField(max_length=30) address = models.CharField(max_length=50) city = models.CharField(max_length=60) state_province = models.CharField(max_length=30) country = models.CharField(max_length=50) website = models.URLField() def __unicode__(self): return self.name class Author(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=40) email = models.EmailField() def __unicode__(self): return u'%s %s' % (self.first_name, self.last_name) class Book(models.Model): title = models.CharField(max_length=100) authors = models.ManyToManyField(Author) publisher = models.ForeignKey(Publisher) publication_date = models.DateField() def __unicode__(self): return self.title

همانطور که در آموزش مدل جنگو توضیح داده شد، دسترسی به مقدار یک فیلد خاص در یک شیء پایگاه داده به سادگی استفاده از یک attribute می باشد. برای مثال، جهت معلوم کردن عنوان یک کتاب با ID پنجاه، به شکل زیر عمل نمودیم:

>>> from mysite.books.models import Book >>> b = Book.objects.get(id=50) >>> b.title u'The Django Book'

ولی چیزی که قبلا ذکر نشده است شیء های مرتبط می باشد – فیلدهای بیان شده به صورت یک ForeignKey یا ManyToManyField – که کمی متفاوت عمل می کنند.

دسترسی به مقادیر Foreign Key

هنگامی که به یک فیلد ForeignKey دسترسی پیدا می کنید، شما شیء مدل مرتبطی دریافت خواهید کرد. برای مثال:

>>> b = Book.objects.get(id=50) >>> b.publisher <Publisher: Apress Publishing> >>> b.publisher.website u'http://www.apress.com/'

با فیلدهای ForeignKey، با روش دیگری مواجه خواهیم بود، ولی این کمی متفاوت است، به دلیل اینکه ارتباط نامتقارن می باشد. جهت بدست آوردن لیست کتاب ها برای ناشر داده شده، از publisher.book_set.all() استفاده کنید:

>>> p = Publisher.objects.get(name='Apress Publishing') >>> p.book_set.all() [<Book: The Django Book>, <Book: Dive Into Python>, ...]

در پشت صحنه، book_set تنها یک QuerySet می باشد (همانطور که در آموزش مدل جنگو توضیح داده شد)، و می تواند همانند QuerySet های دیگر فیلتر یا برش داده شود. برای مثال:

>>> p = Publisher.objects.get(name='Apress Publishing') >>> p.book_set.filter(name__icontains='django') [<Book: The Django Book>, <Book: Pro Django>]

نام attribute فوق یعنی book_set با استفاده از اضافه کردن نام مدل به صورت حروف کوچک به _set تولید شده است.

دسترسی به مقادیر Many-to-Many

مقادیر Many-to-Many همانند مقادیر foreign-key کار می کنند، با این تفاوت که ما با مقادیر QuerySet به جای نمونه های مدل سر کار داریم. برای مثال، در اینجا نحوه ی مشاهده ی نویسندگان برای یک کتاب وجود دارد:

>>> b = Book.objects.get(id=50) >>> b.authors.all() [<Author: Adrian Holovaty>, <Author: Jacob Kaplan-Moss>] >>> b.authors.filter(first_name='Adrian') [<Author: Adrian Holovaty>] >>> b.authors.filter(first_name='Adam') []

در کد فوق، همانند فیلدهای foreignKey، book_set با اضافه کردن نام مدل به صورت حروف کوچک به _set تولید شده است.

>>> a = Author.objects.get(first_name='Adrian', last_name='Holovaty') >>> a.book_set.all() [<Book: The Django Book>, <Book: Adrian's Other Book>]

Here, as with ForeignKey fields, the attribute name book_set is generated by appending the lower case model name to _set.

ایجاد تغییرات برای یک طرح پایگاه داده

هنگامی که دستور syncdb را در آموزش مدل جنگو معرفی کردیم، اشاره شد که syncdb تنها جداولی را که هنوز در پایگاه داده وجود ندارند را ایجاد می کند – این تغییرات همزمان و یا انجام حذف مدل ها نیست. در صورتیکه شما فیلد مدل را اضافه یا تغییر دهید، یا اگر یک مدل را حذف کنید، نیاز خواهید داشت تغییر را به صورت دستی در پایگاه داده اعمال کنید. این فصل نحوه ی انجام این کار را توضیح می دهد.

هنگام سر و کار داشتن با تغییرات طرح، به خاطر داشتن چند نکته درباره نحوه ی کار لایه پایگاه داده اهمیت دارد:

ایجاد تغییرات schema موضوعی از تغییر قسمت های مختلف می باشد – کد پایتون و خود پایگاه داده.

اضافه کردن فیلدها

هنگامی که یک فیلد به جدول/مدل اضافه می کنید، حقه این است که از این خاصیت که فریم ورک یا چارچوب جنگو به یک جدول حاوی ستون ها که در مدل تعریف نشده اند اهمیت نمی دهد استفاده کنیم. استراتژی اضافه کردن ستون در پایگاه داده می باشد، و سپس به روز رسانی مدل برای یک فیلد جدید.

هر چند این مشکل که کدام عمل اول انجام شود در اینجا وجود دارد، زیرا به منظور دانستن نحوه ی ستون جدید پایگاه داده که باید در SQL بیان شود، نیاز است خروجی دستور manage.py sqlall را نگاه کنید که نیازمند این است که فیلد در مدل وجود داشته باشد. (توجه داشته باشید که برای ساختن ستون خود لازم نیست دقیقا از SQL یکسانی که جنگو از آن استفاده می کند استفاده کنید، ولی ایده ی خوبی است که از آن استفاده کنید، تنها برای مطمئن شدن اینکه همه چیز به طور همزمان است.)

راهکار برای این مشکل که کدام عمل اول انجام شود، استفاده از یک توسعه ی محیطی به جای ایجاد تغییرات در production سرور است. (شما در حال استفاده از یک testing/development محیطی می باشید، درست است؟) در اینجا مراحل دقیق وجود دارد.

ابتدا، این مراحل در development environment استفاده کنید (نه بر روی production server)

  1. فیلد را به مدل خود اضافه کنید.
  2. دستور manage.py sqlall [yourapp] را جهت مشاهده ی عبارت جدید CREATE TABLE برا مدل اجرا کنید. تعریف ستون برای فیلد جدید را توجه کنید.
  3. interactive shell پایگاه داده ی خود را اجرا کرده (psql یا mysql و یا می توانید از manage.py dbshell استفاده کنید). یک عبارت ALTER TABLE را که ستون جدید شما را اضافه می کند اجرا کنید.
  4. interactive shell پایتون را از طریق manage.py shell اجرا نموده و مطمئن شوید که فیلد جدید به درستی اضافه شده است از طریق import کردن مدل و واکشی از جدول (مانند MyModel.objects.all()[:5]). در صورتیکه پایگاده داده را به درستی به روز رسانی کرده باشید، عبارت باید بدون خطا کار کند.

سپس بر روی production server این مراحل را انجام دهید:

  1. interactive shell پایگاه داده را اجرا کنید.
  2. عبارت ALTER TABLE ای که در مرحله ی سوم development environment استفاده شد را اجرا کنید.
  3. فیلد را به مدل خود اضافه کنید. در صورتیکه در دست ترجمه ...
  4. وب سرور را برای اعمال تغییرات دوباره راه اندازی کنید.

برای مثال، تصور کنید یک فیلد با نام num_pages را به مدل Book که در آموزش مدل جنگو توضیح داده شد اضافه کرده ایم. ابتدا، درون development environment مدل خود را مانند زیر تغییر می دهیم:

class Book(models.Model): title = models.CharField(max_length=100) authors = models.ManyToManyField(Author) publisher = models.ForeignKey(Publisher) publication_date = models.DateField() num_pages = models.IntegerField(blank=True, null=True) def __unicode__(self): return self.title

(توجه کنید: بخش "ساختن فیلدهای اختیاری" در سایت مدیر را مطالعه کنید، به اضافه ی قسمت نوار کناری زیر یعنی "اضافه کردن ستون ها NOT NULL" برای جزئیات مهم درباره ی اینکه چرا از blank=True و null=True استفاده شده است)

سپس دستور manage.py sqlall books را برای مشاهده ی عبارت CREATE TABLE اجرا می کنیم. بسته به پایگاه داده ی شما، خروجی چیزی شبیه به کد زیر است:

CREATE TABLE "books_book" ( "id" serial NOT NULL PRIMARY KEY, "title" varchar(100) NOT NULL, "publisher_id" integer NOT NULL REFERENCES "books_publisher" ("id"), "publication_date" date NOT NULL, "num_pages" integer NULL );

ستون جدید بدین شکل نشان داده شده است:

"num_pages" integer NULL

در قدم بعدی، interactive shell پایگاه داده را برای توسعه ی پایگاه داده با تایپ کردن psql (برایPostgreSQL) اجرا کرده، و عبارت زیر را درون آن اجرا می کنیم:

ALTER TABLE books_book ADD COLUMN num_pages integer; BEGIN; ALTER TABLE books_book ADD COLUMN num_pages integer; UPDATE books_book SET num_pages=0; ALTER TABLE books_book ALTER COLUMN num_pages SET NOT NULL; COMMIT;

در صورتیکه از این روش استفاده می کنید، بخاطر داشته باشید که باید blank=True و null=True را در مدل خود حذف کنید.

بعد از عبارت ALTER TABLE، با اجرای shell پایتون و اجرای کد زیر در آن مطمئن می شویم که تغییر به درستی کار می کند:

>>> from mysite.books.models import Book >>> Book.objects.all()[:5]

در صورتیکه کد فوق موجب بروز هیچ خطایی نشود، بر روی production server رفته و عبارت ALTER TABLE را در پایگاه داده ی production اجرا می کنیم. سپس، مدل موجود در production environment را به روز رسانی کرده و وب سرور را دوباره راه اندازی می کنیم.

حذف فیلدها

حذف یک فیلد از یک مدل بسیار ساده تر از اضافه کردن آن می باشد. برای حذف یک فیلد، تنها کافیست مراحل زیر را دنبال کنید:

  1. فیلد مورد نظر را از مدل خود حذف کرده و وب سرور را دوباره راه اندازی کنید.
  2. ستون مورد نظر را از پایگاه داده ی خود، با استفاده از دستور زیر حذف کنید:ALTER TABLE books_book DROP COLUMN num_pages;

اطمینان حاصل کنید مراحل فوق را به ترتیب انجام می دهید. در صورتیکه ابتدا ستون را از پایگاه داده حذف کنید، جنگو (Django) به سرعت خطایی را ایجاد خواهد کرد.

حذف فیلدهای Many-to-Many

به دلیل آنکه فیلدهای many-to-many متفاوت از فیلدهای عادی می باشند، روند حذف آن ها نیز متفاوت می باشد:

  1. ManyToManyField را از مدل خود حذف کرده و وب سرور را دوباره راه اندازی کنید.
  2. جدول many-to-many را از پایگاه داده ی خود حذف با استفاده از دستور زیر حذف کنید:DROP TABLE books_book_authors;

همانند بخش قبلی، اطمینان حاصل کنید که مراحل فوق را به ترتیب انجام می دهید.

حذف مدل ها

حذف کردن یک مدل کاملا ساده تر از حذف یک فیلد می باشد. برای حذف یک مدل، تنها کافیست مراحل زیر را دنبال کنید:

  1. مدل مورد نظر را از فایل models.py خود حذف کرده و وب سرور را دوباره راه اندازی کنید.
  2. جدول مورد نظر را از پایگاه داده ی خود به استفاده از کد زیر حذف کنید:DROP TABLE books_book;

    توجه داشته باشد که، ممکن است نیاز باشد تا هر جدول وابسته به این جدول را درون پایگاه داده حذف کنید – مانند، هر جدولی که به books_book دارای کلید خارجی باشد.

همانند بخش قبلی، اطمینان حاصل کنید که مراحل فوق را به ترتیب انجام می دهید.

Managers

در عبارت Book.objects.all()، objects یک attribute ویژه از میان کوئری هایی که به پایگاه داده ارسال می شود می باشد. در آموزش مدل جنگو این attribute، به طور خلاصه به صورت manager مدل توضیح داده شد. حالا زمان آن است که کمی عمیق تر به هویت manager ها و اینکه چگونه می توان از آن ها استفاده کرد بپردازیم.

به طور خلاصه، manager مدل یک شیء می باشد که مدل های جنگو از طریق آن کوئری های پایگاه داده را اجرا می کنند. هر مدل جنگو دارای حداقل یک manager می باشد، و می توان manager های سفارشی ای را به منظور سفارشی سازی دسترسی به پایگاه داده ایجاد نمود.

دو دلیل که ممکن است شما بخواهید یک manager سفارشی ایجاد کنید وجود دارد: برای اضافه کردن متدهای manager اضافه، و یا برای تغییر QuerySet اولیه ای که manager بر می گرداند.

اضافه کردن متدهای اضافه ی Manager

اضافه کردن متدهای اضافه ی manager روشی مقدم برای اضافه کردن عملکرد در سطح جدول برای مدل های شما می باشد. (برای عملکرد در سطح ردیف – مانند توابعی که بر روی یک نمونه ی تکی از یک شیء مدل عمل می کنند – از متدهای مدل استفاده کنید، که پیش تر در این فصل توضیح داده شده است.)

برای مثال، اجازه دهید برای مدل Book یک manager متد title_count() که که یک keyword دریافت کرده و تعداد کتاب هایی که عنوان آن ها حاوی keyword می باشد را بر می گرداند بنویسیم. (این مثال کمی ساختگی می باشد، ولی نحوه ی کار manager ها را نشان می دهد.)

# models.py from django.db import models # ... Author and Publisher models here ... class BookManager(models.Manager): def title_count(self, keyword): return self.filter(title__icontains=keyword).count() class Book(models.Model): title = models.CharField(max_length=100) authors = models.ManyToManyField(Author) publisher = models.ForeignKey(Publisher) publication_date = models.DateField() num_pages = models.IntegerField(blank=True, null=True) objects = BookManager() def __unicode__(self): return self.title

با وجود manager فوق، می توان:

>>> Book.objects.title_count('django') 4 >>> Book.objects.title_count('python') 18

در نکته هایی درباره ی کد فوق را مشاهده می کنید:

چرا می خواهیم یک متد مانند title_count() اضافه کنیم؟ برای ایجاد کدی عمومی برای اجرای کوئری ها که نیازی به کد تکراری نداشته باشیم.

تغییر QuerySet های اولیه ی manager

QuerySet پایه ی manager تمام شیء های سیستم را بر می گرداند. برای مثال، Book.objects.all() تمام کتاب های موجود در پایگاه داده ی book را بر می گرداند.

می توان از طریق بازنویسی متد Manager.get_query_set()، QuerySet پایه ی manager را بازنویسی کرد. get_query_set() باید یک QuerySet با ویژگی هایی که نیاز است را بر گرداند.

برای مثال، مدل زیر دارای دو manager می باشد – یکی تمام شیء ها را بر می گرداند و دیگری تنها کتاب های نوشته شده توسط Roald Dahl را بر می گرداند.

from django.db import models # First, define the Manager subclass. class DahlBookManager(models.Manager): def get_query_set(self): return super(DahlBookManager, self).get_query_set().filter(author='Roald Dahl') # Then hook it into the Book model explicitly. 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.

با مدل نمونه ی فوق، Book.objects.all() تمام کتاب های موجود در پایگاه داده را بر خواهد گرداند، ولی Book.dahl_objects.all() تنها کتاب هایی که توسط Roald Dahl نوشته شده اند را بر خواهد گرداند. توجه داشته باشید که به طور صریح objects را در نمونه Manager عادی قرار داده ایم، زیرا در غیر اینصورت، تنها manager قابل دسترس dahl_objects خواهد بود.

البته، به دلیل آنکه get_query_set() یک شیء QuerySet بر می گرداند، می توانید از filter()، exclude() و تمام متدهای QuerySet در آن استفاده کرد. بنابراین عبارت های زیر درست می باشند:

Book.dahl_objects.all() Book.dahl_objects.filter(title='Matilda') Book.dahl_objects.count()

مثال فوق همچنین به تکنیک های جالب دیگری نیز اشاره می کند: استفاده از manager های چندگانه در یک مدل. می توان به صورت بسیاری از نمونه های Manager() به یک مدل همانطور که می خواهید attach کنید.

برای مثال:

class MaleManager(models.Manager): def get_query_set(self): return super(MaleManager, self).get_query_set().filter(sex='M') class FemaleManager(models.Manager): def get_query_set(self): return super(FemaleManager, self).get_query_set().filter(sex='F') class Person(models.Model): first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) sex = models.CharField(max_length=1, choices=(('M', 'Male'), ('F', 'Female'))) people = models.Manager() men = MaleManager() women = FemaleManager()

مثال فوق به شما اجازه می دهد Person.men.all()، Person.women.all() و Person.people.all() را درخواست کنید. در دست ترجمه ...

در صورتیکه از شیء های Manager سفارشی استفاده می کنید، توجه داشته باشید که اولین Manager ای که جنگو با آن برخورد می کند (به ترتیبی که درون مدل تعریف شده اند) دارای وضعیت ویژه ای می باشد. جنگو اولین Manager تعریف شده در یک کلاس را به صورت Manager پیشفرض تلقی می کند، و چندین بخش از جنگو (بجز برنامه ی مدیر) منحصرا برای آن مدل از این Manager استفاده خواهند کرد. نتیجه این که، اغلب ایده ی خوبی است که با دقت بیشتری manager پیشفرض را انتخاب کنیم، به منظور اجتناب از وضعیتی که نتایج get_query_set() را به دلیل ناتوانی برای بازیابی شیء هایی که می خواهید با آن ها کار کنید بازنویسی کنید.

متدهای Model

متدهای سفارشی در یک مدل برای اضافه کردن عملکرد سفارشی در سطح ردیف به شیء های خودتان اضافه کنید. در حالیکه manager ها برای چیزهایی در سطح جدول در نظر گرفته شده اند، متدهای مدل باید در یک نمونه ی مدل خاص عمل کنند.

این یک تکنیک ارزشمند برای نگهداشتن business logic در یک مکان می باشد – مدل.

ذکر یک مثال ساده ترین راه برای توضیح این موضوع می باشد. در اینجا یک مدل با تعدادی متدهای سفارشی وجود دارد:

from django.contrib.localflavor.us.models import USStateField from django.db import models class Person(models.Model): first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) birth_date = models.DateField() address = models.CharField(max_length=100) city = models.CharField(max_length=50) state = USStateField() # Yes, this is U.S.-centric... def baby_boomer_status(self): "Returns the person's baby-boomer status." import datetime if datetime.date(1945, 8, 1) <= self.birth_date <= datetime.date(1964, 12, 31): return "Baby boomer" if self.birth_date < datetime.date(1945, 8, 1): return "Pre-boomer" return "Post-boomer" def is_midwestern(self): "Returns True if this person is from the Midwest." return self.state in ('IL', 'WI', 'MI', 'IN', 'OH', 'IA', 'MO') def _get_full_name(self): "Returns the person's full name." return u'%s %s' % (self.first_name, self.last_name) full_name = property(_get_full_name)

آخرین متد در مثال فوق یک "property" می باشد. برای اطلاعات بیشتر در مورد property ها به http://www.python.org/download/releases/2.2/descrintro/#property مراجعه کنید.

کاربرد مثال فوق:

>>> p = Person.objects.get(first_name='Barack', last_name='Obama') >>> p.birth_date datetime.date(1961, 8, 4) >>> p.baby_boomer_status() 'Baby boomer' >>> p.is_midwestern() True >>> p.full_name # Note this isn't a method -- it's treated as an attribute u'Barack Obama'

اجرای کوئری های خام SQL

گاهی اوقات می خواهید کوئری هایی را به صورت مستقیم در پایگاه داده ی خود اجرا کنید. به آسانی می توان این کار را از طریق دسترسی به شیء django.db.connection انجام داد که connection فعلی پایگاده داده را نشان می دهد. برای استفاده از آن، connection.cursor() را جهت بدست آوردن یک شیء cursor فراخوانی کنید، سپس جهت اجرای SQL و cursor.fetchone() یا cursor.fetchall برای برگرداندن نتیجه ی ردیف ها cursor.execute(sql, [params]) را فراخوانی کنید:

>>> from django.db import connection >>> cursor = connection.cursor() >>> cursor.execute(""" ... SELECT DISTINCT first_name ... FROM people_person ... WHERE last_name = %s""", ['Lennon']) >>> row = cursor.fetchone() >>> print row ['John']

connection و cursor غالبا "DB-API" استاندارد پایتون را اجرا می کنند که می توانید برا اطلاعات بیشتر در مورد آن به http://www.python.org/peps/pep-0249.html مراجعه کنید. در صورتیکه با DB-API پایتون آشنایی ندارید، توجه داشته باشید که عبارت SQL در cursor.execute() از حفره های "%s" به جای اضافه کردن پارامتر های به صورت مستقیم درون SQL استفاده می کند. در صورتیکه از این تکنیک استفاده می کنید، کتابخانه ی زیرین پایگاه داده به طور خودکار کتیشن هایی در صورت نیاز اضافه خواهد کرد. در دست ترجمه ...

به جای آن که کد view شما به صورت درهم و برهم و پراکنده با عبارت های django.db.connection قرار بگیرد، ایده ی خوبی است که آن ها را در متدهای سفارشی مدل یا متدهای manager قرار دهیم. برای مثال، مثال فوق می تواند درون یک متد manager سفارشی مانند زیر جمع شود:

from django.db import connection, models class PersonManager(models.Manager): def first_names(self, last_name): cursor = connection.cursor() cursor.execute(""" SELECT DISTINCT first_name FROM people_person WHERE last_name = %s""", [last_name]) return [row[0] for row in cursor.fetchone()] class Person(models.Model): first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) objects = PersonManager()

کاربرد مثال فوق:

>>> Person.objects.first_names('Lennon') ['John', 'Cynthia']