آموزش View ها و URLconf ها در جنگو (Django)
در آموزش شروع کار از این کتاب، ما نحوه نصب و راه اندازی سرور جنگو (Django) را توضیح دادیم. در این آموزش شما قواعد ساختن صفحات پویا (داینامیک) از طریق فریم ورک یا چارچوب جنگو (Django) و همچنین استفاده از view ها و URLconf ها را خواهید آموخت.
اولین برنامه جنگو
به عنوان اولین قدم بیایید یک صفحه وب که خروجی معروف Hello world را چاپ می کند را بسازیم.
در صورتیکه شما یک Hello world ساده را بدون استفاده از یک فریم ورک یا چارچوب وب ایجاد کرده باشید، به صورت ساده عبارت Hello world را درون یک فایل متنی وارد کرده و آنرا به عنوان مثال با نام hello.html، در یک پوشه جایی درون وب سرور ذخیره می کردید. دقت کنید که شما دو موضوع کلیدی را در مورد آن صفحه وب در نظر می گیرید: محتویات آن (رشته "Hello world") و URL آن (http://www.example.com/hello.html، و یا شاید http://www.example.com/files/hello.html اگر شما آن فایل را درون یک پوشه زیر مجموعه قرار داده باشید).
در جنگو شما این کار را با کمی تفاوت انجام می دهید. محتویات صفحه توسط یک تابع view تولید می شوند، و URL در یک URLconf تعیین شده است. ابتدا بیایید تابع view حاوی Hello world را بنویسیم.
اولین view شما
داخل پوشه mysite که با دستور django-admin startproject در آموزش قبل ساختید، یک فایل خالی با نام views.py ایجاد کنید. این ماژول پایتون (views.py) حاوی view های ما برای این فصل می باشد. نکته اینکه چیز خاصی برای انتخاب نام views.py وجود ندارد، در جنگو اجباری برای انتخاب نام وجود ندارد، اما ایده خوبی است که مانند یک قرارداد نام آنرا views.py گذاشته تا برای توسعه دهندگان دیگر خوانایی کد شما بیشتر باشد.
view مورد نظر ما بسیار ساده است. اینجا تابع view را مشاهده خواهید کرد، همچنین باید جمله import را داخل فایل views.py تایپ کنید:
from django.http import HttpResponse
def hello(request):
return HttpResponse("Hello world")
بیایید به بررسی کد بالا بپردازیم:
- در ابتدا، ما کلاس HttpResponse را درون ماژول import کردیم، این کلاس در ماژول django.http می باشد که در ادامه کد به آن نیاز خواهیم داشت.
- یک تابع به نام hello را در ماژول تعریف کردیم که تابع view نامیده می شود.
هر تابع view حداقل یک پارامتر را دریافت می کند، که به صورت قرارداد request نامیده می شود. پارامتر request یک شیء می باشد که حاوی اطلاعات درباره request وب فعلی است، همچنین آن یک instance از کلاس django.HttpRequest است. در این مثال ما هیچ کاری را با شیء request انجام نمی دهیم، اما باید آنرا به عنوان اولین پارامتر از تابع view قرار داد.
نکته اینکه نام تابع view اهمیتی برای جنگو ندارد، به صورتی که روش ثابتی برای نام گذاری تابع view وجود ندارد که جنگو بتواند از طریق آن تابع را تشخیص دهد. برای اینکه یک تابع پایتون یک view برای جنگو به شمار بیاید بایستی یک HttpRequest را به عنوان اولین پارامتر دریافت کرده و یک instance از HttpRespose را برگرداند بدین صورت جنگو یک تابع view را از توابع ساده پایتون تشخیص می دهد(البته استثناهایی نیز وجود دارد که در ادامه به آنها خواهیم پرداخت.)
تابع یک خطی ساده: view فقط یک تابع پایتون می باشد که یک HttpRequest را به عنوان اولین پارامتر دریافت می کند و یک instance از HttpResponse را بر می گرداند. برای اینکه یک تابع پایتون بتواند یک view برای جنگو باشد، باید این دو کار را انجام دهد. (البته استثناهایی نیز وجود دارد اما بعدا به آنها خواهیم پرداخت)
اولین URLconf شما
اگر تا اینجای کار دوباره دستور manage.py runserver را اجرا کنید، باز هم صفحه خوش آمدگویی به جنگو را خواهید دید و اثری از Hello world نخواهید دید. دلیل این است پروژه mysite چیزی درباره view نمی داند و تابع hello را نمی شناسد؛ نیاز است به جنگو گفته شود که یک view در یک URL مشخص قرار دارد. برای این منظور، یعنی اتصال تابع view به یک URL مشخص با جنگو، از یک URLconf استفاده می شود.
یک URLconf شبیه به فهرست مطالب در یک وب سایت است که رابطه بین URL ها و توابع view را نمایش می دهد که در واقع بیان کننده رابطه بین URL و تابعی که آن URL بایستی فراخوانی کند، می باشد.به عبارت دیگر به جنگو می گوید که برای هر URL، باید کدام کد فراخوانی شود. برای مثال هنگامی که شما آدرس http://example.com/foo را درخواست می کنید، تابع foo_view() که در ماژول views.py قرار دارد فراخوانی شود.
هنگامی که شما دستور django-admin.py startproject را در آموزش قبلی اجرا کردید، یک URLconf به صورت اتوماتیک برای شما ساخته شد: فایل urls.py به صورت پیش فرض چیزی شبیه به کد زیر می باشد:
from django.conf.urls.defaults import *
# Uncomment the next two lines to enable the admin:
# from django.contrib import admin
# admin.autodiscover()
urlpatterns = patterns('',
# Example:
# (r'^mysite/', include('mysite.foo.urls')),
# Uncomment the admin/doc line below and add 'django.contrib.admindocs'
# to INSTALLED_APPS to enable admin documentation:
# (r'^admin/doc/', include('django.contrib.admindocs.urls')),
# Uncomment the next line to enable the admin:
# (r'^admin/', include(admin.site.urls)),
)
URLconf به طور پیشفرض شامل قسمت هایی از خصوصیات جنگو می باشد که به صورت کامنت وجود دارد، کد اصلی URLconf به شکل زیر می باشد:
from django.conf.urls.defaults import *
urlpatterns = patterns('',
)
بیایید کد فوق را مورد بررسی قرار دهیم:
- در خط اول تمام شیء های ماژول django.conf.urls.defaults که زیر بنای URLconf می باشند به درون ماژول فعلی import شده است، که شامل تابع patterns نیز می شود.
- در خط بعد تابع patterns فراخوانی شده و حاصل کار درون متغیر urlpatterns ریخته شده است. تابع patterns یک آرگومنت ارسال می کند که یک رشته خالی است. (یک رشته که می تواند یک prefix عمومی برای توابع view باشد، که در آموزش view و urlconf پیشرفته درباره آن صحبت خواهد شد.)
نکته اصلی متغیر urlpatterns می باشد که جنگو درون URLconf به آنرا پیدا خواهد کرد. این متغیر رابطه بین URL ها و کد را تعریف می کند. به صورت پیشفرض همانطور که ملاحظه کردید URLconf خالی است (نکته آنکه، جنگو به چه صورت همانطور که در فصل گذشته مشاهده کردید می داند که صفحه خوش آمد گویی را نشان دهد؟ جواب این است که هنگامی که URLconf شما خالی باشد، جنگو فرض می کند شما تنها یک پروژه را آغازکردید، بنابراین، آن صفحه را نمایش می دهد.)
برای اضافه کردن URL و view به URLconf، تنها کافی است یک تاپل پایتون متصل شده به یک الگوی URL را به تابع view اضافه کنید. در کد زیر نحوه انجام این کار مشخص شده است:
from django.conf.urls.defaults import *
from mysite.views import hello
urlpatterns = patterns('',
('^hello/$', hello),
)
(دقت کنید که ما کدها کامنت را برای کوتاهی کد حذف کردیم، شما می توانید اگر دوست دارید آن ها را در کد خود نگهدارید)
دو تغییر در کد فوق ایجاد شده است:
- ابتدا، تابع hello از ماژول خود (mysite/views.py که به زبان پایتون تغییر کرده و به mysite.view تبدیل شده) import شده است.
- همچنین قسمت ('^hello/$, hello) به urlpatterns اضافه شده است. اندیس اول تاپل مبوط به URLpattern می باشد که با URL وارد شده در ارتباط خواهد بود و اندیس دوم اشاره به view دارد.
به طور خلاصه، ما به جنگو گفتیم که هر request ای که به آدرس /hello/ ارسال می شود باید با تابع view یعنی hello کنترل شود.
مسیر پایتون
هنگامی که از جمله import استفاده می کنیم، پایتون به یک لیست از دیکشنری ها مراجعه می کند که مسیر پایتون در آن وجود دارد.
برای مثال اگر مسیر پایتون شما به این شکل باشد ['', '/usr/lib/python2.4/site-packages', '/home/username/djcode']، و شما جمله from foo import bar را اجرا کنید، پایتون به دنبال یک ماژول به نام foo.py در پوشه فعلی می گردد. (اولین اندیس دیکشنری در مسیر پایتون، یک رشته خالی می باشد، که به معنی پوشه یا مسیر فعلی می باشد) اگر فایل مورد جستجو (foo.py) در مسیر جاری وجود نداشت، پایتون به دنبال آن فایل در این مسیر خواهد گشت /usr/lib/python2.4/site-packages/foo.py. اگر فایل درون آن مسیر هم وجود نداشت، مسیر /home/username/djcode/foo.py را نیز جستجو خواهد کرد، در پایان اگر فایل در مسیرهای ذکر شده یافت نشد. خطای importError ایجاد خواهد شد.
اگر مشتاق هستید که مسیر پایتون خودتان را ببینید، interactive interpreter پایتون را باز کرده و کد زیر را وارد کنید.
>>> import sys
>>> print sys.path
عموما شما نسبت به تنظیم مسیر پایتون نگرانی نخواهید داشت، پایتون و جنگو به صورت اتوماتیک در پشت صحنه تنظیمات مربوط به مسیر پایتون را انجام می دهند. (تنظیم مسیر پایتون یکی از وظایفی است که manage.py انجام می دهد)
موضوع قابل بحث syntax ای باشد که URLpattern از آن استفاده می کنند، که با حالت آدرس دهی معمولی کمی متفاوت است:
- جنگو علامت (/) را از مقابل هر URL ورودی قبل از چک کردن URLpatterns حذف می کند. (درباره این موضوع در آموزش view و urlconf پیشرفته صحبت شده است)
- درون الگوی فوق یک علامت (^) و یک علامت ($) وجود دارد، که حروف regular expression می باشد و معانی خاصی دارند: علامت (^) به معنی این می باشد که URL مربوط ابتدای آدرس است و علامت ($) بدین معنی است URL مربوط انتهای آدرس می باشد.
مفهوم فوق با یک مثال خوب قابل توضیح است. اگر بجای الگوی فوق ('^hello/$') از این الگو استفاده کنیم '^hello/' (بدون گذاشتن علامت $ در انتهای URL)، در اینصورت هر URL ای که با /hello/ شروع می شود شامل آدرس URLpattern خواهد شد، مانند /hello/foo و /hello/bar، به همین صورت اگر علامت (^) را از ابتدای URL برداریم ('hello/$') جنگو هر URL ای که با hello/ پایان می یابد را شامل آدرس URLconf خواهد دانست، مانند /foo/bar/hello/، و اگر URL را به این صورت استفاده کنیم hello/، یعنی بدون علامت (^) و ($)، هر URL ای که حاوی hello/ باشد را شامل خواهد شد مانند /foo/hello/bar. بنابراین در اینجا ما از هر دو علامت فوق استفاده کردیم تا تنها آدرس /hello/ بتواند با URL مورد نظر match شود، نه بیشتر و نه کمتر.
نکته دیگر اینکه تابع view یعنی hello به صورت یک شیء بدون فراخوانی تابع به patterns ارسال شده است. این یکی از خصوصیات کلیدی از پایتون (و زبانهای داینامیک دیگر) است: توابع شیء های خاصی هستند، بدین معنی که می توان آن ها را مانند هر متغیر دیگری ارسال کرد.
برای تست کردن تغییرات درون URLconf، سرور جنگو را اجرا کنید، همانطور که در فصل 2 توضیح داده شد، با دستور python manage.py runserver. (اگر سرور از قبل اجرا شده است، دیگر نیازی به اجرا کردن دوباره آن نمی باشد زیرا همانطور که پیش تر گفته شد، به محض ایجاد تغییر در کد و ذخیره آن سرور جنگو خود را بروز رسانی کرده و به صورت اتوماتیک دوباره اجرا می شود.) سپس مرورگر را باز کرده، و به آدرس http://127.0.0.1:8000/hello/ بروید. نوشته Hello world را باید در خروجی تماشا کنید.
هوررا! شما اولین صفحه وب ساخته شده با جنگو را ایجاد کردید.
Reqular Expressions
Regular expression ها یا regexes یک روش فشرده و یا جمع و جور برای تعیین الگوها در متن می باشد. در اینجا تعدادی از نمادهای عمومی regex را مشاهده می کنید:
نماد | مچ می شود با |
---|---|
. (نقطه) | یک تک حرف |
\d | یک عدد یک رقمی |
[A-Z] | هر حرف بین A تا Z بزرگ |
[a-z] | هر حرف بین a تا z کوچک |
[A-Za-z] | هر حرف بین a تا z بدون حساس بودن به بزرگی و کوچکی |
تعداد یک یا بیشتر از عبارت قبل آن (مانند \d تعداد یک یا بیشتر ارقام) | |
[^/] | یک حرف یا بیشتر تا یک (/) |
? | تعداد صفر یا یک از عبارت قبلی (مانند \d?، صفر یا یک رقم) |
* | تعداد صفر یا بیشتر ار عبارت قبلی (مانند \d*، صفر یا بیشتر ارقام) |
{1,3} | تعداد یک و سه از عبارت قبلی (مانند \d{1,3} یک، دو یا سه رقم) |
اشاره ای کوتاه به خطای 404
تا اینجای کار، URLconf ما تنها یک URLpattern تعریف کرده است. چه اتفاقی می افتد اگر یک درخواست با URL متفاوت ارسال شود؟
برای پی بردن به جواب، سرور جنگو را اجرا کرده و یک پیج با همچین آدرسی را باز کنید: http://127.0.0.1:8000/goodbye/ یا http://127.0.0.1:8000/hello/subdirectory، یا حتی http://127.0.0.1:8000/ (روت سایت). شما باید صفحه ای با پیام"page not found" که در شکل 1-3 نشان داده شده است مشاهده کنید. جنگو این پیام را نشان می دهد زیرا درخواست شما URL ای می باشد که در URLconf تعریف نشده است.
شکل 1-3
پیام خطای 404 جنگو بسیار تواناتر از خطای عادی 404 می باشد. همچنین خطای 404 جنگو دقیقا URLconf استفاده شده و الگوی آن را نشان می دهد. با این اطلاعات نشان داده شده شما می توانید به راحتی دلیل ایجاد شدن خطای 404 را متوجه شوید.
طبیعتا اطلاعات نشان داده شده توسط جنگو برای خطای 404 تنها برای شما ایجاد شده است یعنی برای توسعه دهندگان وب. اگر که نمی خواهید این اطلاعات برای عموم نشان داده نشوند، این نکته اهمیت دارد که جنگو زمانی پیام مخصوص به خود را نشان می دهد که پروژه جنگو در حالت debug باشد. در فصل های آینده نحوه غیر فعال کردن حالت debug را توضیح خواهیم داد. تنها چیزی که باید الان بدانید این است که هر پروژه جنگو هنگامی که ساخته می شود به صورت پیشفرض در حالت debug می باشد و در صورتی که این حالت غیر فعال باشد، جنگو خروجی متفاوتی را نشان خواهد داد.
اشاره ای کوتاه به ریشه سایت (site root)
همانطور که در قسمت قبلی گفته شد، حتی اگر شما به آدرس ریشه سایت http://127.0.0.1:8000/ هم مراجعه می کردید خطای 404 ایجاد می شد. برای اینکه بتوان در URLconf آدرس ریشه سایت را با یک تابع view مرتبط کرد مانند مثال زیر باید عمل کرد:
from mysite.views import hello, my_homepage_view
urlpatterns = patterns('',
('^$', my_homepage_view),
# ...
)
نحوه پردازش درخواست
قبل از ادامه کار برای نوشتن دومین تابع view، اجازه دهید مکثی برای فهمیدن بیشتر نحوه عملکرد جنگو داشته باشیم. هنگامی که شما پیام Hello world را با رفتن به آدرس http://127.0.0.1:8000/hello/ در مرورگر خود مشاهده می کنید، جنگو در پشت صحنه چگونه عمل می کند؟
همه چیز با فایل settings.py آغاز می شود. هنگامی که شما فرمان python manage.py runserver را اجرا می کنید، اسکریپت به دنبال فایل settings.py در مسیر همسان با مسیر manage.py می گردد. این فایل حاوی تمام پیکربندی های مربوط به پروژه جنگو می باشد که همگی به صورت حروف بزرگ انگلیسی می باشند: TEMPLATE_DIRS، DATABSE_NAME، و غیره. مهمترین تنظیم ROOT_URLCONF نام دارد. ROOT_URLCONF به جنگو می گوید که کدام ماژول پایتون باید به عنوان URLconf برای این وب سایت استفاده شود.
هنگامی که django-admin.py startproject فایل های settings.py و urls.py را ایجاد می کرد را بخاطر می آورید؟ فایل به صورت اتوماتیک ساخته شده settings.py حاوی تنظیم ROOT_URLCONF است که به فایل اتوماتیک ساخته شده urls.py اشاره می کند. فایل settings.py را باز کرده و خودتان مشاهده کنید، مانند:
ROOT_URLCONF = 'mysite.urls'
کد فوق برابر با mysite/urls.py می باشد.
زمانی که یک درخواست برای یک URL خاص فرستاده می شود، جنگو URLconf ای که در تنظیم ROOT_URLCONF به آن اشاره شده است را بار گذاری می کند. سپس شروع به چک کردن URL ای که در request آمده است با الگوهای موجود در URLconf، تا زمانی که یک الگوی مرتبط پیدا شود. هنگامی که یک الگوی مرتبط پیدا شد، تابع view مربوط به آن الگو را فراخوانی کرده و یک شیء HttpRequest بعنوان اولین پارامتر به آن ارسال می کند. (در مورد HttpRequest به طور ویژه ای در فصل های بعدی صحبت خواهد شد.)
به طور خلاصه:
- یک درخواست به /hello/ فرستاده می شود.
- جنگو ریشه URLconf را با نگاه به تنظیم ROOT_URLCONF تعیین می کند.
- جنگو به تمامی URLpattern ها برای پیدا کردن یک URL مرتبط با /hello/ جستجو می کند.
- اگر URL مرتبطی یافت شد، تابع view مربوط به آن URL فراخوانی می شود.
- تابع view یک HttpResponse بر می گرداند.
- جنگو HttpResponse را برای نشان دادن نتیجه در یک صفحه وب به شکل مناسب تبدیل می کند.
حالا شما به طور کامل نحوه عمکرد جنگو را می دانید، البته تا این حد که چگونه تابع view به URL مربوط به خود از طریق URLconf وصل می شود.
دومین view: دارای محتوای داینامیک
view قبلی تنها یک نمایش آموزنده از نحوه کار جنگو بود، و مثال یک صفحه پویا (dynamic) بحساب نمی آید، زیرا محتوای صفحه همواره یک چیز ثابت می باشد. در هر زمانی که شما آدرس /hello/ را مشاهده کنید یک چیز ثابت را خواهید دید، که همانند یک فایل استاتیک HTML می باشد.
برای دومین view، صفحه ای داینامیک خواهیم ساخت، یک صفحه وب که زمان و تاریخ فعلی را نمایش خواهد داد. این یک مثال ساده و مناسب می باشد، چرا که در این مثال با دیتابیس یا ورودی کاربر سر و کار نخواهیم داشت، تنها زمان داخلی سرور را به صورت خروجی استفاده خواهیم کرد.
برای نوشتن view فوق باید زمان و تاریخ فعلی را محاسبه کرده، و به صورت یک HttpResponse آنرا بر گردانیم. اگر شما با زبان پایتون آشنایی داشته باشید، باید بدانید که پایتون حاوی یک ماژول به نام datetime می باشد که زمان و تاریخ را محاسبه می کند. در این نحوه استفاده از این ماژول را مشاهده خواهید کرد:
>>> import datetime
>>> now = datetime.datetime.now()
>>> now
datetime.datetime(2008, 12, 13, 14, 9, 39, 2731)
>>> print now
2008-12-13 14:09:39.002731
به اندازه کافی استفاده از این ماژول ساده می باشد، و نیازی به انجام کاری توسط جنگو نیست. کد فوق تنها کد پایتون می باشد. تاکید ما بر این است که شما از محتویات کد که تنها برای پایتون می باشد و محتویاتی که مربوط به جنگو می باشد اطلاع کامل پیدا کنید، و به اشتباه در قسمت هایی که نیازی به کد جنگو نمی باشد از آن استفاده بیهوده و اضافه بر سازمان نکنید.
برای ساختن یک view جنگو که زمان و تاریخ جاری را نمایش دهد، نیاز به قرار دادن جمله datetime.datetime.now() درون view و برگرداندن یک HttpResponse می باشد. که در کد زیر مشاهده می کنید:
from django.http import HttpResponse
import datetime
def current_datetime(request):
now = datetime.datetime.now()
html = "<html><body>It is now %s.</body></html>" % now
return HttpResponse(html)
همچنین تابع hello نیز باید درون views.py وجود داشته باشد، ما تابع hello را در این کد فوق برای اختصار حذف کردیم. در اینجا view کامل را مشاهده می کنید.
from django.http import HttpResponse
import datetime
def hello(request):
return HttpResponse("Hello world")
def current_datetime(request):
now = datetime.datetime.now()
html = "<html><body>It is now %s.</body></html>" % now
return HttpResponse(html)
بررسی کد فوق:
- در ابتدا برای محاسبه تاریخ و زمان، ماژول datetime را به بالای views.py اضافه کردیم.
- تابع جدید current_datetime زمان و تاریخ جاری را محاسبه می کند، و آنرا به صورت شیء درون متغیر محلی now ذخیره می کند.
- دومین خط از کد داخل تابع view یک پاسخ (request) HTML با استفاده از قابلیت format-string پایتون ایجاد می کند. %s داخل رشته بدین معناست که مقدار متغیر now جایگزین %s خواهد شد. متغیر now رشته نیست بلکه یک شیء datetime.datetime می باشد، ولی %s آن را به رشته تبدیل می کند، که چیزی شبیه به "14:09:39.002731 13-12-2008" می باشد.
- در پایان، تابع view همانند تابع hello یک شیء HttpResponse که حاوی پاسخ تولید شده است بر می گرداند.
بعد از اضافه کردن تابع جدید به views.py، یک URLpattern نیز داخل urls.py ایجاد می کنیم، که برای جنگو مشخص شود که کدام URL باید view مورد نظر خود را کنترل کند.
from django.conf.urls.defaults import *
from mysite.views import hello, current_datetime
urlpatterns = patterns('',
('^hello/$', hello),
('^time/$', current_datetime),
)
دو تغییر ایجاد شده است. اول اینکه تابع current-datetime را در بالا import کردیم. و دومین تغییر که مهم تر از تغییر اول می باشد، یک URLpattern با آدرس /tim/ برای view جدید اضافه کرده ایم. آیا قلق کار دستتان آمد؟
با نوشتن view جدید و تغییر دادن URLconf سرور را اجرا کرده و درون مرورگر به این آدرس بروید http://127.0.0.1:8000/time/. شما باید زمان جاری را در صفحه مورد نظر مشاهده کنید.
منطقه زمانی جنگو
بسته به رایانه شما، زمان و تاریخ ممکن است چند ساعتی متفاوت باشد. دلیل این است که جنگو دارای منطقه زمانی می باشد و تنظیم پیشفرض آن بر روی America/Chicago می باشد. (این زمان برای جایی است که توسعه دهندگان اصلی در آنجا زندگی می کنند) اگر شما در جای دیگری زندگی می کنید می توانید این پیشفرض را تغییر دهید. برای این تنظیم کامنتی وجود دارد که می توانید در آن لینک مربوط برای به روز رسانی لیست جهانی منطقه زمانی را پیدا کنید.
URLconf و مفهوم Loose Coupling
حالا زمان خوبی است برای پرداختن به فلسفه کلیدی پشت URLconfs و جنگو: قانون loose coupling. به عبارت ساده، loose coupling یک شیوه توسعه نرم افزار است که در ساختن بخش های قابل تعویض اهمیت دارد. اگر دو بخش از کد به صورت loosely couple پیاده سازی شده باشند، هنگامی که در یک بخش تغییری ایجاد می کنیم، بر روی بخش دیگر تاثیر نمی گذارد و یا تاثیر بسیار کمی دارد.
URLconf جنگو یک مثال بسیار خوب از این قانون می باشد. در یک برنامه وب جنگو تعریف های URL و توابع view، به صورت loosely couple نامیده می شوند؛ برای مثال، به تابع current_datetime ملاحظه کنید. اگر بخواهیم URL مربوط یه این تابع view را از /time/ به /current-time/ تغییر دهیم، می توانیم به سرعت و بدون هیچ نگرانی نسبت به تابع view مربوط به آن در URLconf تغییر ایجاد کنیم. به همین صورت اگر بخواهیم تابع view را نیز تغییر دهیم می توانیم بدون تاثیر بر روی URL آن این تابع را تغییر دهیم.
بعلاوه اگر بخواهیم تابع current-date را برای چندین URL نمایش دهیم، می توانیم خیلی آسان URLconf را بدون هیچ گونه لمسی در تابع view تغییر دهیم. در مثال که در زیر مشاهده خواهید کرد، current-datetime برای دو URL در دسترس خواهد بود.
urlpatterns = patterns('',
('^hello/$', hello),
('^time/$', current_datetime),
('^another-time-page/$', current_datetime),
)
URLconf ها و view ها در عمل به صورت loosely couple پیاده سازی شده اند. ما در سرتاسر این کتاب از طریق مثال ها به این فلسفه مهم (loosely coupling) اشاره خواهیم کرد
سومین view: آدرس های پویا (dynamic)
در تابع current_datetime، محتویات صفحه یعنی زمان و تاریخ نشان داده شده، داینامیک بوده، اما URl (/time/) استاتیک می باشد. در اغلب برنامه های داینامیک وب، یک URL حاوی پارامترهایی است که بر روی خروجی صفحه تاثیر می گذارد. برای مثال یک فروشگاه کتاب آنلاین ممکن است برای هر کتاب URL مخصوص به خود را داشته باشد، مانند /books/243/ و /book/81196/.
بیایید view سوم خودمان را با حالت URL داینامیک ایجاد کنیم که درون URL ساعت را دریافت کند. هدف از ساختن این سایت این است که در صورت وارد کردن /time/plus/1/ خروجی یعنی همان زمان و تاریخ در یک ساعت جلوتر نشان دهد، صفحه /time/plus/2/ زمان را 2 ساعت جلوتر از زمان فعلی نشان دهد، صفحه /time/plus/3/ زمان را 3 ساعت جلوتر نشان دهد و الی آخر.
یک تازه کار یا مبتدی ممکن است فکر کند برای هر ساعت یک تابع view مجزا لازم است:
urlpatterns = patterns('',
('^time/$', current_datetime),
('^time/plus/1/$', one_hour_ahead),
('^time/plus/2/$', two_hours_ahead),
('^time/plus/3/$', three_hours_ahead),
('^time/plus/4/$', four_hours_ahead),
)
کد فوق به وضوح مشخص است که ناقص می باشد. نه تنها توابع view اضافه و زائد می باشند، بلکه برنامه اساسا به طور محدود تنها از 4 ساعت تعریف شده پشتیبانی می کند. اگر بخواهیم صفحه ای بسازیم پنج ساعت جلوتر را نشان دهد باید یک تابع View و URLconf برای آن ایجاد کنیم، که کاری تکراری می باشد. نیاز به مقداری اختصار در اینجا داریم.
سخنی درباره URL های بهینه
اگر با برنامه های دیگر توسعه ی وب نظیر Java و یا PHP کار کرده باشید، شاید این فکر به نظر شما خطور کند که باید از یک پارامتر query string استفاده کنید، مانند /time/plus?hours=3، بدین شکل که ساعت مورد نظر را درون پارامتر query string همانطور که نشان داده شد بعد از علامت (?) ارسال کنید.
شما می توانید این مورد را با جنگو پیاده سازی کنید (نحوه ی انجام آن در فرم به خوبی بیان شده است)، اما یکی از فلسفه های هسته ی جنگو این است که URL به صورت کاملا زیبا استفاده شوند. آدرس /time/plus/3/ به مراتب تمیزتر، ساده تر، خواناتر و خواندن آن آسانتر از query string می باشد. URL های زیبا مشخصه ی کیفیت برنامه ی وب می باشند.
حالا چطور می توان ساعت مشخص شده در URL را درون برنامه کنترل کرد؟ همانطور که پیش تر ذکر شد، URLpattern یک regular expression می باشد؛ از این رو، ما می توانیم از الگوی \d برای مچ کردن یک یا بیشتر ارقام استفاده کنیم.
urlpatterns = patterns('',
# ...
(r'^time/plus/\d /$', hours_ahead),
# ...
)
(ما از # ... برای اشاره کردن به این موضوع که ممکن است URLpattern های دیگری نیز درون مثال وجود داشته باشند استفاده می کنیم.)
این URLpattern جدید با هر آدرسی مانند /time/plus/2/، /time/plus/25/ یا حتی /time/plus/100000000/ مچ می شود. بهتر آن است که عدد وارد شده برای ساعت را محدود کنیم، به طوری که حداکثر عددی که بتوان وارد کرد 99 باشد. این بدین معنی است که می خواهیم تنها به وارد کردن اعداد یک یا دو رقمی اکتفا کنیم، شکل آن در regular expression بدین شکل خواهد بود \d{1,2}:
(r'^time/plus/\d{1,2}/$', hours_ahead),
نکته
هنگام ایجاد برنامه های وب، چیزی که همیشه مهم می باشد کنترل بیشترین مقدار ممکن ورودی می باشد، و تصمیم گرفتن در مورد آنکه برنامه باید آن ورودی را پشتیبانی کند یا خیر. ما در اینجا با محدود کردن مقدار ساعت ورودی تا 99 این مورد را کنترل کرده ایم.
یکی از جزئیات مهمی که معرفی شده است در اینجا حرف r می باشد که قبل URL استفاده شده است. این حرف به پایتون می گوید که رشته ی بعد از آن یک raw string می باشد، در این نوع رشته علامت (\) ترجمه یا تفسیر نمی شود. در رشته های معمولی پایتون علامت های (\) به حروف ویژه تفسیر می شوند مانند '\n'، که به یک کاراکتر خط جدید تبدیل می شود. هنگامی که از حرف r قبل یک رشته استفاده شود، مفسر پایتون دیگر علامت های (\) را تفسیر نمی کند، بنابراین r'\n' دیگر یک کاراکتر خط جدید نمی باشد و تنها دو کاراکتر می باشد، یک (\) و یک حرف کوچک n. بین کاربرد علامت (\) در پایتون و regular expression یک برخورد طبیعی وجود دارد، بنابراین شدیدا پیشنهاد می شود که هنگامی که می خواهید از regular expression استفاده کنید حتما از raw string ها استفاده کنید. از حالا به بعد، تمام URLpattern ها در این کتاب به صورت raw string خواهند بود.
اکنون که توانستیم توسط یک URL چندین URL مختلف را در URLpattern پشتیبانی کنیم، این مشکل مطرح می شود که چگونه داده مورد نظر در URL را به تابع view انتقال دهیم، به طوری که توسط یک تابع view تمام ساعت ها را کنترل کنیم. این مشکل با قرار دادن پرانتز به دور داده ی مورد نظر در URLpattern قابل حل می باشد. برای مثال فوق، ما می خواهیم شماره ی وارد شده در URL را به تابع view انتقال دهیم، بنابراین عبارت \d{1,2} را درون پرانتز قرار می دهیم:
(r'^time/plus/(\d{1,2})/$', hours_ahead),
آخرین URLconf شامل دو تابع view قبلی می باشد:
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),
)
ترتیب کد نویسی
در مثال فوق، ابتدا URLpattern نوشته شد و بعد تابع view، اما در مثال های قبل از آن، ابتدا تابع view نوشته شد و بعد URLconf. کدام تکنیک بهتر است؟
روش هر توسعه دهنده متفاوت است.
اگر شما از جمله افراد با مهارت هستید، ممکن است هنگام شروع پروژه ابتدا URLpattern های برنامه ی خود را نوشته و سپس به view های برنامه بپردازید. این روش به دلیل دادن به اصطلاح یک نوع لیست کاری واضح به شما مفید می باشد و اساسا پارامتر های مورد نیاز برای view را در ابتدای کار تعریف می کند.
اگر شما از جمله برنامه نویسانی می باشید که دوست دارند از جزئیات به کلیات جلو بروند، ممکن است ترجیح بدهید ابتدا view ها را بنویسید و سپس URL ها را به آنها مرتبط کنید. این روش نیز مانند روش قبل می تواند مناسب باشد.
در پایان، هر دو روش معتبر می باشند، هر کدام از روش ها که با فکر شما سازگار می باشد را می توانید بکار ببرید.
حالا تابع hours_ahead را ایجاد خواهیم کرد که در URLconf با URL مورد نظر یعنی r'^time/plus/(\d{1,2})/$' مرتبط شده است. تابع hours_ahead بسیار شبیه به تابع current_datetime می باشد که در ابتدا نوشته شده است، با یک تفاوت کلیدی: تابع مذکور (hours_ahead) یک آرگومنت اضافه دریافت می کند، که شماره ی ساعت ها می باشد:
from django.http import Http404, HttpResponse
import datetime
def hours_ahead(request, offset):
try:
offset = int(offset)
except ValueError:
raise Http404()
dt = datetime.datetime.now() datetime.timedelta(hours=offset)
html = "<html><body>In %s hour(s), it will be %s.</body></html>" % (offset, dt)
return HttpResponse(html)
اجازه دهید کد فوق را مورد بررسی قرار دهیم:
- تابع view، hours_ahead، دو پارامتر دریافت می کند: request و offset.
- request همانطور که در تابع hello و current_datetime مشاهده کردید یک شیء HttpRequest می باشد. دوباره می گوییم که هر تابع view همیشه یک شیء HttpRequest به عنوان اولین پارامتر دریافت می کند.
- offset یک رشته است که داخل URLpattern درون پرانتز قرار گرفته است. به عنوان مثال، اگر URL درخواست شده /time/plus/3/ باشد، بنابراین offset رشته ی '3' خواهد بود. اگر URL درخواست شده /time/plus/21/ باشد، بنابراین offset رشته ی '21' خواهد بود. نکته اینکه مقدار که در URLpattern داخل پرانتز قرار گرفته شده است همواره رشته خواهد بود، نه Integer، حتی اگر رشته ی مورد نظر تنها از ارقام ساخته شده باشد مانند '21'.
(از نظر فنی، مقدار درون پرانتز در URLpattern همواره شیء یونیکد می باشد، نه رشته ی ساده ی پایتون، ولی در حال حاضر درباره ی این فرق نگران نباشید.)
در این مثال فوق متغیر مورد نظر offset نامیده شده است، شما می توانید آن را هر چیزی که دوست دارید نام گذاری کنید، البته هر نامی در محدوده ی نام گذاری متغیرهای پایتون. نام متغیر به هیچ وجه مهم نمی باشد، تنها چیز مهم این است که، این متغیر به عنوان دومین پارامتر تابع view بعد از request باشد. (همچنین می توان آنرا به صورت کلمه ی کلیدی نیز بکار برد که در آموزش view و urlconf پیشرفته درباره ی آن صحبت شده است.)
- اولین چیزی که درون تابع انجام شده است استفاده از int() و قرار دادن offset درون آن می باشد. این عمل مقدار یک رشته را به یک integer تبدیل می کند.
توجه داشته باشید در صورتیکه پایتون قادر به تبدیل مقدار رشته به یک integer نباشد، به عنوان مثال اگر مقدار رشته چیزی شبیه به این باشد 'foo'، در این حالت خطای ValueError ایجاد خواهد شد. در مثال فوق، در صورتیکه برنامه با خطای ValueError برخورد کند، استثناء (exception) django.http.Http404 ایجاد خواهد شد، که نمایش صفحه ی خطای "Page not found" می باشد.
خوانندگان هوشیار تعجب خواهند کرد که چگونه می توان به خطای ValueError رسید، در حالی که regular expression در URLpattern (\d{1,2}) تنها ارقام را قبول خواهد کرد، و offset همیشه تنها رشته ی ساخته شده از ارقام خواهد بود؟ جواب این است یک تابع view هیچگونه فرضی نسبت به پارامترهای ورودی خود در این حالت نخواهد کرد و با چک کردن مقدار متغیر درون تابع view به اصطلاح یک محکم کاری نسبت به مقدار متغیر ایجاد خواهد شد، همچنین اگر مبحث loosely coupling را به خاطر بیاورید تابع view جدای از URLconf به کار خود می پردازد.
- در خط بعدی از تابع، زمان و تاریخ فعلی محاسبه شده و شماره ساعت مناسب به آن اضافه شده است. عبارت datetime.datetime.now() را درون تابع current_datetime قبلا مشاهده کرده اید؛ مفهوم جدید در اینجا این است که می توانید با ساختن و اضافه کردن شیء datetime.timedelta به یک شیء datetime.datetime زمان و تاریخ را حساب کرده و نمایش دهید. در نهایت نتیجه این عمل درون متغیر dt ذخیره شده است.
این خط دلیل استفاده از int() را نشان می دهد، چرا که برای استفاده از تابع datetime.timedelta باید پارامتر hours یک integer باشد.
- در قدم بعدی، یک خروجی HTML ساخته شده است، همانطور که در تابع current_datetime مشاهده کردید. تنها تفاوت این است که در اینجا از قابلیت format-string پایتون با دو مقدار استفاده شده است، نه یک مقدار. از این رو دو علامت %s درون رشته و یک تاپل برای درج مقدایر در انتها وجود دارد.
- نهایتا، یک HttpResponse از HTML برگردانده شده است.
با تابع view و URLconf نوشته شده، سرور جنگو را (اگر در حال اجرا نمی باشد) اجرا کنید، و به منظور بررسی اینکه برنامه کار می کند یا خیر به آدرس http://127.0.0.1:8000/time/plus/3/ بروید. سپس به آدرس http://127.0.0.1:8000/time/plus/5/ رفته و همچنین آدرس http://127.0.0.1:8000/time/plus/24/ را نیز امتحان کنید. نهایتا به منظور بررسی اینکه الگوی شما تنها اعداد یک رقمی و دو رقمی را قبول می کند آدرس http://127.0.0.1:8000/time/plus/100/ را نیز امتحان کنید؛ جنگو باید خطای "Page not found" را در این مورد نمایش دهد. همچنین آدرس http://127.0.0.1:8000/time/plus/ (بدون هیچ ساعت تعیین شده ای) نیز باید باعث بروز خطای 404 شود.
صفحات خطای جالب جنگو
چند لحظه ای از برنامه های وبی که تاکنون ساخته ایم لذت ببرید ... حالا بیایید خرابشان کنیم! بیایید به طور عمد یکی از خطاهای پایتون را درون فایل views.py با کامنت کردن عبارت offset = int(offset) در hours_ahead معرفی کنیم.
def hours_ahead(request, offset):
# try:
# offset = int(offset)
# except ValueError:
# raise Http404()
dt = datetime.datetime.now() datetime.timedelta(hours=offset)
html = "<html><body>In %s hour(s), it will be %s.</body></html>" % (offset, dt)
return HttpResponse(html)
سرور جنگو را (اگر در حال اجرا نیست) اجرا کرده و به آدرس /time/plus/3/ بروید. یک صفحه ی خطا با یک مقدار قابل توجهی از اطلاعات را مشاهده خواهید کرد، که همچنین در بالای صفحه پیام TypeError نمایش داده شده است:
"unsupported type for timedelta hours component: Unicode"
چه اتفاقی افتاد؟ تابع datetime.timedelta انتظار دارد پارامتر hours یک integer باشد، و در کد فوق قسمتی از کد که offset را به integer تبدیل می کرد کامنت شده است، که باعث شده است در عبارت datetime.timedelta خطای TypeError ایجاد شود.
هدف از این مثال نشان دادن صفحه ی خطای جنگو بود. مقداری درون صفحه ی خطا بگردید و با اطلاعات مختلف داده شده آشنا شوید.
مسائل قابل توجهی در اینجا وجود دارند:
- در بالای صفحه، اطلاعات کلیدی در مورد استثناء (exception) بدست می آورید: نوع استثناء، پارامترهای استثناء (در این مورد پیام "unsupported type")، فایلی که استثناء در آن ایجاد شده است، و شماره ی خط آن.
- در زیر اطلاعات کلیدی استثناء، صفحه traceback کامل پایتون را برای این استثناء نمایش می دهد. این همانند traceback استانداردی است که در خط فرمان interpreter نشان داده می شود، که در اینجا حالت تعاملی بیشتری دارد. برای هر سطح ("frame") در توده های مشخص، جنگو نام فایل، نام متد/تابع، شماره ی خط، و سورس کد خط مورد نظر را نمایش می دهد.
بر روی خط خاکستری تیره رنگ کد کلیک کنید، خط های قبل و بعد خطی که باعث بروز خطا شده است را مشاهده خواهید کرد.
بر روی "local vars" در زیر هر frame کلید کنید تا جدول تمام متغیرهای محلی و مقادیرشان نشان داده شود. این اطلاعات اشکال زدایی می تواند یک کمک بزرگ برای هر توسعه دهنده باشد.
- بر روی "local vars" در زیر هر frame کلید کنید تا جدول تمام متغیرهای محلی و مقادیرشان نشان داده شود. این اطلاعات اشکال زدایی می تواند یک کمک بزرگ برای هر توسعه دهنده باشد.
در قسمت پایین دکمه ی "Share this traceback on a public Web site" اطلاعات traceback را درون سایت http://www.dpaste.com/ تنها با یک کلیک کپی می کند و شما می توانید URL آن سایت را برای مشاهده ی traceback شما به دیگران بدهید.
- در قسمت پایین دکمه ی "Share this traceback on a public Web site" اطلاعات traceback را درون سایت http://www.dpaste.com/ تنها با یک کلیک کپی می کند و شما می توانید URL آن سایت را برای مشاهده ی traceback شما به دیگران بدهید.
زیر قسمت "Request information"، بخش "Settings" قرار دارد که لیست تمام تنظیمات انجام شده ی جنگو می باشد. (قسمت ROOT_URLCONF پیش تر بیان شده است، و تنظیمات مختلف دیگر جنگو در سراسر کتاب نشان داده خواهد شد.)
صفحه ی خطای جنگو برای نشان دادن اطلاعات بیشتر موارد ویژه، از قبیل خطاهای template syntax بسیار توانا می باشد. در زمان بحث در مورد سیستم tamplate جنگو به این موضوع خواهیم پرداخت. برای حالا، خط offset = int(offset) را از حالت کامنت خارج کنید تا تابع view بتواند دوباره کار کند.
آیا شما از آن دسته از برنامه نویسانی هستید که دوست دارید برنامه ی خود را با قرار دادن عبارت print اشکال زدایی کنید؟ شما می توانید برای انجام چنین کاری بدون بکار بردن عبارت print از صفحه ی خطای جنگو استفاده کنید. در هر نقطه ای از view به صورت موقت یک assert False قرار دهید. سپس شما می توانید متغیرهای محلی و وضعیت برنامه را مشاهده کنید:
def hours_ahead(request, offset):
try:
offset = int(offset)
except ValueError:
raise Http404()
dt = datetime.datetime.now() datetime.timedelta(hours=offset)
assert False
html = "<html><body>In %s hour(s), it will be %s.</body></html>" % (offset, dt)
return HttpResponse(html)
در پایان، واضح است که بسیاری از اطلاعات حساس هستند، و این موضوع که اطلاعات داخلی کد پایتون و تنظیمات جنگو را برای عموم نشان شود کاری ابلهانه می باشد. افراد مخرب می توانند با استفاده از این اطلاعات دست به کارهای نامطلوبی بزنند. به این دلیل، صفحه ی خطای جنگو تنها زمانی نمایش داده می شود که پروژه ی جنگو در حالت debug باشد. نحوه ی غیر فعال کردن حالت debug در فصل دوازدهم توضیح داده خواهد شد. در حال حاضر، دانستن اینکه هر پروژه ی جنگویی در هنگام ساخت به طور پیشفرض در حال debug می باشد. کافی است.