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

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

در آموزش view و urlconf جنگو، اصول اولیه توابع view و URLconf ها توضیح داده شد. این آموزش از کتاب به جزئیان بیشتری درباره ی عمکرد پیشرفته ی این دو مبحث درون فریم ورک یا چارچوب جنگو (Django) خواهد پرداخت.

فوت و فن URLconf

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

ساده کردن import توابع

URLconf زیر را مشاهده کنید، که در آموزش view و urlconf جنگو ساخته شده است:

from django.conf.urls.defaults import * from mysite.views import hello, current_datetime, hours_ahead urlpatterns = patterns('', (r'^hello/$', hello), (r'^time/$', current_datetime), (r'^time/plus/(\d{1,2})/$', hours_ahead), )

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

ولی همانطور که یک برنامه ی جنگو به سمت پیچیدگی رشد می کند، URLconf آن نیز رشد می کند، از این رو استفاده از روش import قبلی می تواند کمی خسته کننده باشد. (برای هر تابع view جدید، باید import کردن آن را نیز فراموش نکرد، و عبارت import در صورتیکه از این روش استفاده کنید بیش از حد بلند می باشد.) این امکان وجود دارد که روش قبلی را با import کردن خود ماژول views دُر بزنید. مثال URLconf زیر برابر با مثال قبلی می باشد:

from django.conf.urls.defaults import * from mysite import views urlpatterns = patterns('', (r'^hello/$', views.hello), (r'^time/$', views.current_datetime), (r'^time/plus/(d{1,2})/$', views.hours_ahead), )

جنگو روش دیگری را نیز جهت تعیین تابع view برای یک الگوی خاص در URLconf ارائه می کند: می توان یک رشته حاوی نام ماژول و نام تابع به جای خود شیء تابع ارسال کرد:

from django.conf.urls.defaults import * urlpatterns = patterns('', (r'^hello/$', 'mysite.views.hello'), (r'^time/$', 'mysite.views.current_datetime'), (r'^time/plus/(d{1,2})/$', 'mysite.views.hours_ahead'), )

(به علامت تک کتیشن دور نام view ها توجه کنید. در کد فوق از 'mysite.views.current_datetime' با علامت کتیشن به جای mysite.views.current_datetime استفاده شده است.)

با استفاده از این تکنیک، import کردن توابع view دیگر ضروری نمی باشد؛ جنگو به طور خودکار با استفاده از رشته ی مشخص شده با نام و مسیر تابع view، تابع view مناسب را import می کند.

یک میانبر دیگر که می توان هنگام استفاده از تکنیک رشته از آن استفاده کرد، از قلم انداختن پیشوند view می باشد. در مثال URLconf، هر رشته ی view با 'mysite.views' شروع شده است، که یک حالت اضافی می باشد. می توان پیشوند مشترک این رشته را حذف کرده و آن را به عنوان اولین آرگومان patterns() مانند زیر استفاده کرد:

from django.conf.urls.defaults import * urlpatterns = patterns('mysite.views', (r'^hello/$', 'hello'), (r'^time/$', 'current_datetime'), (r'^time/plus/(d{1,2})/$', 'hours_ahead'), )

توجه داشته باشید که نقطه ی عقبی را در پیشوند و رشته های view قرار ندهید. جنگو این نقطه ها را به صورت خودکار قرار خواهد داد.

با در نظر گرفتن دو روش فوق، کدام روش بهتر است؟ این بستگی به نحوه ی کد زدن شما و نیازهای کد دارد.

مزایای روش رشته:

مزایای روش شیء تابع:

هر دو روش معتبر می باشند، و می توان حتی این دو روش را درون یک URLconf یکسان با یکدیگر ترکیب کرد. انتخاب با خود شماست.

استفاده از پیشوند چندگانه ی view

در عمل، در صورتیکه از تکنیک رشته استفاده می کنید، ممکن است هنگامی که URLconf دارای پیشوند مشترک نیست ترکیب view ها را پایان دهید. البته در این حالت نیز می توان هنوز از میانبر پیشوند view برای حذف موارد تکراری سود برد. تنها کافیست از شیء های چندگانه ی patterns() به صورت زیر استفاده کنیم:

کد قدیمی:

from django.conf.urls.defaults import * urlpatterns = patterns('', (r'^hello/$', 'mysite.views.hello'), (r'^time/$', 'mysite.views.current_datetime'), (r'^time/plus/(\d{1,2})/$', 'mysite.views.hours_ahead'), (r'^tag/(\w )/$', 'weblog.views.tag'), )

کد جدید:

from django.conf.urls.defaults import * urlpatterns = patterns('mysite.views', (r'^hello/$', 'hello'), (r'^time/$', 'current_datetime'), (r'^time/plus/(\d{1,2})/$', 'hours_ahead'), ) urlpatterns = patterns('weblog.views', (r'^tag/(\w )/$', 'tag'), )

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

