oruji.github.io
oruji.github.ioPersian Tutorials
ویرایش: 1396/11/13 19:19
A A

آموزش View های Generic در جنگو (Django)

در اینجا دوباره موضوع تکراری این کتاب وجود دارد: در بدترین شکل ممکن، توسعه وب کار خسته کننده ای خواهد بود، تاکنون، نحوه ی تلاش فریم ورک یا چارچوب جنگو (Django) برای انجام برخی از این کارهای یکنواخت، در مدل و لایه ی template گفته شده است، همچنین توسعه دهندگان، این یکنواختی را در سطح view نیز تجربه کرده اند.

view های generic جنگو، برای ساده کردن این یکنواختی ها توسعه داده شده اند. view های generic برخی از الگوها و روندهای مشترک یافت شده در توسعه ی view را، در نظر گرفته و آن ها را طوری طراحی کرده اند که شما بتوانید به سرعت view های مشترک را بدون اینکه مجبور باشید کد زیادی بنویسید ایجاد کنید. در حقیقت، تقریبا هر مثال view ای در فصل های گذشته بیان شده است، می توانند با استفاده از view های generic بازنویسی شوند.

آموزش view و urlconf پیشرفته به طور خلاصه نحوه ی ایجاد یک view generic را توضیح داده است. جهت مرور، می توانیم بعضی از وظایف مشترک را شناسایی کنیم، مانند نمایش یک لیست از شیء ها، و نوشتن کدی که یک لیست از هر شیءی رانمایش دهد. سپس مدل مورد نظر می تواند به صورت یک آرگومان اضافه به URLconf ارسال شود.

جنگو view های generic را برای انجام کارهای زیر ارائه کرده است:

روی هم رفته، این view ها، رابط های ساده ای برای انجام رایج ترین وظایفی که توسعه دهندگان با آن روبرو هستند تهیه شده اند.

استفاده از View های Generic

تمام این view ها با ساختن پیکربندی دیکشنری های در فایل URLconf شما، و ارسال کردن آن دیکشنری ها به صورت عضو سوم از تاپل URLconf برای الگوی داده شده استفاده می شوند. (بخش "ارسال انتخاب های اضافه برای توابع view" را در آموزش view و urlconf پیشرفته برای مرور کلی درباره ی این تکنیک مطالعه کنید.)

برای مثال، در اینجا یک URLconf ساده وجود دارد که شما می توانید برای ارائه ی یک صفحه ی استاتیک "about" از آن استفاده کنید:

from django.conf.urls.defaults import * from django.views.generic.simple import direct_to_template urlpatterns = patterns('', (r'^about/$', direct_to_template, { 'template': 'about.html' }) )

هر چند کد فوق ممکن است در نگاه اول کمی جادویی به نظر برسد – نگاه کنید، یک view بدون هیچ کدی! – کد فوق دقیقا با مثال آموزش view و urlconf پیشرفته یکی می باشد: view مورد نظر در آن مثال یعنی direct_to_template به سادگی اطلاعات را از پارامترهای اضافه ی دیکشنری دریافت کرده و زمان render شدن view از آن اطلاعات استفاده می کند.

به این دلیل که view های generic مانند توابع view دیگر یک تابع view عادی می باشد، می توان آن ها را، درون view های خودمان دوباره استفاده کنیم. به عنوان مثال، اجازه دهید مثال "about" را برای مرتبط ساختن URL ها از حالت /about/<whatever>/ برای about/<whatever>.html به طور ثابت render شده گسترش دهیم. ما این کار را با اولین اصلاح URLconf برای اشاره به تابع view انجام خواهیم داد:

from django.conf.urls.defaults import * from django.views.generic.simple import direct_to_template from mysite.books.views import about_pages urlpatterns = patterns('', (r'^about/$', direct_to_template, { 'template': 'about.html' }), (r'^about/(\w )/$', about_pages), )

در قدم بعدی، view مورد نظر یعنی about_pages را خواهیم نوشت:

from django.http import Http404 from django.template import TemplateDoesNotExist from django.views.generic.simple import direct_to_template def about_pages(request, page): try: return direct_to_template(request, template="about/%s.html" % page) except TemplateDoesNotExist: raise Http404()

