رفتن به مطلب
Mohammad Aref

آموزش جنگو Django Framework

پست های پیشنهاد شده

جنگو (Django) یک چارچوب یا فریم ورک (Framework) توسعه وب برای زبان برنامه نویسی پایتون می باشد که در ساخت و نگه داری برنامه های تحت وب با کیفیت بالا کاربرد دارد. فریم ورک و چارچوب جنگو در کاهش انجام کارها و وظایف تکراری و صرفه جویی در زمان به شما کمک می کند. مباحث این تاپیک آموزشی برگرفته از سایت آموزش می باشد.

مقدمه

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

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

یک فریم ورک یا چارچوب وب چیست؟

جنگو یک فریم ورک و چارچوب جدید و برجسته وب است، اما معنی دقیق یک چارچوب یا فریم ورک چیست؟

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

یکی از آسان ترین روش های ساخت یک برنامه وب پایتون، استفاده از CGI می باشد، که در حدود سال های 1998 محبوب بود. تنها کافی است یک اسکریپت پایتون که خروجی HTML تولید می کند نوشته، سپس اسکریپت را با پسوند ".cgi" درون وب سرور ذخیره کنید و در آخر نیز صفحه را درون مرورگر خود مشاهده کنید.

در اینجا یک مثال اسکریپت CGI پایتون وجود دارد که ده کتاب پایانی منتشر شده را از دیتابیس خوانده و نمایش می دهد. تنها کافیست کلیات عملیاتی که در کد زیر انجام شده است را دریابید و نگران جزئیات کد زیر نباشید.

#!/usr/bin/env python

import MySQLdb

print "Content-Type: text/html\n"
print "<html><head><title>Books</title></head>"
print "<body>"
print "<h1>Books</h1>"
print "<ul>"

connection = MySQLdb.connect(user='me', passwd='letmein', db='my_db')
cursor = connection.cursor()
cursor.execute("SELECT name FROM books ORDER BY pub_date DESC LIMIT 10")

for row in cursor.fetchall():
    print "<li>%s</li>" % row[0]

print "</ul>"
print "</body></html>"

connection.close()

در ابتدا، برای تکمیل نیازمندی های CGI، در خط 5 عبارت "Content-Type" و به دنبال آن یک خط خالی چاپ شده است. در خط 5 تا 9 نیز کدهای مربوط به HTML چاپ شده، سپس به دیتابیس وصل شده و یک پرس و جو برای بازیابی اسامی ده کتاب آخر اجرا شده است. در خط 15 با یک حلقه لیست کتاب ها نمایش داده شده است و در آخر نیز HTML و connection دیتابیس بسته شده است.

فهمیدن کد بالا بسیار ساده است. حتی یک برنامه نویس مبتدی نیز می تواند این خطوط را خوانده و هر آنچه از بالا تا پایین انجام می گیرد را بفهمد. همچنین اجرای این کد نیز ساده می باشد، تنها کافی است کد را با پسوند ".cgi" ذخیره کرده و آنرا در وب سرور قرار دهید و توسط مرورگر آدرس آن را فراخوانی کنید.

اما با وجود تمام این سادگی ها، در استفاده کلی از روش بالا ممکن است با مسائل و مشکلاتی روبه رو شویم. به عنوان مثال به سوالات زیر توجه کنید:

  • چه اتفاقی می افتد اگر چندین بخش از برنامه شما نیاز داشته باشند به دیتابیس وصل شوند؟ مطمئنا کد مربوط به اتصال به دیتابیس نیازی نیست در CGI اسکریپت ها به صورت منحصر به فرد تکرار شود. واقع بینانه این است که کد مربوط درون یک تابع به اشتراک گذاشته شده و بارها فراخوانده شود.
  • آیا یک توسعه دهنده (developer) باید در مورد چاپ کردن خط "Content-Type" و بیاد آوردن بستن connection دیتابیس همواره نگرانی داشته باشد؟ این قبیل مسائل باعث کاهش باروری برنامه نویس و بالا رفتن احتمال اشتباه می شود.
  • چه اتفاقی می افتد هنگامی که این کد در محیط های مختلف برای دیتابیس های مختلف استفاده شود؟ در این صورت تنظیمات مختلف برای هر دیتابیس و محیط یک امر ضروری می باشد.
  • چه اتفاقی می افتد هنگامی که یک طراح وب، کسی که هیچ آشنایی و تجربه ای در مورد کد پایتون ندارد بخواهد صفحه را دوباره طراحی کند؟ ایده آل آن است که منطق مربوط به قسمت های خواندن اطلاعات از دیتابیس و نمایش آنها از هم جدا شوند، و اطلاعات خوانده شده درون یک صفحه Html نمایش داده شود تا طراح بتواند صفحه را طراحی کند.

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

الگوی طراحی MVC

بیایید یک مثال که تفاوت بین روش قبلی و روش با استفاده از فریم ورک یا چارچوب وب را نشان می دهد را مورد بررسی قرار دهیم. در اینجا نحوه نوشتن کد قبلی CGI را با استفاده از جنگو نشان داده شده است. اولین چیزی که باید توجه کنید این است که ما عملیات انجام شده در کد قبلی را در سه فایل پایتون (models.py، views.py، urls.py) و یک فایل HTML (latest_books.html) از هم جدا کرده ایم:

# models.py (the database tables)

from django.db import models

class Book(models.Model):
    name = models.CharField(max_length=50)
    pub_date = models.DateField()


# views.py (the business logic)

from django.shortcuts import render_to_response
from models import Book

def latest_books(request):
    book_list = Book.objects.order_by('-pub_date')[:10]
    return render_to_response('latest_books.html', {'book_list': book_list})


# urls.py (the URL configuration)

from django.conf.urls.defaults import *
import views

urlpatterns = patterns('',
    (r'^latest/$', views.latest_books),
)


# latest_books.html (the template)

<html><head><title>Books</title></head>
<body>
<h1>Books</h1>
<ul><ul/>
{% for book in book_list %}
<li>id="19"<li/>
{{ book.name }}
</li>

{% endfor %}
</ul>
</body></html>

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

  • فایل models.py حاوی یک توضیح از جدول دیتابیس می باشد که بصورت کلاس پایتون نمایش داده شده است. این کلاس یک model نامیده می شود. با استفاده از آن شما می توانید رکوردهای درون دیتابیس را با استفاده از کد ساده پایتون ساخته، بازیابی، به روز سازی و حذف کنید.
  • فایل views.py حاوی منطق های برنامه نویسی برای صفحه می باشد. تابع latest_books() با نام view شناخته می شود.
  • فایل urls.py نسبت به url داده شده view مورد نظر را تعیین می کند. در مثال فوق /latest/ با تابع latest_book() مرتبط خواهد شد. به زبان ساده تر، اگر دامنه شما example.com است، هر بازدیدی از آدرس

    محتوای مخفی

      برای مشاهده محتوای مخفی می بایست در انجمن ثبت نام کنید.
    تابع latest_book() را فراخوانی خواهد کرد.
  • فایل latest_books.html یک قالب HTML است که طرح صفحه در آن قرار می گیرد. این قالب از یک زبان template با جملات منطقی پایه مانند {% for book in book_list %} استفاده می کند.

قسمت های فوق یک الگو را دنبال می کنند که Model-View-Controller یا (MVC) نامیده می شود. به عبارت ساده MVC یک روش برای توسعه دادن نرم افزار است به طوری که کد برای تعریف کردن و دسترسی داشتن داده (the model) از منطق (the controller) جدا شده و آن نیز از رابط کاربر (the view) جدا می باشد. (ما در مورد MVC در آموزش مدل جنگو به طور مفصل بحث خواهیم کرد.)

مزیت کلیدی روش MVC این است که اجزا نسبت به یکدیگر به اصطلاح loosely coupled هستند. بدین معنا که هر قسمت مجزا از برنامه تحت وب جنگو هدف خاص خود را دارد و می تواند بدون تاثیربر روی دیگر قسمت ها به طور مستقل تغییر کند. به عنوان مثال، یک توسعه دهنده می تواند مسیر یک بخش داده شده از برنامه را بدون تاثیر بر روی اصل برنامه تغییر دهد. یک طراح می تواند صفحه HTML را بدون کار کردن با کد پایتون تغییر داده و تحویل دهد. یک مدیر دیتابیس می تواند جداول درون دیتابیس را تغییر نام داده و هر تغییری را درون یک قسمت خاص بدهد.

مقدمه ای بر تاریخچه جنگو

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

اگر شما برای مدتی برنامه تحت وب ساخته باشید، شاید با مشکلات CGI، مانند مثال قبلی کتاب که پیش تر توضیح داده شد، آشنا شده باشید. مسیر توسعه دهندگان وب شکلی شبیه به این داشته است:

  • نوشتن یک برنامه وب از ابتدا.
  • نوشتن یک برنامه دیگر از ابتدا.
  • پی بردن به اینکه برنامه در گام اول اشتراکات عمومی زیادی با برنامه در گام دوم دارد.
  • تغییر کد به طوری که برنامه اول بتواند کد خود را با برنامه دوم به اشتراک بگذارد.
  • تکرار کردن گام های 2-4 به صورت چند بار.
  • درک کردن ساخت یک فریم ورک.

این دقیقا نحوه ساخته شدن چارچوب است

جنگو از برنامه های real-word نوشته شده توسط تیم توسعه دهندگان وب در لارنس، کانزاس آمریکا رشد کرد. جنگو در پاییز سال 2003 متولد شد، هنگامی که برنامه نویسان وب در روزنامه Journal-World، آدریان هولاوتی و سیمون ویلیسون استفاده از پایتون را برای ساختن برنامه ها شروع کرده بودند.

تیم World Online مسئولیت تولید و نگهداری چندین سایت اخبار محلی را به عهده داشت، که در یک محیط کاری همراه با ضرب العجل های روزنامه نگاری رشد کرده بود. برای سایت های LJWorld.com، Lawrence.com، و KUsports.com روزنامه نگاران و مدیران خصوصیات جدیدی با برنامه کاری به شدت پر سرعت را درخواست کردند. در نتیجه سیمون و آدریان یک فریم ورک یا چارچوب توسعه وب با صرفه جویی در زمان را توسعه دادند که آن تنها راهی بود که می توانستند برنامه های قابل پشتیبانی در ضرب العجل های سریع را بسازند.

در تابستان سال 2005، بعد از توسعه این فریم ورک، جایی که برای ایجاد سایت های World Online بیشترین تاثیر را داشت، و همچنین هنگامی که جاکُب کاپلان مُس، به آنها ملحق شد، تصمیم گرفته شد چارچوب به صورت نرم افزار کد باز (open source) منتشر شود. آنها چارچوب خود را در جولای سال 2005 با نام جنگو منتشر کردند، به یادبود گیتاریست سبک جاز "جنگو رینهارت".

حالا چندین سال بعد، جنگو یک پروژه کد باز خیلی مشهور با ده ها هزار کاربر و حامی پخش شده در سراسر دنیا می باشد. دو تن از توسعه دهندگان اصلی World Online (آدریان و جاکُب) هنوز هدایت مرکزی برای رشد فریم ورک یا چارچوب را انجام می دهند، اما Django حاصل تلاش و همکاری فراوان کل گروه است.

این تاریخچه مربوط به بحث می باشد زیرا به توضیح دو موضوع کلیدی کمک می کند. اول موقعیت خوب جنگو. از آنجایی که جنگو در یک محیط اخبار متولد شده است، می تواند خصوصیات مختلفی از قبیل سایت مدیر، (که در سایت مدیر به آن پرداخته شده است)را ارائه دهد که به ویژه برای سایت های محتوی (content) مانند Amazon.com، craigslist.org و washingtonpost.com که اطلاعات پویا را ارائه می دهند، مناسب می باشند. البته این بدان معنی نیست که Django فقط برای توسعه دادن آن دسته از سایت ها خوب می باشد، اما این موضوع مانعی برای موثر بودن جنگو برای ساختن انواع دیگر وب سایت های پویا نمی باشد. (بین موثر بودن در چیزی به طور ویژه و بی تاثیر بودن در بقیه چیزها تفاوت بسیار است.)

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

دانش مورد نیاز برنامه نویسی

خوانندگان این آموزش باید قوائد برنامه نویسی شیءگرا را بدانند: ساختار های کنترلی (مانند، if، while، for)، ساختارهای داده (lists، hashes/dictionaries)، متغیرها، کلاس ها و شیء ها (objects).

ممکن است شما فکر کنید تجربه در توسعه وب می تواند بسیار مفید باشد، اما این موضوع یک پیش نیاز برای فهم این کتاب نیست. در سرتاسر این کتاب، ما سعی کردیم از بهترین تمرین ها در توسعه وب برای خوانندگانی که تجربه کمی دارند استفاده کنیم.

دانش مورد نیاز پایتون

فریم ورک و چارچوب جنگو (django) مجموعه ای از کتابخانه های نوشته شده با زبان برنامه نویسی پایتون می باشد. برای گسترش یک سایت با استفاده از Django، شما کد پایتونی می نویسید که از این کتابخانه ها استفاده می کند. به عنوان مقدمه، یاد گرفتن جنگو ترکیبی از دانستن نحوه نوشتن برنامه به زبان پایتون و فهمیدن کار کتابخانه های جنگو می باشد.

به اشتراک گذاری این ارسال


لینک به ارسال
به اشتراک گذاری در سایت های دیگر

شروع کار با جنگو

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

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

نصب کردن پایتون

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

نسخه های پایتون

هسته فریم ورک جنگو با نسخه های پایتون از 2.3 تا 2.7 کار می کند.

اگر شما مطمئن نیستید که از کدام نسخه از پایتون استفاده کنید، آخرین نسخه در سری 2 را انتخاب کنید. هر چند چارچوب جنگو به خوبی با نسخه‌های 2.3 تا 2.7 کار می‌ کند، آخرین نسخه از پایتون بهبودهایی در کارایی و همینطور خصوصیات اضافه‌تری دارد که ممکن است دوست داشته باشید در برنامه‌ی خود استفاده کنید. بنابراین استفاده از آخرین نسخه پایتون شما را در انتخاب آزاد می گذارد.

جنگو و پایتون 3

در زمان نوشتن این کتاب، پایتون 3.0 منتشر شده است، اما جنگو هنوز از آن پشتیبانی نمی‌کند. پایتون 3.0 تعدادی تغییرات اساسی دارد که باعث ناسازگاری آن با نسخه‌های قبلی شده است، در نتیجه ما انتظار کتابخانه‌ها و فریم ورک‌های بیشتر را داریم، از جمله جنگو که تا چند سال آینده آن را جبران خواهد کرد.

اگر شما در یادگیری پایتون تازه کار هستید و نگران این هستید که آیا باید از پایتون 2 یا 3 شروع کنید، ما توصیه می‌کنیم که با پایتون 2 شروع کنید.

مراحل نصب

اگر شما با سیستم عامل های لینوکس و یا مکینتاش کار می کنید، احتمالا پایتون را به صورت نصب شده در سیستم خود دارید. دستور python را در command prompt (یا در Applications/Utilities/Terminal) تایپ کنید. اگر شما چیزی شبیه به این مشاهده کردید، بنابراین پایتون نصب شده است.