پوشش خاص URL ها در حالت Debug

بعد از صحبت درباره ی ساختن urlpatterns به طور پویا، ممکن است بخواهید از این تکنیک برای تغییر رفتار URLconf ها در حالت debug جنگو استفاده کنید. برای انجام این کار، تنها کافیست مقدار تنظیم DEBUG را در زمان اجرا بررسی کنید، مانند زیر:

from django.conf import settings from django.conf.urls.defaults import * from mysite import views urlpatterns = patterns('', (r'^$', views.homepage), (r'^(\d{4})/([a-z]{3})/$', views.archive_month), ) if settings.DEBUG: urlpatterns = patterns('', (r'^debuginfo/$', views.debug), )

در این مثال، آدرس /debuginfo/ تنها در صورتیکه مقدار تنظیم DEBUG، True باشد در دسترس خواهد بود.

استفاده از Named Groups

در تمام مثال های URLconf ای که تاکنون در این کتاب آورده شده است، از regular expression های ساده ی بدون نام استفاده شده است – یعنی اینکه، تنها آن قسمت از URL مورد نظر درون پرانتز قرار داده شده است، و جنگو آن متن داخل پرانتز را به تابع view به صورت یک آرگومان موضعی (positional argument) ارسال می کرد. برای کاربرد پیشرفته تر، این امکان وجود دارد که از گروه های regular expression نام گذاری شده، برای ارسال قسمت هایی از URL، به صورت آرگومان های کیورد به view استفاده کرد.

def sell(item, price, quantity): print "Selling %s unit(s) of %s at %s" % (quantity, item, price)

جهت فراخوانی تابع فوق با آرگومان های موضعی، شما آرگومان ها را به ترتیبی که در تعریف تابع چیده شده اند قرار می دهید:

sell('Socks', '$2.50', 6)

جهت فراخوانی آن با آرگومان های کیورد، شما نام های آرگومان ها را به همراه مقادیر برای آنها تعیین می کنید. عبارات زیر با هم برابر می باشند:

sell(item='Socks', price='$2.50', quantity=6) sell(item='Socks', quantity=6, price='$2.50') sell(price='$2.50', item='Socks', quantity=6) sell(price='$2.50', quantity=6, item='Socks') sell(quantity=6, item='Socks', price='$2.50') sell(quantity=6, price='$2.50', item='Socks')

در پایان، تا زمانی که تمام آرگومان های موضعی قبل از آرگومان های کیورد قرار بگیرند می توان آرگومان های کیورد و موضعی را ترکیب کرد،:

sell('Socks', '$2.50', quantity=6) sell('Socks', price='$2.50', quantity=6) sell('Socks', quantity=6, price='$2.50')

در regular expression های پایتون، نحوه ی کد نویسی برای گروه های نام گذاری شده ی regular expression به این شکل می باشد: (?P<name>pattern)، که name نام گروه و pattern الگوی تطبیق داده شده می باشد.

مثال زیر URLconf ای می باشد که از گروه های بدون نام استفاده کرده است:

from django.conf.urls.defaults import * from mysite import views urlpatterns = patterns('', (r'^articles/(\d{4})/$', views.year_archive), (r'^articles/(\d{4})/(\d{2})/$', views.month_archive), )

مثال زیر URLconf همسان با مثال قبلی می باشد که با استفاده از گروه های نام گذاری شده باز نویسی شده است:

from django.conf.urls.defaults import * from mysite import views urlpatterns = patterns('', (r'^articles/(?P<year>\d{4})/$', views.year_archive), (r'^articles/(?P<year>\d{4})/(?P<month>\d{2})/$', views.month_archive), )

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

به عنوان مثال، برای گروه های بدون نام، یک درخواست به /articles/2006/03/ در یک فراخوانی تابع برابر با کد زیر خواهد بود:

month_archive(request, '2006', '03')

برای گروه های نام گذاری شده، فراخوانی تابع به صورت زیر خواهد بود:

month_archive(request, year='2006', month='03')

در عمل، استفاده از گروه های نام گذاری شده، URLconf ها را کمی واضح تر می کند و آن ها را کمتر در معرض خطاهای ترتیب آرگومان قرار می دهد – و می توان با استفاده از گروه های نام گذاری شده ترتیب آرگومان ها را در تعریف توابع view تغییر داد. در مثال قبلی، در صورت استفاده از گروه های بدون نام اگر می خواستیم آدرس ها را طوری تغییر دهیم که ماه قبل از سال قرار بگیرد، باید این تغییرات را درون تابع month_archive نیز اعمال می کردیم. ولی در صورت استفاده از گروه های نام گذاری شده، تغییر دادن ترتیب پارامتر های داخل پرانتز در regular expression درون URL هیچ تاثیری در view نخواهد داشت.

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

