oruji.github.io
oruji.github.ioPersian Tutorials
ویرایش: 1398/11/2 17:47
A A

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

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

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

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

مرور زبان Template

در ابتدا، اجازه دهید مروری اجمالی به تعدادی از مفاهیم معرفی شده در آموزش template جنگو بپردازیم:

برای جزئیات بیشتر درباره ی اصول اولیه template ها به آموزش template جنگو مراجعه کنید.

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

RequestContext و پردازشگرهای Context

هنگام ارائه ی یک template، شما به یک context نیاز دارید. معمولا این یک نمونه از django.template.Context می باشد، ولی جنگو همچنین دارای یک کلاس فرزند django.template.RequestContext می باشد، که با کمی تفاوت عمل می کند. RequestContext گروهی از متغیرها را برای template context به طور پیشفرض اضافه می کند – چیزی شبیه به شیء HttpRequest یا اطلاعات درباره ی کاربر فعلی وارد شده به سایت.

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

from django.template import loader, Context def view_1(request): # ... t = loader.get_template('template1.html') c = Context({ 'app': 'My app', 'user': request.user, 'ip_address': request.META['REMOTE_ADDR'], 'message': 'I am view 1.' }) return t.render(c) def view_2(request): # ... t = loader.get_template('template2.html') c = Context({ 'app': 'My app', 'user': request.user, 'ip_address': request.META['REMOTE_ADDR'], 'message': 'I am the second view.' }) return t.render(c)

(توجه داشته باشید که به عمد از میانبر render_to_response() در مثال های فوق استفاده نشده است – template ها به صورت دستی بارگذاری شده اند، شیء ها context ساخته شده و template ها ارائه ی شده اند. تمام گام ها برای هدف به وضوح و دقت توضیح داده می شوند.)

هر view سه متغیر یکسان – app، user و ip_address – به template خود ارسال می کند. بهتر به نظر نمی رسید اگر کدهای زائد و اضافی حذف می شدند؟

RequestContext و پردازشگرهای context برای حل این مشکل ساخته شده اند. پردازشگرهای context، به شما اجازه می دهند، یک تعداد از متغیرها که در هر context مجموعه ای دریافت می کنند را به طور خودکار تعیین کنید – بدون اینکه مجبور باشید متغیرها را در هر فراخوانی render_to_response تعیین کنید. مشکل این است که هنگام ارائه ی یک template باید بجای Context از RequestContext استفاده کنید.

سطح پایین ترین روش برای استفاده از پردازشگرهای context ساختن چند پردازشگر و ارسال آنها به RequestContext می باشد. در زیر نحوه ی نوشتن مثال فوق با پردازشگرهای context وجود دارد:

from django.template import loader, RequestContext def custom_proc(request): "A context processor that provides 'app', 'user' and 'ip_address'." return { 'app': 'My app', 'user': request.user, 'ip_address': request.META['REMOTE_ADDR'] } def view_1(request): # ... t = loader.get_template('template1.html') c = RequestContext(request, {'message': 'I am view 1.'}, processors=[custom_proc]) return t.render(c) def view_2(request): # ... t = loader.get_template('template2.html') c = RequestContext(request, {'message': 'I am the second view.'}, processors=[custom_proc]) return t.render(c)

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

در آموزش template جنگو، میانبر render_to_response() معرفی شد که با استفاده از آن دیگر نیازی به فراخوانی loader.get_template()، سپس ساختن یک Context و فراخوانی متد render() در template نخواهد بود. به منظور نشان دادن کار به صورت سطح پایین با پردازشگرهای context، در مثال های بالا از render_to_response() استفاده نشده است، ولی امکان آن وجود دارد که از پردازشگرهای context با render_to_response() استفاده شود. برای انجام این کار از آرگومان context_instance به صورت زیر استفاده می شود:

from django.shortcuts import render_to_response from django.template import RequestContext def custom_proc(request): "A context processor that provides 'app', 'user' and 'ip_address'." return { 'app': 'My app', 'user': request.user, 'ip_address': request.META['REMOTE_ADDR'] } def view_1(request): # ... return render_to_response('template1.html', {'message': 'I am view 1.'}, context_instance=RequestContext(request, processors=[custom_proc])) def view_2(request): # ... return render_to_response('template2.html', {'message': 'I am the second view.'}, context_instance=RequestContext(request, processors=[custom_proc]))

در مثال فوق کد ارائه ی هر template مربوط به view درون یک خط (شکسته شده) خلاصه شده است.

این یک پیشرفت می باشد، ولی ارزیابی اختصار کد فوق، باید اعتراف کرد در این روش نیز تقریبا زیاده روی شده است. کدهای اضافه و زائد (متغیرهای template) به قیمت اضافه کردن کدی دیگر (فراخونی processor ها) حذف شده اند. استفاده کردن پردازشگرهای context در صورتیکه مجبور باشید هر بار processor ها را تایپ کنید شما را از تایپ کردن زیاد از حد نجات نمی دهد.

به این دلیل، جنگو از پردازشگرهای سراسری پشتیبانی می کند. تنظیم TTEMPLATE_CONTEXT_PROCESSORS (در فایل settings.py) پردازشگرهای context ای که باید همواره برای RequestContext بکار برده شوند را طراحی می کند. این نیاز برای تعیین پردازشگرها را در هر بار که از RequestContext استفاده می کنید را حذف می کند.

به طور پیشفرض، TEMPLATE_CONTEXT_PROCESSORS به شکل زیر خواهد بود:

TEMPLATE_CONTEXT_PROCESSORS = ( 'django.core.context_processors.auth', 'django.core.context_processors.debug', 'django.core.context_processors.i18n', 'django.core.context_processors.media', )

این تنظیم یک تاپل از آیتم های قابل فراخوانی می باشد که از یک رابط یکسان مانند تابع custom_proc که در مثال قبلی مشاهده شد استفاده می کنند – توابعی که یک شیء request به صورت آرگومان آن دریافت کرده و یک دیکشنری از آیتم های ادغام شده درون context بر می گرداند. توجه داشته باشید که متغیرها در TEMPLATE_CONTEXT_PROCESORS به صورت رشته مشخص شده اند، که بدین معنی می باشد که پردازشگرها لازم است جایی در مسیر پایتون باشند (بنابراین می توانید از تنظیم به آن ها مراجعه کنید).

هر پردازشگر بدین صورت کار می کند که در صورتیکه یک پردازشگر یک متغیر به context اضافه کند و پردازشگر دوم یک متغیر با همان نام اضافه کند، دومی بر روی اولی بازنویسی می شود.

جنگو تعدادی از پردازشگرهای ساده ی context را ارائه می دهد، از جمله آنهایی که به طور پیشفرض فعال می باشند:

django.core.context_processors.auth

در صورتیکه TEMPLATE_CONTEXT_PROCESSORS حاوی این پردازشگر باشد، هر RequestContext حاوی این متغیرها خواهد بود:

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

django.core.context_processors.debug

این پردازشگر اطلاعات اشکال زدایی (debugging) به سمت لایه ی template هدایت می کند. در صورتیکه TEMPLATE_CONTEXT_PROCESSORS حاوی این پردازشگر باشد، هر RequestContext حاوی این متغیرها خواهد بود:

بدلیل آنکه اطلاعات اشکال زدایی حساس می باشند، این پردازشگر context تنها در صورتیکه دو وضعیت زیر درست باشند متغیرها را به context اضافه می کند:

خوانندگان زیرک توجه خواهند داشته که متغیر template، debug هرگز مقدار False نخواهد داشت، زیرا در صورتیکه DEBUG مقدارش False باشد، متغیر debug در اولین مکان ساکن نخواهد بود.

django.core.context_processors.i18n

در صورتیکه این پردازشگر فعال باشد، هر RequestContext حاوی این متغیرها خواهد بود:

django.core.context_processors.request

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

در صورتیکه template های شما نیاز به دسترسی به attribute های HttpRequest فعلی باشند مانند آدرس Ip، ممکن است از این پردازشگر استفاده کنید:

{{ request.REMOTE_ADDR }}

راهنمایی هایی برای نوشتن پردازشگرهای context خودتان

در زیر راهنمایی هایی برای ایجاد پردازشگر خودتان وجود دارد:

HTML Escaping خودکار

هنگام تولید HTML از template ها، همواره یک مخاطره وجود دارد که یک متغیر شامل کاراکترهایی باشد که بر روی نتیجه ی HTML تاثیر بگذارد. برای مثال، کد زیر را ملاحظه کنید:

Hello, {{ name }}.

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

<script>alert('hello')</script>

با مقدار فوق، template به صورت زیر ارائه خواهد شد:

Hello, <script>alert('hello')</script>

... این بدین معنی است که مرورگر یک جعبه ی خطر جاوا اسکریپت ظاهر خواهد کرد!

به طور مشابه، چه می شود اگر name حاوی یک علامت '<' مانند زیر باشد؟

<b>username

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

Hello, <b>username

مقدار فوق باعث می شود، باقی کد صفحه ی وب به صورت bold نمایش داده خواهند شد!

واضح است که، داده ی ارسال شده توسط کاربر به طور کورکورانه قابل اعتماد نمی باشد، زیرا کاربران مخرب از این حفره برای انجام مقاصد پلید استفاده کنند. این قبیل رفتار امنیتی حمله ی Cross Site Scripting (XSS) نامیده می شوند. (برای اطلاعات بیشتر در مورد امنیت، به آموزش امنیت مراجعه کنید.)

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

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

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

نحوه ی از کار انداختن auto-escaping

در صورتیکه بخواهید حالت پیشفرض یعنی auto-escaping را برای هر وب سایت، هر سطح template و یا هر سطح متغیر، تغییر دهید، می توان به چندین روش عمل کرد.

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

برای متغیرها

جهت غیر فعال کردن auto‑scaping برای یک متغیر، از فیلتر safe می توان استفاده کرد:

This will be escaped: {{ data }} This will not be escaped: {{ data|safe }}

در مثال فوق در صورتیکه data حاوی مقدار '<b>' باشد، خروجی آن به صورت زیر خواهد بود:

This will be escaped: <b> This will not be escaped: <b>

برای بلاک های template

جهت کنترل auto‑escaping برای یک template، template (یا تنها قسمتی از template) را درون تگ autoescape مانند زیر قرار دهید:

{% autoescape off %} Hello {{ name }} {% endautoescape %}

تگ autoescape یک مقدار on یا off به صورت آرگومان دریافت می کند. گاهی اوقات می خواهید زمانی که auto‑escape در قسمتی از کد غیر فعال شده است آن را مجبور کنید که فعال شود مانند مثال زیر:

Auto-escaping is on by default. Hello {{ name }} {% autoescape off %} This will not be auto-escaped: {{ data }}. Nor this: {{ other_data }} {% autoescape on %} Auto-escaping applies again: {{ name }} {% endautoescape %} {% endautoescape %}

تگ auto‑escape درون template هایی که از template پدر ارث بری کرده اند نیز تاثیر می گذارد. به عنوان مثال:

# base.html {% autoescape off %} <h1>{% block title %}{% endblock %}</h1> {% block content %} {% endblock %} {% endautoescape %} # child.html {% extends "base.html" %} {% block title %}This & that{% endblock %} {% block content %}{{ greeting }}{% endblock %}

به این دلیل که auto‑escaping درون template پدر غیر فعال شده است، درون template فرزند نیز غیر فعال خواهد بود، بنابراین در صورتیکه متغیر greeting حاوی رشته ی <b>Hello!</b> باشد، خروجی آن به صورت زیر خواهد بود:

<h1>This & that</h1> <b>Hello!</b>

نکته ها

عموما، نویسندگان template نیازی نیست در مورد auto‑escaping خیلی زیاد نگرانی داشته باشند. توسعه دهندگان سمت پایتون (افرادی که view ها و فیلترهای سفارشی را می نویسند)، نیاز دارند، درباره ی مواردی که در داده نباید escape شوند فکر کنند، و داده را به طور مناسب علامت گذاری کنند. بنابراین کارها در template انجام خواهند شد.