در اینجا با direct_to_template مانند توابع دیگر رفتار کرده ایم. به این خاطر که این تابع یک HttpResponse بر می گرداند، می توانیم به سادگی آن را همانطور که هست برگردانیم. تنها مقداری رفتار خاص در کد فوق وجود دارد که آن هم سر و کار داشتن با template های نا معلوم می باشد. ما قصد استفاده از یک template ای که وجود ندارد و باعث بروز یک خطای سرور می شود را نداریم، بنابراین خطاهای TemplateDoesNotExist را کنترل کرده و خطای 404 را به جای آن بر گردانده ایم.

View های Generic شیء ها

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

اجازه دهید نگاهی به یکی از این view های generic با نام "object list" بیاندازیم. ما از شیء Publisher که در آموزش مدل جنگو از آن استفاده شده است، در اینجا استفاده کرده ایم:

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 Meta: ordering = ['name']

جهت ساختن یک صفحه ی لیست از تمام ناشران، ما از یک URLconf مانند زیر استفاده می کنیم:

from django.conf.urls.defaults import * from django.views.generic import list_detail from mysite.books.models import Publisher publisher_info = { 'queryset': Publisher.objects.all(), } urlpatterns = patterns('', (r'^publishers/$', list_detail.object_list, publisher_info) )

تمام چیزی که برای نوشتن نیاز است کد پایتون می باشد. ولی هنوز نیاز به نوشتن یک template داریم. می توان یک کلید دیگر با نام template_name در آرگومان های اضافه دیکشنری که شامل template مورد نظر می باشد به publisher_info اضافه کرد:

from django.conf.urls.defaults import * from django.views.generic import list_detail from mysite.books.models import Publisher publisher_info = { 'queryset': Publisher.objects.all(), 'template_name': 'publisher_list_page.html', } urlpatterns = patterns('', (r'^publishers/$', list_detail.object_list, publisher_info) )

در صورت نبودن template_name، به هر حال generic view مورد نظر یعنی object_list از نام شیء یکی را استنباط خواهد کرد. در این مورد، template استنباط شده، "books/publisher_list.html" خواهد بود – جزء "books" از نام app گرفته شده است، هنگامی که جزء "publisher" تنها حروف کوچک از نام مدل است آن را تعریف می کند.

این template در مقابل یک context حاوی یک متغیر به نام object_list، render خواهد شد که حاوی تمام شیء های publisher است. یک template خیلی ساده ممکن است شبیه به چیزی مانند زیر باشد:

{% extends "base.html" %} {% block content %} <h2>Publishers</h2> <ul> {% for publisher in object_list %} <li>{{ publisher.name }}</li> {% endfor %} </ul> {% endblock %}

(توجه داشته باشید که template فوق فرض می کند یک base.html وجود دارد، همانطور که در آموزش template جنگو در یک مثال ایجاد کردیم.)

در دست ترجمه/تالیف ...

گسترش View های Generic

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

خوشبختانه، تقریبا در هر یک از این موارد، روش هایی برای گسترش ساده ی view های generic برای کنترل یک آرایه ی بزرگتر از موارد استفاده وجود دارد.

ایجاد Template Context های مساعد

ممکن است توجه کرده باشید لیست ناشران یعنی درون متغیری با نام object_list ذخیره شده است. زمانی این کد زیباتر خواهد شد که، این نام زمانی برای نویسندگان template مساعد خواهد بود که به جای نام object_list نام آن publisher_list باشد؛ محتویات این متغیر با این نام واضح تر خواهد بود.

نام این متغیر را می توان به سادگی با آرگومان template_object_name تغییر داد:

from django.conf.urls.defaults import * from django.views.جنریک import list_detail from mysite.books.models import Publisher publisher_info = { 'queryset': Publisher.objects.all(), 'template_name': 'publisher_list_page.html', 'template_object_name': 'publisher', } urlpatterns = patterns('', (r'^publishers/$', list_detail.object_list, publisher_info) )

درون template، view جنریک یک _list به template_object_name اضافه می کند.

ایجاد یک template_object_name مفید همواره یک ایده ی خوب می باشد. همکاران شما کسانی که template ها را طراحی می کند از شما ممنون خواهند بود.

اضافه کردن context اضافه

گاهی اوقات، ممکن است نیاز باشد برخی اطلاعات اضافه فراتر از اطلاعات تهیه شده توسط view جنریک ارائه شوند. برای مثال، یک لیست از تمام ناشران دیگر در هر صفحه ی جزئیات هر ناشر را تصور کنید. view جنریک مورد نظر یعنی object_detail ناشر را برای context تهیه می کند، ولی به نظر می رسد هیچ راهی برای بدست آوردن یک لیست از تمام ناشران در آن template وجود ندارد.

