آموزش جنگو (Basic) : جلسه هشت | تمپلیت ها و رندرینگ

در این جلسه ابتدا به بررسی نحوه کار با تمپلیت ها در جنگو خواهیم پرداخت و سپس رندر کردن آنها و هندل کردن ارور های 404 در جنگو را بررسی می کنیم . با ما همراه باشید .آموزش جنگو : جلسه هشت | تمپلیت هاتمپلیت (template)به یاد دارید که جلسه پیش اشاره کردیم که می توانیم سند های html را به کاربر بازگردانیم ؟ در این جلسه چگونگی این کار را بررسی می کنیم .ابتدا در دایرکتوری اصلی یا دایرکتوری root پوشه ای با نام templates ایجاد کنید . جنگو دقیقا در همینجا به دنبال سند های html شما خواهد گشت . (منظور از دایرکتوری root پوشه ای است که فایل manage.py در آن قرار دارد و منظور ما این است پوشه templates را باید در کنار همین فایل ایجاد کنید .)تنظیمات تمپلیت ها (templates) در جنگو در فایل polls/polls/settings.py ذخیره شده اند. با تغییر در متغییر templates میتوانید سیستم تمپلیت جنگو را تغییر دهید. این سیستم وظیفه رندر کردن (بازگرداندن سند html به کاربر) و لود کردن آنها را بر عهده دارد.در این فایل تنظیماتی وجود دارد که در هر ماژول یا اپ (که در قسمت INSTALLED_APPS به جنگو معرفی شده باشد)، به دنبال پوشه ای با نام templates می‌گردد. تمام سند هایی که جنگو آنها را رندر می‌کند در این پوشه ها قرار دارد. در ادامه کتاب بصورت کامل خواهید فهمید که ما دقیقا چه کاری انجام می‌دهیم.در مرحله دوم، در پوشه جدیدی که هم اکنون در کنار فایل Manage.py ایجاد کردید، پوشه جدیدی با نام polls بسازید. حال به آن وارد شوید و فایل جدیدی با نام index.html بسازید. حال شما درون این مسیر باید قرار داشته باشید :Polls/templates/polls/index.htmlسختی مسیردهی در جنگو بخاطر نکته زیر است :شما ممکن است بتوانید در مسیر /templates فایل خود را ایجاد کنید اما جنگو در هنگام پیدا کردن این فایل، اولین فایلی که اسم یکسانی با فایلی که به دنبال آن هستید داشته باشد انتخاب خواهد شد و ممکن است بعد ها که دو تا فایل با اسامی یکسان در دو app مختلف وجود دارند، شما را دچار مشکل کنند. نحوه درست ایجاد مسیر ها را می توانید در گوگل جستجو کنید !حال کد زیر را درون فایل index.html بنویسید :{% if latest_question_list %} <ul> {% for question in latest_question_list %} <li><a href=&quot/polls/{{ question.id }}/&quot>{{ question.question_text }}</a></li> {% endfor %} </ul> {% else %} <p>No polls are available.</p> {% endif %}در کد بالا ما پس از بررسی وجود داشتن latest_question_list , روی آن حلقه ای اجرا کردیم و به این صورت سوالاتی (Question) که جدیدترین ها هستند را , نمایش دادیم (توضیحات بیشتر برای بعدا بماند) . حال فایل polls/views.py و تابع index را به صورت زیر تغییر دهید.from django.http import HttpResponse from django.template import loader from .models import Question def index(request): latest_question_list = Question.objects.order_by('-pub_date')[:5] template = loader.get_template('polls/index.html') context = { 'latest_question_list': latest_question_list, } return HttpResponse(template.render(context, request))این کد، تمپلیتی به نام polls/index.html را بارگذاری می کند و به سمت آن متغییری به نام context که حاوی یک کوئری ست (Query set) است را ارسال می کند . Context باید از نوع دیکشنری باشند که نامی برای هر کدام از متغییر هایی که درون هر آیتم هستند باید انتخاب شود. این نام بعدا در تمپلیت استفاده می‌شود . صفحه را با مرورگر خود روی /polls/ بارگیری کنید، و باید یک لیست حاوی سؤال «چه خبر» از آموزش های قبلی ببینید. پیوند و لینک آنها به صفحه جزئیات سؤال اشاره می‌کند.رندر و رندرینگ(render)لود کردن یک تمپلیت , بازگرداندن یک درخواست HTTP و کار کردن با دیتابیس و فرستادن اطلاعات یا شی ها به تمپلیت کار رایجی است که در هر وبسایت اصولا استفاده می شود . پس جنگو کار را برای شما اسان کرده است .کد polls/views.py را به صورت زیر تغییر بدهید .from django.shortcuts import render from .models import Question def index(request): latest_question_list = Question.objects.order_by('-pub_date')[:5] context = {'latest_question_list': latest_question_list} return render(request, 'polls/index.html', context)این قطعه کد دقیقا کار همان کد قبلی را انجام می دهد . تنها تفاوت آن در اخرین خط کد است . در خط اخر ما شی ای را برگردانده ایم . آن شی توسط ()render ایجاد شده است . ()render برای شما تمپلیتی را لود میکند و به ان context را ارسال میکند . Context در فرانت اند یا کد های html نشان داده خواهند شد .این تابع در اولین مقدار خود همان request که از تابع index گرفته شده است را دریافت میکند . در مقدار دوم ادرس تمپلیت را نوشتیم که باید کمی روی ادرس دهی ها بیشتر کار کنیم و در مقدار سوم , این تابع context یا مقداری که به فرانت اند ارسال میشود را از شما دریافت میکند .ارور 404 (404 Error)حال بیایید کمی روی detail view یا همان ویو مربوط به جزییات هر سوال کار کنیم . کد زیر را برای تابع detail بنویسید :from django.http import Http404 from django.shortcuts import render from .models import Question # ... def detail(request, question_id): try: question = Question.objects.get(pk=question_id) except Question.DoesNotExist: raise Http404(&quotQuestion does not exist&quot) return render(request, 'polls/detail.html', {'question': question})گاهی شما نیاز دارید تا اروری را به کاربر بازگردانید . ارورها با توجه به کد آنها معناهای متفاوتی دارند . برای مثال ارور 404 به معنای یافت نشدن صفحه مورد نظر است . این ارور یعنی این صفحه وجود ندارد و در این قطعه از کد ما ان را به کاربر بازگردانده ایم چون رکوردی از دیتابیس که primary_key یا همان id آن با question_id مطابقت کند , وجود نداشت .در خطوط ابتدایی ما تمامی چیز هایی که لازم داریم را import کرده ایم . در خط اول آن نیز از Django.http ما http404 را import کرده ایم (تابعی برای بازگرداندن ارور 404) . در ویو detail ما بدنه ی try و execpt ایجاد کردیم ; میخواهیم تلاش کنیم تا اگر جنگو نتوانست question_id را پیدا کند به ما ارور 404 بازگرداند . در ابتدا مقدار question_id از url که کاربر وارد میکند تشخیص داده میشود .برای مثال http://127.0.0.1:8000/detail/1 به ویو detail شما مقدار 1 را به عنوان به question_id ارسال میکند .در مرحله بعدی ما در بدنه try تلاش میکنیم تا رکوردی از جدول question که pk یا همان id که به هر دو صورت نیز نمایش داده می شوند را بگیریم که مقدار ان برابر با question_id دریافت شده باشد .البته شاید برای شما سوال شود که جنگو چگونه این درخواست ها را قاطی نمیکند . برای مثال کاربری question_id را 1 قرار داده است و کاربر دیگری 2 . جنگو برای درست نشان دادن نتیجه به هر کدام از آنها مقدار request را دریافت میکند و کار مهمی که تابع render انجام می دهد نیز این است که کاربر ها را با استفاده از request تفکیک میکند و پاسخ مناسب با هرکدام را به همان کاربر باز میگرداند .در بدنه except ما ارور 404 را در صورت پیدا نشدن رکورد مناسب با مقادیر دریافتی , به کاربر باز خواهیم گرداند .و در اخر در تابع render , مقدار question که همان رکورد فعلی ماست را به فرانت اند یا همان تمپلیت ارسال کردیم . توجه داشته باشید که باید مقدار ارسال شده یک دیکشنری باشد .حال در فایل polls/detail.html کد زیر را بنویسید . (کد زیر باعث نمایش شی ارسال شده به فرانت اند خواهد شد!){{ question }}یک میانبر (get_object_or_404)اینکه یک رکورد را به وسیله مقدار دریافتی از url پیدا کنید و آن را به کاربر باز گردانید , و اگر جنگو آن را پیدا نکرد به شما 404 را بازگرداند , کاری است که شما معمولا مشغول به آن هستید . جنگو برای این کار میانبری را پیش پای شما گذاشته است ; ابتدا کد زیر را برای تابع detail بنویسید و get_object_or_404 را نیز import کنید .from django.shortcuts import get_object_or_404, render from .models import Question # ... def detail(request, question_id): question = get_object_or_404(Question, pk=question_id) return render(request, 'polls/detail.html', {'question': question})به وسیله کد بالا شما می توانید همان تابع قبلی را سریعتر و با تعداد کد کمتری پیاده سازی کنید . تابع get_object_or_404 دو مقدار را دریافت می کند که اولی نام مدلی است که می خواهید روی آن عملیات جستجو انجام شود . دومین مقدار سری پارامتر هایی است که برای جستجوی شی موردنظر استفاده می کنید (برای مثال در اینجا گفتیم که سوالاتی که id آنها برابر با question_id باشد). معمولا استفاده از روش بیشتر توصیه می شود .نکته : این میانبر فقط روی متد get کار میکند . این به این معناست که اگر شما چند شی داشته باشید و یا بخواهید چند رکورد را پیدا کنید (filter) , این تابع برای شما کار نخواهد کرد و شما فقط یک و فقط یک رکورد را میتوانید به وسیله آن پیدا کنید . برای استفاده از متد filter و این میانبر کافیست بجای این متد , متد get_list_or_404 را import و استفاده کنید . در صورتی که کوئری (query) شما خالی باشد , این متد ارور 404 را باز خواهند گرداند .استفاده از سیستم تمپلیت هابسیار خب ,حال قرار است مقداری از کد های تمپلیت هایی که ایجاد کردیم و نوشتیم را مورد بررسی قرار دهیم . در ویو ()detail ما یک متغییر و شی با نام question را در قالب context که یک دیکشنری بود ارسال کردیم . حال به polls/detail.html بروید و کد زیر را درون آن قرار دهید .<h1>{{ question.question_text }}</h1> <ul> {% for choice in question.choice_set.all %} <li>{{ choice.choice_text }}</li> {% endfor %} </ul>اگر یادتان باشد ما اشاره کردیم که جنگو یک سیستم تمپلیت دارد و در حال حاضر ما از سیستم تمپلیت خود جنگو استفاده میکنیم . جنگو به وسیله سیستم نقطه گذاری اجازه دسترسی به ویژگی ها (attributes) شی را به شما می دهد . برای مثال {{question.question_text}} به جنگو می گوید تا در شی question که از ویو detail برای ما ارسال شده است را بررسی کند و به مقدار question_text آن رجوع کند . {{ }} برای جداسازی کد های فرانت اند و کد هایی است که سیستم تمپلیت جنگو باید آنها را اجرا کند . بنابراین این خط از کد برای ما مقدار question_text سوالی با id=1 را باز خواهد گرداند .و در خط بعدی ما لیستی حاوی از انتخاب ها (choice) که در ابجکت یا رکورد ما وجود دارند ساخته ایم . ما این کار را به کمک حلقه for که سینتکس آن را میتوانید در بالا مشاهده کنید ,انجام داده ایم . این حلقه مقادیر ها را به صورت iterable درمی آورد و به شما اجازه پیمایش بر روی آنها را میدهد .نکته : کتاب ما بر اساس داکیومنت اصلی جنگو پیش می رود . بر اساس همین ما توضیحات چندانی را درباره سیستم تمپلیت ارائه نمی کنیم . در فصل های بعدی به طور کامل به بررسی این سیستم خواهیم پرداخت . (این پروژه ابتدایی صرفا برای یادگیری و اشنایی اولیه با نحوه کار جنگو است و قرار نیست به طور کامل به بررسی جزییات در این پروژه بپردازیم )استفاده از تگ {% url %}بسیار خب ,اگر به یاد داشته باشید ,در فایل polls/index.html ما برای هر سوال یک لینک ساختیم که ما را به صفحه جزییات هدایت میکرد . این لینک به صورت زیر بود که کمی برای خواندن آن ,برنامه نویس دچار مشکل میشد :<li><a href=&quot/polls/{{ question.id }}/&quot>{{ question.question_text }}</a></li>مشکل در اینجاست که اگر پروژه شما دارای تمپلیت های زیادی باشد , دیگر نمی توانید از این روش استفاده کنید . برای حل این مشکل کافی است :1-در path مربوط به تمپلیت و ویو صفحه مورد نظر (که در اینجا detail نام دارد) مقدار name را تعیین کنید که ما از قبل این کار را کردیم و مقدار detail را به عنوان name به آن دادیم .2-از تگ {% url %} برای ساخت یک ادرس اینترنتی در وبسایت استفاده کنید .کد زیر را جایگزین کد قبلی کنید .<li><a href=&quot{% url 'detail' question.id %}&quot>{{ question.question_text }}</a></li>در این کد برای ادرس دهی و ایجاد لینک از تگ مربوطه استفاده کردیم و ابتدا به صفحه detail اشاره کردیم . این نام در واقع همان name است که در path تعریف شده است و شما میتوانید هزاران url و هزاران name مختلف داشته باشید . با این تگ میتوانید به راحتی به همه ی آنها دسترسی داشته باشید .اما صبر کنید ...!در ادرس http://127.0.0.1:8000/detail/2 ما مقدار دیگری نیز داریم که عددی است که ویو ما آن را به عنوان question_id در نظر می گیرد . این مقدار را باید از خود question پیدا کنیم و آن را به این ادرس ارسال کنیم . ما از قبل حلقه for را در index.html ایجاد کردیم و حال کافی است برای هر سوال مقدار id را در لینک آن جای گذاری کنیم . بنابراین در حلقه مینویسیم question.id و به این صورت هر سوال در index.html دارای یک لینک به صفحه detail است و مقدار id مختص به خود را به صورت اتوماتیک به ویوی ()detail ارسال میکند . (کد آن در بالا است)یک مشکل!این پروژه ابتدایی تنها یک اپ (app) دارد ,به همین علت شما میتوانید بدون اشاره به اپ از تگ url در تمپلیت خود استفاده کنید . اما اگر تعداد اپ های شما بیشتر شود ! آنگاه چگونه باید به ادرسی از اپ دیگری اشاره کنید ؟نامگذاری اپ(namespacing)اگر چندین اپ در پروژه خود دارید (که اینجا فرض میکنیم داشته باشیم ) باید برای فایل urls.py هر کدام از اپ ها یک متغییر تعریف کنید . این کار باعث میشود تا مشکلی که در صفحه پیش به آن اشاره کردیم حل شود و شما بتوانید در اپ های خود از تگ {% url %} استفاده کنید .فایل polls/urls.py را به صورت زیر تغییر دهید .from django.urls import path from . import views app_name = 'polls' urlpatterns = [ path('', views.index, name='index'), path('<int:question_id>/', views.detail, name='detail'), path('<int:question_id>/results/', views.results, name='results'), path('<int:question_id>/vote/', views.vote, name='vote'), ]جنگو در هر یک از فایل های urls.py که در هر اپ وجود دارند خواهد گشت و متغییری با نام app_name را پیدا خواهد کرد . این متغییر و مقدار آن همان نام اختصاص داده شده به اپ شماست و حال شما کافی است تا در تگ {% url %} مقدار دوم که اسم url باشد را به صورت “app_name:url_name” تغییر دهید .فایل polls/index.html را باز کنید و خط زیر را از :<li><a href=&quot{% url 'detail' question.id %}&quot>{{ question.question_text }}</a></li>به این خط تغییر دهید .<li><a href=&quot{% url 'polls:detail' question.id %}&quot>{{ question.question_text }}</a></li>همانطور که می بینید ,در تگ url ,ما ابتدا اسم اپ و سپس نام (name) url موردنظر را نوشتیم . حال اگر می خواستیم به ادرس یا url ای از اپ دیگری اشاره کنیم ,کافی بود تا ابتدا اسم آن اپ و سپس نام ادرس را در فایل urls.py که در همان اپ قرار دارد ,بنویسیم .در این جلسه به کامل کردن پروژه جلسه گذشته خود پرداختیم . همچنین مباحثی مانند تمپلیت ها و آدرس دهی آنها , رندر کردن آنها , ارور 404 را بررسی کردیم . در جلسه بعدی فرم ها را بررسی خواهیم کرد !