در صورتیکه یک template می سازید که ممکن است در وضعیت هایی که مطمئن نیستید آیا auto‑escaping فعال است یا خیر استفاده شده باشد، آنگاه یک فیلتر escape برای هر متغیر که نیاز به escape دارد قرار دهید. هنگامی که auto‑escaping فعال باشد، هیچ مشکلی پیش نخواهد آمد – فیلتر escape تاثیری بر روی متغیرهای auto‑escape شده نخواهد داشت.

Escape کردن خودکار رشته ی خام در آرگومان های فیلتر

همانطور که پیش تر ذکر شد، آرگومان های فیلتر می توانند رشته باشند:

{{ data|default:"This is a string literal." }}

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

این بدان معناست که خواهید نوشت:

{{ data|default:"3 < 2" }}

... به جای

{{ data|default:"3 < 2" }} <-- Bad! Don't do this.

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

بارگذاری داخل Template

عموما، شما template ها را در فایل هایی درون filesystem خود ذخیره خواهید کرد، اما می توان از template loader های سفارشی برای بارگذاری template ها از منابع دیگر استفاده کرد.

جنگو دارای دو روش برای بارگذاری template می باشد:

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

بعضی از loader ها به طور پیشفرض غیر فعال می باشند، ولی می توان با ویرایش تنظیم TEMPLATE_LOADER آن ها را فعال کرد. TEMPLATE_LOADERS باید یک تاپل از رشته ها باشد، به طوری که هر رشته یک template loader را نمایش می دهد. این template loader ها به همراه جنگو ارائه شده اند:

جنگو به منظور توجه به تنظیم TEMPLATE_LOADERS از template loader ها استفاده می کند. جنگو از هر loader تا پیدا کردن مطابق آن استفاده می کند.

گسترش Template System

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

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

ساختن یک کتابخانه ی Template

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

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

در صورتیکه یک کتابخانه ی template نوشته اید که به هیچ models/views ای مرتبط نیست، این حالت برای داشتن پکیج برنامه ی جنگو که حاوی تنها یک پکیج templatetags می باشد معتبر و کاملا عادی است. هیچ محدودیتی نسبت به تعداد ماژول هایی که شما در پکیج templatetags قرار می دهید وجود ندارد. تنها به خاطر داشته باشید که یک عبارت {% load %} تگ ها یا فیلترهایی برای نام ماژول پایتون داده شده بارگذاری خواهد کرد، نه نام برنامه.

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

برای معتبر بودن کتابخانه ی تگ، ماژول باید حاوی یک متغیر در سطح ماژول به نام register باشد که یک نمونه از template.Library می باشد. این یک ساختار داده می باشد که تمام تگ ها و فیلترها درون آن عضو شده اند. بنابراین، در بالا ماژول، کد زیر را اضافه کنید:

from django import template register = template.Library()

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

نوشتن فیلترهای سفارشی Template

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

برای مثال، در فیلتر {{ var|foo:"bar" }}، فیلتر foo محتویات متغیر var و آرگومان "bar" را ارسال خواهد کرد.

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

در اینجا یک مثال از تعریف فیلتر را ملاحظه می کنید:

def cut(value, arg): "Removes all values of arg from the given string" return value.replace(arg, '')

در زیر نحوه ی استفاده حذف کردن فاصله های مقدار یک متغیر توسط فیلتر بالا نشان داده شده است:

{{ somevariable|cut:" " }}

اغلب فیلترها آرگومانی دریافت نمی کنند. در این مورد، تنها آرگومان تابع را حذف کنید:

def lower(value): # Only one argument. "Converts a string into all lowercase" return value.lower()

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

register.filter('cut', cut) register.filter('lower', lower)

متد Library.filter() دو آرگومان دریافت می کند:

در صورتیکه از پایتون 2.4 یا بالاتر استفاده می کنید، می توانید بجای روش فوق از regiser.filter() به صورت یک decorator استفاده کنید:

@register.filter(name='cut') def cut(value, arg): return value.replace(arg, '') @register.filter def lower(value): return value.lower()

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

مثال فوق نیز مثال کامل کتابخانه ی template یعنی تهیه ی فیلتر cut می باشد:

from django import template register = template.Library() @register.filter(name='cut') def cut(value, arg): return value.replace(arg, '')

توشتن تگ های template سفارشی

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

آموزش template جنگو نحوه ی کار سیستم template جنگو را در یک روند دو مرحله ای توضیح می دهد: کامپایل و ارائه (compling and rendering). برای تعریفی یک تگ template سفارشی، لازم است نحوه ی مدیریت هر دو مرحله ی فوق در زمانی که جنگو تگ شما را به دست می آورد به آن گفته شود.

هنگامی که جنگو یک template را کامپایل می کند، متن خام template را به note هایی تقسیم می کند. هر node یک نمونه از django.template.Node بوده و دارای یک متد render() می باشد. در نتیجه، یک template کامپایل شده یک لیست از شیء های Node می باشد. برای مثال، template زیر را ملاحظه کنید:

Hello, {{ person.name }}. {% ifequal name.birthday today %} Happy birthday! {% else %} Be sure to come back on your birthday for a splendid surprise message. {% endifequal %}

در حالت کامپایل شدهه ی template، template فوق به صورت لیستی از node های زیر نشان داده شده است:

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

بخش های بعدی، تمامی مراحل نوشتن تگ سفارشی پوشش داده خواهد شد.

نوشتن کامپایل تابع

برای هر تگ template ای که parser با آن مواجه است، یک تابع پایتون با محتویات تگ و خود شیء parser فراخوانی می شود. این تابع موظف است یک نمونه Node بر اساس محتویات تگ بر گرداند.