ولی باید متذکر شد که راهی وجود دارد: تمام view های جنریک یک پارامتر اختیاری اضافه به نام extra_context دریافت می کنند. این که دیکشنری از شیء های اضافه می باشد که به context ارسال شده به template اضافه خواهد شد. بنابراین، جهت ایجاد لیست ناشران، از یک دیکشنری اطلاعات مانند زیر استفاده کرده ایم:

publisher_info = { 'queryset': Publisher.objects.all(), 'template_object_name': 'publisher', 'extra_context': {'book_list': Book.objects.all()} }

کد فوق یک متغیر {{ book_list }} در context موجود در template قرار می دهد. این الگو می تواند برای ارسال هر اطلاعاتی به درون template برای view جنریک استفاده شود که بسیار نیز مفید می باشد.

هر چند، در واقع یک اشکال ظریف در اینجا وجود دارد – می توانید آن را حدس بزنید؟

مشکل باید هنگامی که کوئری های درون extra_context ارزیابی شدند ایجاد شود. زیرا این مثال Book.objects.all() را درون URLconf قرار داده است، در این حالت تنها یک بار ارزیابی می شود (هنگامی که URLconf برای اولین بار بارگذاری می شود). هنگامی که شما ناشران را حذف یا اضافه می کنید، دقت خواهید داشت که view جنریک تا زمانی که وب سرور را دوباره بارگذاری نکرده اید، این تغییرات را منعکس نخواهد کرد.

راه حل، استفاده از یک callback در extra_context به جای متغیر می باشد. هر چیز قابل فراخوانی (مانند یک تابع) ارسال شده به extra_context زمانی که view ارائه شود ارزیابی خواهد شد (به جای تنها یک بار). می توانید با تعریف یک تابع مشکل فوق را حل کنید:

def get_books(): return Book.objects.all() publisher_info = { 'queryset': Publisher.objects.all(), 'template_object_name': 'publisher', 'extra_context': {'book_list': get_books} }

یا می توانید از یک روش کوتاه تر که بر این واقعیت تکیه دارد که Book.objects.all خودش قابل فراخوانی می باشد استفاده کنید:

publisher_info = { 'queryset': Publisher.objects.all(), 'template_object_name': 'publisher', 'extra_context': {'book_list': Book.objects.all} }

به Book.objects.all بدون پرانتز پایانی توجه کنید. این حالت به تابع در واقع بدون فراخوانی آن رجوع می شود.

تماشای زیر مجموعه ای از شیء ها

اکنون اجازه دهید نگاه نزدیک تری به کلید queryset که در طول این مسیر از آن استفاده کرده ایم داشته باشیم. اغلب view های جنریک از این آرگومان های queryset را دریافت می کنند – این نحوه ی فهم view می باشد که کدام مجموعه از شیء ها را نمایش دهد (بخش "انتخاب شیء ها" در فصل پنچم برای مقدمه ی شیء های Queryset را مطالعه کنید).

برای برگزیدن یک مثال ساده، می خواهیم یک لیست از کتاب ها را از با تاریخ انتشار چیدمان کنیم.

book_info = { 'queryset': Book.objects.order_by('-publication_date'), } urlpatterns = patterns('', (r'^publishers/$', list_detail.object_list, publisher_info), (r'^books/$', list_detail.object_list, book_info), )

مثال فوق کاملا ساده می باشد، ولی یک با حالتی ظریف ایده ای را نشان می دهد. البته، معمولا بیشتر از چند بار شیء ها می خواهید که چیدمان کنید. در صورتی که می خواهید لیستی از کتاب ها با یک ناشر خواص ارائه دهید، می توانید از تکنیکی یکسان استفاده کنید:

apress_books = { 'queryset': Book.objects.filter(publisher__name='Apress Publishing'), 'template_name': 'books/apress_list.html' } urlpatterns = patterns('', (r'^publishers/$', list_detail.object_list, publisher_info), (r'^books/apress/$', list_detail.object_list, apress_books), )

دقت داشته باشید که به همراه یک queryset فیلتر شده، همچنین از یک نام template سفارشی استفاده کرده ایم. در صورتی که این کار را انجام ندهیم، view جنریک از template همسان به صورت شیء لیست "vanilla" استفاده خواهد کرد، که ممکن است آنچه که می خواهید نباشد.

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

فیلتر کردن پیچیده با توابع wrapper