فهمیدن الگوریتم Matchin/Grouping

نکته ی مهمی که در استفاده از گروه های نامگذاری شده قابل اهمیت می باشد این است که، یک الگوی URLconf به تنهایی، نمی تواند حاوی گروه های بدون نام و گروه های نام گذاری شده به طور همزمان باشد. در صورتیکه همچین حالتی پیش آید، جنگو هیچ خطایی ایجاد نخواهد کرد، ولی ممکن است URL های شما را، آن طور که انتظار آن را دارید تشخیص ندهد. به طور خاص، در اینجا الگوریتم تجزیه کننده ی URLconf، با توجه به گروه های نام گذاری شده در مقابل گروه های بدون نام در یک regular expression ذکر شده است.

ارسال Option های اضافی به توابع view

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

# urls.py from django.conf.urls.defaults import * from mysite import views urlpatterns = patterns('', (r'^foo/$', views.foo_view), (r'^bar/$', views.bar_view), ) # views.py from django.shortcuts import render_to_response from mysite.models import MyModel def foo_view(request): m_list = MyModel.objects.filter(is_new=True) return render_to_response('template1.html', {'m_list': m_list}) def bar_view(request): m_list = MyModel.objects.filter(is_new=True) return render_to_response('template2.html', {'m_list': m_list})

در کد فوق تقریبا همه چیز تکرار شده است و این نحوه ی کد زنی به هیچ وجه روش زیبایی به نظر نمی رسد. در ابتدا، ممکن است تصور کنید که با استفاده از یک view برای هر دو URL می توان کدهای اضافه را حذف کرد، و URL ها را در URLconf داخل پرانتز قرار داد، و در آخر نیز آن ها را درون view گفته شده مانند زیر بررسی کنیم:

# urls.py from django.conf.urls.defaults import * from mysite import views urlpatterns = patterns('', (r'^(foo)/$', views.foobar_view), (r'^(bar)/$', views.foobar_view), ) # views.py from django.shortcuts import render_to_response from mysite.models import MyModel def foobar_view(request, url): m_list = MyModel.objects.filter(is_new=True) if url == 'foo': template_name = 'template1.html' elif url == 'bar': template_name = 'template2.html' return render_to_response(template_name, {'m_list': m_list})

مشکل راهکار فوق این است که، اگرچه URL های شما با کد شما جفت شده است، ولی در صورتیکه تصمیم بگیرید /foo/ را به /fooey/ تغییر نام دهید، شما نباید فراموش کنید که کد view را نیز تغییر دهید.

راهکار زیبا برای این مورد استفاده از یک پارامتر اختیاری برای URLconf می باشد. هر الگویی در یک URLconf ممکن است شامل یک آیتم سوم باشد: یک دیکشنری از آرگومان های کیورد برای ارسال به تابع view.

با در نظر گرفتن این، می توان مثال قبلی را به این شکل باز نویسی کرد:

# urls.py from django.conf.urls.defaults import * from mysite import views urlpatterns = patterns('', (r'^foo/$', views.foobar_view, {'template_name': 'template1.html'}), (r'^bar/$', views.foobar_view, {'template_name': 'template2.html'}), ) # views.py from django.shortcuts import render_to_response from mysite.models import MyModel def foobar_view(request, template_name): m_list = MyModel.objects.filter(is_new=True) return render_to_response(template_name, {'m_list': m_list})

همانطور که مشاهده می کنید، URLconf در مثال فوق، template_name را درون URLconf تعیین کرده است. تابع view مانند پارامترهای دیگر با template_name رفتار می کند.

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

بخش های بعدی حاوی تعدادی از ایده ها، نسبت به نحوه ی استفاده از تکنیک option های اضافه ی URLconf در پروژه ها می باشد.

Faking Captured URLconf Values

فرض را بر این بگیرید که مجموعه ای از view ها دارید که از یک الگو طبعیت می کنند، البته به همراه URL ای دیگر که با الگو سازگار نبوده ولی منطق view آن یکی است. در این مورد، می توان مقدار داخل پرانتز URL را، با استفاده از option های اضافه ی URLconf، برای کنترل URL اضافه با view همسان جا زد.

برای مثال، ممکن است برنامه ای داشته باشید که برخی داده ها برای یک روز خاص را نمایش دهند، با URL هایی مانند زیر:

/mydata/jan/01/ /mydata/jan/02/ /mydata/jan/03/ # ... /mydata/dec/30/ /mydata/dec/31/

به کار بردن URL های فوق به اندازه ی کافی ساده می باشد – می توان آن ها را درون یک URLconf مانند زیر کنترل کرد (با استفاده از گروه نام گذاری شده):

urlpatterns = patterns('', (r'^mydata/(?P<month>\w{3})/(?P<day>\d\d)/$', views.my_view), )

و یک تابع view مانند زیر:

def my_view(request, month, day): # ....

روش فوق بسیار ساده می باشد – چیزی وجود ندارد که قبلا گفته نشده باشد. تکنیک و فن خاص زمانی پیش می آید، که بخواهید URL دیگری اضافه کنید که از my_view استفاده می کند، ولی حاوی month و day نباشد.

برای مثال، ممکن است بخواهید URL دیگری مانند /mydata/birthday/ اضافه کنید، که برابر با /mydata/jan/06/ باشد. می توان از امکان option های اضافه URLconf مانند زیر استفاده کرد:

urlpatterns = patterns('', (r'^mydata/birthday/$', views.my_view, {'month': 'jan', 'day': '06'}), (r'^mydata/(?P<month>\w{3})/(?P<day>\d\d)/$', views.my_view), )

نکته ی جالب در کد فوق این است که شما هیچوقت مجبور به تغییر تابع view نمی باشد. تابع view تنها پارامترهای month و day سر و کار دارد – مشکلی نیست اگر این پارامترها به صورت URL داخل پرانتز و یا پارامترهای اضافه ارسال شوند.

ساختن یک View Generic

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

def say_hello(person_name): print 'Hello, %s' % person_name def say_goodbye(person_name): print 'Goodbye, %s' % person_name

می توان hello و goodbye را حذف کرده و آنرا به عنوان پارامتر یک تابع قرار داد:

def greet(person_name, greeting): print '%s, %s' % (greeting, person_name)

می توان این فلسفه را در view های جنگو نیز با استفاده از پارامترهای اضافه ی URLconf به کار برد.

با در نظر گرفتن این موضوع، می توان تصورات سطح بالایی را از view ها ایجاد نمود. بجای آنکه فرض کنیم "این view لیستی از شیء های Event را نمایش می دهد،" می توان در نظر گرفت که "آن view لیستی از شیء های BlogEntry را نمایش می دهد". البته به این موضوع دقت کنید که هر دوی آن ها مورد خاصی هستند "یک view لیستی از شیء ها را نمایش می دهد، که نوع شیء قابل تغییر می باشد."

به عنوان مثال نگاهی به کد زیر بیاندازید:

# urls.py from django.conf.urls.defaults import * from mysite import views urlpatterns = patterns('', (r'^events/$', views.event_list), (r'^blog/entries/$', views.entry_list), ) # views.py from django.shortcuts import render_to_response from mysite.models import Event, BlogEntry def event_list(request): obj_list = Event.objects.all() return render_to_response('mysite/event_list.html', {'event_list': obj_list}) def entry_list(request): obj_list = BlogEntry.objects.all() return render_to_response('mysite/blogentry_list.html', {'entry_list': obj_list})

هر دو view فوق اساسا یک کار انجام می دهند: آن ها لیستی از شیء ها را نمایش می دهند. بنابراین اجازه دهید نوع شیءی را که نمایش می دهند را حذف کنیم:

# urls.py from django.conf.urls.defaults import * from mysite import models, views urlpatterns = patterns('', (r'^events/$', views.object_list, {'model': models.Event}), (r'^blog/entries/$', views.object_list, {'model': models.BlogEntry}), ) # views.py from django.shortcuts import render_to_response def object_list(request, model): obj_list = model.objects.all() template_name = 'mysite/%s_list.html' % model.__name__.lower() return render_to_response(template_name, {'object_list': obj_list})

با تغییرات کوچک فوق، ما به طور ناگهانی دارای یک view قابل استفاده ی مجدد و عمومی شده ایم! از حالا به بعد، در هر زمان که نیاز به یک view داشته باشیم که مجموعه ای از شیء ها را لیست کند، می توان به سادگی از object_list به جای نوشتن کد view استفاده کرد. در زیر توضیحاتی درباره ی کارهای انجام داده ی فوق وجود دارد:

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

دادن Option های پیکربندی View

در صورتیکه یک برنامه ی جنگو را توزیع (distribute) می کنید، این احتمال وجود دارد که کاربران شما برخی درجات پیکربندی را بخواهند. در این مورد، ایده ی خوبی است که برای هر انتخاب پیکربندی که فکر می کنید افراد ممکن است بخواهند آن را تغییر دهند، hook هایی را در view ها اضافه کنید.

یک قسمت مشترک از یک برنامه برای ایجاد قابلیت پیکربندی، نام template می باشد:

def my_view(request, template_name): var = do_something() return render_to_response(template_name, {'var': var})

فهمیدن اولویت مقدار داخل پرانتز در مقابل Option های اضافه

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

برای مثال URLconf زیر را مشاهده کنید:

from django.conf.urls.defaults import * from mysite import views urlpatterns = patterns('', (r'^mydata/(?P<id>\d )/$', views.my_view, {'id': 3}), )