برای مثال، اجازه دهید یک تگ template بنویسیم، {% current_time %}، که زمان/تاریخ فعلی را نمایش دهد، که قالب بندی آن نیز بر حسب پارامتر داده شده در تگ در strftime (http://www.djangoproject.com/r/python/strftime/ را مشاهده کنید) باشد. در این مورد تصور می کنیم تگ باید به شکل زیر استفاده شود:

parser برای این تابع پارامتر را دریافت کرده و یک شیء Node بسازد:

from django import template register = template.Library() def do_current_time(parser, token): try: # split_contents() knows not to split quoted strings. tag_name, format_string = token.split_contents() except ValueError: msg = '%r tag requires a single argument' % token.split_contents()[0] raise template.TemplateSyntaxError(msg) return CurrentTimeNode(format_string[1:-1])

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

نوشتن Template Node

گام بعدی در نوشتن تگ های سفارشی، تعریف یک کلاس فرزند Node که حاوی یک متد render() است می باشد. در ادامه ی مثال قبلی، نیاز به تعریف CurrentTimeNode می باشد:

import datetime class CurrentTimeNode(template.Node): def __init__(self, format_string): self.format_string = str(format_string) def render(self, context): now = datetime.datetime.now() return now.strftime(self.format_string)

این دو تابع (__init__() و render()) به طور مستقیم به دو مرحله ی در روند template (compilation and rendering) مرتبط هستند. در نتیجه، تابع __init__() تنها ملزم به ذخیره ی قالب رشته ی برای استفاده ی بعدی می باشد، و تابع render() کار واقعی را انجام می دهد.

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

معرفی تگ

در پایان، نیاز به معرفی تگ با نمونه ی ماژول Library می باشد. معرفی تگ های سفارشی بسیار شبیه به معرفی فیلترهای سفارشی (همانطور که توضیح داده شد) می باشد. تنها یک نمونه template.Library را معرفی کرده و متد tag() آن را فراخوانی کنید. برای مثال:

register.tag('current_time', do_current_time)

متد tag() دو آرگومان دریافت می کند:

همانند معرفی فیلتر، امکان این وجود دارد که از register.tag به صورت یک decorator در پایتون 2.4 و بالاتر استفاده کرد:

@register.tag(name="current_time") def do_current_time(parser, token): # ... @register.tag def shout(parser, token): # ...

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

تنظیم یک متغیر در Context

مثال بخش قبلی به سادگی یک مقدار را بر می گرداند. خیلی اوقات قرار دادن متغیرها به جای برگشت دادن مقادیر مفید خواهد بود. در این روش، نویسندگان template می توانند تنها متغیرهایی که تگ های template شما قرار داده اند را استفاده کنند.

برای قرار دادن یک متغیر در context، از اختصاص دادن دیکشنری برای شیء context در متد render() استفاده می شود. در زیر نسخه ی تغییر کرده ی CurrentTimeNode مشاهده می کنید:

class CurrentTimeNode2(template.Node): def __init__(self, format_string): self.format_string = str(format_string) def render(self, context): now = datetime.datetime.now() context['current_time'] = now.strftime(self.format_string) return ''

(ساختن یک تابع do_current_time2، به اضافه ی معرفی آن تابع به تگ template، current_time2 انجام نشده است، تا خواننده آن ها را به عنوان تمرین انجام داد.)

توجه داشته باشید که render() یک رشته ی خالی را بر می گرداند. render() همواره باید یک رشته بر گرداند، بنابراین در صورتیکه تمام تگ های template یک متغیر را قرار دهند، render() باید یک رشته ی خالی بر گرداند.

در اینجا نحوه ی استفاده ی نسخه ی جدید از تگ نشان داده شده است:

{% current_time2 "%Y-%M-%d %I:%M %p" %}

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

راهکار درست این است که تگ template نامی برای متغیر قرار داده شده تعیین نماید:

{% get_current_time "%Y-%M-%d %I:%M %p" as my_current_time %}

برای انجام چنین کاری، نیاز به تغییر هر دو تابع کامپایل و کلاس Node به صورت زیر می باشد:

import re class CurrentTimeNode3(template.Node): def __init__(self, format_string, var_name): self.format_string = str(format_string) self.var_name = var_name def render(self, context): now = datetime.datetime.now() context[self.var_name] = now.strftime(self.format_string) return '' def do_current_time(parser, token): # This version uses a regular expression to parse tag contents. try: # Splitting by None == splitting by spaces. tag_name, arg = token.contents.split(None, 1) except ValueError: msg = '%r tag requires arguments' % token.contents[0] raise template.TemplateSyntaxError(msg) m = re.search(r'(.*?) as (\w )', arg) if m: fmt, var_name = m.groups() else: msg = '%r tag had invalid arguments' % tag_name raise template.TemplateSyntaxError(msg) if not (fmt[0] == fmt[-1] and fmt[0] in ('"', "'")): msg = "%r tag's argument should be in quotes" % tag_name raise template.TemplateSyntaxError(msg) return CurrentTimeNode3(fmt[1:-1], var_name)

حالا do_current_time() قالب رشته و نام متغیر را به CurrentTimeNode3 ارسال می کند.

Parse کردن تا تگ Template دیگر

تگ های template می توانند به صورت بلاک های حاوی تگ های دیگر (مانند {% if %} و {% for %}) کار کنند. برای ساختن یک تگ template مانند این، در تابع کامپایل خود از parser.parse() استفاده کنید.

در زیر نحوه ی کار تگ {% comment %} انجام شده است:

def do_comment(parser, token): nodelist = parser.parse(('endcomment',)) parser.delete_first_token() return CommentNode() class CommentNode(template.Node): def render(self, context): return ''

parser.parse() یک تاپل از نام تگ های template برای استفاده داخل تگ دریافت می کند و یک نمونه از django.template.NodeList بر می گرداند، که لیست تمام شیء های Node ای می باشد که parser با آن ها قبل از برخورد با نام تگ موجود در تاپل برخورد کرده است.

بنابراین در مثال قبلی، nodelist یک لیست از تمام node های بین {% comment %} و {% endcomment %} بدون در نظر گرفتن خود آن ها می باشد.

بعد از آن که parser.parse() فراخوانی شده است، parser در دست ترجمه ...

سپس CommentNode.render() به سادگی یک رشته ی خالی را بر می گرداند. هر چیزی بین {% comment %} و {% endcomment %} رد شده است.

Parse کردن تا تگ Template دیگر و ذخیره ی محتویات

در مثال قبلی، do_comment هر چیزی را بین {% comment %} و {% endcomment %} رد کرد. همچنین این امکان وجود دارد که با کد بین تگ های template کاری انجام داد.

برای مثال، در اینجا یک تگ template سفارشی، {% upper %}، که هر چیزی بین خودش و {% endupper %} را به حروف بزرگ تبدیل می کند:

{% upper %} This will appear in uppercase, {{ user_name }}. {% endupper %}

همانند مثال قبلی، از parser.parse() استفاده کرده ایم. این بار، نتیجه ی nodelist را به Node ارسال کرده ایم:

def do_upper(parser, token): nodelist = parser.parse(('endupper',)) parser.delete_first_token() return UpperNode(nodelist) class UpperNode(template.Node): def __init__(self, nodelist): self.nodelist = nodelist def render(self, context): output = self.nodelist.render(context) return output.upper()

تنها مفهوم جدید در کد فوق self.nodelist.render(context) در UpperNode.render() می باشد که به سادگی برای هر Node در لیست node متد render() فراخوانی شده است.

برای مثال های بیشتر از ارائه ی پیچیده، کد منبع {% if %}، {% for %}، {% ifequal %} و {% ifchanged %} را مشاهده کنید. این تگ ها در django/template/defaulttags.py موجود می باشند.

میانبر برای تگ های ساده

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

جهت ساده کردن ساختن این قبلی تگ ها، جنگو یک تابع کمک کننده ارائه می کند، simple_tag. این تابع یک متد از django.template.Library می باشد که یک تابع که آن هم یک آرگومان قبول می کند دریافت می کند، کار این تابع پیچیدن در تابع render و کارهای ضروری می باشد که قبلا ذکر شده است مانند معرفی کردن را با template system انجام می دهد.

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

def current_time(format_string): try: return datetime.datetime.now().strftime(str(format_string)) except UnicodeEncodeError: return '' register.simple_tag(current_time)در پایتون 2.4 و بالاتر، می توان از decorator استفاده کرد:@register.simple_tag def current_time(token): # ...

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

تگ های Inclusion

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

این قبیل از تگ ها، تگ های inclusion نامیده می شوند. نوشتن تگ های inclusion بهتر موضوعات دیگر با مثال قابل نشان دادن است. اجازه دهید یک تگ بنویسیم که یک لیست از کتاب ها برای یک شیء Author داده شده تولید می کند. ما از تگ به صورت زیر استفاده می کنیم:

{% books_for_author author %}

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

<ul> <li>The Cat In The Hat</li> <li>Hop On Pop</li> <li>Green Eggs And Ham</li> </ul>

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

def books_for_author(author): books = Book.objects.filter(authors__id=author.id) return {'books': books}

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

<ul> {% for book in books %} </li> {% endfor %} </ul>

در پایان، تگ inclusion با استفاده از فراخوانی متد inclusion_tag() در یک شیء Library ساخته و معرفی می شود.

مثال زیر، در صورتیکه template قبلی در یک فایل با نام book_snipper.html باشد، به صورت زیر معرفی می شود:

register.inclusion_tag('book_snippet.html')(books_for_author)

همچنین در پایتون 2.4 به بالا می توان به شکل زیر نیز کار کرد:

@register.inclusion_tag('book_snippet.html') def books_for_author(author): # ...

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

برای مثال، فرض کنید شما یک تگ inclusion می نویسید که همواره در یک context که حاوی متغیرهای home_link و home_title می باشد استفاده خواهد شد که به صفحه ی اصلی اشاره می کند. تابع پایتون آن به شکل زیر خواهد بود:

@register.inclusion_tag('link.html', takes_context=True) def jump_link(context): return { 'link': context['home_link'], 'title': context['home_title'], }

(توجه داشته باشید که اولین پارامتر باید context نامیده شود.)

template مورد نظر یعنی link.html باید حاوی کد زیر باشد:

Jump directly to <a href="{{ link }}">{{ title }}</a>.

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

{% jump_link %}

نوشتن Template Loader های سفارشی

template loader های داخلی جنگو (که در بخش "داخل بارگذاری Template" توضیح داده شد) معمولا تمام احتیاجات بارگذاری template های شما را پوشش می دهند، ولی در صورتی که نیاز به منطق ویژه ی بارگذاری باشد، نوشتن Template loader برای خودتان واقعا ساده می باشد. برای مثال، می توان template ها را از یک پایگاه داده، یا به طور مستقیم از یک Subversion repository و یا (همانطور که کمی بعد توضیح داده خواهد شد) از یک ZIP archive بارگذاری کرد.

انتظار می رود یک template loader (هر آیتم در تنظیم TEMPLATE_LOADERS می باشد) یک شیء قابل فراخوانی با رابط زیر باشد:

load_template_source(template_name, template_dirs=None)

آرگومان template_name نام template برای بارگذاری می باشد (همانطور که به loader.get_template() یا loader.select_template() ارسال شده است)، و template_dirs یک لیست اختیاری از دیکشنری ها برای جستجو به جای TEMPLATE_DIRS است.

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

در صورتیکه loader برای بارگذاری یک template ناتوان باشد، خطای django.template.TemplateDoesNotExist ایجاد خواهد شد.

همچنین هر تابع loader باید یک attribute تابع is_usable داشته باشد. این attribute یک Boolean می باشد که به موتور template این موضوع را که آیا این loader در نصب پایتون فعلی در دسترس است یا خیر را اطلاع می دهد. برای مثال egg های loader (که قابلیت بارگذاری template ها از egg های پایتون را دارند) در صورتیکه ماژول pkg_resources نصب نشده باشد is_usable را False قرار می دهند، زیرا pkg_resource جهت خواندن داده از egg ها ضروری می باشد.

یک مثال برای روشن کردن موضوع می تواند موثر باشد. در اینجا تابع template loader که می تواند template ها را از یک فایل ZIP بارگذاری کند وجود دارد. مثال زیر به جای TEMPLATE_DIRS به صورت یک مسیر جستجو، یک تنظیم سفارشی با نام TEMPLATE_ZIP_FILES را استفاده می کند، و انتظار دارد که هر آیتم در آن مسیر یک فایل ZIP حاوی template هایی باشد:

from django.conf import settings from django.template import TemplateDoesNotExist import zipfile def load_template_source(template_name, template_dirs=None): "Template loader that loads templates from a ZIP file." template_zipfiles = getattr(settings, "TEMPLATE_ZIP_FILES", []) # Try each ZIP file in TEMPLATE_ZIP_FILES. for fname in template_zipfiles: try: z = zipfile.ZipFile(fname) source = z.read(template_name) except (IOError, KeyError): continue z.close() # We found a template, so return the source. template_path = "%s:%s" % (fname, template_name) return (source, template_path) # If we reach here, the template couldn't be loaded raise TemplateDoesNotExist(template_name) # This loader is always usable (since zipfile is included with Python) load_template_source.is_usable = True

تنها قدم کج در صورتیکه بخواهیم از این loader برای اضافه کردن آن به تنظیم TEMPLATE_LOADERS استفاده کنیم. در صورتیکه کد فوق را در یک پکیج به نام mysite.zip_loader قرار دهیم، سپس mysite.zip_loader.load_template_source را به TEMPLATE_LOADERS اضافه کنیم.

پیکربندی Template System در حالت مستقل

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

برای حل این مشکل، نیاز است امکان پیکربندی دستی را استفاده کنید. به طور خلاصه، نیاز است قسمت های مناسب template systm را import کرده و سپس، قبل از فراخوانی هر تابع template ای، django.conf.settings.configure() را با هر تنظیمی که می خواهید تعیین کنید فراخوانی کنید.