نیاز رایج دیگر، فیلتر کردن شیء های داده شده در یک صفحه ی لیست از طریق برخی کلیدها در URL می باشد. کمی قبل تر، نام ناشر را به طور مستقیم درون URLconf قرار می دادیم، ولی چه می شد اگر می خواستیم یک view بنویسیم که تمام کتاب ها را از طریق برخی ناشران دلخواه نمایش دهد؟ راهکار wrap کردن جنریک view مورد نظر یعنی object_list جهت اجتناب از نوشتن مقدار زیادی کد به صورت دستی می باشد. به طور معمول، با نوشتن یک URLconf شروع می کنیم:

urlpatterns = patterns('', (r'^publishers/$', list_detail.object_list, publisher_info), (r'^books/(\w )/$', books_by_publisher), )

در قدم بعدی، view مورد نظر یعنی books_by_publisher را خواهیم نوشت:

from django.shortcuts import get_object_or_404 from django.views.جنریک import list_detail from mysite.books.models import Book, Publisher def books_by_publisher(request, name): # Look up the publisher (and raise a 404 if it can't be found). publisher = get_object_or_404(Publisher, name__iexact=name) # Use the object_list view for the heavy lifting. return list_detail.object_list( request, queryset = Book.objects.filter(publisher=publisher), template_name = 'books/books_by_publisher.html', template_object_name = 'book', extra_context = {'publisher': publisher} )

کد فوق جواب می دهد، زیرا واقعا هیچ چیز خاصی درباره ی view های جنریک وجود ندارد – کد فوق تنها توابع پایتون می باشد. همانند هر تابع view ای، view های جنریک مجموعه ای خاص از آرگومان ها دریافت کرده و شیء های HttpResponse را بر می گردانند. در نتیجه، wrap کردن یک تابع در اطراف یک view جنریک به طور باور نکردنی ساده می باشد که کار اضافه قبل (یا بعد؛ به بخش بعدی را مطالعه کنید) view جنریک را کنترل می کند.

انجام کار اضافه

آخرین الگوی رایجی که مورد بحث قرار خواهیم داد، انجام کار اضافه قبل یا بعد از فراخوانی view جنریک می باشد.

تصور کنید یک فیلد last_accessed در شیء Author موجود می باشد که برای پیگیری آخرین زمانی که کسی author را نگاه کرده است استفاده می شود. view جنریک object_dtail، البته هیچ چیزی درباره ی این فیلد نمی داند، ولی یک بار دیگر به سادگی یک view سفارشی، برای نگه داشتن فیلد به روز رسانی شده می نویسیم.

در ابتدا، نیاز به اضافه کردن یک بخش جزئیات نویسنده در URLconf برای اشاره به یک view سفارشی داریم:

from mysite.books.views import author_detail urlpatterns = patterns('', # ... (r'^authors/(?P<author_id>\d )/$', author_detail), # ... )

سپس تابع wrapper خودمان را می نویسیم:

import datetime from django.shortcuts import get_object_or_404 from django.views.جنریک import list_detail from mysite.books.models import Author def author_detail(request, author_id): # Delegate to the view جنریک and get an HttpResponse. response = list_detail.object_detail( request, queryset = Author.objects.all(), object_id = author_id, ) # Record the last accessed date. We do this *after* the call # to object_detail(), not before it, so that this won't be called # unless the Author actually exists. (If the author doesn't exist, # object_detail() will raise Http404, and we won't reach this point.) now = datetime.datetime.now() Author.objects.filter(id=author_id).update(last_accessed=now) return response

می توان یک روش همسان برای تغییر پاسخ برگدانده شده از طریق view جنریک استفاده کرد. در صورتیکه بخواهیم یک نسخه ی متنی از لیست نویسندگان با قابلیت دانلود تهیه کنیم، می توان از یک view شبیه به زیر استفاده کرد:

def author_list_plaintext(request): response = list_detail.object_list( request, queryset = Author.objects.all(), mimetype = 'text/plain', template_name = 'books/author_list.txt' ) response["Content-Disposition"] = "attachment; filename=authors.txt" return response

کد فوق جواب خواهد داد، چرا که view ها جنریک شیء های ساده ی HttpResponse را بر می گردانند که می توانند مانند دیکشنری برای مجموعه ای از HTTP header ها رفتار کنند. ضمنا Content-Disposition، به مرورگر یاد می دهد که صفحه به جای اینکه در مرورگر نمایش دهد، آن را دانلود و ذخیره کند.