در کد فوق، هر دوی regular expression و دیکشنری اضافه، حاوی یک id می باشند که دیکشنری اضافه دارای اولویت می باشد. این بدین معناست که هر درخواستی (مانند /mydata/2/ یا /mydata/432432) بدین صورت رفتار خواهد کرد که مقدار id بدون در نظر گرفتن مقدار داخل پرانتز URLconf عدد 3 می باشد.

خوانندگان زیرک در این مورد توجه خواهند داشت، که قرار دادن id داخل پرانتز در regular expression اتلاف وقت می باشد، زیرا مقدار آن همواره توسط مقدار دیکشنری باز نویسی می شود. درست آن است که؛ در دست ترجمه ...

استفاده از آرگومان های پیشفرض View

فوت و فن مناسب دیگر، تعیین پارامتر های پیشفرض برای آرگومان های view می باشد. با استفاده از این تکنیک در صورتیکه مقدار یک پارامتر اصلا تعیین نشود از مقدار پیشفرض تعیین شده استفاده می کند.

مثال:

# urls.py from django.conf.urls.defaults import * from mysite import views urlpatterns = patterns('', (r'^blog/$', views.page), (r'^blog/page(?P<num>\d )/$', views.page), ) # views.py def page(request, num='1'): # Output the appropriate page of blog entries, according to num. # ...

در کد فوق، هر دو الگوی URL به یک view اشاره می کنند – views.page – ولی اولین الگو درون URL هیچ مقداری داخل پرانتز قرار نداده است. در صورتیکه اولین الگو تطبیق پیدا کند، تابع page() از مقدار آرگومان پیشفرض num یعنی '1' استفاده خواهد کرد. همچنین در صورت تطبیق الگوی دوم، تابع page() از هر مقداری که توسط regular expression درون پرانتز حاصل می شود استفاده خواهد کرد.

(توحه داشته باشید که بایستی مقدار پیشفرض آرگومان را رشته ی '1' قرار دهیم، نه یک integer. زیرا هر مقداری که داخل پرانتز URLconf برای num قرار گرفته است همواره یک رشته خواهد بود.)

استفاده از این تکنیک، در رابطه با option های پیکربندی نیز متداول می باشد، همانطور که پیش تر توضیح داده شد. مثال زیر مثال بهبود پیدا کرده ی بخش "دادن option های پیکربندی view" با تهیه ی یک مقدار پیشفرض برای template_name می باشد:

def my_view(request, template_name='mysite/my_view.html'): var = do_something() return render_to_response(template_name, {'var': var})

موارد خاص View ها

گاهی اوقات شما الگویی در URLconf خواهید داشت که مجموعه ی بزرگی از URL ها را کنترل می کند، ولی شما تنها به یک مورد خاص از آن ها نیاز خواهید داشت. در این موارد، از روش خطی یک URLconf ای که پردازش شده است می توانید استفاده کنید و مورد خاص را اول قرار دهید.

برای مثال، می توانید صفحات "add an object" در سایت مدیر جنگو را به صورت نمایش داده شده با یک URLpattern مانند زیر تصور کنید:

urlpatterns = patterns('', # ... ('^([^/] )/([^/] )/add/$', views.add_stage), # ... )

کد فوق با URL هایی از قبیل /myblog/entries/add/ و /auth/groups/add/ تطبیق پیدا می کند. اگرچه صفحه ی "add" برای یک شیء user (/auth/user/add/) یک مورد خاص می باشد – تمام فیلدهای فرم را نمایش نمی دهد، بلکه دو فیلد رمز عبور و الی آخر را نمایش می دهد. ما این مشکل را به شکل زیر حل کرده ایم:

def add_stage(request, app_label, model_name): if app_label == 'auth' and model_name == 'user': # do special-case code else: # do normal code

ولی روش فوق برای یک دلیل که چندین بار در این فصل آن را لمس کرده ایم روش زیبایی نیست: روش فوق منطق URL را درون view قرار می دهد. راهکار بهتر این است که، از این واقعیت سود ببریم که URLconf ها از بالا به پایین پردازش می شوند:

urlpatterns = patterns('', # ... ('^auth/user/add/$', views.user_add_stage), ('^([^/] )/([^/] )/add/$', views.add_stage), # ... )

با استفاده از روش فوق، درخواست برای /auth/user/add/ از طریق user_add_stage کنترل خواهد شد. اگرچه که URL با الگوی دوم مطابقت دارد، ولی ابتدا با الگوی بالاتر تطبیق پیدا می کند.

پوشش دادن متن در URL ها

هر آرگومان پوشش داده شده ای به صورت یک رشته ی یونیکد پایتون به view فرستاده می شود. به عنوان مثال در خط URLconf زیر:

(r'^articles/(?P<year>\d{4})/$', views.year_archive),

آرگومان year برای views.year_archive() یک رشته خواهد بود، نه یک integer، حتی اگر \d{4} تنها با رشته های از نوع integer مطابقت داشته باشد.

بخاطر داشتن این موضوع هنگامی که کد view را می نویسید مهم می باشد. بسیاری از توابع داخلی پایتون نسبت به قبول کردن تنها شیء های از نوع مشخص حساس می باشند. یکی از خطاهای متداول ساختن یک شیء datetime.date با مقادیر رشته به جای مقادیر integer می باشد:

>>> import datetime >>> datetime.date('1993', '7', '9') Traceback (most recent call last): ... TypeError: an integer is required >>> datetime.date(1993, 7, 9) datetime.date(1993, 7, 9)

تبدیل شده ی به یک URLconf و view، خطایی مانند زیر خواهد بود:

# urls.py from django.conf.urls.defaults import * from mysite import views urlpatterns = patterns('', (r'^articles/(\d{4})/(\d{2})/(\d{2})/$', views.day_archive), ) # views.py import datetime def day_archive(request, year, month, day): # The following statement raises a TypeError! date = datetime.date(year, month, day)

به جای day_archive() می توان به این شکل آنرا اصلاح نمود:

def day_archive(request, year, month, day): date = datetime.date(int(year), int(month), int(day))

توجه داشته باشید که تابع int() خودش هنگامی که یک رشته ی غیر قابل تبدیل به integer به آن ارسال شود یک خطای ValueError ایجاد می کند، ولی از آن جهت که regular expression در URLconf در این مورد تنها رشته های قابل تبدیل به integer (مانند "245") را قبول می کند دیگر نگرانی برای این موضوع وجود نخواهد داشت.

تعیین آنچه را که URLconf به دنبال آن جستجو می کند

هنگامی که یک درخواست ارسال می شود، جنگو در تلاش بر می آید که الگوهای URLconf را برای URL درخواست شده به صورت رشته ی پایتون تطبیق دهد. این شامل پارامترهای GET یا POST و یا نام دامین نمی شود. همچنین شامل علامت (/) آغازین نیز نمی شود، زیرا هر URL دارای یک علامت (/) آغازین می باشد.

برای عنوان مثال، در یک درخواست به http://www.example.com/myapp/، جنگو تلاش می کند تا آن را با myapp/ تطبیق دهد. در یک درخواست به http://www.exampl.com/myapp/?page=3 جنگو تلاش می کند تا آن را با myapp/ تطبیق دهد.

method درخواست (مانند POST، GET) هنگام عبور کردن URLconf به حساب نمی آید. به عبارت دیگر، تمام method های درخواست درون تابع همسان برای URL همسان بررسی می شوند. اجرا کردن method های درخواست به عهده ی تابع view می باشد.

تصور سطح بالا از توابع view

صحبت کردن درباره ی روش branching based در method درخواست، اجازه دهید به نحوه ی ساخت یک روش زیبا از انجام این روش نگاهی بیاندازیم. لایه ی URLconf و view زیر را ملاحظه کنید:

# urls.py from django.conf.urls.defaults import * from mysite import views urlpatterns = patterns('', # ... (r'^somepage/$', views.some_page), # ... ) # views.py from django.http import Http404, HttpResponseRedirect from django.shortcuts import render_to_response def some_page(request): if request.method == 'POST': do_something_for_post() return HttpResponseRedirect('/someurl/') elif request.method == 'GET': do_something_for_get() return render_to_response('page.html') else: raise Http404()

در مثال فوق، some_page()، درخواست های POST و GET را کاملا متفاوت کنترل می کند. تنها چیزی که به صورت مشترک دارا می باشند یک URL به اشتراک گذاشته می باشد: /somepage/. به همین دلیل، اینکه با هر دوی POST و GET در یک تابع view سر و کار داشت، روش زیبایی نمی باشد. روش مناسب آن است که دو تابع view مجزا داشته باشیم – یکی درخواست های GET را کنترل کرده و دیگری درخواست های POST را – و مطمئن بود که هر یک تنها در زمان مناسب فراخوانی می شوند.

می توان با توشتن یک تابع view که نماینده ی دیگر view ها می باشد، قبل و بعد اجرا برخی جملات منطقی این کار را انجام داد. در مثال زیر نحوه ی انجام این روش در some_page() وجود دارد:

# views.py from django.http import Http404, HttpResponseRedirect from django.shortcuts import render_to_response def method_splitter(request, GET=None, POST=None): if request.method == 'GET' and GET is not None: return GET(request) elif request.method == 'POST' and POST is not None: return POST(request) raise Http404 def some_page_get(request): assert request.method == 'GET' do_something_for_get() return render_to_response('page.html') def some_page_post(request): assert request.method == 'POST' do_something_for_post() return HttpResponseRedirect('/someurl/') # urls.py from django.conf.urls.defaults import * from mysite import views urlpatterns = patterns('', # ... (r'^somepage/$', views.method_splitter, {'GET': views.some_page_get, 'POST': views.some_page_post}), # ... )

اجازه دهید کد فوق را مورد بررسی قرار دهیم:

حالا، ما دارای یک تابع generic خوب برای خودمان می باشیم که منطق نمایندگی دادن به یک view را با استفاده از request.method به اصطلاح encapsulate کرده است. هیچ چیزی درباره ی method_splitter() گره خورده به برنامه ی خاص نیست، بنابراین می توان در پروژه های دیگر نیز از آن استفاده کرد.

یک روش برای بهبود method_splitter() وجود دارد. همانطور که نوشته شده است، این تابع فرض می کند که view های GET و POST آرگومان های غیر از request دریافت نمی کنند. چه می شود اگر بخواهیم از method_splitter یا view هایی که، پارامترهایی متنی را از URL ها می گیرند و یا آرگومان های اختیاری برای خود می گیرند استفاده کنیم؟

برای انجام چنین کاری، می توان از یک ویژگی زیبای پایتون استفاده کرد: آرگومان های متغیر با علامت ستاره. در ابتدا مثالی را نشان خواهیم داد و سپس آن را توضیح می دهیم:

def method_splitter(request, *args, **kwargs): get_view = kwargs.pop('GET', None) post_view = kwargs.pop('POST', None) if request.method == 'GET' and get_view is not None: return get_view(request, *args, **kwargs) elif request.method == 'POST' and post_view is not None: return post_view(request, *args, **kwargs) raise Http404

در کد فوق، method_splitter() را تغییر داده ایم، به طوری که آرگومان های کیورد GET و POST در آن حذف شده اند و جای آن ها از *args و **kwargs (به علامت ستاره توجه کنید) استفاده شده است. این یک خصوصیت پویای پایتون می باشد که به یک تابع اجازه می دهد یک تعداد دلخواه از آرگومان ها را که نام آن ها تا زمان اجرا مشخص نخواهد بود قبول کند. در صورتیکه یک علامت ستاره تکی در تعریف یک تابع قبل یک پارامتر قرار دهید، هر آرگومان موضعی به آن تابع در یک تاپل جمع خواهد شد. در صورتیکه دو علامت ستاره قبل از یک پارامتر در تعریف تابع قرار دهید، هر آرگومان کیورد به آن تابع داخل یک دیکشنری جمع می شوند.

برای مثال:

def foo(*args, **kwargs): print "Positional arguments are:" print args print "Keyword arguments are:" print kwargs

در زیر نحوه ی عملکرد آن مشخص شده است:

>>> foo(1, 2, 3) Positional arguments are: (1, 2, 3) Keyword arguments are: {} >>> foo(1, 2, name='Adrian', framework='Django') Positional arguments are: (1, 2) Keyword arguments are: {'framework': 'Django', 'name': 'Adrian'}

به method_splitter() باز می گردیم، می توانید مشاهده کنید که برای قبول کردن هر آرگومان به تابع و ارسال آنها به همراه view مناسب از *args و **kwargs استفاده کرده ایم. ولی بعد از انجام این کار، دو فراخوانی به kwargs.pop() برای بدست آوردن آرگومان های GET و POST در صورت دسترس ایجاد کرده ایم. (از pop() با یک مقدار پیشفرض None جهت دوری از خطای KeyError در صورتیکه یکی از آن ها تعریف نشده باشد استفاده شده است.)

Wrapping View Functions

آخرین فن view سود جستن از یک تکنیک پیشرفته ی پایتون می باشد. تصور کنید در سرتاسر view های مختلف متوجه دسته ای از کدهای تکراری شده اید مانند مثال زیر:

def my_view1(request): if not request.user.is_authenticated(): return HttpResponseRedirect('/accounts/login/') # ... return render_to_response('template1.html') def my_view2(request): if not request.user.is_authenticated(): return HttpResponseRedirect('/accounts/login/') # ... return render_to_response('template2.html') def my_view3(request): if not request.user.is_authenticated(): return HttpResponseRedirect('/accounts/login/') # ... return render_to_response('template3.html')

در کد فوق، هر view با بررسی اینکه request.user تایید شده است یا خیر شروع می شود – کاربر فعلی با موفقیت وارد سایت شده است – و به مسیر /accounts/login/ تغییر مسیر داده می شود. (توجه داشته باشید که هنوز request.user توضیح داده نشده است – در بخش کاربران عضویت و session درباره ی این موضوع بحث شده است – ولی، همانطور که ممکن است تصور کنید، request.user کاربر فعلی را نمایش می دهد)

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

def requires_login(view): def new_view(request, *args, **kwargs): if not request.user.is_authenticated(): return HttpResponseRedirect('/accounts/login/') return view(request, *args, **kwargs) return new_view

تابع فوق، requires_login، یک تابع view (view) را دریافت می کند و یک تابع view جدید (new_view) بر می گرداند. تابع جدید، new_view داخل requires_login تعریف شده و منطق بررسی request.user.is_authenticated() و محول کردن view اصلی (view) را کنترل می کند.

حالا می توان بررسی if not request.user.is_authenticated() از view ها حذف کرد و با requires_login در URLconf قرار داد:

from django.conf.urls.defaults import * from mysite.views import requires_login, my_view1, my_view2, my_view3 urlpatterns = patterns('', (r'^view1/$', requires_login(my_view1)), (r'^view2/$', requires_login(my_view2)), (r'^view3/$', requires_login(my_view3)), )

کد فوق تاثیر یکسانی را به صورت قبل داراست، ولی با زوائد کد کمتر. حالا ما یک تابع generic زیبا ساخته ایم – requires_login() که می توان برای هر تابع view به منظور نیاز به ورود به سایت استفاده کرد.

در بر داشتن URLconf های دیگر

در صورتیکه قصد دارید کد شما در سایت های گوناگون ایجاد شده توسط جنگو استفاده شود، شما باید URLconf های خود را به روشی تنظیم کنید که "including" برای آن ها مقدور باشد.

در هر نقطه، URLconf شما می تواند ماژول های دیگر URLconf را شامل شود. برای مثال، URLconf زیر حاوی URLconf های دیگری نیز می باشد:

from django.conf.urls.defaults import * urlpatterns = patterns('', (r'^weblog/', include('mysite.blog.urls')), (r'^photos/', include('mysite.photos.urls')), (r'^about/$', 'mysite.views.about'), )

(روش فوق را قبل تر در سایت مدیر مشاهده کرده اید، هنگامی که سایت مدیر جنگو معرفی شده است. سایت مدیر دارای URLconf خودش می باشد که شما تنها داخل URLconf دیگر آن ها را وارد کردید.)

یک مفهوم مهم در اینجا وجود دارد: regular expression ها که در این مثال به یک include() اشاره می کنند، دارای علامت ($) نمی باشند. هر زمان که جنگو با include() برخورد می کند، هر بخش از URL مطابقت داده شده با آن نقطه را برش می دهد و رشته ی باقیمانده برای URLconf ای که Include شده را جهت پردازش بیشتر می فرستد.

به مثال زیر توجه کنید:

from django.conf.urls.defaults import * urlpatterns = patterns('', (r'^(\d\d\d\d)/$', 'mysite.blog.views.year_detail'), (r'^(\d\d\d\d)/(\d\d)/$', 'mysite.blog.views.month_detail'), )

با این دو URLconf، در اینجا نحوه ی کنترل تعدادی از درخواست ها وجود دارند:

نحوه ی کار با include() پارامترهای پوشش داده شده

یک URLconf شامل شده، هر پارامتر پوشش داده شده ای از URLconf های پدر را دریافت می کند، برای مثال:

# root urls.py from django.conf.urls.defaults import * urlpatterns = patterns('', (r'^(?P<username>\w )/blog/', include('foo.urls.blog')), ) # foo/urls/blog.py from django.conf.urls.defaults import * urlpatterns = patterns('', (r'^$', 'foo.views.blog_index'), (r'^archive/$', 'foo.views.blog_archive'), )

در مثال فوق، متغیر پوشش داده شده ی username به URLconf شامل شده و هر تابع view موجود در آن URLconf ارسال شده است.

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

نحوه option های اضافه URLconf در کار با include()

به طور مشابه، می توان انتخاب های اضافه ی URLconf به include() ارسال کرد، همانطور که می توان option های اضافه ی URLconf را به یک view معمولی ارسال کرد – به صورت یک دیکشنری. هنگامی که شما این کار را انجام دهید، هر خط در URLconf شامل شده، option اضافه ی ارسال شده خواهد بود.

برای مثال، دو دسته ی URLconf زیر دارای عملکرد یکسان می باشند.

دسته ی اول:

# urls.py from django.conf.urls.defaults import * urlpatterns = patterns('', (r'^blog/', include('inner'), {'blogid': 3}), ) # inner.py from django.conf.urls.defaults import * urlpatterns = patterns('', (r'^archive/$', 'mysite.views.archive'), (r'^about/$', 'mysite.views.about'), (r'^rss/$', 'mysite.views.rss'), )

دسته ی دوم:

# urls.py from django.conf.urls.defaults import * urlpatterns = patterns('', (r'^blog/', include('inner')), ) # inner.py from django.conf.urls.defaults import * urlpatterns = patterns('', (r'^archive/$', 'mysite.views.archive', {'blogid': 3}), (r'^about/$', 'mysite.views.about', {'blogid': 3}), (r'^rss/$', 'mysite.views.rss', {'blogid': 3}), )