Python 2.4.1 (#2, Mar 31 2005, 00:05:10)
[GCC 3.3 20030304 (Apple Computer, Inc. build 1666)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>

در غیر اینصورت شما نیاز دارید تا پایتون را دانلود و نصب کنید. نصب آن بسیار آسان و سریع می باشد. همچنین دستورالعمل های نصب آن در آدرسhttp://www.python.org/download/ در دسترس می باشد.

در هر زمان دو نسخه متفاوت از جنگو در دسترس می باشد: آخرین نسخه رسمی و نسخه trunk. نسخه‌ای که شما تصمیم به نصب آن دارید به اولیت شما بستگی دارد. آیا شما یک نسخه تست شده و پایدار از جنگو را می خواهید، یا یک نسخه که حاوی آخرین خصوصیات باشد، و شاید شما بتوانید جنگو را برای خودتان گسترش دهید؟

توصیه می کنیم با نسخه رسمی شروع کنید، اما شناختن و دانستن این که نسخه توسعه trunk وجود دارد نیز مهم است، زیرا شما با عضو شدن در انجمن توسعه جنگو می توانید با مستندات جنگو و ساختن آن آشنا شوید.

مراحل نصب انتشار رسمی جنگو

انتشارهای رسمی دارای شماره نسخه می‌باشند، همانند 1.0.3 یا 1.1، و آخرین نسخه‌ی آن همیشه در این آدرس در دسترس است

محتوای مخفی

    برای مشاهده محتوای مخفی می بایست در انجمن ثبت نام کنید.

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

اگر شما به نسخه prepackaged دسترسی ندارید، می‌توانید به صورت دستی آن را دانلود کرده و نصب کنید. برای انجام چنین کاری، ابتدا باید پرونده tarball را که معمولا نامی مشابه Django-1.0.2-final.tar.gz دارد را دانلود کنید، سپس آن را از حالت فشرده خارج کرده و در خط فرمان setup.py install را اجرا کنید.

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

tar xzvf Django-1.0.2-final.tar.gz
cd Django-*
sudo python setup.py install

در سیستم عامل ویندوز استفاده از 7-Zip (http://www.djangoproject.com/r/7zip/) را توصیه می کنیم. فایل را از حالت فشرده خارج کرده و محیط DOS (Command Promt) را با دسترسی مدیر باز کرده و درون پوشه ای که با نام Django-1.0 ... شروع می‌شود دستور زیر را وارد کنید:

python setup.py install

فایل های جنگو درون پوشه site-packages برای فایل های نصب شده پایتون نصب خواهند شد، معمولا در این آدرس قرار دارد.

usr/lib/python2.4/site-packages

تست کردن نصب جنگو

بعد از نصب جنگو برای اینکه اطمینان حاصل کنید جنگو به درستی نصب شده و کار می‌کند، درون خط فرمان به شاخه‌ی دیگری بروید (به طور مثال، شاخه‌ای که شامل شاخه‌ی django نباشد) و interactive interpreter پایتون را با تایپ کردن python اجرا کنید. اگر نصب با موفقیت انجام شده باشد، شما باید قادر به import کردن ماژول جنگو باشید:

>>> import django
>>> django.VERSION
(1, 1, 0, 'final', 1)

مثال های Interactive Interpreter

interactive interpreter پایتون یک برنامه خط فرمان است که به شما اجازه می‌دهد تا کدها و برنامه‌های پایتون را به صورت تعاملی بنویسید. برای شروع کار با آن، تنها کافی است دستور python را در خط فرمان اجرا کنید.

در سرتاسر این کتاب، از بخش های interactive interpreter پایتون استفاده شده است. شما می توانید این مثال ها را با سه علامت بزرگتر (<<<) تشخیص دهید. اگر می خواهید این کدها را کپی کنید، نباید علامت های بزرگتر را انتخاب کنید.

جملات چند خطی در interactive interpreter با استفاده از سه نقطه (...)، مشخص می شوند:

>>> print """This is a
... string that spans
... three lines."""
This is a
string that spans
three lines.
>>> def my_function(value):
...     print value
>>> my_function('hello')
hello

این سه نقطه در شروع دستورات چند خطی بوسیله shell پایتون ایجاد می شوند، یعنی جزء بخش هایی نیست که ما وارد کرده باشیم.

آغاز یک پروژه

هنگامی که شما پایتون، جنگو و (به طور اختیاری) دیتابیس خود را نصب کردید، شما می توانید اولین گام را در توسعه یک برنامه جنگو، با ساختن یک پروژه بردارید.

یک پروژه مجموعه ای از تنظیمات برای یک نمونه از جنگو، شامل تنظیمات دیتابیس، گزینه های ویژه جنگو و برنامه ویژه تنظیمات می باشد.

اگر این اولین باری است که از جنگو استفاده می کنید، شما باید مراقب بعضی از تنظیمات اولیه باشید. یک پوشه جدید برای شروع کار بسازید، شاید چیزی شبیه به این/home/username/djcode/.

این پوشه باید کجا قرار بگیرد

اگر زمینه فکری شما PHP باشد، شاید درون document root سرور (جایی مانند /var/www) کد خود را قرار دهید. موقع کدنویسی با جنگو، این کار را انجام ندهید. این ایده خوبی نیست که هر کد پایتونی را داخل document root سرور قرار دهید، زیرا در این حالت این ریسک وجود دارد که امکان دارد بقیه قادر شوند کد خام شما را درون وب تماشا کنند و این خوب نیست.

کد خود را در یک پوشه ای بیرون از document root قرار دهید.

به مسیر پوشه ای که ساخته اید رفته، و دستور django-admin.py startproject mysite را اجرا کنید. دستور فوق پوشه mysite را درون پوشه ای که قرار دارید می سازد.

اگر شما پیام "permission denied" را هنگام اجرا کردن django-admin.py startproject مشاهده کردید، شما نیاز خواهید داشت حق دسترسی فایل را تغییر دهید. برای این منظور، به پوشه ای که django-admin.py در آن نصب شده است رفته (مانند /usr/local/bin) و دستور chmod x django-admin.py را اجرا کنید.

دستور startproject یک پوشه حاوی چهار فایل می سازد:

mysite/
__init__.py
manage.py
settings.py
urls.py

توضیح فایل های بالا در زیر آمده است:

  • __init__.py: یک فایل مورد نیاز پایتون می باشد که توسط آن پوشه mysite یک پکیج تلقی می شود. همچنین فایل فوق یک فایل خالی می باشد، و معمولا شما هیچ چیزی درون آن اضافه نخواهید کرد.
  • manage.py: یک مزیت خط فرمان می باشد که به شما اجازه می دهد با این پروژه جنگو در روش های مختلف در تعامل باشید. دستور python manage.py help را تایپ کنید تا آنچه را که می توانید با این فایل انجام دهید را مشاهده کنید. شما نباید هرگز این فایل را ویرایش یا دستکاری کنید این فایل صرفا برای راحتی کار در این پوشه ساخته شده است.
  • settings.py: تنظیمات/پیکربندی برای پروژه جنگو می باشد. نگاهی به آن بیاندازید تا تنظیمات در دسترس، به همراه مقادیر پیشفرض آن را مشاهده کنید.
  • urls.py: URL ها برای پروژه جنگو. برای لحظاتی فکر کنید فهرست مطالب سایت شما خالی باشد.با وجود حجم کم، این فایل ها کارکرد برنامه جنگو را تشکیل می دهند.

با وجود حجم کم، این فایل ها کارکرد برنامه جنگو (Django) را تشکیل می دهند.

اجرا کردن سرور

سرور جنگو (همچنین runserver هم نامیده می شود بعد از دستور راه اندازی آن) یک سرور سبک و داخلی جنگو می باشد که شما می توانید با استفاده از آن سایت خود را توسعه دهید. با استفاده از این سرور شما می تونید به سرعت سایت خود را توسعه دهید، بدون سر و کارداشتن با تنظیمات سرور (مانند Apache) تا زمانی که شما آماده شوید برای راه اندازی سایت. این سرور به طور اتوماتیک هنگامی که شما در کد خود تغییر ایجاد می کنید آن تغییرات را در خودش بروز سازی می کند بدون اینکه شما نیازی به راه اندازی مجدد آن پیدا کنید.

برای اجرا کردن سرور، به درون پوشه پروژه رفته (cd mysite)، و دستور زیر را اجرا کنید:

python manage.py runserver

شما چیزی شبیه به این خواهید دید:

Validating models...
0 errors found.

Django version 1.0, using settings 'mysite.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

دستور بالا سرور را به صورت داخلی روی پورت 8000 راه اندازی می کند، که تنها بر روی رایانه شما قابل دسترس خواهد بود. حالا که سرور در حال اجراست، مسیر http://127.0.0.1:8000/ را روی مرورگر خود مشاهده کنید. شما یک صفحه مانند شکل 1-2 مشاهده خواهید کرد.

do.php?imgf=156976754698291.jpg

شکل 1-2

در پایان، نکته مهمی که درباره سرور خود جنگو می باشد را بیان می کنیم، اگر چه این سرور برای توسعه مناسب می باشد، ولی در یک زمان تنها می تواند یک درخواست (request) جواب گو باشد، و همچنین از نظر امنیتی نیز کامل نیست.

تغییر دادن هاست یا پورت

به طور پیشفرض، دستور runserver سرور را روی پورت 8000 اجرا می کند، که تنها برای اتصالات محلی می باشد. اگر پورت سرور را تغییر دهید، آنرا به صورت آرگومان به خط فرمان ارسال کنید.

python manage.py runserver 8080

با تعیین آدرس IP، شما می توانید به سرور بگویید تا به اتصالات غیر محلی (non-local) اجازه اتصال دهد. این بویژه زمانی مفید خواهد بود که شما بخواهید سایت را با دیگر اعضای تیم خود به اشتراک بگذارید. IP آدرس 0.0.0.0 به سرور می گوید به هر رابط شبکه گوش کن:

python manage.py runserver  0.0.0.0:8000

هنگامی که شما دستور فوق را اجرا کنید، رایانه های دیگر در شبکه محلی شما قادر خواهند بود سایت جنگوی شما را با آدرس IP شما در مرورگر خود تماشا کنند، مانند http://192.168.1.103:8000/. (نکته این که شما باید تنظیمات شبکه خودتان را برای تعیین آدرس IP خودتان روی شبکه محلی داشته باشید. کاربران یونیکس، باید سعی کنند ifconfig را در یک خط فرمان برای گرفتن اطلاعات اجرا کنند. کاربران ویندوز باید با دستور ipconfig این کار را انجام دهند.)

به اشتراک گذاری این ارسال


لینک به ارسال
به اشتراک گذاری در سایت های دیگر

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

در این آموزش شما قواعد ساختن صفحات پویا (داینامیک) از طریق فریم ورک یا چارچوب جنگو (Django) و همچنین استفاده از view ها و URLconf ها را خواهید آموخت.

اولین برنامه جنگو

به عنوان اولین قدم بیایید یک صفحه وب که خروجی معروف Hello world را چاپ می کند را بسازیم.

در صورتیکه شما یک Hello world ساده را بدون استفاده از یک فریم ورک یا چارچوب وب ایجاد کرده باشید، به صورت ساده عبارت Hello world را درون یک فایل متنی وارد کرده و آنرا به عنوان مثال با نام hello.html، در یک پوشه جایی درون وب سرور ذخیره می کردید. دقت کنید که شما دو موضوع کلیدی را در مورد آن صفحه وب در نظر می گیرید: محتویات آن (رشته "Hello world") و URL آن (http://www.example.com/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، باید کدام کد فراخوانی شود. برای مثال هنگامی که شما آدرس

محتوای مخفی

    برای مشاهده محتوای مخفی می بایست در انجمن ثبت نام کنید.
را درخواست می کنید، تابع 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" که در شکل زیر نشان داده شده است مشاهده کنید. جنگو این پیام را نشان می دهد زیرا درخواست شما URL ای می باشد که در URLconf تعریف نشده است.

do.php?imgf=157246186899341.jpg

پیام خطای 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 را درون سایت

    محتوای مخفی

      برای مشاهده محتوای مخفی می بایست در انجمن ثبت نام کنید.
    تنها با یک کلیک کپی می کند و شما می توانید URL آن سایت را برای مشاهده ی traceback شما به دیگران بدهید.
  • در قسمت پایین دکمه ی "Share this traceback on a public Web site" اطلاعات traceback را درون سایت

    محتوای مخفی

      برای مشاهده محتوای مخفی می بایست در انجمن ثبت نام کنید.
    تنها با یک کلیک کپی می کند و شما می توانید 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 می باشد. کافی است.

به اشتراک گذاری این ارسال


لینک به ارسال
به اشتراک گذاری در سایت های دیگر

Template ها در جنگو (Django)

در آموزش view و urlconf دیدیم که کد HTML را می توانیم به صورت یک متن به طور مستقیم درون view بگنجانیم:

def current_datetime(request):
    now = datetime.datetime.now()
    html = "<html><body>It is now %s.</body></html>" % now
    return HttpResponse(html)

یکی از معایب روش بالا این است که برای هرگونه اعمال تغییر در طراحی صفحه نیازمند تغییر در کد پایتون می باشیم. می دانیم که طراحی سایت نیازمند تغییرات متعددی است در نتیجه استفاده از این روش موجب تغییر به مراتب بیشتر کد پایتون می باشد. بنابراین مناسب تر آن است که طراحی صفحه و ایجاد تغییرات در آن، نیازی به اصلاح و تغییر کد پایتون نداشته باشد.
نوشتن کد پایتون و طراحی HTML دو موضوع متفاوت از یکدیگر بوده و نظم و قانون مربوط به خود را دارند، و بیشتر محیط های توسعه ی وب حرفه ای این مسئولیت ها را جدا کرده و به افراد جدا (حتی به بخش و حوزه ی جدا) واگذار می کنند. طراحان و کد نویسان HTML/CSS نباید نیاز به ویرایش کد پایتون برای کار خود داشته باشند.
اگر طراحان بتوانند بر روی کدهای html در همان زمان که برنامه نویسان در حال کار با کدهای پایتون هستند، کار کنند. بهترین بهره وری از کار را خواهیم داشت زیرا دیگر نیازی نیست که یک فرد منتظر تمام کردن کد توسط فردی دیگر باشد چرا که در یک فایل کد HTML و کد پایتون هر دو به صورت جداگانه وجود دارد.
این حالت را (که کد HTML به طور مستقیم درون view قرار بگیرد) به اصطلاح hard-code می نامند، بدین معنا که کد HTML به صورت غیر مستقل داخل کد پایتون قرار دارد و تغییر آن کار ساده ای نخواهد بود.

اگرچه تکنیک فوق برای توضیح و یا شرح نحوه ی کارکرد view در آموزش قبلی مناسب به نظر می آید، اما به دلایل ذکر شده در بالا این ایده ی خوبی نیست که کد HTML به صورت hard-code به طور مستقیم درون view قرار گیرد.

همانطور که ذکر کردیم جدا کردن طراحی صفحه از کد پایتون مربوط به آن بسیار واضح و منطقی است. با استفاده از سیستم template جنگو می توانیم کدهای مربوط به HTML را از کد پایتون جداسازی کنیم، که در این آموزش از کتاب، به این موضوع خواهیم پرداخت.

قواعد Template System

template جنگو یک رشته ای از متن می باشد که برای جدا کردن نمایش یک سند از داده ی خود در نظر گرفته شده است. قالب حفره ها و تکه کدهای (تگ های template) مختلفی را تعریف می کند که نحوه ای که سند باید نمایش داده شود را تنظیم می کند. به طور معمول template ها برای تولید HTML استفاده می شوند، اما template های جنگو به همان اندازه در تولید هر فرمت متنی توانا هستند.

بیایید با یک مثال ساده شروع کرده و آن را مورد بررسی قرار دهیم:

<html>
<head><title>Ordering notice</title></head>

<body>

<h1>Ordering notice</h1>

<p>Dear {{ person_name }},</p>

<p>Thanks for placing an order from {{ company }}. It's scheduled to
ship on {{ ship_date|date:"F j, Y" }}.</p>

<p>Here are the items you've ordered:</p>

<ul>
{% for item in item_list %}
    <li>{{ item }}</li>
{% endfor %}
</ul>

{% if ordered_warranty %}
    <p>Your warranty information will be included in the packaging.</p>
{% else %}
    <p>You didn't order a warranty, so you're on your own when
    the products inevitably stop working.</p>
{% endif %}

<p>Sincerely,<br />{{ company }}</p>

</body>
</html>

template فوق یک HTML بنیادی با برخی متغیرها و تگ های template استفاده شده در آن می باشد. بیایید template فوق را مورد بررسی قرار دهیم.

هر متنی که داخل دو آکولاد قرار گرفته باشد (مانند {{ person_name }}) یک متغیر است. این بدین معنی می باشد که "مقدار ارزش متغیر با نام داده شده را درج کند" به عبارت دیگر اگر (person_name) داخل آکولاد قرار نگیرد خروجی Dear person_name را درج خواهد کرد و نام شخص را به ما نشان نمی دهد. (چطور ارزش متغیرها را می توان تعیین کرد؟ در این فصل خواهید فهمید.)
هر متنی که داخل یک آکولاد و علامت درصد قرار بگیرد (مانند {% if ordered_warranty %}) یک تگ template می باشد. تعریف یک تگ بسیار وسیع می باشد: تگ به template می گوید که "کاری را انجام بده".

مثال template فوق حاوی یک تگ for ({% for item in item_list %}) و یک تگ if ({% if ordered_warranty %}) می باشد.

تگ for بسیار شبیه به عبارت for در پایتون می باشد، که به ما اجازه می دهد درون سرتاسر آیتم های یک لیست به ترتیب بگردید و یا تغییراتی را بر روی همه یا یک سری از آیتم ها اعمال کنیم. تگ if همانطور که انتظار آن می رود، به صورت یک عبارت if منطقی عمل می کند. در این مورد خاص، تگ مورد نظر بررسی می کند که آیا ارزش متغیر ordered_warranty ،True می باشد یا خیر. اگر بدین صورت بود یعنی اگر مقدار آن True بود، سیستم template جنگو هر آنچه که بین {% if ordered_warranty %} و {% else %} باشد را نمایش خواهد داد، در غیر اینصورت سیستم template جنگو هر آنچه را که بین {% else %} و {% endif %} باشد را نمایش خواهد داد. توجه داشته باشید که تک {% else %} کاملا اختیاری می باشد.

نهایتا، پاراگراف دوم template فوق حاوی یک مثال از filter می باشد، که مناسب ترین روش برای تغییر قالب بندی (formatting) یک متغیر می باشد. در این مثال، {{ ship_date|date:"F j, Y" }}، متغیر ship_date به فیلتر date ارسال شده، و آرگومان "F j, Y" نیز به فیلتر date داده شده است. فیلتر date تاریخ را بر اساس آرگومان تغیین شده قالب بندی می کند. فیلترها با استفاده از علامت (|) مربوط می شوند، همانند کاربرد آن در یونیکس.

هر قالب جنگو به چندین تگ و فیلتر داخلی (built-in) دسترسی دارد، تعدادی از آن ها در ادامه فصل های کتاب مورد بحث قرار خواهند گرفت. همچنین این امکان وجود دارد که فیلتر و تگ برای خودتان ایجاد کنید.

استفاده از template جنگو

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

در اینجا ابتدایی ترین راه که می تونید قالب جنگو را در کد پایتون استفاده کنید آمده است:

ساختن شیء Template از طریق ایجاد کد قالب به صورت رشته.

فراخوانی متد render() از شیء Template با دادن یک مجموعه ای از متغیرها (the context). این متد قالب را به صورت رشته ی کامل با تمام متغیرها و تگ های قالب بر می گرداند.

به کد زیر توجه کنید:

>>> from django import template
>>> t = template.Template('My name is {{ name }}.')
>>> c = template.Context({'name': 'Adrian'})
>>> print t.render(c)
My name is Adrian.
>>> c = template.Context({'name': 'Fred'})
>>> print t.render(c)
My name is Fred.

در بخش بعدی جزئیات هر خط از کد بالا شرح داده خواهد شد.

ساختن شیء های template

ساده ترین راه برای ساختن شیء Template به طور مستقیم به عنوان نمونه ذکر کردن آن می باشد. کلاس Template درون ماژول django.template می باشد و سازنده ی (cunstructor) آن یک آرگومان دریافت می کند که کد خام template می باشد. بیایید نمونه آن را درون interactive interpreter پایتون مشاهده کنیم.

درون دایرکتوری mysite ساخته شده توسط دستور django-admin.py startproject (بیان شده در شروع کار جنگو)، دستور python manage.py shell را تایپ کرده و interactive interpreter را باز کنید.

خط فرمان ویژه پایتون

در صورتیکه در گذشته از پایتون استفاده کرده باشید، ممکن است متعجب شوید که چرا بجای فرمان python، از فرمان python manage.py shell استفاده شده است. هر دو فرمان باعث باز شدن interactive interpreter خواهد شد، با این تفاوت که دستور manage.py shell قبل از آغاز شدن interpreter، به جنگو اعلام می کند که از کدام فایل تنظیمات استفاده کند. بسیاری از قسمت ها در جنگو، حاوی سیستم template می باشد، که شما قادر به استفاده از آن ها نخواهید بود مگر آنکه فریم ورک یا چارچوب بداند از کدام تنظیمات استفاده کند.

اگر در مورد نحوه ی انجام کار فوق کنجکاو هستید، در اینجا توضیح نحوه ی عملکرد آن در پشت صحنه، وجود دارد. جنگو به دنبال یک متغیر محیطی با نام DJANGO_SETTINGS_MODULE می گردد که باید در مسیر import فایل settings.py شما قرار گرفته باشد. به عنوان مثال، با فرض اینکه mysite مسیر پایتون شما می باشد، DJANGO_SETTINGS_MODULE ممکن است در 'mysite.settings'، قرار گرفته باشد.

هنگامی که شما فرمان python manage.py shell را اجرا می کنید، فرمان مذکور به DJANGO_SETTINGS_MODULE توجه می کند. شما را تشویق می کنیم برای به حداقل رساندن پیچیدگی و تنظیماتی که باید انجام دهید از python manage.py shell در این مثال ها استفاده کنید

هنگامی که با جنگو بیشتر آشنا شوید، احتمالا استفاده از manage.py shell را کنار گذاشته و خودتان به صورت دستی DJANGO_SETTINGS_MODULE را در .bash_profile یا محیط های دیگر تنظیم خواهید داد.

اجازه دهید با اصول اولیه سیستم template جنگو آشنا شویم:

>>> from django.template import Template
>>> t = Template('My name is {{ name }}.')
>>> print t

با وارد کردن کد فوق، چیزی شبیه به این مشاهده خواهید کرد:

<django.template.Template object at 0xb7d5f24c>

عبارت 0xb7d5f24c هویت شیء پایتون می باشد که در هر زمان متفاوت می باشد واز حیطه بحث ما خارج می باشد.

هنگامی که یک شیء Template ایجاد می کنید، سیستم template جنگو کد template را به صورت داخلی کامپایل می کند، و شکل بهینه آن برای ارائه دادن (rendering) آماده می شود، اما در صورتیکه کد template شما دارای اشکال باشد، فراخوانی Template() موجب بروز خطا خواهد شد:

>>> from django.template import Template
>>> t = Template('{% notatag %}')
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  ...
django.template.TemplateSyntaxError: Invalid block tag: 'notatag'

در موارد زیر خطای TemplateSyntaxError رخ خواهد داد:

  • تگ های نا معتبر
  • آرگومان های نا معتبر درون تگ های معتبر
  • فیلترهای نا معتبر
  • آرگومان های نا معتبر درون فیلترهای معتبر
  • کد template نا معتبر
  • تگ های بسته نشده (تگ های for باید بسته شوند)

ارائه دادن (rendering) یک Template

هنگامی که شما یک شیء Template دارید، می توانید از طریق یک context به آن داده ارسال کنید. context مجموعه ای از نام متغیرهای template و ارزش های آن ها می باشد. Template با استفاده از context متغیرهای خودش را با ارزش مورد نظر جایگزین کرده و تگ ها را نیز اجرا می کند.

context با کلاس Context در جنگو نشان داده می شود، که درون ماژول django.template قرار دارد. سازنده ی آن یک آرگومان اختیاری دریافت می کند: یک دیکشنری نام های متغیر و ارزش های آن. برای پر کردن template متد render() شیء Template را با context فراخوانی می کنیم:

>>> from django.template import Context, Template
>>> t = Template('My name is {{ name }}.')
>>> c = Context({'name': 'Stephane'})
>>> t.render(c)
u'My name is Stephane.'

نکته ای که باید در اینجا اشاره کنیم این است که مقدار برگشتی (t.render(c یک رشته ی معمولی پایتون نمی باشد، بلکه یک شیء یونیکد است. که با نمایش حرف u در ابتدای آن نشان داده می شود. جنگو در سراسر فریم ورک یا چارچوب به جای رشته ی معمولی پایتون از شیء های یونیکد استفاده می کند. پشتیبانی یونیکد جنگو نسبتااز طیف گسترده ای از حروف برای برنامه های شما بدون زحمت و دردسر پشتیبانی می کند.

دیکشنری ها و context ها

دیکشنری پایتون یک رابطه ی بین کلید ها و ارزش های متغیر می باشد. یک context همانند یک دیکشنری می باشد، با این تفاوت که حالت تابعی به خود گرفته است و در آموزش template پیشرفته مورد بحث قرار خواهد گرفت.

نام متغیرها باید با حروف الفبای انگلیسی شروع شوند (A-Z or a-z) و می توانند حاوی حروف الفبا، عدد، خط تیره (_) و نقطه باشند (نقطه ها مورد ویژه ای هستند که بعدا راجع به آن صحبت خواهیم کرد.)، همچنین نام متغیرها به حروف کوچک و بزرگ حساس می باشند.

در اینجا یک مثال از کامپایل template و rendering آن شبیه به مثالی که در ابتدای فصل مشاهده کردید وجود دارد.

>>> from django.template import Template, Context
>>> raw_template = """<p>Dear {{ person_name }},</p>
...
... <p>Thanks for placing an order from {{ company }}. It's scheduled to
... ship on {{ ship_date|date:"F j, Y" }}.</p>
...
... {% if ordered_warranty %}
... <p>Your warranty information will be included in the packaging.</p>
... {% else %}
... <p>You didn't order a warranty, so you're on your own when
... the products inevitably stop working.</p>
... {% endif %}
...
... <p>Sincerely,<br />{{ company }}</p>"""
>>> t = Template(raw_template)
>>> import datetime
>>> c = Context({'person_name': 'John Smith',
...     'company': 'Outdoor Equipment',
...     'ship_date': datetime.date(2009, 4, 2),
...     'ordered_warranty': False})
>>> t.render(c)
u"<p>Dear John Smith,</p>\n\n<p>Thanks for placing an order from Outdoor
Equipment. It's scheduled to\nship on April 2, 2009.</p>\n\n\n<p>You
didn't order a warranty, so you're on your own when\nthe products
inevitably stop working.</p>\n\n\n<p>Sincerely,<br />Outdoor Equipment
</p>"

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

  • در ابتدا کلاس های Template و Context از ماژول django.template به درون برنامه import شده اند.
  • متن خام template درون متغیر raw_template ذخیره شده است. باید توجه داشته باشیم که در رشته های معمولی تنها می توان از یک خط استفاده کرد. واگر بخواهیم از رشته های چند خطی استفاده کنیم بایستی از علامت (" " ") برای رشته ی مورد نظر استفاده کنیم.
  • در قدم بعدی، یک شیء template به نام t ساخته شده و رشته ی raw_template به سازنده ی آن ارسال شده است.
  • ماژول datetime از کتابخانه ی استاندارد پایتون درون برنامه import شده است، چرا که در ادامه ی کار مورد استفاده قرار خواهد گرفت.
  • یک شیء context به نام c ساخته شده است، سازنده ی context یک دیکشنری پایتون دریافت می کند که نام های متغیرها را با ارزش ها مرتبط می سازد. برای مثال در اینجا person_name به 'John Smith' و company به 'Outdoor Equipment'، و غیره مرتبط شده است.
  • در نهایت، متد render() از شیء template فراخوانی شده، و context به آن ارسال شده است. این متد یک template قابل ارائه را بر می گرداند، بدین معنی که تمام متغیرها با ارزش و مقدار واقعی جایگزین شده و تمام تگ های template اجرا شده اند.
  • توجه داشته باشید که پاراگراف "You didn't order a warranty" نمایش داده می شود چرا که متغیر ordered_warranty به صورت False ارزیابی شده است. همچنین توجه کنید تاریخ April 2, 2009 به حالت آن قالب بندی که برای آن مشخص شده است نمایش داده شده است (F j, Y).
  • در صورتیکه تازه با زبان برنامه نویسی پایتون آشنا شده باشید، ممکن است تعجب کنید که رشته ی خروجی به رفتن به خط بعد حرف (\n) را نمایش می دهد. دلیل زیرکی interactive interpreter پایتون می باشد: فراخوانی (t.render(c یک رشته بر می گرداند، و interactive interpreter به صورت پیشفرض به جای شکل چاپی رشته شکل نمایشی آن را نشان می دهد. در صورتیکه می خواهید رشته با حالت چاپی نمایش داده شود، یعنی بجای حرف (\n) رشته به خط بعد برود، می توانید از عبارت print استفاده کنید: (print t.render(c.

مسائل فوق اصول استفاده از template system جنگو می باشد: نوشتن یک رشته ی template، ساختن شیء Template، ساختن Context و فراخوانی متد render().

Context های چندگانه در یک Template

با یک شیء Template می توانید چندین context را ارائه دهید. برای مثال:

>>> from django.template import Template, Context
>>> t = Template('Hello, {{ name }}')
>>> print t.render(Context({'name': 'John'}))
Hello, John
>>> print t.render(Context({'name': 'Julie'}))
Hello, Julie
>>> print t.render(Context({'name': 'Pat'}))
Hello, Pat

هر گاه مانند مثال فوق از template همسان برای چندین context استفاده می کنید ، بسیار بهینه تر می باشد اگر یک مرتبه یک شیء Template ساخته و سپس متد render() را برای چند بار فراخوانی کنید:

# Bad
for name in ('John', 'Julie', 'Pat'):
    t = Template('Hello, {{ name }}')
    print t.render(Context({'name': name}))

# Good
t = Template('Hello, {{ name }}')
for name in ('John', 'Julie', 'Pat'):
    print t.render(Context({'name': name}))

تجزیه ی template جنگو کاملا سریع می باشد. در پشت صحنه بیشترین تجزیه در جنگو از طریق فراخوانی یک regular expression می باشد. این متضاد با موتور های template ای می باشد که پایه اساس آنها XML باشد، چرا که بسیار کند تر از موتور template جنگو می باشند.

متغیرهای دیگر قابل ارسال به Context

مثال های آورده شده تاکنون، یک مقدار ساده را به context ها ارسال کرده اند که به غیر مثال datetime.date غالبا رشته بوده اند. در صورتی که template system جنگو با ظرافت خاصی تعداد بیشتری از داده های با ساختار پیچیده را مانند لیست ها، دیکشنری ها و شیء های ساخته شده را نیز کنترل می کند.

کلید گذشتن از داده های با ساختار پیچیده در template های جنگو حرف نقطه ( . ) می باشد، با استفاده از نقطه می توان به کلیدهای دیکشنری، attribute ها، متد ها و اندیس های یک شیء نیز دسترسی داشت.

بهترین توضیح استفاده از چند مثال می باشد. برای نمونه، فرض کنید شما یک دیکشنری پایتون را به یک template ارسال کرده اید. برای دسترسی به مقادیر دیکشنری مورد نظر با کلید دیکشنری از یک نقطه استفاده کنید:

>>> from django.template import Template, Context
>>> person = {'name': 'Sally', 'age': '43'}
>>> t = Template('{{ person.name }} is {{ person.age }} years old.')
>>> c = Context({'person': person})
>>> t.render(c)
u'Sally is 43 years old.'

به طور مشابه، نقطه ها همچنین اجازه می دهند به attribute های شیء نیز دسترسی پیدا کنید. برای مثال، شیء datetime.date پایتون دارای attribute های year، month و day می باشد، می توان با استفاده از یک نقطه به attribute های آن در template جنگو دسترسی پیدا کرد:

>>> from django.template import Template, Context
>>> import datetime
>>> d = datetime.date(1993, 5, 2)
>>> d.year
1993
>>> d.month
5
>>> d.day
2
>>> t = Template('The month is {{ date.month }} and the year is {{ date.year }}.')
>>> c = Context({'date': d})
>>> t.render(c)
u'The month is 5 and the year is 1993.'

در مثال زیر از یک کلاس ساخته شده استفاده شده است که نشان می دهد از نقطه ها می توان جهت دسترسی به شیء های دلخواه نیز استفاده کرد:

>>> from django.template import Template, Context
>>> class Person(object):
...     def __init__(self, first_name, last_name):
...         self.first_name, self.last_name = first_name, last_name
>>> t = Template('Hello, {{ person.first_name }} {{ person.last_name }}.')
>>> c = Context({'person': Person('John', 'Smith')})
>>> t.render(c)
u'Hello, John Smith.'

از طریق نقطه ها همچنین می توانند به متدهای درون یک شیء نیز دسترسی پیدا کرد. برای مثال هر رشته ی پایتون، دارای متدهای upper() و isdigit() می باشد، و می توان آن ها را در template جنگو فراخوانی و استفاده کرد:

>>> from django.template import Template, Context
>>> t = Template('{{ var }} -- {{ var.upper }} -- {{ var.isdigit }}')
>>> t.render(Context({'var': 'hello'}))
u'hello -- HELLO -- False'
>>> t.render(Context({'var': '123'}))
u'123 -- 123 -- True'

توجه داشته باشید که پرانتزهای مربوط به متدها را در template نباید به کار برد. همچنین نمی توان به متدها آرگومان ارسال کرد؛ شما می توانید تنها متدهایی را فراخوانی کنید که هیچ آرگومانی دریافت نمی کنند. (کمی بعد در این فصل این فلسفه توضیح داده خواهد شد.)

همچنین نقطه ها برای دسترسی به اندیس های لیست نیز استفاده می شوند، برای مثال:

>>> from django.template import Template, Context
>>> t = Template('Item 2 is {{ items.2 }}.')
>>> c = Context({'items': ['apples', 'bananas', 'carrots']})
>>> t.render(c)
u'Item 2 is carrots.'

اندیس های منفی لیست را در template نمی توان استفاده کرد، برای مثال، متغیر {{ items.-1 }} موجب بروز خطای TemplateSyntaxError خواهد شد.

لیست های پایتون

یادآوری: لیست های پایتون با اندیس صفر شروع می شوند، بدین معنی که اولین اندیس صفر، دومین اندیس یک و به همین ترتیب ادامه خواهد داشت.

کاربرد نقطه ها در template را می توان بدین شکل خلاصه کرد: هنگامی که template system با یک نقطه در مقابل نام یک متغیر برخورد می کند، به دنبال مسائل زیر جستجو خواهد کرد:

دیکشنری (مانند foo["bar"])
attribute (مانند foo.bar)
فراخوانی متد (مانند foo.bar())
اندیس لیست (مانند foo[2])
نقطه ها می توانند به صورت تودرتو نیز استفاده شوند. به عنوان نمونه،مثال زیر از عبارت {{ person.name.upper }} استفاده کرده است که ابتدا درون یک دیکشنری به جستجو می پردازد (person['name']) سپس یک متد را فراخوانی می کند (upper()):

>>> from django.template import Template, Context
>>> person = {'name': 'Sally', 'age': '43'}
>>> t = Template('{{ person.name.upper }} is {{ person.age }} years old.')
>>> c = Context({'person': person})
>>> t.render(c)
u'SALLY is 43 years old.'

فراخوانی متد و رفتار آن

فراخوانی متد کمی پیچیده تر از دیگر انواع جستجو می باشد. در زیر مواردی که باید به خاطر داشته باشیم بیان شده است:

اگر در طی فراخوانی متد، یک متد خطایی تولید کرد، آن خطا اعمال خواهد شد مگر آنکه کلاس آن خطا دارای attribute ای به نام silent‑variable‑failure با مقدار True باشد. در صورتیکه کلاس خطا دارای attribute مورد نظر باشد، متغیر درون template مقدار خالی را نشان خواهد داد، به عنوان مثال:

>>> t = Template("My name is {{ person.first_name }}.")
>>> class PersonClass3:
...     def first_name(self):
...         raise AssertionError, "foo"
>>> p = PersonClass3()
>>> t.render(Context({"person": p}))
Traceback (most recent call last):
...
AssertionError: foo

>>> class SilentAssertionError(AssertionError):
...     silent_variable_failure = True
>>> class PersonClass4:
...     def first_name(self):
...         raise SilentAssertionError
>>> p = PersonClass4()
>>> t.render(Context({"person": p}))
u'My name is .'

فراخوانی متد تنها زمانی کار خواهد کرد که متد مورد نظر هیچ آرگومان ضروری نداشته باشد. در غیر اینصورت system در پی نوع دیگر بعدی (اندیس لیست) خواهد رفت.
بدیهی است که برخی از متدها دارای اثرات زیان آور، و حتی می توانند یک حفره ی امنیتی باشند، و اجازه ی دسترسی template system به آن متدها کاری ناشیانه می باشد.
به عنوان نمونه، فرض کنید شما یک شیء BankAccount دارید که دارای یک متد delete() می باشد. اگر یک template حاوی چیزی شبیه به این باشد {{ account.delete }}، در صورتیکه account شیء BankAccount باشد، هنگام ارائه template شیء مورد نظر حذف خواهد شد!

برای جلوگیری از این موضوع، باید بدین شکل عمل کرد:

def delete(self):
    # Delete the account
delete.alters_data = True

template system هر متدی که با روش فوق مشخص شده باشد را اجرا نخواهد کرد. در مثال فوق، اگر یک template حاوی {{ account.delete }} باشد و متد delete() دارای alter_data=True باشد، در این صورت متد delete() هنگام ارائه ی template اجرا نخواهد شد و بجای آن، بی صدا رد خواهد شد.

نحوه ی کنترل متغیرهای نا معتبر

به صورت پیشفرض، اگر یک متغیر وجود نداشته باشد، template system آن را به صورت یک رشته ی خالی ارائه می دهد. به عنوان مثال:

>>> from django.template import Template, Context
>>> t = Template('Your name is {{ name }}.')
>>> t.render(Context())
u'Your name is .'
>>> t.render(Context({'var': 'hello'}))
u'Your name is .'
>>> t.render(Context({'NAME': 'hello'}))
u'Your name is .'
>>> t.render(Context({'Name': 'hello'}))
u'Your name is .'

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

بازی با شیء های Context

در اغلب مواقع، شیء های Context را با ارسال داده ی دیکشنری به آن تعریف می کنند. می توان ایتم های شیء Context را بعد از تعریف نیز مانند دیکشنری استاندارد پایتون حذف یا اضافه کرد:

>>> from django.template import Context
>>> c = Context({"foo": "bar"})
>>> c['foo']
'bar'
>>> del c['foo']
>>> c['foo']
Traceback (most recent call last):
  ...
KeyError: 'foo'
>>> c['newvariable'] = 'hello'
>>> c['newvariable']
'hello'

 

به اشتراک گذاری این ارسال


لینک به ارسال
به اشتراک گذاری در سایت های دیگر

تگ ها و فیلترهای اولیه Template

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

تگ if/else

تگ {% if %} یک متغیر را مورد ارزیابی قرار می دهد، و در صورتی که مقدار متغیر True (مانند وجود داشتن، خالی نبودن، و مقدار False نبودن) بود، سیستم template جنگو هر آنچه را که بین تگ {% if %} و {% endif %} باشد را نمایش خواهد داد، مانند:

{% if today_is_weekend %}
    <p>Welcome to the weekend!</p>
{% endif %}

استفاده از یک تگ {% else %} اختیاری می باشد:

{% if today_is_weekend %}
    <p>Welcome to the weekend!</p>
{% else %}
    <p>Get back to work.</p>
{% endif %}

مقدار True و False در پایتون

در پایتون و template system جنگو شیء های زیر به منزله ی False تلقی می شوند:

  • یک لیست خالی ([])
  • یک تاپل خالی (())
  • یک دیکشنری خالی ({})
  • یک رشته ی خالی ('')
  • عدد صفر (0)
  • شیء ویژه ی None
  • شیء False
  • شیء های ساخته شده که رفتار داده ی Boolean خود را تعریف می کنند.

به غیر از موارد فوق همه چیز True ارزیابی می شود.

تگ {% if %} عبارات and، or و not را برای آزمودن چندین متغیر، یا منفی کردن متغیر داده شده قبول می کند. به عنوان مثال:

{% if athlete_list and coach_list %}
    Both athletes and coaches are available.
{% endif %}

{% if not athlete_list %}
    There are no athletes.
{% endif %}

{% if athlete_list or coach_list %}
    There are some athletes or some coaches.
{% endif %}

{% if not athlete_list or coach_list %}
    There are no athletes or there are some coaches.
{% endif %}

{% if athlete_list and not coach_list %}
    There are some athletes and absolutely no coaches.
{% endif %}

تگ های {% if %} اجازه ی نمی دهند عبارات and و or در یک تگ همسان استفاده شوند، دلیل آن است که ترتیب محاسبه ی آن ها مبهم خواهد بود. برای عنوان مثال کد زیر نا معتبر است:

{% if athlete_list and coach_list or cheerleader_list %}

استفاده از پرانتز برای کنترل کردن ترتیب عملکردها در سیستم template پشتیبانی نمی شود. در صورتیکه نیاز به استفاده از پرانتز بود، می توان اعمال منطقی را بیرون از template انجام داده و نتیجه را به عنوان متغیر template ارسال کرد، و یا آنکه از تگهای {% if %} به صورت تودرتو استفاده کرد. مانند:

{% if athlete_list %}
    {% if coach_list or cheerleader_list %}
        We have athletes, and either coaches or cheerleaders!
    {% endif %}
{% endif %}

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

{% if athlete_list or coach_list or parent_list or teacher_list %}

تگی با نام {% elif %} وجود ندارد. می توان از تگ های {% if %} تودرتو برای این کار استفاده کرد:

{% if athlete_list %}
    <p>Here are the athletes: {{ athlete_list }}.</p>
{% else %}
    <p>No athletes are available.</p>
    {% if coach_list %}
        <p>Here are the coaches: {{ coach_list }}.</p>
    {% endif %}
{% endif %}

اطمینان حاصل کنید که هر تگ {% if %} با یک تگ {% endif %} بسته شده است. در غیر اینصورت خطای TemplateSyntaxError ایجاد خواهد شد.

تگ for

تگ {% for %} اجازه می دهد تا به ترتیب در سراسر ایتم های یک لیست یا ... چرخ بزنید. همانند عبارت for در پایتون؛ for X in Y، Y لیست مورد نظر می باشد و X در هر نوبت از حلقه یکی از ایتم های Y می باشد. سیستم template در هر بار، هر چیزی که بین {% for %} و {% endfor %} باشد را اجرا می کند.

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

<ul>
{% for athlete in athlete_list %}
    <li>{{ athlete.name }}</li>
{% endfor %}
</ul>

اضافه کردن کلمه ی reversed موجب می شود که حلقه به صورت بر عکس جریان پیدا کند:

{% for athlete in athlete_list reversed %}
...
{% endfor %}

می توان از تگ های {% for %} به صورت تودرتو نیز استفاده کرد:

{% for athlete in athlete_list %}
    <h1>{{ athlete.name }}</h1>
    <ul>
    {% for sport in athlete.sports_played %}
        <li>{{ sport }}</li>
    {% endfor %}
    </ul>
{% endfor %}

می توان از الگوی کد زیر برای بررسی کردن اندازه ی لیست استفاده کرد، همچنین در صورت خالی بودن لیست، می توان یک پیام را نمایش داد:

{% if athlete_list %}
    {% for athlete in athlete_list %}
        <p>{{ athlete.name }}</p>
    {% endfor %}
{% else %}
    <p>There are no athletes. Only computer programmers.</p>
{% endif %}

به دلیل آنکه الگوی فوق بسیار رایج می باشد، تگ for از یک تگ {% empty %} پشتیبانی می کند که به شما اجازه می دهد در صورت خالی بود لیست آنچرا که می خواهید تعریف کنید. مثال زیر معادل کد قبلی می باشد:

{% for athlete in athlete_list %}
    <p>{{ athlete.name }}</p>
{% empty %}
    <p>There are no athletes. Only computer programmers.</p>
{% endfor %}

در سیستم template از عبارت break پشتیبانی نمی شود، بدین معنی که عبارتی برای شکستن روال عادی حلقه وجود ندارد. برای انجام چنین کاری می توان متغیر لیست را طوری تغییر داد که تنها حاوی مقادیری باشد که می خواهیم حلقه در آن بچرخد. به طور مشابه از عبارت continue نیز پشتیبانی نمی شود (از عبارت continue در پایتون برای انتقال حرکت حلقه به طور ناگهانی به ابتدای حلقه استفاده می شود.) (در همین فصل، در بخش فلسفه ی محدودیت ها دلیل محدودیت های فوق را درخواهید یافت.)

داخل هر حلقه ی {% for %}، می توانید به یک متغیر template با نام forloop دسترسی پیدا کنید. این متغیر دارای attribute هایی می باشد که دارای اطلاعاتی در مورد حرکت حلقه می باشند.

forloop.counter همواره عددی را نمایش می دهد که حاکی از تعداد حرکت حلقه می باشد، همچنین مقدار آن در بار اول حرکت حلقه عدد یک (1) می باشد:

{% for item in todo_list %}
    <p>{{ forloop.counter }}: {{ item }}</p>
{% endfor %}

forloop.counter0 شبیه به forloop.counter می باشد، تنها فرق آن در این است که مقدار آن در اولین بار حرکت حلقه عدد صفر (0) می باشد.

forloop.revcounter همواره عددی را نمایش می دهد که حاکی از تعداد ایتم های باقی مانده ی حلقه می باشد. در اولین حرکت حلقه مقدار آن مجموع ایتم های موجود در لیست می باشد. در آخرین حرکت حلقه مقدار آن برابر با عدد یک (1) خواهد بود.

forloop.revcounter0 شبیه به forloop.revcounter می باشد، فرق آن در این است که در اولین حرکت حلقه مقدار آن به اندازه ی مجموع ایتم های لیست منهای یک می باشد و در آخرین حرکت حلقه مقدار آن برابر با عدد صفر (0) می باشد.

forloop.first یک مقدار Boolean می باشد. در صورتی که حلقه در حرکت اول خود باشد مقدار True بر می گرداند که برای موارد خاص مناسب می باشد:

{% for object in objects %}
    {% if forloop.first %}<li class="first">{% else %}<li>{% endif %}
    {{ object }}
    </li>
{% endfor %}

forloop.last یک مقدار Boolean می باشد. در صورتی که حلقه در حرکت آخر خود باشد مقدار True بر می گرداند، بیشترین استفاده از این attribute برای گذاشتن علامت (|) مابین لیستی از لینک ها می باشد:

{% for link in links %}
{{ link }}
{% if not forloop.last %} | {% endif %}
{% endfor %}

خروجی کد فوق چیزی شبیه به این خواهد بود:

Link1 | Link2 | Link3 | Link4

کاربرد رایج دیگر آن در قرار دادن ویرگول بین لغات می باشد:

Favorite places:
{% for p in places %}{{ p }}{% if not forloop.last %}, {% endif %}{% endfor %}

forloop.parentloop در حلقه های تودرتو استفاده می شود، بدین صورت که از طریق آن می توان درون حلقه ی داخلی به attribute های حلقه ی خارجی دسترسی پیدا کرد:

{% for country in countries %}
    <table>
    {% for city in country.city_list %}
        <tr>
        <td>Country #{{ forloop.parentloop.counter }}</td>
        <td>City #{{ forloop.counter }}</td>
        <td>{{ city }}</td>
        </tr>
    {% endfor %}
    </table>
{% endfor %}

متغیر forloop تنها در داخل حلقه ها در دسترس می باشد. بعد از آنکه template به تگ {% endfor %} می رسد، forloop از بین می رود.

context و متغیر forloop

هنگامی درون تگ {% for %} متغیری همنام با forloop داشته باشید، برای جلوگیری از overwrite شدن متغیر forloop جنگو فرض می کند که متغیر forloop شما forloop.parentloop می باشد. نیازی به نگرانی درباره ی این موضوع نمی باشد ولی توصیه می شود متغیری با نام forloop درون template استفاده نشود.

تگ ifequal/ifnotequal

سیستم template جنگو به طور عمد یک زبان برنامه نویسی کامل نمی باشد، در نتیجه اجازه ی اجرای عبارت های دلخواه پایتون را نمی دهد. (برای اطلاعات بیشتر نسبت به این محدودیت به بخش فلسفه ی محدودیت ها در همین بخش مراجعه کنید.) با این وجود، سیستم template جنگو برای مقایسه ی دو مقدار و نمایش چیزی در صورت مساوی بودن آن ها امکانات کاملی را پشتیبانی می کند (برای این منظور تگ {% ifequal %} در جنگو پیش بینی شده است.)

تگ {% ifequal %} دو مقدار را با هم مقایسه می کند و در صورت مساوی بودن، هر آنچه که مابین {% ifequal %} و {% endifequal %} می باشد را نمایش می دهد.

مثال زیر دو متغیر user و currentuser را مقایسه می کند:

{% ifequal user currentuser %}
    <h1>Welcome!</h1>
{% endifequal %}

همچنین می توان برای مقایسه ی دو مقدار از رشته ی خام درون تک کوتیشن و یا دابل کوتیشن استفاده کرد:

{% ifequal section 'sitenews' %}
    <h1>Site News</h1>
{% endifequal %}

{% ifequal section "community" %}
    <h1>Community</h1>
{% endifequal %}

درست مثل {% if %}، تگ {% ifequal %} نیز به طور اختیاری از تگ {% else %} پشتیبانی می کند:

{% ifequal section 'sitenews' %}
    <h1>Site News</h1>
{% else %}
    <h1>No News Here</h1>
{% endifequal %}

تنها متغیرهای template مانند؛ رشته ها، اعداد صحیح، و اعداد اعشاری می توانند به عنوان آرگومان {% ifequal %} قرار بگیرند. مثال های زیر معتبر می باشند:

{% ifequal variable 1 %}
{% ifequal variable 1.23 %}
{% ifequal variable 'foo' %}
{% ifequal variable "foo" %}

نوع داده های دیگر از قبیل دیکشنری ها، لیست ها، و Boolean، برای {% ifequal %} مجاز نمی باشند. مثال های زیر نامعتبر می باشند:

{% ifequal variable True %}
{% ifequal variable [1, 2, 3] %}
{% ifequal variable {'key': 'value'} %}

کامنت ها

تنها در HTML و پایتون، زبان template جنگو اجازه می دهد از کامنت ها استفاده شود. برای طراحی یک کامنت از {# #} استفاده می شود:

{# This is a comment #}

هنگامی که template اجرا می شود، کامنت ها هیچ خروجی ای نخواهند داشت.

از تگ فوق نمی توان برای کامنت های چند خطی استفاده کرد. مثال زیر به عنوان کامنت در نظر گرفته نمی شود و مانند دیگر عبارت template به حساب می اید:

This is a {# this is not
a comment #}
test.

در صورتیکه می خواهید از کامنت های چند خطی استفاده کنید؛ بایستی از تگ {% template comment %} مانند زیر استفاده کرد:

این یک کامنت چند خطی است.{% comment %} {% endcomment %}

فیلتر ها

همانطور پیش تر در این فصل توضیح داده شد، فیلترها روش های ساده ای برای تغییر مقدار متغیرها قبل از نمایش آن ها می باشند. فیلتر ها مانند زیر از علامت پایپ (|) استفاده می کنند:

{{ name|lower }}

کد فوق مقدار متغیر {{ name }} را بعد از عبور از فیلتر lower به حروف کوچک انگلیسی تبدیل می کند.

فیلترها می توانند به صورت زنجیروار نیز استفاده شوند؛ بدین صورت که آن ها می توانند پشت سر هم قرار بگیرند. در این حالت خروجی فیلتر قبلی وارد فیلتر بعدی شود. در مثال زیر اولین المان (element) لیست دریافت شده و به حروف بزرگ انگلیسی تبدیل می شود:

{{ my_list|first|upper }}

بعضی فیلترها آرگومان نیز دریافت می کنند. آرگومان فیلتر بعد از علامت (:) و همواره داخل دابل کوتیشن مانند مثال زیر قرار می گیرد:

{{ bio|truncatewords:"30" }}

مثال فوق 30 لغت اول متغیر bio را نمایش می دهد.

در زیر تعداد از مهمترین فیلتر ها توضیح داده شده اند:

  • addslashes: فیلتر مذکور یک علامت (\) قبل از علامت های (\)، تک کتیشن، و دابل کوتیشن قرار می دهد. این فیلتر برای متن های تولید شده درون رشته های جاوا اسکریپت مفید می باشند.
  • date: تاریخ و یا زمان را بر طبق قالبی که به آن به عنوان پارامتر داده می شود، مانند مثال زیر قالب بندی می کند:
{{ pub_date|date:"F j, Y" }}
  • length: طول یک مقدار را بر می گرداند. برای یک لیست، تعداد المان های آن و برای یک رشته تعداد حروف آن را بر می گرداند. (توسعه دهندگان پایتون توجه داشته باشند که این فیلتر برای هر شیء پایتونی که تحوه تعیین طول آن مشخص شده باشد کار می کند؛ مانند هر شیءی که دارای متد __len__() باشد.)

فلسفه محدودیت ها

اکنون که تا حد زیادی با زبان template جنگو آشنا شده اید، وقت آن رسیده است که به برخی از محدودیت های عمدی موجود در این زبان، به همراه فلسفه ی این که چرا این زبان به این شکل کار می کند اشاره کنیم.

قواعد زبان template جنگو ،بیشتر از هر جزء دیگری از برنامه های وب، بسیار خصوصی بوده و انتخاب های برنامه نویس بسیار گسترده می باشد. حقیقتی است که پایتون دارای پشتیبانی از ده ها زبان template سورس باز می باشد. که احتمالا هر کدام به این دلیل ساخته شده اند که توسعه دهنده معتقد بر این بوده است که زبان های template موجود، کافی نمی باشند. (در حقیقت، این فرمان عبور، برای یک توسعه دهنده پایتون، برای نوشتن زبان template خودش می باشد، اگر تاکنون همچین کاری نکرده اید، می توانید آن را امتحان، زیرا یک تمرین جالب می باشد.)

با در نظر گرفتن این موضوع، جالب است این را بدانید که جنگو نیازی به استفاده از زبان template آن ندارد؛ یعنی می توان از هر زبان template دیگر نیز استفاده کرد. زیرا جنگو برای این در نظر گرفته شده است که یک فریم ورک یا چارچوب وب full-stack، برای تهیه ی تمام قسمت های ضروری برای توسعه دهندگان باشد تا یک تولید کننده باشند، خیلی وقت ها استفاده از سیستم template جنگو (Django) مناسب تر از کتابخانه های templte دیگر پایتون می باشد، ولی این یک نیازمندی موکد نمی باشد. همانطور که در بخش اینده ی این آموزش از کتاب، "استفاده از template ها در view" مشاهده خواهید کرد، استفاده از زبان های template دیگر بسیار آسان می باشد.

واضح است که ترجیح می دهیم از خود زبان template جنگو استفاده کنیم. template system دارای اصولی نسبت به نحوه ی توسعه ی وب انجام شده در World Online و تجربیات متعدد سازندگان جنگو می باشد. در زیر به تعدادی از این فلسفه ها اشاره خواهیم کرد:

  • منطق برنامه (Business logic) باید از ارائه منطق (Presentation logic) جدا باشد. توسعه دهندگان جنگو، سیستم template جنگو را تنها به چشم ابزاری برای کنترل نمایش و نمایش منطق نگاه می کنند و نه بیشتر. سیستم template جنگو نباید از حالت تابع گرایی پشیتبانی کند که از این هدف اصلی خارج شود.
  • به همین دلیل، فراخوانی کد پایتون به طور مستقیم درون template های جنگو امکان پذیر نیست. تمام برنامه نویسی، اساسا محدود به محدوده ی تگ های template ها و آن چه که می توان در این محدوده انجام داد می باشد. می توان از تگ های سفارشی template برای انجام وظایف دلخواه استفاده کرد، ولی در صورتی که خارج از تگ های template جنگو باشد به طور عمدی اجازه داده نخواهد شد کد پایتون دلخواه اجرا شود.
  • کد template باید از HTML/XML جدا باشد. اگرچه سیستم template برای تولید HTML استفاده می شود، ولی سیستم template تنها باری قالب های غیر HTML ای، مانند متن ساده در نظر گرفته شده است. برخی از زبان های دیگر template ای که بر اساس XML می باشند، در آن ها تمام منطق template داخل تگ ها یا attribute ها XML قرار می گیرند، در صورتیکه جنگو به صورت عمدی از این محدودیت اجتناب کرده است. استفاده از XML می تواند موجب بوجود آمدن اشتباهات غیر قابل اجتناب شود که پیام های خطای آن نیز کمی غیر قابل فهم می باشند، همچنین استفاده از موتور XML برای parse کردن template ها باعث ایجاد یک سری وظایف اضافه ی غیر قابل قبول در پردازش template می شود.
  • راحتی طراحان برای کار با کد HTML در نظر گرفته شده است. سیستم template طوری طراحی نشده است که به زیبایی قابل نمایش درون editor هایی مانند Dreamweaver نمایش داده شود. جنگو از نویسندگان template انتظار دارد به طور مستقیم و راحت HTML را ویرایش کنند.
  • فرض بر این است که طراحات HTML برنامه نویسان پایتون نیستند. نسبت به این که اغلب template های صفحات وب به وسیله ی طراحان نوشته شده است نه برنامه نویسان، بنابراین در template system دانش پایتون فرض نشده است.
  • هدف اختراع یک زبان برنامه نویسی نیست. هدف تنها ارائه ی یک سبک برنامه نویسی مانند استفاده از حلقه و غیره می باشد، که برای ایجاد یک نمایش از تصمیمات ضروری می باشد.

استفاده از template ها در view

اصول اولیه ی استفاده از template system را آموختیم؛ اجازه دهید از این دانش برای ساخت view استفاده کنیم. تابع current_datetime را در ماژول mysite.views که در فصل گذشته ساخته شد را دوباره فراخوانی کنید:

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)

اجازه دهید view فوق را با استفاده از سیستم template جنگو تغییر دهیم. در ابتدا ممکن است فکر کنید، باید کاری شبیه به کد فوق را انجام دهید:

from django.template import Template, Context
from django.http import HttpResponse
import datetime

def current_datetime(request):
    now = datetime.datetime.now()
    t = Template("<html><body>It is now {{ current_date }}.</body></html>")
    html = t.render(Context({'current_date': now}))
    return HttpResponse(html)

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

ممکن است ابتدا اینطور به نظر برسد که template را درون جایی روی سیستم ذخیره کردن و با استفاده از توابع داخلی پایتون محتویات فایل مورد نظر را بخوانید. در مثال زیر فرض شده است که template درون فایل /home/djangouser/templates/mytemplate.html ذخیره شده است:

from django.template import Template, Context
from django.http import HttpResponse
import datetime

def current_datetime(request):
    now = datetime.datetime.now()
    # Simple way of using templates from the filesystem.
    # This is BAD because it doesn't account for missing files!
    fp = open('/home/djangouser/templates/mytemplate.html')
    t = Template(fp.read())
    fp.close()
    html = t.render(Context({'current_date': now}))
    return HttpResponse(html)

روش فوق نیز با توجه به معایبی که در ادامه بیان می کنیم، روش مناسبی نمی باشد:

  • روش فوق در زمان نبود فایل به هیچ وجه کار نخواهد کرد. فراخوانی open()، در صورتیکه فایل mytemplate.html وجود نداشته باشد و یا اینکه قابل خواندن نباشد باعث ایجاد خطای IOError خواهد شد.
  • مکان template در این روش داخل کد پایتون قرار خواهد گرفت. در صورت استفاده از این تکنیک برای هر تابع view، باید درون هر فایل به صورت تکراری آدرس فایل را قرار بدهیم.
  • روش فوق حاوی تعداد زیادی کدهای تکراری خسته کننده می باشد. در هر فایل شما باید fp.open() ،fp.read() و fp.close() را قرار دهید و بدیهی است که در صورت فراموش کردن این کدها برنامه شما با مشکل روبرو می شود.

برای حل کردن موارد فوق، از بار گذاری template و دایرکتوری های آن استفاده می شود.

بار گذاری Template

جنگو با هدف حذف کردن کدهای اضافه در برنامه هنگام فراخوانی و همچنین درون template، برای بارگذاری template ها یک API بسیار مناسب و قدرتمند تهیه کرده است.

به منظور استفاده از API بار گذاری template، ابتدا می بایست به فریم بگویید که template ها را در کجا ذخیره خواهید کرد. برای انجام چنین کاری نیازمند فایل settings می باشید. (در فصل گذشته هنگام معرفی تنظیم ROOT_URLCONF، در مورد فایل settings.py صحبت شد.)

اگر از ابتدا کتاب را دنبال کرده اید، فایل settings.py را باز کرده و تنظیم TEMPLATE_DIRS را پیدا کنید. تنظیم فوق، یک تاپل خالی که احتمالا حاوی برخی کامنت های پیشفرض که به صورت خودکار تولید شده است، می باشد:

TEMPLATE_DIRS = (
    # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
    # Always use forward slashes, even on Windows.
    # Don't forget to use absolute paths, not relative paths.
)

تنظیم فوق به مکانیسم بار گذاری template جنگو می گوید که باید در کجا به دنبال template ها بگردد. مسیر و دایرکتوری را که می خواهید template های خود را ذخیره کنید مانند مثال زیر درون TEMPLATE_DIRS اضافه کنید:

TEMPLATE_DIRS = (
    '/home/django/mysite/templates',
)

توجه به چند نکته ضروری است:

  • می توانید هر مسیری را که می خواهید تعیین کنید، البته تا زمانیکه مسیر و template ها برای کاربر قابل خواندن باشد. در صورتیکه جای مناسبی را برای template های خود به یاد نمی آورید، پیشنهاد می کنیم یک دایرکتوری داخل پروژه خود بسازید (داخل دایرکتوری mysite که در شروع کار جنگو ساخته شد).
  • در صورتیکه حاوی تنها یک مسیر می باشد، فراموش نکنید که در پایان رشته ی آدرس یک علامت کاما (,) قرار دهید!

غلط:

# Missing comma!
TEMPLATE_DIRS = (
    '/home/django/mysite/templates'
)

صحیح:

# Comma correctly in place.
TEMPLATE_DIRS = (
    '/home/django/mysite/templates',
)

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

اگر شما با سیستم عامل ویندوز کار می کنید، درون آدرس مورد نظر می بایست نام حرف دیسک درایو را اضافه کرده و همچنین مانند مثال زیر بجای علامت (\) از علامت (/) استفاده کنید:

TEMPLATE_DIRS = (
    'C:/www/django/templates',
)

ساده ترین راه این است که از آدرس های کامل (بدین معنی که مسیر دایرکتوری ها از ریشه ی سیستم آدرس دهی شود) استفاده کنید. همانطور که می دانید فایل settings تنها کد پایتون می باشد و با استفاده از این خصوصیت می توان کمی منعطف تر از قبل کار کرد، بدین صورت که می توان آدرس دهی درون تنظیم TEMPLATE_DIRS را به صورت پویا مانند زیر طراحی کرد:

import os.path

TEMPLATE_DIRS = (
    os.path.join(os.path.dirname(__file__), 'templates').replace('\\','/'),
)

مثال فوق از متغیر مجیک (magic) پایتون یعنی __file__ استفاده کرده است، بدین صورت که این متغیر به صورت خودکار حاوی نام فایل ای می باشد که در آن است. همچنین با استفاده از os.path.dirname نام دایرکتوری ای که حاوی فایل settings.py می باشد بدست می اید، سپس رشته ی template که در دومین پارامتر داده است به انتهای آن اضافه می شود و در پایان جای علامت های (\\) با علامت (/) جایگزین می باشد (اگر در سیستم عامل ویندوز باشید.)

هنگامی که درون فایل settings.py از کد پویای پایتون استفاده کنید، باید اشاره کنیم که درون فایل settings.py از هرگونه خطا و اشتباه کد پایتون جلوگیری کنید. در صورتیکه درون این فایل یک خطای نوشتاری و یا خطای runtime وجود داشته باشد، سایت ایجاد شده توسط جنگو معیوب خواهد شد.

بعد از تنظیم TEMPLATE_DIRS، گام بعدی تغییر کد view می باشد که باید از بار گذاری template به جای استفاده مستقیم template، استفاده کرد. باز می گردیم به تابع current_datetime، و آن را مانند زیر تغییر می دهیم:

from django.template.loader import get_template
from django.template import Context
from django.http import HttpResponse
import datetime

def current_datetime(request):
    now = datetime.datetime.now()
    t = get_template('current_datetime.html')
    html = t.render(Context({'current_date': now}))
    return HttpResponse(html)

در مثال فوق، از تابع django.template.loader.get_template() بجای بارگذاری template از سیستم به صورت دستی استفاده شده است. تابع get_template() نام template را به عنوان آرگومان دریافت کرده، و محلی را در سیستم template در آن وجود دارد را پیدا کرده، آن را باز کرده و و یک شیء template کامپایل شده را بر می گرداند.

template مورد نظر در مثال فوق current_datetime.html می باشد، ولی هیچ چیز خاصی درباره ی پسوند .html وجود ندارد. شما می توانید هر پسوندی که می خواهید برای آن قرار دهید، و یا حتی می توانید پسوند فایل را کلا حذف کنید.

برای تعیین مکان template در روی سیستم، تابع get_template() مسیر فایل را که در TEMPLATE_DIRS وجود دارد را با نام template که به آن ارسال می کنید ترکیب می کند. به عنوان مثال اگر TEMPLATE_DIRS حاوی آدرس /home/django/mysite/templates باشد، تابع get_template() مثال فوق فایل زیر را جستجو خواهد کرد.

/home/django/mysite/templates/current_datetime.html

در صورتیکه تابع get_template() موفق به یافتن template با نام داده شده نشود، خطای TemplateDoesNotExist ایجاد خواهد شد. برای دیدن خطای فوق سرور جنگو را با دستور python manage.py runserver درون دایرکتوری پروژه جنگو اجرا کنید. سپس مرورگر خود را باز کرده و به آدرس مورد نظر برای مثال http://127.0.0.1:8000/time/ بروید. با فرض اینکه تنظیم DEBUG به صورت True می باشد و هنوز template مورد نظر یعنی current_datetime.html ساخته نشده است، شما باید یک صفحه خطای جنگو را با نام TemplateDoesNotExist مشاهده کنید.

do.php?imgf=157270206589911.jpg

صفحه ی خطای فوق همانند صفحه ای است که در آموزش view و urlconf جنگو توضیح داده شد، تنها با یک قسمت اطلاعات اضافه تر: بخش Template-loader postmortem. این بخش به شما می گوید که جنگو کدام template ها را بار گذاری کرده است، و همچنین دلیل رد کردن template ها را نیز در بر می گیرد (مانند File does not exist). این اطلاعات هنگامی که شما می خواهید خطاهای بارگذاری template را بر طرف کنید بسیار با ارزش می باشند.

در پایان می بایست current_datetime.html را درون دایرکتوری template خود ساخته و کد زیر را درون آن قرار دهید:

<html><body>It is now {{ current_date }}.</body></html>

صفحه را ذخیره کرده و صفحه ی مرورگر خود را به اصطلاح refresh کنید، شما می بایست صفحه تولید شده حاوی زمان و تاریخ فعلی خود را مشاهده کنید.

تابع render_to_response()

همانطور که قبلا اشاره کردیم، بارگذاری یک template شامل پر کردن یک Context و بر گرداندن نتیجه ی یک template توسط یک شیء HttpResponse، می باشد. همانطور که گفته شد در روش فوق با استفاده از get_template() به جای template های مستقیم و یا آدرس درون کد، توانستیم کد خود را بهینه تر کنیم. ولی همچنان نیاز به یک حرکت زیبا برای بهینه تر کردن کد وجود دارد. جنگو یک میانبر (shortcut) ارائه می دهد که از طریق آن تنها می توان با یک خط کد template را بار گذاری کرده، آن را ارائه داده و یک HttpResponse را بر گردانید.

این میانبر یک تابع است که render_to_response() نام دارد، که در ماژول django.shortcuts قرار دارد. اغلب اوقات شما بجای بارگذاری template ها و ساختن Context و شیء ها HttpResponse به صورت دستی از render_to_response() استفاده خواهید کرد، مگر آنکه کارفرای شما بر اساس مجموع خط های موجود در کدی که نوشته اید نسبت به کار شما تصمیم گیری کند.

در زیر تابع بهینه شده ی current_datetime که با استفاده از render_to_response() دوباره نویسی شده است را مشاهده می کنید:

from django.shortcuts import render_to_response
import datetime

def current_datetime(request):
    now = datetime.datetime.now()
    return render_to_response('current_datetime.html', {'current_date': now})

آن چه که تغییر پیدا کرده است:

  • در مثال فوق get_template، Template، Context و HttpResponse دیگر import نشده اند، و به جای آن ها تنها django.shortcuts.render_to_response، import شده است.
  • داخل تابع current_datetime، همچنان متغیر now محاسبه شده است، ولی بارگذاری template، ساختن Context، ارائه ی template و ساختن HttpResponse تماما تنها با فراخوانی render_to_response() انجام شده است. زیرا render_to_response() یک شیء HttpResponse بر می گرداند، و تنها کافی است آن مقدار را در view بر گردانده شود.

اولین آرگومان تابع render_to_response() نام template مورد استفاده می باشد. دومین آرگومان در صورت ارسال، باید یک دیکشنری باشد که جهت ساختن Context برای template استفاده می شود. در صورتیکه دومین آرگومان را ارسال نکنید، render_to_response() از یک دیکشنری خالی استفاده می کند.

تکنیک locals()

آخرین تابع current_datetime نوشته شده ملاحظه کنید:

def current_datetime(request):
    now = datetime.datetime.now()
    return render_to_response('current_datetime.html', {'current_date': now})

درون یک برنامه شما چندین بار مانند مثال فوق، مقادیری را حساب کرده و درون متغیرهایی ذخیره می کنید (مانند متغیر now در کد قبلی)، و متغیرها را به template مورد نظر ارسال می کنید. برنامه نویسان راحت طلب باید توجه داشته باشند که این کمی اضافه کاری است که باید متغیرها موقت ساخته و نام گذاری کنند و نام آن ها را برای template ارسال کنند. نه تنها این کار زائدی می باشد، بلکه تایپ اضافی درون برنامه نیز می باشد.

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

def current_datetime(request):
    current_date = datetime.datetime.now()
    return render_to_response('current_datetime.html', locals())

در مثال فوق، بجای تعیین کردن دیکشنری context به صورت دستی همانند قبل، مقدار locals() ارسال شده است، که شامل تمام متغیرهای تعریف شده در آن نقطه از اجرای تابع می باشد. در نتیجه، متغیر now به current_date تغییر نام پیدا کرده است، زیرا درون template نیز نام current_date معرفی شده است. در مثال فوق، locals() یک پیشرفت عظیم را ارائه نمی کند، ولی این تکنیک می تواند شما را از شر تایپ کردن زیاد خلاص می کند، البته اگر شما چندین متغیر template برای تعریف دارید و یا اینکه کمی تنبل هستید.

چیزی که باید در مورد استفاده از locals() مراقب آن باشید این است که این تابع حاوی تمام متغیرهای محیطی و شاید خیلی بیشتر از آنچه شما واقعا نیاز دارید می باشد، در مثال قبلی، locals() همچنین حاولی request نیز می باشد. البته استفاده از locals() به برنامه و نوع استفاده شما بستگی دارد.

دایرکتوری های زیر مجموعه در get_template()

ذخیره ی تمام template ها درون یک دایرکتوری می تواند کمی سنگین باشد. ممکن است بخواهید template ها را درون دایرکتوری های زیر مجموعه ی دایرکتوری template قرار داهید.

ذخیره ی template ها درون دایرکتوری های زیرمجموعه ی دایرکتوری template بسیار آسان می باشد. تنها کافیست درون get_template()، نام دایرکتوری زیرمجموعه و بعد از آن یک علامت (/) و بعد نام template را قرار دهید:

t = get_template('dateapp/current_datetime.html')

همچنین می توانید از این امکان در تابع render_to_response() نیز استفاده کنید بدین صورت که اعمال فوق را درون اولین پارامتر این تابع قرار می دهید:

return render_to_response('dateapp/current_datetime.html', {'current_date': now})

هیچ محدودیتی نسبت به تعداد دایرکتوری های زیر مجموعه ی استفاده شده وجود ندارد و می توانید در صورت نیاز از هر تعداد از آن ها استفاده کنید.

نکته

کاربران ویندوز، مطمئن شوید که از علامت (/) به جای (\) استفاده می کنید. تابع get_template() طراحی آدرس دهی را بر اساس Unix-style فرض می کند.

تگ include

اکنون که مکانیزم بارگذاری template شرح داده شده است، می توان یک تگ داخلی template و مزایای آن را معرفی کرد: {% include %}. این تگ اجازه می دهد تا محتویات template های دیگر را درون template فعلی وارد کرد. آرگومان این تگ باید نام template ای باشد که می خواهید وارد template فعلی بکنید. همچنین این آرگومان می تواند یک متغیر و یا کد مستقیم HTML باشد که درون تک کتیشن یا دابل کتیشن قرار دارد. زمانی که یک کد همسان درون چندین template وجود دارد، می توان برای جلوگیری از تکرار کد نویسی از تگ include استفاده کرد.

هر دو مثال زیر برای include معتبر می باشند:

{% include 'nav.html' %}
{% include "nav.html" %}

مثال زیر محتویات template، includes/nav.html را وارد template خواهد کرد:

{% include 'includes/nav.html' %}

مثال زیر محتویات template ای را که نامش درون متغیر template_name ذخیره شده است را وارد می کند:

{% include template_name %}

همانند get_template()، نام فایل template با اضافه شدن به مسیر دایرکتوری template که در تنظیم TEMPLATE_DIRS قرار دارد تعیین می شود.

template های وارد شده با context مربوط به template که آن ها را وارد کرده است ارزیابی می شوند. به عنوان مثال دو template زیر را ملاحظه کنید.

# mypage.html

<html>
<body>
{% include "includes/nav.html" %}
<h1>{{ title }}</h1>
</body>
</html>

# includes/nav.html

<div id="nav">
    You are in: {{ current_section }}
</div>

در صورتیکه که شما mypage.html را با یک context حاوی current_section ارائه (render) کنید، متغیر template وارد شده همانطور که انتظار خواهید داشت در دسترس خواهد بود.

در صورتیکه یک تگ {% include %}، template ای را وارد کند که وجود نداشته باشد، یکی از حالت های زیر پیش می اید:

  • اگر تنظیم DEBUG به صورت True در نظر گرفته شده باشد، صفحه ی خطای جنگو با خطای TemplateDoesNotExist نمایش داده خواهد شد.
  • اگر تنظیم DEBUG به صورت False در نظر گرفته شده باشد، تگ مورد نظر بدون هیچ خطایی رد شده، و درون قسمت تگ include چیزی نمایش داده نخواهد شد.

ارث بری در Template

مثال های template ای که تاکنون استفاده شده است از نظر محتوی خیلی مختصر و کم حجم بوده اند. ولی در دنیای واقعی، شما تز template system جنگو برای ساختن صفحات HTML با حجم و محتویات بسیار گسترده استفاده خواهید کرد. در این صورت با یک مشکل مشترک و عمومی توسعه ی وب رو به رو خواهید بود: چگونه می توان از بوجود آمدن صفحات و کدها تکراری در ساخت صفحات وب که اجتناب ناپذیر است جلوگیری کرد؟

روش عمومی برای حل این مشکل استفاده از incude های سمت سرور می باشد، دستورالعمل هایی که شما می توانید داخل صفحه ی خود صفحات دیگر را نیز وارد کنید. در واقع، جنگو از این روش با استفاده از تگ template {% include %} پشتیبانی می کند که توضیح آن پیش تر داده شده است. ولی روش بهتری برای حل این مشکل وجود دارد که یکی از استراتژی های مطلوب جنگو به شمار می اید که "ارث بری template" نام دارد.

در واقع، ارث بری template، این اجازه را می دهد که یک اسکلت بندی پایه و اولیه از template بسازید که حاوی تمام بخش های عمومی می باشد. این بخش ها به صورت block ها تعریف می شوند و template هایی که از آن template پدر، ارث بری می کنند می توانند آن بخش ها را دوباره نویسی و یا به اصطلاح overwrite کنند.

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

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
    <title>The current time</title>
</head>
<body>
    <h1>My helpful timestamp site</h1>
    <p>It is now {{ current_date }}.</p>

    <hr>
    <p>Thanks for visiting my site.</p>
</body>
</html>

تا اینجا مشکلی وجود نخواهد داشت، ولی چه اتفاقی می افتد اگر بخواهید یک template برای یک view دیگر مانند تابع hours_ahead که در آموزش view و urlconf جنگو وجود داشت ایجاد کنید؟ در صورتیکه بخواهید یک template کامل دیگر برای view دوم بسازید، چیزی شبیه به مثال زیر خواهد بود:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
    <title>Future time</title>
</head>
<body>
    <h1>My helpful timestamp site</h1>
    <p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p>

    <hr>
    <p>Thanks for visiting my site.</p>
</body>
</html>

کاملا واضح هست که مقدار زیادی کد تکراری در دو مثال فوق وجود دارد. تصور کنید اگر بخواهید صفحات بیشتری داشته باشید مانند یک سایت بزرگ که حاوی style sheet ها و یا مقادیر زیادی کد Java Script می باشد چه خواهید کرد؟

راهکار include سمت سرور برای این مشکل راه حل مناسبی نمی باشد، چرا که به عنوان مثال برای قسمت بالایی کد شما یک template با نام header.html خواهید داشت:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>

و برای قسمت پایین کد یک template با نام footer.html مانند مثال زیر:

    <hr>
    <p>Thanks for visiting my site.</p>
</body>
</html>

با یک استراتژی مانند ارث بری هر دو قسمت بسیار آسان پیاده سازی خواهند شد. در مثال فوق هر دو صفحه تیتر صفحه را نشان می دهند (<h1>My helpful timestamp site</h1>) و نمی توان آن را درون header.html قرار داد، چرا که <title> در هر صفحه متفاوت از صفحه ی دیگر خواهد بود. و اگر <titile> درون header.html قرار داده می شد، دیگر امکان تغییر آن برای هر صفحه به طور جدا گانه امکان پذیر نبود و تمام صفحات می بایست یک <title> را نمایش بدهند.

ارث بری template جنگو این مشکلات را حل کرده است. نسخه ی insite-out از include های سمت سرور را بیاد می آورید. به جای تعریف تکه کد های مشترک، تکه کد هایی که تعریف می شوند متفاوت از یکدیگر هستند.

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

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
    <title>{% block title %}{% endblock %}</title>
</head>
<body>
    <h1>My helpful timestamp site</h1>
    {% block content %}{% endblock %}
    {% block footer %}
    <hr>
    <p>Thanks for visiting my site.</p>
    {% endblock %}
</body>
</html>

مثال فوق که base.html نام دارد، یک اسکلت بندی ساده ی HTML می باشد که برای تمام صفحات استفاده خواهد شد. صفحات درون سایت که از صفحه ی فوق ارث بری کرده اند و به عبارتی صفحات فرزند نامیده می شوند می توانند خصوصیات مشخص شده در کلاس پدر را بازنویسی و یا تغییر دهند، می توانند خصوصیاتی را اضافه کنند و حتی می توانند خصوصیتی را نیز حذف کنند و در خودشان از استفاده نکنند. (در صورتیکه با مثال های کتاب پیش میروید فایل فوق را با نام base.html درون دایرکتوری template ذخیره کنید.)

در اینجا از یک تگ template ای که آن را مشاهده کرده اید استفاده می کنید: تگ {% block %}. تمام تگ های {% block %} به موتور template این را می گویند که template فرزند ممکن است این قسمت از template را باز نویسی کند.

اکنون یک template پدر موجود می باشد، می توان از آن در فایل current_datetime.html به شکل زیر استفاده کرد:

{% extends "base.html" %}

{% block title %}The current time{% endblock %}

{% block content %}
<p>It is now {{ current_date }}.</p>
{% endblock %}

همچنین فایل hours_ahead را نیز که در آموزش view و urlconf جنگو در مورد آن صحبت شده است را نیز در مثال زیر مشاهده خواهید کرد: (در صورتیکه همگام با مثال های کتاب پیش میروید hours_ahead را از حالت قبلی که به صورت کد مستقیم درون view نوشته شده است خارج کرده و مانند زیر به صورت یکی از template ها درون دایرکتوری template دخیره کنید.)

{% extends "base.html" %}

{% block title %}Future time{% endblock %}

{% block content %}
<p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p>
{% endblock %}

آیا روش فوق روش زیبایی نیست؟ هر template حاوی کدهای منحصر به خود می باشد. هیچ کد زائدی وجود ندارد. در صورتیکه نیاز به تغییر طراحی در یک سایت عظیم دارید، تنها کافیست که درون template پدر تغییر ایجاد کنید، در اینصورت تمام template هایی که از کلاس پدر ارث بری کرده اند نیز به سرعت تغییر خواهند خواهند کرد.

در زیر نحوه ی کار روش فوق توضیح داده شده است. هنگامی که template، current_datetime.html بارگذاری می شود، موتور template به تگ {% extends %} نگاه می کند، که به این اشاره دارد که فایل مورد نظر یک template فرزند می باشد. در اینصورت موتور template به سرعت template پدر را بارگذاری می کند که در این مورد template پدر base.html می باشد.

توجه داشته باشید که template های فرزند بلاک footer را تعریف نکرده اند، template system از مقدار مشخص شده ی بلاک درون template پدر بجای آن استفاده کرده است. مقدار مشخص شده ی درون یک تگ {% block %} در یک template پدر همیشه به صورت یک مقدار یدکی استفاده می شود.

ارث بری تاثیری بر روی context template نخواهد داشت. به عبارت دیگر، هر template فرزند به هر یک از متغیر های template درون context دسترسی خواهد داشت.

می توان به صورت چندین سطح از ارث بری استفاده کرد. یکی از روش های عمومی استفاده از روش ارث بری در سه سطح می باشد:

یک template با نام base.html که بسازید که محتویات اصلی سایت را در بر بگیرد، محتویاتی که به ندرت تغییر خواهند کرد، البته اگر نگوییم هرگز تغییر نخواهند کرد.

یک template با نام base_SECTION.html برای هر بخش از سایت خود ایجاد کنید (مانند base_photos.html و base_forum.html). این template ها باید از base.html ارث بری کنند و باید حاوی طراحی مربوط به خود باشند.

template های هر نوع از صفحه را ایجاد کنید، مانند صفحه ی انجمن (forum) یا یک آلبوم عکس. این template ها از template مناسب مربوط به خود ارث بری کنند.

در زیر دستور العمل های کار با ارث بری template را مشاهده می کنید:

  • در صورتیکه درون یک template از {% extends %} اسفتاده می کنید، این تگ می بایست به عنوان اولین تگ template در نظر گرفته شود، در غیر اینصورت ارث بری template کار نخواهد کرد.
  • معمولاً، بهتر است از تگ های {% block %} بیشتری درون template پدر استفاده شود، بیاد داشته باشید که، template ها فرزند اجباری برای تعریف تمام block های template پدر ندارند، پس می توان هر تعداد block مناسب به صورت پیش فرض درون template پدر قرار داد، و سپس در صورت نیاز به هرکدام آن را درون template فرزند استفاده کرد.
  • در صورتیکه شما تکه کدی تکراری درون تعدادی از template ها پیدا کردید، این می تواند بدین معنا باشد که شما باید آن تکه کد را به درون یک {% block %} در template پدر انتقال دهید.
  • در صورتیکه نیاز به محتویات یک block درون template پدر دارید، می توانید از {{ block.super }} استفاده کرد، که یک متغیر magic می باشد که یک متن ارائه شده از template پدر را تهیه می کند. این روش در صورتیکه می خواهید بجای بازنویسی کلی یک block محتویات آن را اضافه کنید مناسب می باشد.
  • نمی توان چند تگ {% block %} با نام یکسان درون یک template تعریف کرد. دلیل وجود همچین محدودیتی این است که یک block در هر دو جهت کار می کند. یک تگ block تنها یک حفره برای پر کردن، تهیه نمی کند، بلکه محتویاتی نیز برای این حفره در template پدر تعریف می کند. در صورتیکه دو block همنام درون یک template وجود داشته باشد، template پدر نخواهد دانست کدام محتویات block را استفاده کند.
  • نام template ای که به {% extends %} ارسال می شود مانند get_template() بارگذاری می شود. نام template به تنظیم TEMPLATE_DIRS اضافه شده است.

در اغلب موارد، آرگومانی که به {% extends %} ارسال می شود یک رشته خواهد بود، ولی این آرگومان می تواند یک متغیر نیز باشد، در صورتیکه شما نام template پدر را تا زمان اجرا نمی دانید. این خصوصیت اجازه می دهد یک حرکت پویا انجام دهید.

به اشتراک گذاری این ارسال


لینک به ارسال
به اشتراک گذاری در سایت های دیگر

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

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

بسیاری از وب سایت های پیچیده یک سری ترکیب از دو موضوع را تهیه می کنند. به عنوان نمونه Amazon.com، یک مثال عالی برای یک سایت پایگاه داده محور می باشد. هر صفحه ی محصول اساسا یک پرس وجو (Query) قالب بندی شده به صورت HTML، درون پایگاه داده ی محصول آمازون می باشد و هنگامی که شما یک بررسی مشتری را ارسال می کنید، آن بررسی ها درون پایگاه داده درج می شوند.

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

(نکته: بهتر است با تئوری های پایگاه داده ی relational و SQL تاحدودی آشنا باشید. آشنا سازی برای این مفاهیم خارج از حوصله ی این کتاب می باشد، ولی حتی اگر شما یک کاربر جدید پایگاه داده می باشید باز هم این کتاب را بخوانید. ممکن است بتوانید مفاهیم و اصول اولیه پایگاه داده را درک کنید.)

روش اولیه اجرای پرس و جوهای پایگاه داده در view ها

همانطور که در آموزش view و urlconf جنگو توضیح داده شد، روش اولیه ی برای تولید خروجی درون یک view (از طریق نوشتن کد به طور مستقیم درون view) برای بازیابی داده از یک پایگاه داده درون یک view می باشد. این روش ساده می باشد زیرا فقط نیاز به استفاده از کتابخانه ی پایتون برای اجرای یک پرس و جوی SQL و کار با نتایج بدست آمده می باشد.

در مثال view زیر، از کتابخانه ی MySQLdb (قابل دسترس از طریق آدرس

محتوای مخفی

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

from django.shortcuts import render_to_response
import MySQLdb

def book_list(request):
    db = MySQLdb.connect(user='me', db='mydb', passwd='secret', host='localhost')
    cursor = db.cursor()
    cursor.execute('SELECT name FROM books ORDER BY name')
    names = [row[0] for row in cursor.fetchall()]
    db.close()
    return render_to_response('book_list.html', {'names': names})

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

  • پارامترهای connection پایگاه داده به صورت مستقیم درون کد قرار گرفته اند. ایده آل آن است که، این پارامترها درون پیکربندی جنگو ذخیره شوند.
  • تعداد زیادی کد تکراری مانند ساختن connection، ساختن cursor، اجرا کردن جملات و بستن connection در مثال فوق وجود دارد. برای بهره وری مناسب تر ایده آل آن است که تنها نیازمند تعیین نتایج مورد نیاز برنامه باشیم.
  • روش فوق برنامه نویس را به MySQL گره می زند. در صورتیکه برنامه نویس بخواهد پایگاه داده ی خود را تغییر دهد، به عنوان مثال، اگر بخواهد بجای MySQL از PostgreSQL استفاده کند، می بایست از یک adapter پایگاه داده ی متفاوت (psycopg به جای MySQLdb)، پارامتر های connection دیگر و همچنین بسته به ذات پایگاه داده، از جملات SQL متفاوت دیگر استفاده کرد. ولی همانطور که قبلا هم اشاره کردیم ایده آل آن است که، در صورت تغییر پایگاه داده، برنامه نویس دیگر نگران تغییر جملات و ... نباشد.

همانطور که انتظار داریم، لایه ی پایگاه داده ی جنگو راه حلی برای حل این مشکلات ارائه کرده است. مثال زیر کد بازنویسی شده ی قبلی را با استفاده از API پایگاه داده ی جنگو نشان می دهد:

from django.shortcuts import render_to_response
from mysite.books.models import Book

def book_list(request):
    books = Book.objects.order_by('name')
    return render_to_response('book_list.html', {'books': books})

کد فوق کمی بعد در همین فصل شرح داده خواهد شد. تنها برای حالا کافی است دید کلی نسبت به این API پیدا کنید.

الگوی توسعه MTV (یا MVC)

تاکنون در این کتاب، همواره به درون کد تمرکز شده است، اجازه دهید برای لحظاتی به بررسی یک برنامه ی وب پایگاه داده محور جنگو بپردازیم.

همانطور که در فصل گذشته بیان شد، جنگو به نحوی طراحی شده است که برنامه نویس را تشویق کند تا اصول loose coupling و جداسازی بین قسمت های یک برنامه را رعایت کند. در صورتیکه این فلسفه را دنبال کنید، ایجاد تغییرات در یک قسمت مشخص از برنامه هیچ تاثیری بر روی قسمت های دیگر برنامه نخواهد داشت. برای نمونه در توابع view، اهمیت جداسازی منطق برنامه از قسمت ظاهر برنامه با استفاده از template توضیح داده شد. در لایه ی پایگاه داده، همین فلسفه را برای دسترسی به داده بکار خواهیم برد.

این سه قسمت با یکدیگر (دسترسی به داده، منطق برنامه و ظاهر برنامه) یک مفهومی را در بر دارد که الگوی Model-View-Controller (MVC) از معماری نرم افزار نامیده می شود. در این الگو، Model به لایه ی دسترسی داده اشاره می کند، View به بخشی از سیستم که به انتخاب آنچه را که باید نمایش دهد و اینکه چطور باید آن را نمایش دهد اشاره می کند و Controller به بخشی از سیستم که بسته به ورودی کاربر تصمیم می گیرد کدام view باید استفاده شود اشاره دارد.

چرا از شکل مخفف استفاده می شود؟

هدف از تعریف کردن الگوها بن شکل مخفف مانند MVC، غالبا برای ساده تر کردن صحبت و مکاتبه بین توسعه دهندگان می باشد. بجای آنکه به همکارتان بگویید؛ "بیا یک دسترسی داده بسازیم، سپس بیا جدا کنیم لایه ها را برای کنترل نمایش آن ها، و بعد از آن بیا یک لایه در میان آن قرار دهیم که آن را تنظیم و مرتب کند." می توانید از این خصوصیت استفاده کرده و بگویید؛ "بیا در اینجا از الگوی MVC استفاده کنیم."

جنگو الگوی MVC را به اندازه ی کافی از نزدیک دنبال می کند به طوری که می توان آن را یک فریم ورک یا چارچوب MVC نامید. در زیر نحوه ی تقسیم بندی M، V و C به درون جنگو را مشاهده می کنید:

  • M، بخش دسترسی به داده می باشد که توسط لایه پایگاه داده جنگو کنترل می شود و در این فصل توضیح داده خواهد شد.
  • V، انتخاب داده برای نمایش و نحوه ی نمایش آن، که با view ها و template ها کنترل شده است.
  • C، بخشی که توسط خود فریم ورک یا چارچوب کنترل می شود، بدینصورت که با توجه به URLconf مشخص شده تابع پایتون مناسب برای URL داده شده را فراخوانی می کند.
  • به دلیل آنکه C ("Controler") توسط خود فریم فرک کنترل می شود و بیشترین تحرکات در models ،template و view ها اتفاق می افتد، جنگو به صورت یک فریم ورک یا چارچوب MTV نیز شناخته می شود. در الگوی توسعه ی MTV:
  • M مخفف "Model"، لایه ی دسترسی داده می باشد. این لایه حاوی همه اطلاعات درباره ی داده یعنی نحوه ی دسترسی به داده، نحوه ی معتبر ساختن داده، رفتارهایی که داده دارد و ارتباط بین داده می باشد.
  • T مخفف "Template"، لایه ی نمایش می باشد. این لایه حاوی مسائلی درباره ی نحوه ی نمایش می باشد: این که اطلاعات باید به چه نحوی روی یک صفحه ی وب و یا سند دیگر نمایش داده شود.
  • V مخفف "view"، لایه ی منطق برنامه می باشد. این لایه حاوی منطق برنامه برای دسترسی به model و ارائه ی به template مناسب می باشد. می توانید این لایه را به صورت پلی بین model ها و template ها تصور کنید.

در صورتیکه با چارچوب یا فریم ورک های دیگر توسعه ی وب مانند Ruby on Rails کار کرده باشید، ممکن است تصور کنید view های جنگو نقش "Controller" را بازی می کنند و template ها هم در حکم "views" می باشند. همچین تفسیری درباره ی MVC مایه ی تاسف می باشد. تفسیر جنگو نسبت به MVC، این است که "view" ارائه دادن داده به کاربر را شرح می دهد، این لزوما تنها ظاهر داده نمی باشد، بلکه منظور داده ی ارائه شده می باشد. در مقابل، Ruby on Rails و فریم ورک های مانند آن پیشنهاد می کنند که تصمیم گیری درباره ی اینکه کدام داده باید به کاربر ارائه داده شود از جمله وظایف controller می باشد، در حالیکه، ظاهر داده به طور موکد بر عهده ی view می باشد.

به اشتراک گذاری این ارسال


لینک به ارسال
به اشتراک گذاری در سایت های دیگر

پیکربندی پایگاه داده

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

فرض بر این است که سرور پایگاه داده نصب و فعال شده است، و همچنین یک پایگاه داده درون آن ساخته شده است (به عنوان مثال، با استفاده از دستور CREATE DATABASE STATEMENT). در صورتیکه از SQLite استفاده می کنید، به هیچگونه نصب نیاز نخواهد بود، زیر SQLite از فایل های مستقلی روی سیستم برای ذخیره ی داده های خود استفاده می کند.

درست مانند TEMPLATE_DIRS که در فصل گذشته استفاده شد، پیکربندی مربوط به پایگاه داده نیز درون فایل settings می باشد:

DATABASE_ENGINE = ''
DATABASE_NAME = ''
DATABASE_USER = ''
DATABASE_PASSWORD = ''
DATABASE_HOST = ''
DATABASE_PORT = ''

در زیر خلاصه هایی از هر تنظیم توضیح داده شده است:

DATABASE_ENGINE به جنگو می گوید که کدام موتور پایگاه داده استفاده می شود. در صورتیکه می خواهید از یک پایگاه داده درون جنگو استفاده کنید، تنظیم DATABASE_ENGINE باید یکی از رشته های داخل جدول زیر باشد.

تنظیم پایگاه داده Adaper مورد نیاز
postgresql PostgreSQL psycopg version 1.x,

محتوای مخفی

    برای مشاهده محتوای مخفی می بایست در انجمن ثبت نام کنید.
postgresql_psycopg2 PostgreSQL psycopg version 2.x,

محتوای مخفی

    برای مشاهده محتوای مخفی می بایست در انجمن ثبت نام کنید.
mysql MySQL MySQLdb,

محتوای مخفی

    برای مشاهده محتوای مخفی می بایست در انجمن ثبت نام کنید.
sqlite3 SQLite No adapter needed if using Python 2.5 . Otherwise, pysqlite,

محتوای مخفی

    برای مشاهده محتوای مخفی می بایست در انجمن ثبت نام کنید.
oracle Oracle cx_Oracle,

محتوای مخفی

    برای مشاهده محتوای مخفی می بایست در انجمن ثبت نام کنید.

توجه داشته باشید برای استفاده از هر کدام نیاز به دانلود و نصب adapter مناسب خود می باشد. هر کدام از پایگاه های داده ی فوق به صورت آزاد درون وب در دسترس می باشند؛ تنها کافیست به سایت مورد نظر که درون جدول 1-5 آمده است مراجعه کرده آن را دانلود کنید. در صورتیکه با سیستم عامل لینوکس کار می کنید، package-management سیستم شما ممکن است package های مناسبی را ارئه دهد. (برای مثال به دنبال package هایی با نام python-postgresql یا python‑psycopg بگردید.

مثال:

DATABASE_ENGINE = 'postgresql_psycopg2'

DATABASE_NAME نام پایگاه داده ی شما را به جنگو اطلاع می دهد:

DATABASE_NAME = 'mydb'

در صورتیکه از SQLite استفاده می کنید، آدرس کامل پایگاه داده ی خود را درون این تنظیم قرار دهید:

DATABASE_NAME = '/home/django/mydata.db'

DATABASE_USER نام کاربری که برای اتصال به پایگاه داده لازم است را به جنگو اطلاع می دهد.
DATABASE_PASSWORD رمز عبوری که برای اتصال به پایگاه داده لازم است را به جنگو اطلاع می دهد. در صورتیکه از SQLite استفاده می کنید یا اینکه می خواهید پسوردی در نظر نگیرید، می توانید آن را خالی بگذارید.
DATABASE_HOST host مورد نظری که برای اتصال به پایگاه داده لازم است را به جنگو اطلاع می دهد. در صورتیکه پایگاه داده ی شما همانند نصب جنگو در یک رایانه می باشد (مانند localhost) آن را خالی بگذارید. همچنین اگر از SQLite استفاده می کنید نیز می توانید آن را خالی بگذارید.
MySQL در اینجا یک مورد خاص می باشد. در صورتیکه مقدار این تنظیم (DATABASE_HOST) با علامت (/) شروع شود و شما از MySQL استفاده کنید، MySQL از طریق یک Unix socket برای socket تعیین شده متصل خواهد شد:

DATABASE_HOST = '/var/run/mysql'

هنگامی که تنظیمات فوق را انجام داده و فایل settings.py ذخیره کردید، ایده ی خوب است که پیکربندی انجام شده را مورد آزمون قرار دهید. برای انجام این کار، مانند فصل گذشته درون دایرکتوری پروژه ی mysite دستور python manage.py shell را اجرا کنید. (همانطور که در فصل گذشته اشاره شد دستور manage.py shell روشی برای اجرای interpreter پایتون، با تنظیمات صحیح فعال شده ی جنگو می باشد. در این مورد این ضروری است، زیرا نیاز است جنگو تنظیمات فایل را به منظور به دست آوردن اطلاعات connection پایگاه داده برای استفاده بداند.)

درون خط فرمان، دستورات زیر را به منظور آزمودن پیکربندی پایگاه داده تایپ کنید:

>>> from django.db import connection
>>> cursor = connection.cursor()

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

 

راه حل پیام خطا
تنظیم DATABASE_ENGINE را با چیزی به غیر از رشته ی خالی پر کنید. مقادیر معتبر برای این تنظیم در جدول 1-5 وجود دارد. You haven't set the DATABASE_ENGINE setting yet.
به جای دستور python از دستور python manage.py shell باید استفاده کرد. Environment variable DJANGO_SETTINGS_MODULE is undefined.
Adapter پایگاه داده ی مناسبی نصب نشده است (مانند psycopg یا MySQLdb). Adapter ها با جنگو مچ نشده اند، بنابراین می بایست Adapter مورد نظر را دانلود و نصب کنید. Environment variable DJANGO_SETTINGS_MODULE is undefined.
SQLite Error loading _____ module: No module named _____.
تنظیم DATABASE_ENGINE را با یک موتور پایگاه داده که در جدول 1-5 وجود دارد پر کنید. _____ isn't an available database backend.
تنظیم مربوط به DATABASE_USER باید تغییر کنید و همچنین تنظیمی باشد که وجود دارد، و یا یک نام کاربری برای پایگاه داده باید ساخته شود. role _____ does not exist
اطمینان حاصل کنید که DATABASE_HOST و DATABASE_PORT صحیح بوده و سرور پایگاه داده در حال اجرا می باشد. could not connect to server

ایجاد اولین App

اکنون که مشخص شد اتصال به پایگاه داده با موفقیت انجام شده است، زمان آن رسیده است که یک app برای جنگو ایجاد کنیم، app یک بسته از کد جنگو می باشد که model ها و view ها در آن قرار می گیرد، که همگی با هم درون یک پکیج پایتون قرار گرفته اند و یک برنامه (application) جنگو را نمایش می دهند.

در شروع کار جنگو نحوه ی ساختن یک پروژه ی جنگو توضیح داده شد، اکنون فرق بین یک پروژه با یک app(اپلیکیشن)در چیست؟ تفاوت آن در پیکربندی در مقابل کد است:

  • یک پروژه یک نمونه از یک مجموعه ی خاص از app های جنگو به همراه پیکربندی برای آن ها می باشد.
  • از نظر فنی، تنها فرق اساسی که در پروژه وجود دارد ایجاد یک فایل settings می باشد، که اطلاعات connection پایگاه داده، لیست app های نصب شده، تنظیم TEMPLATE_DIRS و غیره را تعریف می کند.
  • app یک دسته ی قابل حمل از عمکرد جنگو می باشد، که معمولا شامل model ها و view ها می باشد که با هم در یک پکیج پایتون قرار دارند.
  • برای مثال، جنگو به همراه تعدادی از app ها، از قبیل سیستم کامنت و رابط خودکار مدیر، ارائه شده است، نکته ی قابل توجه درباره ی این app ها این است که، آن ها قابل حمل می باشند بدین معنی که می تواند آن ها را در چندین پروژه دیگر نیز دوباره استفاد کرد.

مقدار بسیار کمی قوانین سخت و محکم نسبت به وفق دادن کد جنگو با این طرح وجود دارد. در صورتیکه بخواهید یک وب سایت ساده بسازید، ممکن است تنها از یک app استفاده کنید. و اگر می خواهید یک وب سایت بزرگ و پیچیده با چندین قسمت مرتبط به هم مانند یک سیستم تجارت الکترونیک و یک تابلوی پیام (message board) بسازید، احتمالا این قسمت ها را درون app ها جدا قرار خواهید داد، به طوری که قادر باشید در صورت نیاز آن ها را به صورت منحصر به فرد در اینده استفاده کنید.

در واقع، لزوما همیشه نیاز به ساختن app ها در پروژه نیست، به عنوان نمونه توابع view که تاکنون ساخته شده است حاکی این موضوع می باشد. در آن مواردبه سادگی یک فایل views.py ساخته شده و با توابع view مورد نیاز درون آن پر می شد، و URLconf نیز به آن توابع اشاره می کرد. نیازی به app ها نبود.

با این وجود، در یک صورت باید از app ها استفاده شود: در صورتیکه شما از لایه ی پایگاه داده ی (models) جنگو استفاده می کنید، باید یک app جنگو بسازید. model ها باید درون app ها ایجاد شوند. در نتیجه، به منظور ساختن model ها، نیزا به ساختن یک app جدید می باشد.

درون دایرکتوری پروژه ی mysite، دستور زیر را برای ساختن app مورد نظر با نام books اجرا کنید:

python manage.py startapp books

دستور فوق هیچ خروجی ای را تولید نمی کند، در عوض دایرکتوری books را درون دایرکتوری mysite می سازد. در زیر محتویات این دایرکتوری را مشاهده می کند:

books/
    __init__.py
    models.py
    tests.py
    views.py

این فایل ها حاوی model ها و view ها برای این app می باشد.

درون ویرایشگر متن مورد علاقه ی خود نگاهی به فایل های models.py و views.py بیاندازید. جفت فایل ها خالی هستند، به غیر از کامنت ها و یک import که درون فایل models.py قرار دارد.

به اشتراک گذاری این ارسال


لینک به ارسال
به اشتراک گذاری در سایت های دیگر

تعریف Model ها در پایتون

همانطور که پیش تر در این فصل توضیح داده شد، "M" در "MTV" مخفف "Model" می باشد. model جنگو توصیف و شرح داده در پایگاه داده ی شما می باشد، که به صورت کد پایتون نمایش داده می شود. model لایه ی داده ی شما می باشد معادل عبارات SQL مانند CREATE TABLE با این تفاوت که این عبارات بجای SQL درون پایتون می باشند و همچنین آن ها شامل مواردی بیشتر از تعریف ستون های پایگاه داده می باشند. جنگو برای اجرای کد های SQL در پشت صحنه و برگرداندن ساختارهای داده ی پایتون مناسب از model ها استفاده می کند که از طریق آن ها ردیف های جداول پایگاه داده را نمایش می دهد. جنگو همچنین از model ها برای نمایش مفاهیم سطح بالا که SQL لزوما قادر به کنترل آن ها نمی باشد نیز استفاده می شود.

در صورتیکه با پایگاه های داده آشنا می باشید، این تفکر برای شما ایجاد می شود که "آیا تعریف کردن data model ها بجای SQL درون پایتون کار زائد و اضافه ای نیست؟" جنگو به چندین دلیل به این روش عمل می کند:

  • درون گرایی (استفاده از کد های SQL) مستلزم بار اضافی بوده و همچنین ناقص می باشد. به منظور تهیه ی API ها برای دسترسی به داده، جنگو به طریقی نیاز به شناختن لایه ی پایگاه داده دارد، و دو روش برای انجام آن وجود دارد. روش اول به طور واضح توضیح دادن داده در پایتون می باشد، و روش دوم تعیین data model ها درون پایگاه داده در هنگام اجرا می باشد.
  • روش دوم تمیز تر به نظر می رسد، زیرا داده های جداول شما در یک جا می باشند، ولی این روش دارای مشکلاتی می باشد. اول اینکه تعیین داده های درون پایگاه داده در زمان اجرا بدیهی است که نیازمند یک بار اضافه خواهد بود. در صورتیکه چارچوب یا فریم ورک در هر زمان بخواهد درون پایگاه داده اعمالی را انجام دهد خود به خود یک درخواست را پردازش کرده است، و یا حتی، در زمان اولیه وب سرور ممکن است یک بار اضافی در سطح غیر قابل قبول را متحمل شود. دوم آنکه برخی از پایگاه های داده، به ویژه نسخه های قدیمی MySQL، metadata مناسبی را به طور دقیق ذخیره نمی کنند.
  • کد نویسی در پایتون لذت بخش می باشد، و نگه داشتن همه چیز در محدوده ی کد پایتون باعث می شود تعداد باری که شما را مجبور کند محیط کد نویسی را تغییر دهد کم می کند. این موضوع یعنی نگه داشتن برنامه نویس در یک محیط کد نویسی بهره وری و راندمان را تا حد امکان افزایش می دهد. نوشتن کد SQL و سپس پایتون و دوباره SQL کاری ذهنیت برنامه نویس را مختل خواهد کرد.
  • داشتن data model های ذخیره شده به صورت کد بجای استفاده مستقیم از پایگاه داده، کنترل model هار بسیار آسان تر خواهد کرد. با این روش می توان به سادگی رد تغییرات در لایه های داده را دنبال کرد.
  • SQL تنها اجازه ی استفاده از داده های قطعی موجود می دهد، اغلب سیستم های پایگاه داده، به عنوان مثال؛ داده های تخصصی برای نمایش دادن آدرس های email یا URL را تهیه نمی کنند. در صورتیکه model های جنگو این کار را انجام می دهند. مزیت داده های نوع سطح بالا بهره وری بیشتر آن ها و قابلیت دوباره استفاده شدن آن ها می باشد.
  • SQL در پایگاه های داده ی مختلف یکجور نبوده و ناسازگار می باشد. بدین معنی که جملات و عبارت SQL برای مثال join ها و یا ... در پایگاه های داده ی مختلف مانند MySQL، PostgreSQL و یا SQLite ممکن است متفاوت از یکدیگر پیاده سازی شوند و هرکدام به روش خود آن را پیاده سازی کنند.

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

اولین Model شما

برای داشتن مثالی که هم در این فصل و فصل بعدی بتوان از آن استفاده کرد و آن را پیشرفت داد، بر روی لایه ی داده ی book/author/publisher تمرکز خواهد شد. از این مثال به دلیل اینکه دارای رابطه های شناخته شده ای بین books، authors و publishers می باشد، و داده های استفاده شده داده های مقدماتی استفاده شده در SQL می باشد.

داده های استفاده شده با خصوصیات زیر فرض شده اند:

  • یک نویسنده دارای یک نام، یک فامیلی و یک نشانی پست الکترونیک می باشد.
  • یک ناشر دارای یک نام، یک نشانی کوچه، یک شهر، یک استان، یک کشور و یک وب سایت می باشد.
  • یک کتاب دارای یک عنوان و یک تاریخ انتشار می باشد. همچنین هر کتاب دارای یک یا بیشتر نویسنده (رابطه ی چند به چند با authors) و یک ناشر (رابطه ی یک به چند به publishers) می باشد.

اولین قدم برای استفاده کردن لایه ی پایگاه داده با جنگو، بیان آن به صورت کد پایتون می باشد. درون فایل models.py که با استفاده از دستور startapp ساخته شده است، کد زیر را وارد کنید:

from django.db import models

class Publisher(models.Model):
    name = models.CharField(max_length=30)
    address = models.CharField(max_length=50)
    city = models.CharField(max_length=60)
    state_province = models.CharField(max_length=30)
    country = models.CharField(max_length=50)
    website = models.URLField()

class Author(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=40)
    email = models.EmailField()

class Book(models.Model):
    title = models.CharField(max_length=100)
    authors = models.ManyToManyField(Author)
    publisher = models.ForeignKey(Publisher)
    publication_date = models.DateField()

اجازه دهید کد فوق را به طور اجمالی مورد بررسی قرار دهیم. اولین نکته ی قابل توجه این است که هر model به صورت یک کلاس پایتون نمایش داده شده است و هر model کلاس فرزند از کلاس پدر django.db.models.Model می باشد. کلاس پدر Model، حاوی تمام ابزار آلات ضروری برای ساختن شیء هایی می باشد که توانایی تعامل با یک پایگاه داده را داشته باشند. مشاهده می کنید که تعریف فیلدها به چه اندازه مختصر و ساده می باشند. باور کنید یا نه، این تمام کدی می باشد که برای داشتن دسترسی داده ی اولیه با جنگو لازم است.

هر model عموما با یک جدول پایگاه داده و هر attribute در یک model با یک ستون جدول در پایگاه داده مطابق می باشد، نام attribute مطابق با نام ستون، و نوع فیلد (مانند CharField) مطابق با نوع ستون (مانند varchar) می باشد. برای مثال مدل Publisher برابر با جدول زیر می باشد (با فرض عبارت CREATE TABLE درون پایگاه داده ی PostgreSQL):

CREATE TABLE "books_publisher" (
    "id" serial NOT NULL PRIMARY KEY,
    "name" varchar(30) NOT NULL,
    "address" varchar(50) NOT NULL,
    "city" varchar(60) NOT NULL,
    "state_province" varchar(30) NOT NULL,
    "country" varchar(50) NOT NULL,
    "website" varchar(200) NOT NULL
);

در واقع، جنگو می تواند عبارت CREATE TABLE را به صورت خودکار تولید کند که در کد فوق مشاهده کردید.

در این قانون که برای هر جدول پایگاه داده، یک کلاس در نظر گرفته می شود، در مورد رابطه های چند به چند یک استثنا وجود دارد. در مثال مدل های فوق، Book دارای یک فیلد چند به چند با نام authors می باشد. این فیلد مشخص می کند که هر کتاب دارای یک یا چند نویسنده می باشد، در صورتیکه جدول پایگاه داده Book دارای ستونی به نام authors نمی باشد. در عوض، جنگو یک جدول اضافه می سازد (یک "join table" چند به چند) که رابطه بین کتاب ها و نویسندگان را کنترل می کند.

در پایان، توجه داشته باشید که به طور صریح برای هر یک از مدل ها یک کلید اصلی (primary key) تعریف نشده است. جنگو به صورت خودکار برای هر مدل یک فیلد auto_incremen integer primary key ایجاد می کند که id نام دارد. هر مدل جنگو لازم است یک ستون primary key داشته باشد.

نصب کردن Model

کد مربوط به مدل ها نوشته شد، حالا اجازه دهید جدول ها را درون پایگاه داده نیز ایجاد کنیم. به منظور انجام این کار؛ قدم اول این است که مدل های ساخته شده درون پروژه جنگو را فعال کنیم. برای انجام این کار باید app مورد نظر یعنی books را به لیست "installed apps" درون فایل settings اضافه کنیم.

فایل settings.py را دوباره باز کرده، و تنظیم INSTALLED_APPS را پیدا کنید. INSTALLED_APPS به جنگو می گوید که کدام app ها برای پروژه مورد نظر فعال هستند. به طور پیشفرض، این تنظیم به این شکل خواهد بود:

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
)

به طور موفت با گذاشتن علامت (#) در ابتدای هر کدام از رشته ها آن ها را کامنت کنید. (این رشته ها موارد کامنت شده ای برای راحتی کار می باشند که در فصل های بعدی آن ها را فعال کرده و در مورد آن ها بحث خواهیم کرد.) همچنین تنظیمات مربوط به MIDDLEWARE_CLASSES را نیز کامنت کنید؛ مقادیر پیشفرض در MIDDLEWARE_CLASSES به برخی از app هایی که ما آن ها را کامنت کردیم وابسته می باشند. سپس 'mysite.books'را به لیست INSTALLED_APPS اضافه کنید، در پایان تنظیم انجام داده شده چیزی شبیه به مثال زیر خواهد بود:

MIDDLEWARE_CLASSES = (
    # 'django.middleware.common.CommonMiddleware',
    # 'django.contrib.sessions.middleware.SessionMiddleware',
    # 'django.contrib.auth.middleware.AuthenticationMiddleware',
)

INSTALLED_APPS = (
    # 'django.contrib.auth',
    # 'django.contrib.contenttypes',
    # 'django.contrib.sessions',
    # 'django.contrib.sites',
    'mysite.books',
)

(همانطور که در فصل گذشته، هنگام تنظیم TEMPLATE_DIRS گفته شد، لازم است حتما یک علمت کاما (,) در انتهای mysite.books قرار دهید، زیرا mysite.books در اینجا یک تک المان تاپل پایتون می باشد، اتفاقا، نویسندگان این کتاب ترجیح می دهند بعد از هر المان تاپل، بدون در نظر گرفتن این که یک تک المان است یا خیر، یک کاما در انتهای آن قرار میدهند. این کار باعث می شود از فراموش کردن قرار دادن کاما در انتهای المان های تک تاپل جلوگیری شود، و گذاشتن کامای اضافه هیچ مشکلی به وجود نخواهد آورد.)

'mysite.books' به app ای که با کار می کنیم اشاره می کند. هر app ای که در تنظیم INSTALLED_APPS قرار دارد با آدرس کامل پایتون آن نمایش داده می شود (مسیر پکیج ها با نقطه جدا شده و به پکیج app هدایت می شوند.)

اکنون که app مورد نظر در فایل settings فعال شد، می توانیم جداول پایگاه داده را درون پایگاه داده ایجاد کنیم. ابتدا، اجازه دهید با دستور زیر از معتبر بودن کد های برنامه اطمینان حاصل کنیم:

python manage.py validate

دستور validate بررسی می کند که ایا کدها و منطق مدل های نوشته صحیح می باشد یا خیر. در صورتیکه همه چیز درست باشد، شما پیام message 0 errors found را مشاهده خواهید کرد. در غیر اینصورت، اطمینان حاصل کنید کدهای مدل شما صحیح می باشد. خطای خروجی، اطلاعات مفیدی درباره ی ایراد موجود در کد، در اختیار شما می گذارد.

هر زمان که تصور کردید مشکلاتی درون مدل ها وجود دارد، دستور python manage validate را اجرا کنید. دستور فوق به شما کمک می کند تا تمام مشکلات موجود در مدل را بر طرف کنید.

در صورتیکه کد مربوط به مدل شما معتبر می باشد، دستور زیر را برای تولید عبارت های CREATE TABE مربوط به مدل ها در app مورد نظر (books) وارد کنید:

python manage.py sqlall books

در دستور فوق، books نام app می باشد. این نام همان چیزی است که شما با اجرای دستور manage.py startapp ایجاد کرده اید. هنگامی که شما دستور فوق manage.py sqlall books را اجرا می کنید چیزی شبیه به این را در خروجی مشاهده خواهید کرد:

BEGIN;
CREATE TABLE "books_publisher" (
    "id" serial NOT NULL PRIMARY KEY,
    "name" varchar(30) NOT NULL,
    "address" varchar(50) NOT NULL,
    "city" varchar(60) NOT NULL,
    "state_province" varchar(30) NOT NULL,
    "country" varchar(50) NOT NULL,
    "website" varchar(200) NOT NULL
)
;
CREATE TABLE "books_author" (
    "id" serial NOT NULL PRIMARY KEY,
    "first_name" varchar(30) NOT NULL,
    "last_name" varchar(40) NOT NULL,
    "email" varchar(75) NOT NULL
)
;
CREATE TABLE "books_book" (
    "id" serial NOT NULL PRIMARY KEY,
    "title" varchar(100) NOT NULL,
    "publisher_id" integer NOT NULL REFERENCES "books_publisher" ("id") DEFERRABLE INITIALLY DEFERRED,
    "publication_date" date NOT NULL
)
;
CREATE TABLE "books_book_authors" (
    "id" serial NOT NULL PRIMARY KEY,
    "book_id" integer NOT NULL REFERENCES "books_book" ("id") DEFERRABLE INITIALLY DEFERRED,
    "author_id" integer NOT NULL REFERENCES "books_author" ("id") DEFERRABLE INITIALLY DEFERRED,
    UNIQUE ("book_id", "author_id")
)
;
CREATE INDEX "books_book_publisher_id" ON "books_book" ("publisher_id");
COMMIT;

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

  • نام جدول ها به طور خودکار با ترکیب نام app (books) و نام مدل (publisher، book و author) به صورت حروف کوچک تولید می شود.
  • همانطور که پیش تر توضیح داده شد، جنگو برای هر جدول به طور خودکار یک کلید اصلی (با نام id) اضافه می کند.
  • رابطه ی کلید خارجی با عبارت REFERENCES، صریح و روشن ساخته شده است.
  • عبارت CREATE TABLE برای پایگاه داده ای که شما استفاده می کنید مناسب خواهد بود، بنابراین تعیین نوع فیلد مانند auto_increment (MySQL)، serial (PostgreSQL)، یا integer primary key (SQLite) به طور خودکار توسط جنگو انجام خواهد شد. همچنین در مورد کتیشن برای نام ستون ها (مانند استفاده از دابل کتیشن یا تک کتیشن) نیز این موضوع صدق می کند. مثال فوق خروجی برای PostgreSQL می باشد.

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

python manage.py syncdb

بعد از اجرای دستور فوق، خروجی شبیه به مثال زیر مشاهده خواهید کرد:

Creating table books_publisher
Creating table books_author
Creating table books_book
Installing index for books.Book model

دستور syncdb یک هماهنگی ساده بین مدل ها و پایگاه داده می باشد. دستور فوق تمام مدل های موجود در هر app را درون تنظیم INSTALLED_APPS بررسی می کند، و در صورتیکه جدول مناسب برای مدل های مورد نظر وجود نداشته باشد آن ها را ایجاد می کند. توجه داشته باشید که syncdb تغییرات بوجود آمده در مدل ها و یا حذف مدل ها را با پایگاه داده هماهنگ نمی کند؛ در صورتیکه شما یک تغییر را در مدل ایجاد کرده و یا مدلی را حذف کنید، هنگامی که بخواهید پایگاه داده را با این تغییرات به روز رسانی کنید، دستور syncdb نمی تواند این کار را انجام دهد. (در انتهای همین فصل این موضوع بحث خواهد شد.)

در صورتیکه که دستور python manage.py syncdb را دوباره اجرا کنید، اتفاقی رخ نخواهد داد، زیرا هیچ مدل به app مورد نظر (books) اضافه نشده است و یا هیچ app ای به تنظیم INSTALLED_APPS اضافه نشده است. بنابراین اجرای دستور python manage.py syncdb همواره مشکلی بوجود نخواهد آورد.

در صورتیکه کنجکاو هستید، می توانید برای لحظاتی به درون خط فرمان پایگاه داده رفته جداول ایجاد شده درون پایگاه داده ی خود را مشاهده کنید. می توانید به صورت دستی دستوراتی را که می خوهید درون خط فرمان کلاینت اجرا کنید (مانند psql برای PostrgreSQL) یا اینکه می توانید دستور python manage.py dbshell، را اجرا کنید، و بسته به تنظیم DATABASE_SERVER، خواهید فهمید کدام خط فرمان اجرا می شود. شیوه ی دوم تقریبا همیشه مناسب تر می باشد.

به اشتراک گذاری این ارسال


لینک به ارسال
به اشتراک گذاری در سایت های دیگر

اصول اولیه دسترسی به داده

هنگامی که شما یک مدل ساخته می شود، جنگو به طور خودکار یک API سطح بالا پایتون را برای کار با آن مدل ها ایجاد می کند. دستور python manage.py shell را اجرا کرده و کدهای زیر را امتحان کنید:

>>> from books.models import Publisher
>>> p1 = Publisher(name='Apress', address='2855 Telegraph Avenue',
...     city='Berkeley', state_province='CA', country='U.S.A.',
...     website='http://www.apress.com/')
>>> p1.save()
>>> p2 = Publisher(name="O'Reilly", address='10 Fawcett St.',
...     city='Cambridge', state_province='MA', country='U.S.A.',
...     website='http://www.oreilly.com/')
>>> p2.save()
>>> publisher_list = Publisher.objects.all()
>>> publisher_list
[<Publisher: Publisher object>, <Publisher: Publisher object>]

چند خط کد فوق کار زیادی را انجام می دهند. نکات برجسته در کد فوق:

  • ابتدا، مدل Publisher به import شده است. با این کار می توان با جدول پایگاه داده که حاوی اطلاعات ناشران می باشد در ارتباط بود.
  • یک شیء Publisher با مقدار دهی مقدار دهی اولیه ی آن با مقادیری برای هر فیلد (name، address و غیره ...) ساخته شده است.
  • برای ذخیره ی شیء درون پایگاه داده، متد save() آن فراخوانی شده است. در پشت صحنه، جنگو عبارت INSERT برای SQL را در اینجا اجرا می کند.
  • برای بازیابی اطلاعات ناشران از پایگاه داده، از attribute مورد نظر Publisher.objects برای بدست آوردن مجموعه تمام ناشران استفاده شده است. لیست تمام شیء های Publisher با استفاده از عبارت Publisher.objects.all() واکشی شده است. در پشت صحنه جنگو یک عبارت SELECT را به صورت SQL در اینجا اجرا می کند.

نکته ای با اهمیتی که در این مورد واضح و یا روشن به نظر نمی رسد اینکه، هنگامی که شما با استفاده از API مدل جنگو در حال ساختن شیء ها می باشید، جنگو تا وقتی که متد save() را فراخوانی نکنید شیء ها را درون پایگاه داده ذخیره نمی کند.

p1 = Publisher(...)
# At this point, p1 is not saved to the database yet!
p1.save()
# Now it is.

در صورتیکه می خواهید یک شیء ساخته و آن را درون پایگاه داده با یک حرکت ذخیره کنید، می توانید از متد objects.create() استفاده کنید. مثال زیر با مثال قبلی برابر است:

>>> p1 = Publisher.objects.create(name='Apress',
...     address='2855 Telegraph Avenue',
...     city='Berkeley', state_province='CA', country='U.S.A.',
...     website='http://www.apress.com/')
>>> p2 = Publisher.objects.create(name="O'Reilly",
...     address='10 Fawcett St.', city='Cambridge',
...     state_province='MA', country='U.S.A.',
...     website='http://www.oreilly.com/')
>>> publisher_list = Publisher.objects.all()
>>> publisher_list

به طور طبیعی می توانید کارهای بسیار زیادی را به استفاده از API پایگاه داده ی جنگو انجام دهید، اما در ابتدا با کارهای کوچک بسنده می کنیم.

‫اضافه كردن نمايش رشته اي براي مدل‬

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

[<Publisher: Publisher object>, <Publisher: Publisher object>]

می توان این مشکل را به راحتی با اضافه کردن متد __unicode__() به کلاس Publisher حل کرد. متد __unicode__() به پایتون می گوید که یک شیء را به چه شکل در خروجی نمایش دهد. می توانید نحوه ی استفاده از آن را در عمل با اضافه کردن یک متد __unicode__() درون سه مدل فوق مشاهده کنید:

from django.db import models

class Publisher(models.Model):
    name = models.CharField(max_length=30)
    address = models.CharField(max_length=50)
    city = models.CharField(max_length=60)
    state_province = models.CharField(max_length=30)
    country = models.CharField(max_length=50)
    website = models.URLField()

    def __unicode__(self):
        return self.name

class Author(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=40)
    email = models.EmailField()

    def __unicode__(self):
        return u'%s %s' % (self.first_name, self.last_name)

class Book(models.Model):
    title = models.CharField(max_length=100)
    authors = models.ManyToManyField(Author)
    publisher = models.ForeignKey(Publisher)
    publication_date = models.DateField()

    def __unicode__(self):
        return self.title

همانطور که مشاهده می کنید، متد __unicode__() می تواند هر آنچه را که برای برای نمایش یک شیء نیاز است انجام دهید. در مثال فوق متدهای __unicode__() برای Publisher و Book به سادگی نام و عنوان شیء را به ترتیب برمی گردانند، ولی متد __unicode__() برای Author کمی پیچیده تر از بقیه می باشد، بدین صورت که فیلد های first_name و last_name را به یک فاصله به هم وصل کرده و بر می گرداند.

تنها الزام برای متد __unicode__() این است که یک شیء یونیکد را بر می گرداند. در صورتیکه متد __unicode__() یک شیء یونیکد را بر نگرداند به عنوان مثال یک integer بر گرداند، در اینصورت پایتون خطای TypeError را با پیامی مانند "coercing to Unicode: need string or buffer, int found"ایجاد خواهد کرد.

آبجكت هاي يونيكد‬

شیء یونیکد چیست؟

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

رشته های معمولی پایتون رمزی شده (encoded) می باشند، بدین معنی که آن ها به صورت رمز شده مانند ASCII، ISO-8859-1 یا UTF-8 استفاده می شوند. در صورتیکه بخواهید حروف تجملی (هرچیزی خارج از 128 حرف ASCII مانند 0-9 و A-Z) را درون یک رشته ی معمولی پایتون ذخیره کنید، این حروف هنگام چاپ و یا نمایش به صورت به هم ریخته در می ایند. مشکلات زمانی رخ می دهند که شما داده ای را درون یک encoding ذخیره کرده و سعی می کنید آن را با یک encoding متفاوت دیگر ترکیب کنید و یا سعی می کنید آن را درون یک برنامه که دارای یک encoding مشخص می باشد نمایش دهید. همه ی ما صفحات وب و پست الکترونیکی را مشاهده کرده ایم که با حالت "??? ??????" و یا دیگر حروف خراب شده اند؛ این مشکلات عموما مشکلات encoding تلقی می شوند.

شیء های یونیکد، اگر چه encoding ندارند؛ ولی آن ها از یک مجموعه از حروف جهانی و سازگار که "Unicode" نامیده می شوند استفاده می کنند. هنگامی که شما با شیء های یونیکد در پایتون سر و کار دارید، می توانید بدون هیچگونه نگرانی نسبت به مسائل encoding آن ها را با یکدیگر ترکیب و استفاده کنید.

جنگو در سرتاسر چارچوب یا فریم ورک از شیء های یونیکد استفاده می کند. شیء های مدل به صورت شیء های یونیکد بازیابی شده اند، view ها با داده ی یونیکد ارتباط برقرار می کنند و template ها به صورت یونیکد ارائه می شوند. عموما، توسعه دهندگان درون فریم ورک جنگو هیچگونه نگرانی نسبت به درست بودن encoding درون برنامه های نوشته شده توسط جنگو نخواهند داشت.

توجه داشته باشید که سطح بالا بودن مطالب در اینجا باعث می شود که اطلاعات شما در مورد شیء های یونیکد کمی گمگ و مبهم باشد، در صورتیکه می خواهید می توانید در نشانی زیر مورد آن اطلاعات بیشتری کسب کنید.

http://www.joelonsoftware.com/articles/Unicode.html

برای اینکه تغییرات ایجاد شده درون مدل ها (اضافه کردن متد __unicode__()) اعمال شود، از shell پایتون خارج شده و با دستور python manage.py shell دوباره وارد آن شوید. (این ساده ترین راه برای اعمال تغییرات ایجاد شده می باشد) حالا لیست شیء های Publisher قابل فهم تر شده اند:

>>> from books.models import Publisher
>>> publisher_list = Publisher.objects.all()
>>> publisher_list
[<Publisher: Apress>, <Publisher: O'Reilly>]

اطمینان حاصل کنید که تمام مدل های تعریف شده دارای متد __unicode__() می باشند، نه فقط برای راحتی خودتان هنگامی که از interactive interpreter استفاده می کنید، بلکه جنگو نیز در مکان های زیادی از خروجی __unicode__() برای نمایش شیء ها استفاده می کند.

در پایان، توجه داشته باشید که __unicode__() یک مثال خوب برای اضافه کردن یک رفتار برای مدل ها می باشد. یک مدل جنگو بیشتر از جدول لایه پایگاه داده برای یک شیء توضیح می دهد؛ همچنین مدل جنگو هر عملکردی را که شیء نحوه انجام آن را می داند را توضیح دهد. __unicode__() یک مثال برای عملکرد می باشد بدین معنی که یک مدل نحوه ی نمایش خود را می داند.

‫درج و به روز رساني داده‬

طریقه ی درج کردن داده درون پایگاه داده را مشاهده کردید: ابتدا یک نمونه از یک مدل را با استفاده از ارگومان های آن مانند زیر ساخته:

>>> p = Publisher(name='Apress',
...         address='2855 Telegraph Ave.',
...         city='Berkeley',
...         state_province='CA',
...         country='U.S.A.',
...         website='http://www.apress.com/')

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

>>> p.save()

عملیات بالا را تقریبا می تواند به شکل زیر به زبان SQL ترجمه کرد:

INSERT INTO books_publisher
    (name, address, city, state_province, country, website)
VALUES
    ('Apress', '2855 Telegraph Ave.', 'Berkeley', 'CA',
     'U.S.A.', 'http://www.apress.com/');

به دلیل آنکه مدل Publisher از یک کلید اصلی به نام id که با خاصیت افزایش خودکار تعریف شده است استفاده می کند، نخستین فراخوانی save() یک کار بیشتر انجام می دهد: ارزش کلید اصلی را برای رکورد محاسبه کرده و آن را درون attribute مورد نظر یعنی id که درون نمونه (instance) می باشد، قرار می دهد:

>>> p.id
52    # this will differ based on your own data

فراخوانی های بعدی save() رکورد را در مکانی ذخیره می کند، بدون ساختن رکورد جدید (مانند عملکرد عبارت Upadate به جای INSERT در SQL)

>>> p.name = 'Apress Publishing'
>>> p.save()

عملیات فوق را می توان تقریبا به شکل زیر به SQL تبدیل کرد:

UPDATE books_publisher SET
    name = 'Apress Publishing',
    address = '2855 Telegraph Ave.',
    city = 'Berkeley',
    state_province = 'CA',
    country = 'U.S.A.',
    website = 'http://www.apress.com'
WHERE id = 52;

بله، توجه داشته باشید که نه تنها فیلد مورد نظر بلکه تمام فیلدها به روز رسانی خواهند شد. برای پی بردن به نحوه ی اجرای پرس و جوی زیر بخش "به روز رسانی چندین شیء در یک عبارت" را مطالعه کنید:

UPDATE books_publisher SET
    name = 'Apress Publishing'
WHERE id=52;

 

به اشتراک گذاری این ارسال


لینک به ارسال
به اشتراک گذاری در سایت های دیگر

‫واكشي آبجكت ها‬

برای ساختن برنامه های وب دانستن نحوه ی ساخت و به روز رسانی رکورد های پایگاه داده ضروری می باشد، ولی احتمال این وجود دارد که برنامه های وبی که خواهید ساخت نیاز به به اجرای پرس و جوهای بیشتری نیز داشته باشد. روش بازیابی رکوردها را نیز پیش تر مشاهده کردید:

>>> Publisher.objects.all()
[<Publisher: Apress>, <Publisher: O'Reilly>]

مدل کد فوق در SQL چیزی شبیه به کد زیر خواهد بود:

SELECT id, name, address, city, state_province, country, website
FROM books_publisher;

نكته‬
توجه داشته باشید که جنگو هنگامی که به دنبال داده ها می باشد از SELECT * استفاده نمی کند، در عوض لیست تمام فیلدها را به صورت واضح بازیابی می کند. در بعضی شرایط SELECT * می تواند کندتر باشد و (خیلی مهم است) لیست کردن فیلدها به طرز نزدیکی از مکتب پایتون (Zen of Python) پیروی کند: "صراحت بهتر از ابهام است".

اجازه دهید از نزدیک بخش های Publisher.objects.all() را مورد بررسی قرار دهیم:

  • ابتدا، یک مدل داریم که Publisher تعریف شده است. چیز خاصی در اینجا وجود ندارد: هنگامی که شما می خواهید داده ای را جستجو کنید از یک مدل برای آن داده استفاده می کنید.
  • و اما در مورد attribute متد objects. این attribute یک manager نامیده می شود. manager ها به تفصیل در آموزش مدل پیشرفته توضیح داده شده اند. چیزی که برای الان باید بدانید این است که manager ها از تمامی عملکردهای سطح جدول در داده مراقبت می کنند که برای جستجوی داده ها بسیار پر اهمیت می باشند.
  • تمام مدل ها به طور خود کار یک manager، objects دریافت می کنند؛ شما در هر زمان که بخواهید نمونه های مدل را جستجو کنید از استفاده خواهید کرد.
  • در نهایت، all() یک متد برای manager، objects می باشد که تمام ردیف های پایگاه داده را بر می گرداند. اگرچه این شیء شبیه به لیست می باشد، اما در واقع یک QuerySet می باشد (یک شیء که مجموعه مشخصی از ردیف های پایگاه داده می باشد.) در ادامه فصل تنها درباره آن ها همانند لیست ها بحث خواهیم کرد.

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

به طور طبیعی، کم پیش می اید که بخواهیم همه چیز را از پایگاه داده یکباره واکشی کنیم؛ در اغلب موارد، می خواهید با قسمتی از داده ها سر و کار داشته باشید. در API جنگو، شما می توانید داده ی خود را با استفاده از متد filter() فیلتر کنید:

>>> Publisher.objects.filter(name='Apress')
[<Publisher: Apress>]

filter() یک آرگومان به صورت کیورد در یافت می کند که معادل آن در SQL عبارت WHERE می باشد. معادل کد SQL مثال قبلی چیزی مانند کد زیر بود:

SELECT id, name, address, city, state_province, country, website
FROM books_publisher
WHERE name = 'Apress';

جهت محدود کردن بیشتر خروجی می توانید چندین آرگومان را به درون filter() ارسال کنید:

>>> Publisher.objects.filter(country="U.S.A.", state_province="CA")
[<Publisher: Apress>]

می توان ارسال چندین آرگومان به متد filter() درون SQL مانند عبارت AND داست. در نتیجه، مثال فوق می تواند به شکل زیر یه کد SQL تبدیل شود:

SELECT id, name, address, city, state_province, country, website
FROM books_publisher
WHERE country = 'U.S.A.'
AND state_province = 'CA';

توجه داشته باشید که به طور پیشفرض جستجو درون SQL با علامت = دقیقا یکسان بودن را بررسی می کند. نوع دیگر جستجو نیز در دسترس می باشد:

>>> Publisher.objects.filter(name__contains="press")
[<Publisher: Apress>]

همانطور که مشاهده می کنید دو علامت (_) بین name و contains قرار دارد. مانند خود پایتون، جنگو از این خصوصیت برای اشاره به این که چیزی به صورت "magic" در حال اتفاق افتادن می باشد استفاده می کند (در اینجا قسمت __contains به عبارت LIKE در SQL تبدیل می شود.)

SELECT id, name, address, city, state_province, country, website
FROM books_publisher
WHERE name LIKE '%press%';

انواع دیگر جستجو نیز در دسترس می باشد، از جمله icontains (عبارت LIKE که به حروف کوچک و بزرگ حساس می باشد)، starstwith، endswith و range (پرس و جوی BETWEEN در SQL).

بازیابی شیء های تک

مثال های filter() فوق همگی یک QuerySET را بر می گردانند، که می توان با آن ها مانند لیست ها کار کرد. گاهی اوقات بسیار مناسب می باشد که تنها یک تک شیء را از پایگاه داده واکشی کنیم. برای این منظور متد get() در دسترس می باشد:

>>> Publisher.objects.get(name="Apress")
<Publisher: Apress>

بجای یک لیست (QuerySet) یک تک شیء برگشت داده شده است. به دلیل آنکه در صورتیکه نتیجه پرس و جو (query) چند شیء باشد موجب بروز خطای MultipleObjectsReturned خواهد شد:

>>> Publisher.objects.get(country="U.S.A.")
Traceback (most recent call last):
    ...
MultipleObjectsReturned: get() returned more than one Publisher --
    it returned 2! Lookup parameters were {'country': 'U.S.A.'}

همچنین در صورتیکه هیچ شیءی برگدانده نشود موجب بروز خطای DoesNotExist خواهد شد

>>> Publisher.objects.get(name="Penguin")
Traceback (most recent call last):
    ...
DoesNotExist: Publisher matching query does not exist.

خطای DoesNotExist یک attribute از کلاس مدل می باشد (Publisher.DoesNotExist). در برنامه های خود می توانید مانند زیر خطاها را کنترل کنید:

try:
    p = Publisher.objects.get(name='Apress')
except Publisher.DoesNotExist:
    print "Apress isn't in the database yet."
else:
    print "Apress is in the database."

 

به اشتراک گذاری این ارسال


لینک به ارسال
به اشتراک گذاری در سایت های دیگر

مرتب سازی داده ها

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

در برنامه های جنگو، ممکن است بخواهید نتیجه استخراج شده از پایگاه داده را بر اساس یکی از معیارها مرتب کنید (برای مثال بر اساس حروف الفبا). برای انجام این کار از متد order_by() استفاده می شود:

>>> Publisher.objects.order_by("name")
[<Publisher: Apress>, <Publisher: O'Reilly>]

مثال فوق فرق چندانی با متد قبلی یعنی all() ندارد، تنها فرق در این است که حالا SQL ترتیب تعیین شده را نیز لحاظ می کند:

SELECT id, name, address, city, state_province, country, website
FROM books_publisher
ORDER BY name;

شما می توانید بر اساس هر فیلدی که مایلید خروجی را مرتب کنید:

>>> Publisher.objects.order_by("address")
[<Publisher: O'Reilly>, <Publisher: Apress>]

>>> Publisher.objects.order_by("state_province")
[<Publisher: Apress>, <Publisher: O'Reilly>]

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

>>> Publisher.objects.order_by("state_province", "address")
 [<Publisher: Apress>, <Publisher: O'Reilly>]

همچنین می توان ترتیب چیدمان را با استفاده از یک علامت (-) به صورت بر عکس انجام داد:

>>> Publisher.objects.order_by("-name")
[<Publisher: O'Reilly>, <Publisher: Apress>]

در اغلب موارد شما همواره می خواهید در حالت پیشفرض خروجی داده های شما بر اساس یکی از فیلد های چیده شده باشد، در این صورت جنگو این امکان را فراهم می کند که به صورت پیشفرض درون مدل خود این کار انجام دهید، این امکان زمانی مفید واقع می شود که بخواهید به صورت مکرر از order_by() استفاده کنید، در این صورت منطقی این است که روش زیر را انتخاب کنید:

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']

در مثال فوق، یک مفهوم جدید معرفی شده است: class Meta، کلاسی می باشد که درون کلاس Publisher تعریف شده است (این کلاس درون class Publisher به صورت داخل رفته indented قرار گرفته است). می توان از کلاس Meta روی هر مدل برای تعیین اختیارات مختلفی استفاده کرد. که در اینجا ما از اختیار مرتب کردن آن استفاده کرده ایم. این اختیار به جنگو می گوید که اگر به صورت واضح برای چیدمان داده از order_by() استفاده نشده است، تمام شیء های Publisher هر زمان که با API پایگاه داده بازیابی شدند، باید بر اساس فیلد name مرتب شوند.

جستجوی زنجیری

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

>>> Publisher.objects.filter(country="U.S.A.").order_by("-name")
[<Publisher: O'Reilly>, <Publisher: Apress>]

همانطور که انتظار می رود، معادل کد فوق در SQL استفاده از یک WHERE و یک ORDER BY می باشد:

SELECT id, name, address, city, state_province, country, website
FROM books_publisher
WHERE country = 'U.S.A'
ORDER BY name DESC;

تقسیم کردن داده

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

>>> Publisher.objects.order_by('name')[0]
<Publisher: Apress>

کد فوق به شکل زیر تبدیل می شود:

SELECT id, name, address, city, state_province, country, website
FROM books_publisher
ORDER BY name
LIMIT 1;

به همین صورت، می توان زیر مجموعه ای از داده را نیز بازیابی کرد:

>>> Publisher.objects.order_by('name')[0:2]

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

SELECT id, name, address, city, state_province, country, website
FROM books_publisher
ORDER BY name
OFFSET 0 LIMIT 2;

توجه داشته باشید که قسیم کردن به صورت منفی در اینجا پشتیبانی نمی شود:

>>> Publisher.objects.order_by('name')[-1]
Traceback (most recent call last):
  ...
AssertionError: Negative indexing is not supported.

با وجود این می توانید به راحتی محدودیت فوق را دور بزنید. تنها کافیست عبارت order_by() را به صورت زیر تغییر دهید:

>>> Publisher.objects.order_by('-name')[0]

به روز رسانی چند شیء در یک عبارت

در بخش "درج و به روز رسانی داده" اشاره شد که متد save() تمام ستون های یک ردیف را به روز رسانی می کند. بسته به برنامه ی شما، ممکن است بخواهید تنها یک قسمت از ستون ها را به روز رسانی کنید.

به عنوان مثال، تصور کنید می خواهیم Apress را درون Publisher از 'Apress' به 'Apress Publishing' تغییر دهیم. با استفاده از save() به این صورت عمل می توان کرد:

>>> p = Publisher.objects.get(name='Apress')
>>> p.name = 'Apress Publishing'
>>> p.save()

که درون SQL نیز به این شکل تبدیل خواهد شد:

SELECT id, name, address, city, state_province, country, website
FROM books_publisher
WHERE name = 'Apress';

UPDATE books_publisher SET
    name = 'Apress Publishing',
    address = '2855 Telegraph Ave.',
    city = 'Berkeley',
    state_province = 'CA',
    country = 'U.S.A.',
    website = 'http://www.apress.com'
WHERE id = 52;

(توجه داشته باشید که مثال فوق فرض را بر این گرفته است که Apress دارای id = 52 می باشد.)

می توان در مثال فوق مشاهده نمود که متد save() جنگو نه تنها ستون name را بلکه ارزش تمام ستون ها را به روز رسانی می کند. منطقی تر و بهتر آن است که تنها ستون مورد نظر تغییر کند. برای انجام چنین کاری می توان از متد update() موجود در شیء های QuerySet استفاده مانند زیر استفاده کرد:

>>> Publisher.objects.filter(id=52).update(name='Apress Publishing')

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

UPDATE books_publisher
SET name = 'Apress Publishing'
WHERE id = 52;

متد update() بر روی هر QuerySet ای کار می کند، بدین معنی که می توان چندین رکورد را نیز ویرایش کرد. در مثال زیر نحوه تغییر country درون هر رکورد Publisher از 'U.S.A' به USA نشان داده شده است.

>>> Publisher.objects.all().update(country='USA')
2

متد update() یک integer بر می گرداند که تعداد رکوردهای تغییر داده شده می باشد، در مثال فوق 2 رکورد تغییر داده شده اند.

حذف کردن شیء ها

برای حذف کردن یک شیء از پایگاه داده، به سادگی متد delete() را می توان فراخوانی کرد:

>>> p = Publisher.objects.get(name="O'Reilly")
>>> p.delete()
>>> Publisher.objects.all()
[<Publisher: Apress Publishing>]

همچنین می توان چندین شیء را نیز با فراخوانی متد delete() از هر QuerySet حذف کرد. این حالت مانند متد update() که در بخش گذشته نشان داده شد می باشد:

>>> Publisher.objects.filter(country='USA').delete()
>>> Publisher.objects.all().delete()
>>> Publisher.objects.all()
[]

هنگام حذف کردن داده ها مراقب باشید! برای احتیاط از حذف شدن تمام داده های موجود در یک جدول مشخص، در صورتیکه می خواهید هر آنچه که درون جدول می باشد را حذف کنید، می توان از متد all() استفاده کرد. به عنوان مثال کد زیر کار نخواهد کرد:

>>> Publisher.objects.delete()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'Manager' object has no attribute 'delete'

ولی اگر متد all() را اضافه کنید کار خواهد کرد:

>>> Publisher.objects.all().delete()

در صورتیکه می خواهید تنها بخشی از داده را حذف کنید، نیازی به متد all() نمی باشد:

>>> Publisher.objects.filter(country='USA').delete()

 

به اشتراک گذاری این ارسال


لینک به ارسال
به اشتراک گذاری در سایت های دیگر

سایت و رابط مدیر در جنگو (Django)

برای طبقه ی خاصی از وب سایت ها، یک رابط مدیر (admin interface) بخشی ضروری زیر ساخت وب سایت می باشد. رابط مدیر یک رابط تحت وب می باشد، که مختص مدیران مورد اعتماد سایت می باشد و اضافه کردن، ویرایش و حذف محتویات سایت را مقدور می سازد. رابطی که شما برای پست کردن به بلاگ خود از آن استفاده می کنید، ابزاری که کاربران برای به روز رسانی مطالبی که برای آن ها ساخته اید به کار می برند و محیطی که مدیران سایت برای اداره ی نقطه نظرات کاربران از آن استفاده می کنند مثال هایی برای استفاده از رابط مدیر می باشند.

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

بنابراین روش جنگو برای این مسائل خسته کننده و تکراری چه می باشد؟ جنگو تنها با چند خط نه بیشتر تمام این کارها را برای شما انجام می دهد. با ساختن رابط مدیر جنگو تمام این مشکلات حل خواهد شد.

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

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

پکیج های django.contrib

مدیر خودکار جنگو بخشی از یک سلسله از عملکرد جنگو می باشد که django.contrib نامیده می شود (بخشی از کد جنگو که حاوی افزودنی های مفید برای هسته فریم ورک یا چارچوب می باشد). می توانید django.contrib در جنگو را معادل کتابخانه ی استاندار پایتون فرض کنید. این پکیج ها به وسیله ی جنگو جمع آوری و تدوین شده اند، به طوری که نیازی به دوباره نویسی برخی از اصول در برنامه ها نمی باشد.

سایت مدیر اولین بخش از django.contrib می باشد که در این کتاب پوشش داده شده است که از نظر فنی، django.contrib.admin نامیده می شود. از خصوصیات دیگر قابل استفاده درون django.contrib می توان سیستم حق دسترسی (django.contrib.auth)، سیستم پشتیبانی از session ها (django.contrib.sessions) و حتی یک سیستم برای نقطه نظرات کاربر (django.contrib.comments)را نام برد. شما خصوصیات مختلف django.contrib را به صورت فردی حرفه ای در جنگو فرا خواهید گرفت، زمان زیادی را در پکیج django.contrib برای بحث در مورد این خصوصیات خواهیم گذاشت.

فعال کردن رابط مدیر

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

ابتدا، چند تغییر در فایل تنظیمات انجام دهید:

  • django.contrib.admin را به تنظیم INSTALLED_APPS اضافه کنید. (ترتیب قرارگیری محتویات INSTALLED_APPS اهمیت ندارد، ولی در این کتاب برای خوانایی بیشتر آن ها را بر اساس حروف الفبا مرتب کرده ایم.)
  • اطمینان حاصل کنید که INSTALLED_APPS حاوی 'django.contrib.auth'، 'django.contrib.contenttypes' و 'django.contrib.sessions' می باشد. سایت مدیر جنگو به این سه پکیج نیاز دارد. (در صورتیکه همزمان با تمرینات کتاب یعنی پروژه ی mysite پیش می روید، توجه داشته باشید که در آموزش مدل جنگو این سه پکیج را کامنت کردیم. همین حالا آن ها را از حالت کامنت خارج کنید.)
  • 1. اطمینان حاصل کنید که MIDDLEWARE_CLASSES حاوی 'django.middleware.common.CommonMiddleware'، 'django.contrib.sessions.middleware.SessionMiddleware' و 'django.contrib.auth.middleware.AuthenticationMiddleware' می باشد (دوباره، در صورتیکه همزمان با تمرینات کتاب پیش می روید، توجه داشته باشید که در آموزش مدل جنگو ایتم های فوق کامنت شده اند، پس آن ها را از حالت کامنت خارج کنید.)

در قدم دوم، دستور python manage.py syncdb را اجرا کنید. دستور فوق جداول پایگاه داده اضافه که رابط مدیر از آنها استفاده می کند را نصب می کند. در اولین باری که دستور sycdb را به 'django.contrib.auth' درون INSTALLED_APPS اجرا می کنید، سوالی مبنی درباره ی ساختن یک superuser از شما پرسیده می شود. در صورتیکه آن را انجام ندهید، نیاز است که دستور python manage.py createsuperuser را به طور جداگانه برای ساختن یک حساب کاربری مدیر اجرا کنید، در غیر اینصورت قادر نخواهید بود به سایت مدیر وارد شوید. (نکته ی مهم: دستور python manage.py createsuperuser تنها زمانی در دسترس خواهد بود که 'django.contrib.auth' درون تنظیم INSTALLED_APPS موجود باشد.)

قدم سوم، اضافه کردن سایت مدیر به URLconf (درون فایل urls.py) می باشد. به صورت پیشفرض، urls.py تولید شده توسط django-admin.py startproject حاوی کدهای کامنت شده ای برای مدیر جنگو می باشد و تمام کاری که شما باید انجام دهید خارج کردن آن کدها از حالت کامنت می باشد. چیزی شبیه به کد زیر لازم است:

# Include these import statements...
from django.contrib import admin
admin.autodiscover()

# And include this URLpattern...
urlpatterns = patterns('',
    # ...
    (r'^admin/', include(admin.site.urls)),
    # ...
)

با اضافه کردن سایت مدیر به URLconf، حالا می توان سایت مدیر جنگو را در عمل مشاهده کرد. تنها کافیست سرور جنگو را اجرا کرده (python manage.py runserver) و نشانی http://127.0.0.1:8000/admin/ را درون مرورگر جستجو کنید.

استفاده از سایت مدیر

سایت مدیر برای استفاده ی کاربران غیر فنی طراحی شده است، و به همین دلیل می بایست بسیار واضح باشد. با این وجود، ویژگی های اولیه آن را به صورت سریع به شما نشان خواهیم کرد.

اولین چیزی که شما مشاهده خواهید کرد صفحه ی ورود مانند شکل زیر می باشد.

do.php?imgf=157305369839321.jpg

با نام کاربری و رمز عبوری که در زمان اضافه کردن superuser ساخته اید وارد شوید. در صورتیکه قادر به وارد شدن نیستید، اطمینان حاصل کنید که واقعا یک superuser ساخته اید (سعی کنید دستور python manage.py createsuperuser را اجرا کنید.)

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

do.php?imgf=157305369840612.jpg

هر نوعی از داده در سایت مدیر جنگو دارای یک change list و یک edit form می باشد. change list ها تمام شیء های در دسترس درون پایگاه داده را نشان می دهند، و edit form ها، اجازه ی اضافه کردن تغییر دادن و یا حذف رکورد های مشخص در پایگاه داده را به شما می دهند.

زبان های دیگر

در صورتیکه زبان اصلی شما زبان انگلیسی نمی باشد و مرورگر شما برای یک زبان دیگری به جز زبان انگلیسی تنظیم شده است، می توانید با یک تغییر سریع همه چیز را درون سایت مدیر جنگو به صورت ترجمه شده به زبان مورد دلخواه مشاهده کنید. تنها کافیست 'django.middleware.locale.localeMiddleware' را به تنظیم MIDDLEWARE_CLASSES اضافه کنید، اطمینان حاصل کنید که آن را بعد از 'django.contrib.sessions.middleware.SessionMiddleware' قرار دهید.

هنگامی که تنظیم فوق انجام داده شد، صفحه ی اصلی مدیر را دوباره بارگذاری کنید. در صورتیکه یک ترجمه برای زبان شما در دسترس باشد، بخش های مختلف از رابط مانند لینک های "Change password" و "Log out" در بالای صفحه و لینک های "Groups" و "Users" در وسط صفحه به زبان مورد نظر ترجمه خواهند شد. جنگو با ترجمه ی ده ها زبان ارائه شده است.

برای بار گذاری صفحه ی change list برای کاربران بر روی لینک "Change" در ردیف "Users" کلیک کنید.

do.php?imgf=157305369841833.jpg

صفحه ی فوق تمام کاربران موجود در پایگاه داده را نمایش می دهد، می توانید این را به صورت پرس وجو پایگاه داده یک نسخه ی زیبا شده از عبارت SELECT * FROM auth_user تصور کنید. در صورتیکه همراه با مثال های کتاب حرکت می کنید، تنها یک کاربر را مشاهده خواهید کرد، ولی هنگامی که کاربران بیشتری وجود داشته باشد، امکانات مفیدی از قبیل فیلتر کردن، چیدن و جستجوی بین آیتم ها را مشاهده خواهید کرد. امکان فیلتر کردن در سمت راست صفحه موجود می باشد، چیدمان از طریق کلیک کردن روی یک header ستون عملی می شود، و جعبه ی جستجو (search box) در قسمت بالا صفحه می باشد اجازه می دهد بر اساس نام کاربری به جستجو بپردازید.

بر روی نام کاربری که ساخته اید کلیک کنید، فرم ویرایش برای کاربر را مشاهده خواهید کرد.

do.php?imgf=157305369843194.jpg

صفحه ی فوق این اجازه را می دهد که خصوصیات کاربر را تغییر دهید، مانند نام و نام خانوادگی و حق دسترسی های گوناگون. (توجه داشته باشید که برای تغییر رمز عبور کاربر به جای ویرایش کد hash، می بایست بر روی "change password form" در زیر فیلد رمز عبور کلیک کرد.) نکته ی دیگر قابل توجه اینکه فیلدهای مختلف به شکل های مختلف پیاده سازی شده اند؛ به عنوان مثال، فیلد زمان و تاریخ دارای کنترل تقویم، فیلدهای Boolean دارای checkbox ها و فیلدهای حروف حاوی فیلد ساده ی مربوط به متن می باشند.

می توان برای حذف کردن یک رکورد بر روی دکمه ی delete در قسمت پایین سمت چپ از فرم ویرایش کلیک کرد. بسته به شیء هایی که قصد حذف کردن آن را دارید، صفحه ی مربوط به تایید برای حذف رکورد نمایان خواهد شد، به عنوان مثال، در صورتیکه یک ناشر را حذف کنید، هر کتاب مربوط به ناشر نیز حذف خواهد شد!

همچنین می توان با کلیک کردن بر روی "Add" درون ستون مناسب از صفحه ی خانگی مدیر یک رکورد را اضافه کرد. بعد از کلیک صفحه ای با فیلدهای خصوصیات خالی نمایان می شود که آماده برای پر شدن است.

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

do.php?imgf=157305369845935.jpg

هنگامی که یک شیء موجود را ویرایش می کنید، متوجه یک لینک History در قسمت راست بالای صفحه خواهید شد. هر تغییری درون رابط مدیر اتفاق بیافتد در اینجا ثبت خواهد شد، و می توان با کیک کردن بر روی لینک History آن تغییر را بررسی کرد.

do.php?imgf=157305374779451.jpg

به اشتراک گذاری این ارسال


لینک به ارسال
به اشتراک گذاری در سایت های دیگر

اضافه کردن مدل ها به سایت مدیر

یک بخش بسیار مهمی هنوز انجام نشده است. اجازه دهید مدل های خود را به سایت مدیر اضافه کنیم، بنابراین می توان شیء های درون جداول پایگاه داده ی مورد نظر را با این رابط عالی حذف، اضافه و تغییر داد. در این فصل نیز مثال books را دنبال خواهیم کرد که در آن سه مدل به نام های Publisher، َAuthor و book تعریف شده است.

درون دایرکتوری books (mysite/books)، یک فایل با نام admin.py ایجاد کرده و کد زیر درون آن وارد کنید:

from django.contrib import admin
from mysite.books.models import Publisher, Author, Book

admin.site.register(Publisher)
admin.site.register(Author)
admin.site.register(Book)

کد فوق برای هر یک از مدل ها یک رابط به مدیر جنگو اضافه می کند.

هنگامی که این کار انجام داده شد، به صفحه ی خانگی مدیر درون مرورگر خود رفته (http://127.0.0.1/admin/)، شما باید قسمت "Books" را با لینک های Authors، Books و Publishers مشاهده کنید. (برای موثر واقع شدن تغییرات می بایست سرور را متوقف کرده و دوباره اجرا runserver کنید)

شما هم اکنون برای هر از این سه مدل یک رابط مدیر در حال کار خواهید داشت. بسیار ساده است!

می توانید زمانی را برای اضافه کردن و تغییر رکوردها اختصاص دهید، تا داده هایی را درون پایگاه داده اضافه کنید. در صورتیکه مثال های آموزش مدل جنگو (ساختن شیء های Publisher) را دنبال کرده و آن ها را حذف نکرده باشید، آن رکوردهای ساخته شده در آموزش مدل جنگو را در صفحه ی لیست تغییرات publisher مشاهده خواهید کرد.

یکی از خصوصیات با ارزشی که می توان در اینجا ذکر کرد، کنترل سایت مدیر برای کلیدهای خارجی و رابطه های چند به چند می باشد، هر دوی این حالت ها در مدل Book وجود دارند، برای یادآوری کد زیر مدل Book می باشد:

class Book(models.Model):
    title = models.CharField(max_length=100)
    authors = models.ManyToManyField(Author)
    publisher = models.ForeignKey(Publisher)
    publication_date = models.DateField()

    def __unicode__(self):
        return self.title

درون صفحه ی "Add book" مدیر (http://127.0.0.1:8000/admin/books/book/add/)، Publisher (یک ForeignKey) با یک جعبه ی انتخاب (select box) نمایش داده شده است، و فیلد Authors (یک ManyToManyField) با یک جعبه ی چند انتخابی (multiple-select box) نمایش داده شده است. هر دو فیلد فوق دارای یک علامت سبز رنگ جمع در جلوی خود می باشند که برای اضافه کردن رکوردهای مرتبط به آن ها می باشد. برای مثال، در صورتیکه شما بر روی آن علامت سبز رنگ جلوی فیلد "Publisher" کلیک کنید، یک پنجره ی pop-up برای اضافه کردن یک ناشر باز می باشد. بعد از آنکه یک ناشر در پنجره ی باز شده ساخته شود، صفحه ی "Add book" با جدید ترین ناشر ساخته شده به روزرسانی می شود.

نحوه کار سایت مدیر

در پشت صحنه، سایت مدیر چه طور کار می کند؟ نحوه ی کار آن بسیار ساده می باشد.

هنگامی که جنگو URLconf را هنگام اجرای سرور در urls.py بارگذاری می کند، عبارت admin.autodiscover() که در درون این برای فعال کردن سایت مدیر قرار داده شده است اجرا می شود. این تابع درون تنظیم INSTALLED_APPS به دنبال فایلی به نام admin.py که درون هر یک از ایتم ها جستجو می کند. در صورتیکه admin.py درون یکی از app های داده شده وجود داشته باشد، کد درون آن فایل اجرا می شود.

در فایل admin.py درون app مورد نظر یعنی books، فراخوانی هر admin.site.register() به سادگی مدل داده شده را درون سایت مدیر عضو می کند. سایت مدیر تنها یک رابط ویرایش/تغییر برای مدل های عضو شده را نمایش می دهد.

همانطور که مشاهده شد، سایت مدیر به صورت خودکار Users و Groups را نمایش می دهد که درون app خود یعنی django.contrib.auth می باشند که این app شامل فایل admin.py خود و Users و Groups درون آن می باشد. app های دیگر django.contrib، مانند django.contrib.redirects، نیز خودشان را درون سایت مدیر اضافه می کنند.

از این گذشته، سایت مدیر جنگو تنها یک application جنگو با مدل های خود، view ها و URLpattern ها می باشد. با استفاده از URLconf آن را به برنامه ی خود اضافه می کنید، تنها درون view ها خود از آن استفاده می کنید. می توان template ها، view ها و URLpattern های آن را با رفتن به مسیر django.contrib/admin مشاهده و بازرسی کنید ولی تحریک به تغییر چیزی به طور مستقیم در آنجا نشوید چرا که امکانات استفاده ی غیر مستقیم زیادی برای شما وجود دارد.

ایجاد فیلدهای اختیاری

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

برای تعیین کردن فیلد email اختیاری، مدل Book را ویرایش می کنیم (این مدل درون فایل mysite/books/models.py می باشد.) برای این کار به سادگی blank=True را به مانند مثال زیر به فیلد email اضافه می کنیم:

class Author(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=40)
    email = models.EmailField(blank=True)

حرکت فوق به جنگو می گوید که برای آدرس پست الکترونیک نویسندگان می توان مقدار خالی را نیز در نظر گرفت. به صورت پیش فرض، تمام فیلدها blank=False می باشند، بدین معنی که مقدار خالی را نمی توان برای آن ها در نظر گرفت.

تا حالا، بجز متد __unicode__()، مدل های ما به صورت جداول پایگاه داده ما بکار می رفتند یعنی مدل ها عبارات پایتونی معادل عبارات CREATE TABE در SQL بودند. با اضافه کردن blank=True، ما شروع به گسترش دادن مدل فراتر از تعریف ساده ی مشابه با جدول پایگاه داده کرده ایم. حال، کلاس مدل در حال تبدیل شدن به یک مجموعه ی غنی از دانش درباره ی آنچه که شیء های Author هستند و می توانند انجام دهند، می باشد. نه تنها فیلد email یک ستون VARCHAR در پایگاه داده می باشد، بلکه همچنین می دانیم که یک فیلد اختیاری در سایت مدیر جنگو نیز می باشد.

do.php?imgf=157305395935881.jpg

این حالت بدین معنی می باشد که فیلد مورد نظر اختیاری می باشد. حالا می توان نویسندگان را بدون نیاز به تهیه ی آدرس الکترونیک اضافه کرد؛ در صورتیکه فرم مورد نظر را با فیلد خالی آدرس الکترونیک Submit کنید دیگر هیچ پیام "This field is required" با رنگ قرمز مشاهده نخواهید کرد.

هنگامی که blank=True اضافه شد، فرم ویرایش "Add author" را دوباره بارگذاری کنید (http://127.0.0.1:8000/admin/books/author/add/)، متوجه خواهید شد که نام فیلد "Email" دیگر به صورت تو پر و پر رنگ نمی باشد.

ایجاد فیلد های از نوع تاریخ و عددی به صورت اختیاری

نکته ی مهم در رابطه با blank=True این است که انجام این حرکت بر روی فیلدهای از نوع عددی و تاریخ نیازمند مقداری توضیح و پیش زمینه می باشد.

SQL برای تعیین مقدار خالی دارای روش خاص خودش می باشد – یک مقدار ویژه به نام NULL. NULL می تواند به معنای ناشناخته، نامعتبر یا ... معنی شود.

در SQL یک مقدار NULL با یک مقدار رشته ی خالی متفاوت می باشد، درست مثال شیء None در پایتون که با یک رشته ی خالی پایتون ("") متفاوت می باشد. این بدان معنی است که ممکن است یک فیلد حرفی (مانند یک ستون VARCHAR) به طور همزمان هم حاوی مقدار NULL و هم حاوی یک رشته ی خالی باشد.

این موضوع می تواند موجب ابهام و گیجی نا خواسته شود. "چرا این رکورد دارای مقدار NULL ولی آن رکورد دیگر دارای یک رشته ی خالی است؟ ایا تفاوتی وجود دارد، یا فقط داده به صورت نا سازگار وارد شده است؟" و: "چطور می توان تمام رکوردهایی که دارای مقدار خالی می باشند را بدست آورد – ایا می توان هر دو رکوردهای NULL و رشته ی خالی را جستجو کرد، یا یک از آن ها را با مقدار رشته ی خالی انتخاب کرد؟"

برای کمک به خلاص شدن از این قبیل ابهامات، جنگو به صورت خودکار عبارت های CREATE TABLE (که در آموزش مدل جنگو توضیح داده شده اند) اضافه و برای هر ستون مقدار NOT NULL را در نظر می گیرد. برای مثال، کد زیر عبارت تولید شده برای مدل Author در آموزش مدل جنگو می باشد:

CREATE TABLE "books_author" (
    "id" serial NOT NULL PRIMARY KEY,
    "first_name" varchar(30) NOT NULL,
    "last_name" varchar(40) NOT NULL,
    "email" varchar(75) NOT NULL
)
;

در اغلب موارد، این حالت پیشفرض برای برنامه مطلوب بوده و شما را از درد سر های تناقض و نا هماهنگی های داده حفظ می کند. و برای بقیه ی قسمت های جنگو به خوبی کار می کند، مانند سایت مدیر جنگو، که در زمان خالی گذاشتن یک فیلد یک رشته ی خالی (نه مقدار NULL) وارد می کند.

ولی برای نوع ستون هایی که رشته ی خالی را به عنوان مقدار معتبر قبول نمی کنند – ازقبیل تاریخ ها، زمان ها، و اعداد، یک استثناء وجود دارد. در صورتیکه سعی کنید یک رشته ی خالی برای ستون های ذکر شده وارد کنید، بسته به نوع پایگاه داده ی شما احتمالا خطای پایگاه داده رخ خواهد. در این حالت، NULL تنها راه برای تعیین مقدار خالی می باشد. در مدل های جنگو، می توان مقدار NULL را با اضافه کردن null=True به یک فیلد تعیین کرد.

در صورتیکه بخواهید اجازه دهید مقادیر خالی در فیلد تاریخ (مانند DateField، TimeField، DateTimeField) یا فیلد عددی (مانند IntegerField، DecimalField، FloatField) قرار گیرند نیاز است از null=True و blank=True با هم استفاده کنید.

اجازه دهید مدل Book را برای اینکه بتوان فیلد publication_date را خالی گذاشت تغییر دهیم:

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

اضافه کردن null=True پیچیده تر از اضافه کردن blank=True می باشد، زیرا null=True از نظر معنایی پایگاه داده را تغییر می دهد – در این حالت عبارت CREATE TABLE تغییر می کند بدین شکل که NOT NULL از فیلد publication_date حذف می شود. برای کامل کردن این تغییر، نیاز است پایگاه داده به روز رسانی شود.

به دلایلی، جنگو تلاشی برای تغییرات خودکار در شمای پایگاه داده نمی کند، بنابراین مسئولیت اجرای یک عبارت ALTER TABLE هر زمان که که تغییری در مدل ایجاد شد، به عهده خودتان می باشد. می توان از manage.py dbshell برای این منظور استفاده کرد. در کد زیر نحوه ی حذف NOT NULL در این مورد خاص را مشاهده می کنید:

ALTER TABLE books_book ALTER COLUMN publication_date DROP NOT NULL;

(توجه داشته باشید که کد فوق برای پایگاه داده ی PostgreSQL می باشد.)

توضیح و بحث عمیق تر در مورد این تغییرات در آموزش مدل پیشرفته بحث شده است.

به سایت مدیر بر می گردیم، اکنون فرم ویرایش "Add book" اجازه می دهد تا مقدار خالی را برای publication date قرار دهیم.

سفارشی کردن نام فیلدها

در فرم های ویرایش سایت مدیر، نام هر فیلد از روی نام مدل فیلد تولید می شود. الگوریتم آن بسیار ساده می باشد: جنگو تنها خط های تیره (_) را با فاصله ها جایگزین کرده و حروف اول را تبدیل به حروف بزرگ می کند، بنابراین، برای مثال، فیلد publication_date مدل Book دارای نام "Publication date" خواهد بود.

اگر چه نام فیلدها درون مدل به طور زیبایی درون سایت مدیر نیز تغییر می کنند، ولی در برخی موارد ممکن است بخواهید آن نام ها را خودتان تعیین کنید. با استفاده از verbose_name می توان این کار را انجام داد.

برای مثال، در زیر نحوه ی تغییر نام فیلد Author.email به "e-mail" نشان داده شده است:

class Author(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=40)
    email = models.EmailField(blank=True, verbose_name='e-mail')

تغییر فوق را ایجاد کرده و صفحه را دوباره بارگذاری کنید، شما باید فیلد مورد نظر با نام جدید مشاهده کنید.

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

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

class Author(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=40)
    email = models.EmailField('e-mail', blank=True)

حرکت فوق برای فیلدهای ManyToManyField و ForeignKey کار نخواهد کرد، زیرا در این فیلدها باید اولین آرگومان یک کلاس مدل باشد. در این موارد، می بایست از verbose_name به صورت واضح استفاده کرد.

به اشتراک گذاری این ارسال


لینک به ارسال
به اشتراک گذاری در سایت های دیگر

کلاس های ModelAdmin مرسوم

نغییراتی که تاکنون اعمال شد – blank=True، null=True و verbose_name – تغییراتی در سطح مدل بوده اند، نه تغییرات در سطح مدیر. خیلی پیش می آید که تغییرات مورد نیاز سایت مدیر در یک بخش از مدل انجام پذیرد و استفاده شوند، هیچ چیز تعیین شده ای برای مدیر وجود ندارد.

گذشته از این، سایت مدیر جنگو امکانات و اختیارات قدترمندی برای سفارشی کردن نحوه ی کار سایت مدیر برای مدل خاص ارائه می کند.

سفارشی کردن لیست های تغییر

اجازه دهید از طریق تعیین کردن فیلدها که درون لیست تغییر برای مدل Author قرار دارند وارد مسائل سفارشی سازی مدیر شویم. به طور پیشفرض، لیست تغییر نتیجه ی __unicode__() برای هر شیء را نمایش می دهد. در آموزش مدل جنگو، متد __uncode__() برای شیء های Author جهت نمایش نام و فامیلی با هم تعریف شد.

class Author(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=40)
    email = models.EmailField(blank=True, verbose_name='e-mail')

    def __unicode__(self):
        return u'%s %s' % (self.first_name, self.last_name)

در نتیجه لیست تغییر برای شیء های Author نام و نام خانوادگی را با هم نمایش می دهد، همانطور که در شکل زیر مشاهده می کنید.

do.php?imgf=157305425571161.jpg

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

from django.contrib import admin
from mysite.books.models import Publisher, Author, Book

class AuthorAdmin(admin.ModelAdmin):
    list_display = ('first_name', 'last_name', 'email')

admin.site.register(Publisher)
admin.site.register(Author, AuthorAdmin)
admin.site.register(Book)

در زیر مثال فوق، توضیح داده شده است:

  • کلاس AuthorAdmin ساخته شده است. کلاس AuthorAdmin که از کلاس پدر خود یعنی django.admin.ModelAdmin ارث بری کرده است پیکربندی های تعیین شده برای مدل مدیر را نگه می دارد. تنها یک پیکربندی – list_display، انجام شده است که نام فیلدهایی را که قرار است درون لیست تغییر نمایش داده شوند، درون یک تاپل قرار گرفته اند. البته نام فیلدها باید در مدل وجود داشته باشند.
  • فراخوانی admin.site.register با اضافه کردن AuthorAdmin بعد از Author تغییر کرده است که می توان آنرا بدین صورت خواند: "مدل Author با امکانات AuthorAdmin عضو شده است."
  • تابع admin.site.register() کلاس فرزند ModelAdmin را به صورت اختیاری به عنوان دومین آرگومان دریافت می کند. در صورتیکه دومین آرگومان را تعیین نکنید (همانطور در مورد Publisher و Book صدق می کند)، جنگو از اختیار پیشفرض مدیر برای آن مدل استفاده می کند.

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

do.php?imgf=15730542557222.jpg

در قدم بعدی اجازه دهید یک نوار جستجوی ساده اضافه کنیم. مانند زیر search_fields را به AuthorAdmin اضافه کنید:

class AuthorAdmin(admin.ModelAdmin):
    list_display = ('first_name', 'last_name', 'email')
    search_fields = ('first_name', 'last_name')

صفحه را درون مرورگر دوباره بارگذاری کنید، شما باید یک نوار جستجو را در قسمت بالای صفحه مشاهده کنید (شکل 10-6). در کد فوق تنها به صفحه ی تغییر گفته شده است که یک نوار جستجو برای جستجو بر حسب فیلدهای نام و نام خانوادگی ایجاد کند. همانطور که ممکن است کاربر انتظار داشته باشد، جستجو به حروف کوچک و بزرگ حساس نمی باشد و درون هر دو فیلد صورت می گیرد، بنابراین برای رشته ی "bar" هم نویسنده ای که نام وی Barney بوده را خواهد یافت و هم نویسنده ای که نام خانوادگی وی Hobarson می باشد را خواهد یافت.

do.php?imgf=157305425573273.jpg

در قدم بعدی، اجازه دهید تعدادی فیلتر برای صفحه ی لیست تغییر مدل Book اضافه کنیم:

from django.contrib import admin
from mysite.books.models import Publisher, Author, Book

class AuthorAdmin(admin.ModelAdmin):
    list_display = ('first_name', 'last_name', 'email')
    search_fields = ('first_name', 'last_name')

class BookAdmin(admin.ModelAdmin):
    list_display = ('title', 'publisher', 'publication_date')
    list_filter = ('publication_date',)

admin.site.register(Publisher)
admin.site.register(Author, AuthorAdmin)
admin.site.register(Book, BookAdmin)

در کد فوق با آیتم های زیادی سر و کار داریم، در کد فوق یک کلاس BookAdmin از نوع ModelAdmin به طور جداگانه ساخته شده است. ابتدا، یک list_display تنها برای ایجاد لیست زیباتر تعریف شده است. سپس، از list_filter استفاده شده است، که فیلدهایی برای استفاده را درون تاپل قرار می دهد و در سمت راست صفحه ی تغییر لیست نمایش داده می شود. برای فیلدهای تاریخ، جنگو میانبرهایی را برای فیلتر لیست می کند "Today"، "Past 7 days"، "This month"و "This year".

do.php?imgf=157305425574394.jpg

list_filter فقط محدود به DateField نمی باشد بلکه با داده های نوع دیگر نیز کار می کند. (سعی کنید با فیلدهای BooleanField و foreignKey هم کار کنید) فیلترها تا زمانیکه حداقل دو مقدار برای انتخاب فرم وجود داشته باشد نمایش داده می شوند.

روش دیگربرای ارائه ی فیلترهای تاریخ استفاده از date_hierarcy مانند کد زیر می باشد:

class BookAdmin(admin.ModelAdmin):
    list_display = ('title', 'publisher', 'publication_date')
    list_filter = ('publication_date',)
    date_hierarchy = 'publication_date'

با اضافه کرد date_hierarchy صفحه ی لیست تغییر یک نوار تاریخ در بالای لیست ایجاد می شود، که در شکل 12-6 مشاهده می کنید، که با یک لیست از سال های در دسترس شروع می کند سپس می توان برای ماه حتی نمایان شدن روز بر روی آن کلیک کرد.

do.php?imgf=157305425575935.jpg

توجه داشته باشید که date_hierarchy یک رشته دریافت می کند، نه یک تاپل، زیرا تنها یک فیلد تاریخ می تواند در آن استفاده شود.

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

class BookAdmin(admin.ModelAdmin):
    list_display = ('title', 'publisher', 'publication_date')
    list_filter = ('publication_date',)
    date_hierarchy = 'publication_date'
    ordering = ('-publication_date',)

این اختیار چیدمان مدیر دقیقا به صورت چیدمان در کلاس Meta کار می کند، با این تفاوت که آن تنها از اولین فیلد نام در لیست استفاده می کند. تنها یک لیست یا تاپل از نام فیلدها به آن ارسال کنید و یک علامت (-) در ابتدای فیلد برای چیدمان نزولی آن قرار دهید.

صفحه ی لیست تغییر را برای مشاهده ی حرکت فوق دوباره بارگذاری کنید. توجه داشته باشید که تیتر "Publication date" حالا شامل یک فلش کوچک می باشد که به نحوه ی چیدمان اشاره می کند.

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

به اشتراک گذاری این ارسال


لینک به ارسال
به اشتراک گذاری در سایت های دیگر

سفارشی کردن فرم های ویرایش

همانطور که لیست تغییر می تواند سفارشی شده باشد، فرم های ویرایش نیز می توانند با روش های مختلف سفارشی شوند.

ابتدا اجازه دهید روش چیده شدن فیلدها را سفارشی کنیم. به طور پیشفرض، ترتیب فیلدها در یک فرم ویرایش مطابق با ترتیبی است که آنها در مدل تعریف شده اند. می توان با استفاده از اختیار fields در کلاس فرزند ModelAdmin آن را تغییر داد:

class BookAdmin(admin.ModelAdmin):
    list_display = ('title', 'publisher', 'publication_date')
    list_filter = ('publication_date',)
    date_hierarchy = 'publication_date'
    ordering = ('-publication_date',)
    fields = ('title', 'authors', 'publisher', 'publication_date')

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

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

class BookAdmin(admin.ModelAdmin):
    list_display = ('title', 'publisher', 'publication_date')
    list_filter = ('publication_date',)
    date_hierarchy = 'publication_date'
    ordering = ('-publication_date',)
    fields = ('title', 'authors', 'publisher')

نتیجتا، فرم ویرایش برای کتاب ها هیچ روشی برای تعیین publication date ارائه نمی کند. این حرکت می تواند در صورتیکه یک ویرایشگر ترجیح می دهد نویسندگانش تاریخ را عقب نزنند مفید باشد.

هنگامی که یک کاربر از این فرم برای اضافه کردن یک کتاب جدید استفاده می کند، جنگو به آسانی مقدار publication_date را None در نظر می گیرد – بنابراین اطمینان حاصل کنید که فیلد مورد نظر دارای null=True می باشد.

استفاده ی عمومی دیگر فرم ویرایش سفارشی برای کار با فیلدهای چند به چند می باشد. همانطور که در فرم ویرایش برای کتاب ها مشاهده کردید، سایت مدیر هر ManyToManyField را به صورت یک جعبه ی چند انتخابه نمایش می دهد، که منطقی ترین راه برای ورودی HTML برای استفاده می باشد – ولی استفاده از جعبه های چند انتخابه می تواند کمی سخت باشد. در صورتیکه که می خواهید چند ایتم را انتخاب کنید، باید برای انجام چنین کاری کلید control و یا در Mac کلید command را نگه پایین نگه داشته و آن ها را انتخاب کنید. سایت مدیر برای این مشکل یک توضیحی را در زیر این فیلد قرار داده است، ولی هنوز هم دارای مشکلاتی می باشد مخصوصا زمانی که فیلد شما حاوی صدها انتخاب باشد.

راه حل سایت مدیر filter_horizontal می باشد. آنرا به BookAdmin اضافه کرده و نتیجه را مشاهده کنید:

class BookAdmin(admin.ModelAdmin):
    list_display = ('title', 'publisher', 'publication_date')
    list_filter = ('publication_date',)
    date_hierarchy = 'publication_date'
    ordering = ('-publication_date',)
    filter_horizontal = ('authors',)

فرم ویرایش برای کتاب ها را دوباره بارگذاری کنید، بخش "Authors" حالا از یک رابط جاوا اسکریپت کاراتر استفاده می کند که می توان به راحتی درون انتخاب ها جستجو کردن و هر آنچه را که درون "Available authors" انتخاب می کنید را به درون "Chosen authors" و بر عکس ارسال می کند.

do.php?imgf=157305452530571.jpg

اکیدا توصیه می شود که برای فیلدهای ManyToManyField که بیشتر از 10 ایتم دارند از filter_horizontal استفاده کنید. روش فوق بسیار آسانتر از استفاده از جعبه ی چند انتخابه می باشد. توجه داشته باشید که همچنین می توان برای چندین فیلد نیز از filter_horizontal استفاده کرد – تنها کافیست نام هر کدام را درون تاپل قرار دهید.

کلاس های ModelAdmin همچنین از filter_vertical که تنها در فیلدهای ManyToManyField و نه ForeignKey کار می کند نیز پشتیبانی می کنند، به طور پیشفرض، سایت مدیر برای فیلدهای ForeignKey به سادگی از <select> استفاده می کند، ولی همانطور که برای ManyToManyField نیز از <select> استفاده می شود، ممکن است شما مایل نباشید برای نمایش تمام شیء ها به صورت منوی باز شونده در پایین بار اضافی ای را متحمل شوید. برای مثال در صورتیکه پایگاه داده ی کتاب حاوی صدها ناشر شده باشد، فرم "Add book" مدتی برای بارگذاری زمان خواهد برد، زیرا باید تمام ناشران را برای نمایش در <select> بارگذاری کند.

برای حل این مشکل می بایست از raw_id_fields استفاده کرد. نام فیلد ForeignKey را درون این تاپل قرار دهید، فیلدهای مذکور به جای <select> به صورت یک متن ساده نمایش داده می شوند (<input type="text")

class BookAdmin(admin.ModelAdmin):
    list_display = ('title', 'publisher', 'publication_date')
    list_filter = ('publication_date',)
    date_hierarchy = 'publication_date'
    ordering = ('-publication_date',)
    filter_horizontal = ('authors',)
    raw_id_fields = ('publisher',)

do.php?imgf=157305452532122.jpg

درون جعبه ی ورودی چه چیزی وارد می کنید؟ ID مربوط به پایگاه داده ی ناشر. نمی توان به طور عادی ID هر شخص را به خاطر سپرد، به همین دلیل یک شکل ذره بین نیز در شکل فوق مشاهده می کنید که می توان با کلیک بر روی آن توسط یک پنجره ی pop-up ناشران را که می خواهید اضافه کنید.

کاربران، گروه ها و حق دسترسی ها

بدلیل اینکه با یک superuser وارد می شویم، می توان برای ساختن، ویرایش و حذف کردن هر شیء اقدام کرد. به طور طبیعی، محیط های مختلف نیازمند حق دسترسی های متفاوتی می باشد – بدین صورت نباید باشد که هر شخصی بتواند superuser باشد. سایت مدیر جنگو یک سیستم حق دسترسی را بکاربرده است که می توان برای دادن دسترسی به کاربران خاص برای بخش هایی ار رابط که مورد نیاز است استفاده کرد.

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

  • گزینه ی "active" کنترل می کند که ایا کاربر فعال است یا خیر. در صورتیکه این گزینه تیک نخورده باشد و کاربر سعی کند وارد شود، به وی اجازه ی ورود داده نمی شود حتی اگر رمز عبور وی معتبر باشد.
  • گزینه ی "staff" کنترل می کند که ایا کاربر اجازه ی ورود به رابط مدیر را دارد یا خیر (مانند اینکه ایا کاربر مورد نظر به عنوان یکی از کارمندان شرکت در نظر گرفته شده است یا خیر).
  • گزینه ی "superuser" به کاربر دسترسی کامل را برای اضافه، ساختن و حذف کردن هر ایتم در رابط مدیر را می دهد. در صورتیکه یک کاربر این گزینه را داشته باشد، در آن صورت تمام قوانین دسترسی برای آن کاربر نقض خواهد شد.

کاربران مدیر "Normal"، کاربر superuser نیستند بلکه دارای دسترسی مدیر از طریق دسترسی های اختصاص داده شده به آنها می باشند که می تواند شامل محدودیت های دسترسی باشد. هر شیء قابل ویرایش از طریق رابط مدیر (مانند کتاب ها، نویسندگان، ناشران) دارای سه حق دسترسی می باشد: حق دسترسی ساخت، حق دسترسی ویرایش و حق دسترسی حذف. اختصاص دادن حق دسترسی ها به یک کاربر، محدوده اختیارات آن کاربر را برای ایجاد، حذف و یا اعمال تغییرات تعیین می کند.

زمانیکه یک کاربر می سازید، کاربر ساخته شده هیچ حق دسترسی ای ندارد، و برای دادن حق دسترسی های خاص حاضر می باشد. برای مثال، شما می توانید حق دسترسی ای برای تغیییر ناشران به آن بدهید، ولی حق حذف کردن ناشران را به کاربر ندهید. توجه داشته باشید که این حق دسترسی ها برای هر مدل تعریف شده می باشند و نه برای هر شیء، بنابراین شما می توانید اینطور بگویید که "john می تواند روی هر کتاب تغییر ایجاد کند"، ولی نمی توان گفت "john می تواند بر روی هر کتابی که با Apress منتشر شده است تغییرات ایجاد کند." قابلیت دوم، حق دسترسی های هر شیء می باشد که کمی پیچیده تر بوده و خارج از حوصله ی این کتاب می باشد ولی در مستندات جنگو درباره ی آن صحبت شده است.

نکته

دسترسی به ویرایش کاربران و حق دسترسی ها نیز از طریق سیستم حق دسترسی کنترل شده است. در صورتیکه به شخصی حق دسترسی برای ویرایش کاربران را بدهید، آن شخص قادر به ویرایش حق دسترسی های خود می باشد که این ممکن است آن چیزی نباشد که شما می خواهید! دادن حق دسترسی کاربر برای ویرایش کاربران اساسا تغییر کاربر به superuser می باشد.

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

چه زمانی و به چه دلیل از رابط مدیر استفاده می شود و چه زمانی استفاده نمی شود

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

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

  • گزارشگر مسئولیت داشت برای پروژه با یکی از توسعه دهندگان ملاقات کند و داده ی در دسترس را توضیح دهد.
  • توسعه دهنده مدل های جنگو را برای متناسب بودن با آن داده ها طراحی کرده سپس سایت مدیر را برای گزارشگر باز می کرد.
  • گزارشگر سایت مدیر را بررسی کرده و کمبود ها و فیلدهای غیر اصلی را به آن اشاره می کرد، توسعه دهنده مکررا مدل ها را تغییر می داد.
  • هنگامی که مدل ها مورد توافق قرار گرفت، گزارشگر شروع به وارد کردن داده با استفاده از سایت مدیر می کرد. هم زمان، برنامه نویس می تواند روی توسعه ی عمومی قسمت های views و template ها کار کند (قسمت جالب کار).

ورای وظایف واضح برای وارد کردن داده، سایت مدیر در موارد دیگر که در زیر آمده است نیز مفید می باشد:

  • بررسی کردن داده ی مدل ها: هنگامی که تعدادی مدل تعریف می کنید، فراخوانی آن ها در رابط مدیر و وارد کردن برخی داده های ساختگی می تواند کاملا مفید باشد. در برخی موارد، می تواند باعث آشکار شدن اشتباهات data-modeling و یا مشکلات دیگر با مدل های شما شود.
  • مدیریت داده ی بدست آمده: برای برنامه هایی که بر روی آمدن داده از منابع بیرونی تکیه دارند، سایت مدیر روشی ساده برای بازرسی یا ویرایش این داده ها به شما می دهد. ممکن است آن را کم قدرت تصور کنید، ولی بسیار مناسب تر از امکان خط فرمان پایگاه داده ی شما می باشد.
  • مدیریت داده ی app ها به صورت سریع: می توان برای ساخت مدیریت داده ی بسیار سبک app از سایت مدیر استفاده کرد. در صورتیکه که تنها می خواهید برای نیازهای خود چیزی بسازید، نه برای مصارف عمومی، سایت مدیر می تواند راه مناسبی باشد.

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

به اشتراک گذاری این ارسال


لینک به ارسال
به اشتراک گذاری در سایت های دیگر

فرم ها در جنگو

فرم های HTML ستون فقرات تعامل وب سایت ها می باشند، برای فرم های ساده می توان جعبه ی جستجوی گوگل را مثال زد و برای فرم های پیچیده می توان رابط های ثبت نام برای سایت های بزرگ را مثال زد. در این قسمت به نحوه دسترسی به داده های فرم که توسط کاربر ارائه می شود، تایید کردن آن و انجام برخی کارها به روی آن ها خواهیم پرداخت. در طول فصل، HttpRequest و شیء های Form را توضیح خواهیم داد.

دریافت داده از شیء Request

شیء های HttpRequest در آموزش view و urlconf جنگو هنگامی که اولین توابع view را ایجاد کردیم معرفی شده است، ولی صحبت زیادی درباره ی آن ها در آن زمان صورت نگرفت. یادآوری می کنیم که هر تابع view یک شیء HttpRequest به عنوان اولین پارامتر دریافت می کند، همانطور که در تابع hello() مشاهده می کنید:

from django.http import HttpResponse

def hello(request):
    return HttpResponse("Hello world")

شیء های HttpRequest مانند متغیر request فوق، دارای attribute های و متدهای جالبی می باشند که می بایست با آن ها آشنا شوید. می توان از این attribute ها برای به دست آوردن اطلاعات درباره ی درخواست فعلی استفاده کرد (مانند مرورگری که در حال بارگذاری صفحه ی فعلی می باشد)، در زمانی که تابع view اجرا شده است.

اطلاعات درباره آدرس

شیء های HttpRequest حاوی چندین بخش از اطلاعات درباره ی آدرس فعلی درخواست شده می باشد:

جدول ۱-۷

متد/Attribute توضیح مثال
request.path مسیر کامل، بدون شامل بودن دامین ولی با علامت (/) پیشین "/hello/"
Request.get_host() میزبان (مانند دامین در گفتگوی رایج) "127.0.0.1:8000" یا "www.example.com"
Request.get_full_path() مسیر به همراه query string (اگر در دسترس باشد) "/hello/?print=true"
Request.is_secure() در صورتیکه درخواست از طریق HTTPS ساخته شده باشد True و در غیر اینصورت False بر می گرداند. True یا False

 

همواره از attribute ها/متدها به جای کد مستقیم آدرس درون view ها استفاده کنید. حرکت مذکور انعطاف پذیری کد را جهت استفاده در مکان های دیگر بیشتر می کند. یک مثال ساده:

# BAD!
def current_url_view_bad(request):
    return HttpResponse("Welcome to the page at /current/")

# GOOD
def current_url_view_good(request):
    return HttpResponse("Welcome to the page at %s" % request.path)

اطلاعات دیگر درباره Request

request.META یک دیکشنری پایتون است که تمام HTTP header های درخواست داده شده را شامل شده است – مانند آدرس IP و همچنین نام و نسخه ی مرورگر کاربر. توجه داشته باشید که لیست header های در دسترس به آن header هایی بستگی دارد که کاربر فرستاده و وب سرور قرار داده است. برخی از کلید های در دسترس رایج در این دیکشنری:

  • HTTP_REFERER - اشاره کردن به آدرس، در صورت وجود.
  • HTTP_USER_AGENT – رشته ی مرورگر کاربر، در صورت وجود. که چیزی شبیه به این می باشد:
  • "Mozilla/5.0 (X11; U; Linux i686; fr-FR; rv:1.8.1.17) Gecko/20080829 Firefox/2.0.0.17".
  • REMOTE_ADDR – آدرس IP کلاینت مانند "12.345.67.89". (در صورتیکه درخواست از میان پروکسی ارسال شده باشد، ممکن است با یک جدا کننده ی کاما نشان داده شود مانند "12.345.67.89,23.456.78.90")

توجه داشته باشید، چراکه request.META یک دیکشنری پایتون می باشد، در صورتیکه بخواهید به کلیدی که وجود ندارد دسترسی پیدا کنیدخطای KeyError رخ خواهد داد. (زیرا HTTP header داده ی خارجی می باشد – که توسط مرورگر کاربر شما تایید شده است – نباید به آن ها اعتماد شود، و باید همواره برنامه ی خود را طوری طراحی کنید که در صورت خالی بودن یک header خاص یا عدم وجود به شکل درستی آن را رد کند.) می بایست از یک عبارت try/except یا متد get() برای مواردی که یک کلید تعریف نشده است استفاده کرد:

# BAD!
def ua_display_bad(request):
    ua = request.META['HTTP_USER_AGENT']  # Might raise KeyError!
    return HttpResponse("Your browser is %s" % ua)

# GOOD (VERSION 1)
def ua_display_good1(request):
    try:
        ua = request.META['HTTP_USER_AGENT']
    except KeyError:
        ua = 'unknown'
    return HttpResponse("Your browser is %s" % ua)

# GOOD (VERSION 2)
def ua_display_good2(request):
    ua = request.META.get('HTTP_USER_AGENT', 'unknown')
    return HttpResponse("Your browser is %s" % ua)

شما را تشویق می کنیم تا یک view کوچک بنویسید که تمام داده های request.META را نمایش دهد بنابراین می توانید با دلیل وجود آن ها را آشنا شوید:

def display_meta(request):
    values = request.META.items()
    values.sort()
    html = []
    for k, v in values:
        html.append('<tr><td>%s</td><td>%s</td></tr>' % (k, v))
    return HttpResponse('<table>%s</table>' % '\n'.join(html))

همچنین می توان view فوق را با استفاده از سیستم template جنگو به جای نوشتن کد مستقیم HTML تبدیل کنید. همچنین سعی کنید request.path و دیگر متدهای HttpRequest را از فصل قبل اضافه کنید.

اطلاعات درباره داده تایید (submit) شده

خارج از ابر داده های پایه درباره ی درخواست، شیء های HttpRequest دارای دو attribute هستند که حاوی اطلاعات تایید شده توسط کاربر می باشند: request.GET و request.POST. هر دوی این ها شیء های دیکشنری مانند می باشند که از طریق آن ها می توان به داده های نوع GET و POST دسترسی پیدا کرد.

شیء های دیکشنری مانند

هنگامی که گفته می شود request.GET و request.POST شیء های دیکشنری مانند می باشند، منظور این است که آن ها مانند دیکشنری های استاندارد پایتون رفتار می کنند ولی از نظر فنی دیکشنری پایتون نمی باشند. به عنوان مثال، request.GET و request.POST دارای متدهای get()، keys() و value() هستند، می توان درون کلیدها با for key in request.GET به جستجو پرداخت.

بنابراین چرا با یکدیگر فرق می کنند؟ زیرا هر دوی request.GET و request.POST دارای متدهای اضافی ای می باشند که دیکشنری های معمولی فاقد آن متدها می باشند.

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

داده ی POST عموما تایید شده از <form> HTML می باشد، در حالی که داده ی GET می تواند از یک <form> یا query string در آدرس صفحه آمده باشد.

یک مثال ساده کنترل فرم

مثال books کتاب را با کتاب ها، نویسندگان و ناشران دنبال می کنیم، اجازه دهید یک view ساده را که به کاربران اجازه ی جستجو کتاب ها را بر اساس عنوان می دهد، بسازیم.

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

from django.shortcuts import render_to_response

def search_form(request):
    return render_to_response('search_form.html')

همانطور که در آموزش view و urlconf جنگو یاد گرفتیم، view فوق را می تواند هر جایی در مسیر پایتون قرار دهیم. در اینجا آن را درون books/views.py قرار می دهیم.

template همراه، search_form.html چیزی شبیه به کد زیر خواهد بود:

<html>
<head>
    <title>Search</title>
</head>
<body>
    <form action="/search/" method="get">
        <input type="text" name="q">
        <input type="submit" value="Search">
    </form>
</body>
</html>

URLpattern در urls.py نیز می تواند چیزی شبیه به کد زیر باشد:

from mysite.books import views

urlpatterns = patterns('',
    # ...
    (r'^search-form/$', views.search_form),
    # ...
)

(توجه داشته باشید که ماژول views به جای آنکه به این شکل from mysite.views imort search_form به درون برنامه import شود، به طور مستقیم import شده است، زیرا این روش کوتاه تر می باشد، این روش import در آموزش view و urlconf پیشرفته با جزئیات بیشتری بحث خواهد شد.)

حالا، در صورتیکه دستور runserver را اجرا کنید و آدرس http://127.0.0.1:8000/search‑form/ درون مرورگر ملاحظه کنید، رابط جستجو را ملاحظه خواهید کرد که به اندازه ی کافی ساده می باشد.

سعی کنید که فرم را تایید کنید، که در اینصورت خطای 404 جنگو را مشاهده خواهید کرد. فرم به آدرس /search/ اشاره می کند، که هنوز اجرا نشده است. اجازه دهید این مشکل را با view دوم حل کنیم:

# urls.py

urlpatterns = patterns('',
    # ...
    (r'^search-form/$', views.search_form),
    (r'^search/$', views.search),
    # ...
)

# views.py

def search(request):
    if 'q' in request.GET:
        message = 'You searched for: %r' % request.GET['q']
    else:
        message = 'You submitted an empty form.'
    return HttpResponse(message)

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

  • <form> یک متغیر q تعریف می کند. هنگامی که فرم تایید می شود، ارزش q از طریق GET (method="get") به آدرس /search/ فرستاده می شود.
  • view جنگو که آدرس /search/ (search()) را کنترل می کند به ارزش q در request.GET دسترسی دارد.

نکته مهم برای اشاره در اینجا این است که، به طور صریح بررسی شده است که 'q'درون request.GET وجود دارد یا خیر. همانطور که در بخش گذشته به request.META اشاره شد، شما نباید به هرچیزی که توسط کاربر فرستاده می شود اعتماد کنید و یا حتی فرض کنید که در وهله ی اول چیزی ارسال نمی شود. در صورتیکه این بررسی صورت نگیرد، هر ارسالی از فرم خالی باعث بروز خطای KeyError خواهد شد:

# BAD!
def bad_search(request):
    # The following line will raise KeyError if 'q' hasn't
    # been submitted!
    message = 'You searched for: %r' % request.GET['q']
    return HttpResponse(message)

پارامترهای query string

داده ی GET در query string ارسال می شود (مانند /search/?q=django)، می توان برای دسترسی به متغیرهای query string از request.GET استفاده کرد. در آموزش view و urlconf جنگو در بخش معرفی سیستم URLconf جنگو، آدرس های زیبای جنگو با آدرس های قدیمی PHP/Java مانند /time/plus?hours=3 مقایسه شد و همچنین گفته شد که در فرم نحوه ی دسترسی به به این آدرس های قدیمی نشان داده خواهد شد. حالا نحوه ی دسترسی به پارامترهای query string در view (مانند hours=3 در مثال فوق) را می دانید – با استفاده از request.GET.

داده ی پست نیر مانند داده ی GET عمل می کند – تنها از request.POST به جای request.GET استفاده می کند. تفاوت بین GET و POST در چیست؟ از روش GET هنگامی که تنها یک درخواست برای بدست آوردن داده می باشد استفاده می شود. از داده ی پست هر زمان که ارسال درخواست دارای برخی تاثیرات زیان بار باشد استفاده می شود – تغییر دادن داده، اراسل یک پست الکترونیک، و یا چیز دیگر که ورای نمایش ساده ی داده باشد. در مثال جستجوی ما، از روش GET استفاده شده است که زیرا هیچ داده ای قرار نیست درون سرور تغییر کند. (در صورتیکه می خواهید اطلاعات بیشتری در رابطه با روش های GET و POST به دست آورید می توانید به این آدرس مراجعه کنید: use of HTTP GET and POST).

اکنون که از روش GET استفاده شده است، اجازه دهید درون پایگاه داده به جستجوی کتاب مورد نظر بگردیم:

from django.http import HttpResponse
from django.shortcuts import render_to_response
from mysite.books.models import Book

def search(request):
    if 'q' in request.GET and request.GET['q']:
        q = request.GET['q']
        books = Book.objects.filter(title__icontains=q)
        return render_to_response('search_results.html',
            {'books': books, 'query': q})
    else:
        return HttpResponse('Please submit a search term.')

نکاتی که در کد فوق وجود دارد:

  • گذشته از بررسی اینکه 'q' درون request.GET وجود دارد یا خیر، همچنین در کد فوق قبل از اینکه مقادی به پایگاه داده ارسال شود اطمینان حاصل شده است که request.GET['q'] مقدار خالی نداشته باشد.
  • درون کد فوق از Book.objects.filter(title__icontains=q) جهت پرسیدن اینکه آیا عنوانی شامل 'q' درون جدول کتاب ها وجود دارد یا خیر استفاده شده است. icontains یک نوع جستجو (که در آموزش مدل جنگو توضیح داده شده است) می باشد، عبارت می تواند تقریبا به این حالت ترجمه شود که "کتاب هایی که حاوی q بدون حساسیت به حروف بزرگ و کوچک می باشند را جستجو کن".
  • این یک روش بسیار ساده برای جستجوی کتاب می باشد. البته پیشنهاد نمی شود که از icontains در پایگاه داده های بزرگ محصولات استفاده شود زیرا در این حالت می تواند بسیار کند عمل کند.
  • books (یک لیست از شیء های کتاب) به template ارسال شده است. کد template برای search_results.html ممکن است شامل چیزی شبیه به این باشد:
<p>You searched for: <strong>{{ query }}</strong></p>

{% if books %}
    <p>Found {{ books|length }} book{{ books|pluralize }}.</p>
    <ul>
        {% for book in books %}
        <li>{{ book.title }}</li>
        {% endfor %}
    </ul>
{% else %}
    <p>No books matched your search criteria.</p>
{% endif %}

به کاربرد فیلتر template فوق یعنی pluralize توجه کنید، در صورتیکه مقدار لیست books بیشتر از یکی باشد حرف "s" را بر می گرداند.

اصلاح نمودن مثال ساده کنترل فرم

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

ابتدا، کنترل کردن یک ورودی خالی توسط تابع search() بسیار ضعیف می باشد – تنها نمایش داده یک پیام "Please submit a search term."، نیازمند این است که کاربر دکمه ی برگشت مرورگر را برای بازگشت به صفحه فشار دهد. این یک حالت نامطلوب و غیر حرفه ای می باشد، و در صورتیکه همیشه به این شکل کار می کنید، شما از امتیازات ویژه ی جنگو خود را محروم خواهید کرد.

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

from django.http import HttpResponse
from django.shortcuts import render_to_response
from mysite.books.models import Book

def search_form(request):
    return render_to_response('search_form.html')

def search(request):
    if 'q' in request.GET and request.GET['q']:
        q = request.GET['q']
        books = Book.objects.filter(title__icontains=q)
        return render_to_response('search_results.html',
            {'books': books, 'query': q})
    else:
        return render_to_response('search_form.html', {'error': True})

(توجه داشته باشید که تابع search_form() در کد فوق ایجاد شده است، بنابراین شما می توانید هر دوی view را در یک مکان مشاهده کنید.)

در مثال فوق تابع search() در صورتیکه فرم ارسالی خالی باشد، برای ارائه ی دوباره ی template مورد نظر یعنی search_form.html اصلاح شده است. و بدلیل نمایش یک پیام خطا در آن template، یک متغیر template ارسال شده است. حالا می توان search_form.html را جهت بررسی متغیر error بدین شکل ویرایش کرد:

<html>
<head>
    <title>Search</title>
</head>
<body>
    {% if error %}
        <p style="color: red;">Please submit a search term.</p>
    {% endif %}
    <form action="/search/" method="get">
        <input type="text" name="q">
        <input type="submit" value="Search">
    </form>
</body>
</html>

می توان همچنان از این template برای view اصلی یعنی search_form() استفاده کرد، زیرا search_form() هیچ متغیر error ای به template ارسال نمی کند – بنابراین پیام خطا در این مورد نشان داده نخواهد شد.

با تغییر فوق در اینجا، برنامه ی فعلی بهتر شده است، ولی این سوال پیش می آید: آیا وجود تابع search_form() واقعا ضروری است؟ یک درخواست به آدرس /search/ (بدون هیچ پارامتری از نوع GET) یک فرم خالی را نمایش خواهد داد (ولی با یک خطا). می توان تابع search_form() را به همراه URLpattern های همراه آن، تا زمانیکه کسی با هیچ پارامتر GET ای از /search/ بازدید می کند حذف نمود:

def search(request):
    error = False
    if 'q' in request.GET:
        q = request.GET['q']
        if not q:
            error = True
        else:
            books = Book.objects.filter(title__icontains=q)
            return render_to_response('search_results.html',
                {'books': books, 'query': q})
    return render_to_response('search_form.html',
        {'error': error})

با تغییر فوق، در صورتیکه یک کاربر آدرس /search/ را با هیچ پارامتر GET ای بازدید کند، فرم جستجو را با هیچ پیام خطایی مشاهده خواهد کرد. در صورتیکه یک کاربر فرم را با یک مقدار خالی برای 'q' ارسال کند، صفحه ی جستجو را با یک پیام خطا مشاهده خواهد کرد. و در پایان، در صورتیکه یک کاربر فرم را به یک مقدار پر برای 'q' اراسل کند، نتایج جستجو در پایگاه داده را مشاده خواهد کرد.

می توان یک اصلاح پایانی جهت حذف یک قسمت زائد برای برنامه ی فوق ایجاد نمود. اکنون که دو view و آدرس را یکی شد و /search/ هر دوی نمایش فرم جستجو و نمایش نتیجه را کنترل می کند، فرم HTML در search_form.html نباید یک آدرس مستقیم داشته باشد. بجای کد زیر:

<form action="/search/" method="get">

می توان بدین شکل عمل کرد:

<form action="" method="get">

action="" یعنی "فرم را به آدرس همین صفحه ی فعلی ارسال کن." با تغییر فوق در اینجا، در صورتیکه همیشه تابع search() را به آدرس دیگر ارسال می کرده اید، دیگر لازم نیست تغییر دادن action را بیاد بیاورید.

تایید اعتبار آسان

مثال جستجوی بخش قبلی از نظر منطقی هنوز هم ساده است، مخصوصا در مورد تایید اعتبار داده؛ تنها اطمینان حاصل شد که مقدار ارسالی خالی نباشد. بسیاری از فرم های HTML حاوی یک سطح تایید اعتبار می باشند که پیچیده تر از اطمینان حاصل کردن از مقدار غیر خالی است. همه ی پیام های خطایی به این شکل درون وب دیده ایم:

  • "لطفا یک آدرس الکترونیکی معتبر وارد کنید. 'foo' یک آدرس پست الکترونیکی نیست."
  • "لطفا یک کد پستی پنج رقمی معتبر وارد کنید. '123' پست الکترونیکی نیست."
  • "لطفا یک تاریخ معتبر با قالب بندی YYYY-MM-DD وارد کنید."
  • لطفا رمز عبوری وارد کنید که حداقل هشت حرف داشته و حاوی حداقل یک عدد باشد.

یک نکته در تایید اعتبار جاوا اسکریپت

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

هیچ کاری نمی توانید انجام دهید، غیر از اینکه همواره داده های ارسالی از سمت کاربر را در سمت سرور (مانند view های جنگو) تایید اعتبار کنید. باید اینگونه تصور کرد که تایید اعتبار جاوا اسکریپت نوعی ویژگی قابل استفاده اضافی می باشد، نه تنها به معنی یک تایید اعتبار.

اجازه دهید view قبلی یعنی search() را کمی پیچیده کنیم، به طوری که کاربر تنها بتواند داده ی ورودی برای جستجو را کمتر یا مساوی با بیست حرف وارد کند. (برای مثال، می توان این چنین گفت که هر چیزی طولانی تر مقدار گفته شده می تواند باعث کندی جستجو شود.) چه طور می توان آن را انجام داد؟ ساده ترین روش ممکن است قرار دادن منطق مورد نظر به طور مستقیم درون view می باشد، مانند زیر:

def search(request):
    error = False
    if 'q' in request.GET:
        q = request.GET['q']
        if not q:
            error = True
        elif len(q) > 20:
            error = True
        else:
            books = Book.objects.filter(title__icontains=q)
            return render_to_response('search_results.html',
                {'books': books, 'query': q})
    return render_to_response('search_form.html',
        {'error': error})

حالا، در صورتیکه سعی کنید یک داده را برای جستجو با بیشتر از بیست حرف ارسال کنید، اجازه ی جستجو داده نخواهد شد؛ یک پیام خطا دریافت خواهید کرد. ولی پیام خطا در search_form.html در حال حاضر "Please submit a search term." می باشد. – بنابراین باید طوری آن را تغییر داد که برای هر دو مورد صحیح باشد:

<html>
<head>
    <title>Search</title>
</head>
<body>
    {% if error %}
        <p style="color: red;">Please submit a search term 20 characters or shorter.</p>
    {% endif %}
    <form action="/search/" method="get">
        <input type="text" name="q">
        <input type="submit" value="Search">
    </form>
</body>
</html>

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

در واقع مشکل آن است که از یک مقدار Boolean سایده برای خطا استفاده شده است، در حالیکه باید از یک لیست رشته های پیام خطا استفاده شود، در زیر نحوه ی حل این مشکل نشان داده شده است:

def search(request):
    errors = []
    if 'q' in request.GET:
        q = request.GET['q']
        if not q:
            errors.append('Enter a search term.')
        elif len(q) > 20:
            errors.append('Please enter at most 20 characters.')
        else:
            books = Book.objects.filter(title__icontains=q)
            return render_to_response('search_results.html',
                {'books': books, 'query': q})
    return render_to_response('search_form.html',
        {'errors': errors})

سپس نیاز به ایجاد یک پیچیدگی کوچک درون فایل search_form.html برای بیان اینکه بجای یک مقدار Boolean یک لیست از error ها ارسال شده است.

<html>
<head>
    <title>Search</title>
</head>
<body>
    {% if errors %}
        <ul>
            {% for error in errors %}
            <li>{{ error }}</li>
            {% endfor %}
        </ul>
    {% endif %}
    <form action="/search/" method="get">
        <input type="text" name="q">
        <input type="submit" value="Search">
    </form>
</body>
</html>

 

به اشتراک گذاری این ارسال


لینک به ارسال
به اشتراک گذاری در سایت های دیگر

ساخت یک فرم تماس

اگرچه در سرتاسر مثال فرم جستجوی کتاب بررسی کرده و به طور ظریف آن را اصلاح نمودیم، ولی اساسا هنوز این مثال ساده می باشد: زیرا تنها دارای یک فیلد 'q' می باشد. به دلیل سادگی بیش از حد مثال مذکور حتی از کتابخانه ی فرم جنگو برای آن استفاده ای نشد. ولی فرم های پیچیده تر برای رفتار پیچیده تر فراخوانی می شوند – و حالا، چیزی پیچده تر را توسعه خواهیم داد: یک سایت فرم تماس.

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

با template مورد نظر contact_form.html شروع می کنیم:

<html>
<head>
    <title>Contact us</title>
</head>
<body>
    <h1>Contact us</h1>

    {% if errors %}
        <ul>
            {% for error in errors %}
            <li>{{ error }}</li>
            {% endfor %}
        </ul>
    {% endif %}

    <form action="/contact/" method="post">
        <p>Subject: <input type="text" name="subject"></p>
        <p>Your e-mail (optional): <input type="text" name="email"></p>
        <p>Message: <textarea name="message" rows="10" cols="50"></textarea></p>
        <input type="submit" value="Submit">
    </form>
</body>
</html>

سه فیلد تعریف شده است: موضوع، آدرس پست الکترونیک و پیام. دومین فیلد اختیاری می باشد، ولی دو فیلد دیگر الزاما باید پر شوند. توجه داشته باشید که به جای method="get" در اینجا از method="POST" استفاده شده است، زیرا ارسال فرم ممکن است دارای یک اثر زیان بار باشد – فرستادن یک پست الکترونیکی. همچنین، کد نمایش خطا از template قبلی search_form.html کپی شده است.

در صورتیکه کار را با view بخش قبلی search() ادامه دهیم، یک نسخه ی خام از contact() ممکن است چیزی شبیه به کد زیر باشد:

from django.core.mail import send_mail
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response

def contact(request):
    errors = []
    if request.method == 'POST':
        if not request.POST.get('subject', ''):
            errors.append('Enter a subject.')
        if not request.POST.get('message', ''):
            errors.append('Enter a message.')
        if request.POST.get('email') and '@' not in request.POST['email']:
            errors.append('Enter a valid e-mail address.')
        if not errors:
            send_mail(
                request.POST['subject'],
                request.POST['message'],
                request.POST.get('email', 'noreply@example.com'),
                ['siteowner@example.com'],
            )
            return HttpResponseRedirect('/contact/thanks/')
    return render_to_response('contact_form.html',
        {'errors': errors})

(در صورتیکه مثال های کتاب را دنبال می کنید، ممکن است مردد باشید که آیا view فوق را درون فایل books/views.py قرار دهید یا خیر. کد فوق هیچ کاری با برنامه ی books انجام نمی دهد، بنابراین آیا می توان جای دیگری آن را قرار داد؟ این کاملا به تصمیم شما بستگی دارد؛ جنگو هیچ توجهی به این موضوع ندارد، البته تا زمانیکه شما درون URLconf به درستی به view مورد نظر اشاره کنید. ترجیح شخصی ما بر این است که یک دایرکتوری جدا به نام contact هم سطح با دایرکتوری books بسازید. این دایرکتوری یک __init__.py و views.py خواهد داشت.)

چند نکته ی جدید که در مثال فوق اتفاق افتاده است:

  • بررسی شده است که request.method مقدار 'POST' باشد. تنها در صورتیکه فرم ارسال شده باشد مقدار آن True خواهد بود؛ در صورتیکه شخصی تنها فرم تماس را مشاهده کند مقدار آن True نخواهد بود. (در مورد مشاهده ی فرم تماس request.method مقدار 'GET' خواهد داشت، زیرا در حالت عادی جستجو در وب، مرورگرها از GET استفاده می کنند نه از POST. این حالت روش زیبایی را برای مجزا کردن مورد نمایش فرم از مورد پردازش فرم ایجاد می کند.)
  • بجای request.GET، برای دسترسی به داده های ارسال شده ی فرم از request.POST استفاده شده است. این حرکت بسیار ضروری می باشد، زیرا تگ <form> در contact_form.html از method="post" استفاده کرده است. در صورتیکه view از طریق POST قابل دسترسی باشد، بنابراین request.GET خالی خواهد بود.
  • در مثال فوق، دو فیلد subject و message به صورت الزامی وجود دارد، بنابراین باید هر دوی آن ها تایید اعتبار شوند، توجه داشته باشید که به جای request.POST[] از request.POST.get() استفاده شده است، هنگام استفاده از request.POST[] در صورتیکه کلید مورد نظر وجود نداشته باشد موجب بروز خطای MultiValueDictKeyError خواهد شد، برای جلوگیری از بروز خطا در صورت نبود کلید از request.POST.get() استفاده شده است که آرگومان دوم مقدار پیشفرضی می باشد که در صورت نبود کلید این مقدار برگردانده می شود.
  • اگرچه فیلد email یک فیلد الزامی نمی باشد، ولی در صورت وارد کردن مقداری برای این فیلد تایید اعتبار شده است. الگوریم تایید اعتبار در اینجا بسیار شکننده است، زیرا تنها بررسی شده است که رشته ی مورد نظر حاوی علامت (@) باشد. در دنیای واقعی، شما تایید اعتبارهایی قویتر نیاز دارید (جنگو همچنین تایید اعتباری را ارائه کرده است، که کمی بعد توضیح داده خواهد شد.)
  • از تابع django.core.mail.send_mail برای فرستادن یک پست الکترونیکی استفاده شده است. این تابع دارای چهار آرگومان الزامی می باشد: موضوع پست الکترونیکی، بدنه ی پست الکترونیکی، آدرس فرستنده و یک لیستی از آدرس های گیرندگان. send_mail یک wrapper مناسب در اطراف کلاس EmailMessage می باشد، که ویژگی های پیشرفته ای مانند ضمیمه ها (attachments)، پست های الکترونیک چند قسمتی و کنترل کامل سرتاسر header های پست الکترونیک را ارائه می دهد.
  • توجه داشته باشید که به منظور فرستاند پست الکترونیک با استفاده از send_mail()، سرور شما باید برای فرستاده mail، پیکربندی شده باشد و همچنین تنظیمات خاصی برای این منظور نیز لازم می باشد که برای اطلاعات بیشتر در این باره می توانید به آدرس Sending email مراجعه کنید.
  • بعد از فرستادن پست الکترونیک، با برگرداند یک شیء HttpResponseRedirect به یک صفحه ی "success" تغییر مسیر داده شده است. ولی باید دلیل استفاده از redirect را به جای برای مثال render_to_response() را توضیح دهیم.
  • دلیل: در صورتیکه یک کاربر دکمه ی "Refresh" را در آن صفحه ای که از طریق روش POST بارگذاری شده است فشار دهد، آن درخواست تکرار خواهد شد. این می تواند اغلب باعث بروز رفتاری ناخواسته شود، مانند اضافه شدن چندین بار یک رکورد تکراری در پایگاه داده – یا در مثال فوق، پست الکترونیکی دوباره فرستاده می شود. در صورتیکه کاربر بعد از POST به صفحه ی دیگری redirect شود، بنابراین هیچ شانسی برای تکرار شدن درخواست وجود ندارد.
  • شما باید همواره یک redirect برای نتایج POST موفقیت آمیز ایجاد کنید.

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

مشکل دیگر نمایش دوباره ی فرم می باشد. در زمان ایجاد خطاهای اعتبار، بهترین تمرین نمایش دوباره ی فرم با داده های قبلی ارسال شده می باشد، به طوری که کاربر بتواند چیزی را که به طور اشتباه پر کرده است را مشاهده کند (و همچنین کاربر نیازی به دوباره پر کردن فیلدهایی که درست بوده اند پیدا نکند). می توان به صورت دستی داده های POST را به به سمت template پس فرستاد، ولی لازم است هر فیلد HTML را برای درج کردن مقدار مناسب در مکان مناسب ویرایش کرد:

# views.py

def contact(request):
    errors = []
    if request.method == 'POST':
        if not request.POST.get('subject', ''):
            errors.append('Enter a subject.')
        if not request.POST.get('message', ''):
            errors.append('Enter a message.')
        if request.POST.get('email') and '@' not in request.POST['email']:
            errors.append('Enter a valid e-mail address.')
        if not errors:
            send_mail(
                request.POST['subject'],
                request.POST['message'],
                request.POST.get('email', 'noreply@example.com'),
                ['siteowner@example.com'],
            )
            return HttpResponseRedirect('/contact/thanks/')
    return render_to_response('contact_form.html', {
        'errors': errors,
        'subject': request.POST.get('subject', ''),
        'message': request.POST.get('message', ''),
        'email': request.POST.get('email', ''),
    })

# contact_form.html

<html>
<head>
    <title>Contact us</title>
</head>
<body>
    <h1>Contact us</h1>

    {% if errors %}
        <ul>
            {% for error in errors %}
            <li>{{ error }}</li>
            {% endfor %}
        </ul>
    {% endif %}

    <form action="/contact/" method="post">
        <p>Subject: <input type="text" name="subject" value="{{ subject }}"></p>
        <p>Your e-mail (optional): <input type="text" name="email" value="{{ email }}"></p>
        <p>Message: <textarea name="message" rows="10" cols="50">**{{ message }}**</textarea></p>
        <input type="submit" value="Submit">
    </form>
</body>
</html>

کد فوق بسیار ضعیف و غیر ضروری می باشد، و فرصت های زیادی را برای خطاهای فردی را ایجاد می کند.

اولین کلاس Form

جنگو دارای یک کتابخانه ی فرم با نام django.forms می باشد، که بسیاری از مسائل بررسی شده در این فصل را کنترل می کند – از نمایش فرم تا تایید اعتبار. اجازه دهید برنامه ی فرم تماس را با استفاده از فرم فریم ورک یا چارچوب جنگو دوباره بنویسیم.

کتابخانه ی "newforms" جنگو

ممکن است تاکنون چیزی درباره ی django.newforms شنیده باشید. هنگامی که درباره ی django.newforms صحبت می شود، صحبت درباره ی چیزی است که حالا django.forms نام دارد – که در این فصل به پرداخته شده است.

دلیل تغییر نام این کتابخانه تاریخی می باشد. هنگامی که جنگو برای عموم منتشر شد، دارای سیستم فرم های پیچیده و گیج کننده به نام django.forms بود. این کتابخانه دوباره به صورت کامل بازنویسی شد، و نسخه ی جدید django.newforms نامیده شد به طوری که افراد هنوز می توانستند از نسخه ی قدیمی استفاده کنند. هنگامی که جنگو 1.0 منتشر شد، django.forms کنار گذاشته شد، و django.newforms به django.forms تغییر نام داد.

روش اصلی برای استفاده از فریم ورک یا چارچوب فرم ها تعریف یک کلاس Form برای هر <form> HTML می باشد که با آن سرکار دارید. در مثال ما، تنها یک <form> داریم، بنابراین یک کلاس Form هم خواهیم داشت. این کلاس می تواند در هرجایی که می خواهید قرار بگیرد – حتی به طور مستقیم درون فایل views.py – ولی مانند یک قرارداد کلاس های Form درون یک فایل جدا به نام forms.py قرار می گیرند. این فایل را در مسیر فایل views.py بسازید، و کد زیر را درون آن وارد کنید:

from django import forms

class ContactForm(forms.Form):
    subject = forms.CharField()
    email = forms.EmailField(required=False)
    message = forms.CharField()

کد فوق همانند مدل های جنگو می باشد. هر فیلد در فرم با یک نوع از کلاس فیلد – CharField و EmailField نوع هایی هستند که در کد فوق استفاده شده اند – به صورت attribute های کلاس Form نشان داده می شود. هر فیلد به صورت پیشفرض الزامی (required) می باشد، بنابراین برای اختیاری کردن فیلد email، required=False تعیین شده است.

اجازه دهید از طریق interactive interpreter پایتون کاری را که این کلاس انجام می دهد را با هم مشاهده کنیم، اولین کاری که می تواند انجام دهد نمایش خودش به صورت HTML می باشد:

>>> from contact.forms import ContactForm
>>> f = ContactForm()
>>> print f
<tr><th><label for="id_subject">Subject:</label></th><td><input type="text" name="subject" id="id_subject" /></td></tr>
<tr><th><label for="id_email">Email:</label></th><td><input type="text" name="email" id="id_email" /></td></tr>
<tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" /></td></tr>

جنگو یک label برای هر فیلد اضافه کرده است. هدف ایجاد رفتار تا حد ممکن بهینه به طور پیشفرض می باشد.

کد فوق خروجی پیشفرض در قالب بندی <table> HTML می باشد، ولی خروجی های داخلی دیگری نیز وجود دارند:

>>> print f.as_ul()
<li><label for="id_subject">Subject:</label> <input type="text" name="subject" id="id_subject" /></li>
<li><label for="id_email">Email:</label> <input type="text" name="email" id="id_email" /></li>
<li><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" /></li>
>>> print f.as_p()
<p><label for="id_subject">Subject:</label> <input type="text" name="subject" id="id_subject" /></p>
<p><label for="id_email">Email:</label> <input type="text" name="email" id="id_email" /></p>
<p><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" /></

توجه داشته باشید که تگ های باز و بسته ی <table>، <ul> و <form> درون خروجی وجود ندارند، به طوری که می توان هر ردیف اضافه ای را در صورت نیاز اضافه کرد.

این متدها تنها میانبرهای برای موارد رایج نمایش تمام فرم می باشند. همچنین می توان برای هر فیلد خاص نیز فرم را نمایش داد:

>>> print f['subject']
<input type="text" name="subject" id="id_subject" />
>>> print f['message']
<input type="text" name="message" id="id_message" />

دومین کاری که شیء های Form می توانند انجام دهند تایید اعتبار داده می باشد. برای تایید اعتبار داده، یک شیء Form جدید ایجاد کرده و یک دیکشنری از داده هایی که نام فیلد را به داده ی مورد نظر مربوط می کند به آن ارسال کنید.

>>> f = ContactForm({'subject': 'Hello', 'email': 'adrian@example.com', 'message': 'Nice site!'})

هنگامی که داده ها را با نمونه ی Form مربوط ساختید، شما یک "bound" فرم ساخته اید:

>>> f.is_bound
True

متد is_valid() را روی هر bound Form برای یافتن اینکه داده معتبر است یا خیر فراخوانی کنید. ما برای هر فیلد یک مثدار معتبر ارسال کردیم، بنابراین Form به کلی معتبر می باشد:

>>> f.is_valid()
True

در صورتیکه فیلد email را ارسال نکنیم، همچنان معتبر خواهد ماند، زیرا این فیلد را به صورت required=False تعیین کرده ایم:

>>> f = ContactForm({'subject': 'Hello', 'message': 'Nice site!'})
>>> f.is_valid()
True

ولی، در صورتیکه subject یا message را در نظر نگیریم، فرم دیگر معتبر نخواهد بود:

>>> f = ContactForm({'subject': 'Hello'})
>>> f.is_valid()
False
>>> f = ContactForm({'subject': 'Hello', 'message': ''})
>>> f.is_valid()
False

می توان اطلاعات بیشتری را از طریق پیام های خطا بدست آورد:

>>> f = ContactForm({'subject': 'Hello', 'message': ''})
>>> f['message'].errors
[u'This field is required.']
>>> f['subject'].errors
[]
>>> f['email'].errors
[]

هر نمونه ی bound Form دارای attribute های خطا می باشد که یک دیکشنری با نام فیلد های مرتبط شده با لیست های پیام خطا ارائه می دهد:

>>> f = ContactForm({'subject': 'Hello', 'message': ''})
>>> f.errors
{'message': [u'This field is required.']}

در پایان، برای نمونه هایی Form ای که دارای داده های معتبر می باشند، یک attribute با نام cleaned_data در دسترس می باشد. این attribute یک دشکنری از داده های ارسال (submit) شده می باشد. فریم ورک یا چارچوب فرم جنگو نه تنها داده را تایید اعتبار می کند، بلکه آن را به یک نوع مناسب پایتون تبدیل می کند.

>>> f = ContactForm({'subject': 'Hello', 'email': 'adrian@example.com', 'message': 'Nice site!'})
>>> f.is_valid()
True
>>> f.cleaned_data
{'message': u'Nice site!', 'email': u'adrian@example.com', 'subject': u'Hello'}

فرم تماس ما تنها با رشته ها سر کار دارد که به شیء های یونیکد تبدیل شده اند – ولی در صورتیکه از یک IntegerField یا dateField استفاده کنیم، فریم ورک یا چارچوب فرم از شیء های مناسب integer یا datetime.date برای فیلد داده شده استفاده خواهد کرد.

به اشتراک گذاری این ارسال


لینک به ارسال
به اشتراک گذاری در سایت های دیگر

استفاده کردن از شیء های فرم درون view

در زیر نحوه ی استفاده از کلاس های فرم به جای روش قبلی را مشاهده خواهید کرد:

# views.py

from django.shortcuts import render_to_response
from mysite.contact.forms import ContactForm

def contact(request):
    if request.method == 'POST':
        form = ContactForm(request.POST)
        if form.is_valid():
            cd = form.cleaned_data
            send_mail(
                cd['subject'],
                cd['message'],
                cd.get('email', 'noreply@example.com'),
                ['siteowner@example.com'],
            )
            return HttpResponseRedirect('/contact/thanks/')
    else:
        form = ContactForm()
    return render_to_response('contact_form.html', {'form': form})

# contact_form.html

<html>
<head>
    <title>Contact us</title>
</head>
<body>
    <h1>Contact us</h1>

    {% if form.errors %}
        <p style="color: red;">
            Please correct the error{{ form.errors|pluralize }} below.
        </p>
    {% endif %}

    <form action="" method="post">
        <table>
            {{ form.as_table }}
        </table>
        <input type="submit" value="Submit">
    </form>
</body>
</html>

نگاه کنید که تا چه مقدار قادر به حذف کردن کدهای اضافه ی قبلی می باشیم! فریم ورک یا چارچوب فرم جنگو نمایش HTML، تایید اعتبار، تبدیل داده ها و نمایش دوباره ی فرم با خطاها را کنترل می کند.

سعی کنید به صورت محلی روش فوق را امتحان کنید، با فیلدها خالی فرم را ارسال کنید، با فیلد نا معتبر برای email فرم را ارسال کنید، و در پایان فرم را با داده ی معتبر ارسال کنید. (البته بسته پیکربندی mail-server، ممکن است هنگام فراخوانی send_mail() دریافت کنید، که بحث آن جدا می باشد.)

تغییر نحوه ارائه فیلدها

ممکن است اولین چیزی که مورد توجه شما قرار گیرد این باشد که فیلد message به صورت <input type="text"> نمایش داده شده است، و باید یک <textarea> باشد. می توان این مشکل را به شکل زیر حل کرد:

from django import forms

class ContactForm(forms.Form):
    subject = forms.CharField()
    email = forms.EmailField(required=False)
    message = forms.CharField(widget=forms.Textarea)

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

Think of the Field classes as representing validation logic, while widgets represent presentation logic.

تنظیم حداکثر طول

یک از رایج ترین نیازهای تایید اعتبار بررسی اندازه ی قطعی یک فیلد می باشد. برای اندازه گیری خوب، باید کلاس ContactForm را محدود به subject ای با صد حرف کرد. برای انجام چنین کاری، تنها یک max_length برای CharField قرار می دهیم:

from django import forms

class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    email = forms.EmailField(required=False)
    message = forms.CharField(widget=forms.Textarea)

همچنین یک آرگومان اختیاری min_length نیز در دسترس می باشد.

تنظیم مقدار اولیه

به منظور بهبود این فرم، اجازه دهید یک مقدار اولیه برای فیلد subject اضافه کنیم: "I love your site". برای انجام چنین کاری می توان از آرگومان initial هنگامی که یک نمونه Form را می سازیم استفاده کرد.

def contact(request):
    if request.method == 'POST':
        form = ContactForm(request.POST)
        if form.is_valid():
            cd = form.cleaned_data
            send_mail(
                cd['subject'],
                cd['message'],
                cd.get('email', 'noreply@example.com'),
                ['siteowner@example.com'],
            )
            return HttpResponseRedirect('/contact/thanks/')
    else:
        form = ContactForm(
            initial={'subject': 'I love your site!'}
        )
    return render_to_response('contact_form.html', {'form': form})

حالا، فیلد subject با مقدار از پیش پر شده ی مورد نظر نمایش داده خواهد شد.

سفارشی کردن قوانین تایید اعتبار

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

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

می خواهیم یک تایید اعتبار اضافه در فیلد message ایجاد کنیم، بنابراین یک متد clean_message() به کلاس فرم خود اضافه می کنیم:

from django import forms

class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    email = forms.EmailField(required=False)
    message = forms.CharField(widget=forms.Textarea)

    def clean_message(self):
        message = self.cleaned_data['message']
        num_words = len(message.split())
        if num_words < 4:
            raise forms.ValidationError("Not enough words!")
        return message

سیستم فرم جنگو به طور خودکار هر متدی را که با clean_ شروع و با نام یک فیلد تمام شود جستجو می کند. در صورتیکه همچین متدی وجود داشته باشد، در مدت تایید اعتبار فراخوانی خواهد شد.

به طور خاص، متد clean_message() بعد از تایید اعتبار منطقی پیشفرض برای فیلد داده شده فراخوانی می شود (در این مورد، تایید اعتبار منطقی برای یک CharField الزامی، می باشد.) زیرا داده ی فیلد در اینصورت تا حدی پردازش شده خواهد بود، این داده ی پردازش شده را از self.cleaned_data بدست می آوریم. همچنین، در این صورت لازم نیست درباره ی بررسی کردن اینکه مقدار وجود دارد و خالی نمی باشد نگران باشیم؛ این کار با تایید کننده ی اعتبار پیشفرض انجام شده است.

بسادگی از ترکیب len() و split() برای شمارش کلمات استفاده کرده ایم. در صورتیکه کاربر کلمات کمی وارد کند، خطای forms.ValidationError ایجاد خواهد شد. رشته ی اضافه شده به این خطا به صورت یک آیتم اضافه شده به لیست خطاها به کاربر نمایش داده خواهد شد.

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

تعیین label ها

به طور پیشفرض، label ها در در جنگو به طور خودکار از طریق جا به جا کردن خط تیره با فاصله و همچنین تبدیل حرف اول به حرف بزرگ ساخته می شوند – بنابراین label برای فیلد email به این شکل خواهد بود: "Email". (این روش آشنا نیست؟ این الگوریتم همان الگوریتمی می باشد که مدل های جنگو از آن برای محاسبه ی مقدار پیشفرض verbose_name برای فیلدها از آن استفاده می کردند که در آموزش مدل جنگو توضیح داده شده است.)

ولی، همانند مدل های جنگو، می توان label فیلد داده شده را سفارشی کرد. تنها کافیست مانند زیر از label استفاده شود:

class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    email = forms.EmailField(required=False, label='Your e-mail address')
    message = forms.CharField(widget=forms.Textarea)

سفارشی کردن طرح فرم

template ما یعنی contact_form.html از {{ form.as_table }} برای نمایش فرم استفاده کرده است، ولی می توان فرم را به روش های دیگری نیز برای کنترل ذره به ذره نمایش داد.

سریع ترین روش برای سفارشی کردن نمایش فرم ها استفاده از CSS می باشد. تولید کننده ی خودکار لیست های خطا دقیقا از <ul class="errorlist"> استفاده می کنند به طوری که می توان با استفاده از CSS آن ها را علامت گذاری کرد:

<style type="text/css">
    ul.errorlist {
        margin: 0;
        padding: 0;
    }
    .errorlist li {
        background-color: red;
        color: white;
        display: block;
        font-size: 10px;
        margin: 0 0 3px;
        padding: 4px 5px;
    }
</style>

زمانی این روش مناسب می باشد که بخواهیم حالت پیشفرض ارائه شده را تغییر دهیم. {{ form.as_table }} و ... میانبرهای مفیدی برای توسعه ی برنامه می باشند، ولی هرچیزی درباره ی روش نمایش یک فرم می تواند غالبا درون خود template دوباره نویسی شده و تغییر کند، و شما احتمالا انجام اینکار را برای خودتان خواهید یافت.

هر فیلدی (<input type="text">، <select>، <textarea> و ...) می تواند به صورت جداگانه با دسترسی داشتن به {{ form.fieldname }} در template ارائه شده باشد، و هر خطای مربوط به آن با یک فیلد به صورت {{ form.fieldname.errors }} قابل دسترسی می باشد. با در نظر گرفتن نکات فوق، می توان یک template سفارشی برای فرم تماس با کد زیر ایجاد نمود:

<html>
<head>
    <title>Contact us</title>
</head>
<body>
    <h1>Contact us</h1>

    {% if form.errors %}
        <p style="color: red;">
            Please correct the error{{ form.errors|pluralize }} below.
        </p>
    {% endif %}

    <form action="" method="post">
        <div class="field">
            {{ form.subject.errors }}
            <label for="id_subject">Subject:</label>
            {{ form.subject }}
        </div>
        <div class="field">
            {{ form.email.errors }}
            <label for="id_email">Your e-mail address:</label>
            {{ form.email }}
        </div>
        <div class="field">
            {{ form.message.errors }}
            <label for="id_message">Message:</label>
            {{ form.message }}
        </div>
        <input type="submit" value="Submit">
    </form>
</body>
</html>

{{ form.message.errors }} در صورتیکه خطاها موجود باشند رشته ی خالی در صورتیکه فیلد معتبر باشد یک <ul class="errorlist"> را نمایش می دهد. همچنین می توان با form.message.errors به صورت یک Boolean رفتار کنید و یا حتی درون آن مثل یک لیست جستجو کنید. برای مثال:

<div class="field{% if form.message.errors %} errors{% endif %}">
    {% if form.message.errors %}
        <ul>
        {% for error in form.message.errors %}
            <li><strong>{{ error }}</strong></li>
        {% endfor %}
        </ul>
    {% endif %}
    <label for="id_message">Message:</label>
    {{ form.message }}
</div

 

به اشتراک گذاری این ارسال


لینک به ارسال
به اشتراک گذاری در سایت های دیگر

View ها و URLconf های پیشرفته

در آموزش 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 قرار ندهید. جنگو این نقطه ها را به صورت خودکار قرار خواهد داد.

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

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

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

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

  • در این روش دسته بندی توابع view آسان می باشد. بخش "دسته بندی توابع 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 هایی اضافه به صورت آرگومان های کیورد ارسال می شود. برای اطلاعات بیشتر بخش بعدی را مطالعه کنید.

ارسال 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 استفاده کرد. در زیر توضیحاتی درباره ی کارهای انجام داده ی فوق وجود دارد:

  • کلاس های مدل به طور مستقیم به صورت پارامتر model ارسال شده اند. پارامترهای ارسالی اضافه در URLconf می توانند نه تنها رشته ها، بلکه هر نوعی از شیء های پایتون را ارسال کنند.
  • خط model.objects.all() یک مثال ساده از duck typing می باشد: "در صورتیکه چیزی مانند اردک راه برود و مانند اردک صحبت کند، می توان با آن مانند اردک رفتار کرد." دقت کنید که کد مورد نظر نمی داند، که نوع شیء model چه می باشد، تنها نکته ی لازم این است که model دارای یک attribute به نام objects باشد که آن نیز به نوبه ی خود دارای یک متد all() باشد.
  • در تعیین نام template از model.__name__.lower() استفاده شده است. هر کلاس پایتون دارای یک attribute با نام __name__ می باشد که نام کلاس را بر می گرداند. این ویژگی برای زمان هایی مانند مثال فوق مفید می باشد، هنگامی که نام کلاس را تا زمان اجرای برنامه نمی دانیم. برای مثال مقدار __name__ برای کلاس BlogEntry رشته ی 'BlogEntry' می باشد.
  • تفاوت جزئی بین این مثال و مثال قبلی این است که، متغیر generic ای با نام object_list به template ارسال شده است. می توان به سادگی نام این متغیر را به blogentry_list و یا even_list تغییر داد، ولی ما به عنوان تمرین برای خواننده آن را بدین صورت باقی گذاشته ایم.

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

به اشتراک گذاری این ارسال


لینک به ارسال
به اشتراک گذاری در سایت های دیگر

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

مهمان
ارسال پاسخ به این موضوع ...

×   شما در حال چسباندن محتوایی با قالب بندی هستید.   حذف قالب بندی

  تنها استفاده از ۷۵ اموجی مجاز می باشد.

×   لینک شما به صورت اتوماتیک جای گذاری شد.   نمایش به عنوان یک لینک به جای

×   محتوای قبلی شما بازگردانی شد.   پاک کردن محتوای ویرایشگر

×   شما مستقیما نمی توانید تصویر خود را قرار دهید. یا آن را اینجا بارگذاری کنید یا از یک URL قرار دهید.


×