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

آموزش جنگو Django Framework

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

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

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

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

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

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

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

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

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

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

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

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

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

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

مثال:

# urls.py

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

urlpatterns = patterns('',
    (r'^blog/$', views.page),
    (r'^blog/page(?P<num>\d )/$', views.page),
)

# views.py

def page(request, num='1'):
    # Output the appropriate page of blog entries, according to num.
    # ...

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

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

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

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

موارد خاص View ها

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

# urls.py

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

urlpatterns = patterns('',
    (r'^articles/(\d{4})/(\d{2})/(\d{2})/$', views.day_archive),
)

# views.py

import datetime

def day_archive(request, year, month, day):
    # The following statement raises a TypeError!
    date = datetime.date(year, month, day)

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

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

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

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

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

برای عنوان مثال، در یک درخواست به

محتوای مخفی

    برای مشاهده محتوای مخفی می بایست در انجمن ثبت نام کنید.
جنگو تلاش می کند تا آن را با myapp/ تطبیق دهد. در یک درخواست به

محتوای مخفی

    برای مشاهده محتوای مخفی می بایست در انجمن ثبت نام کنید.
جنگو تلاش می کند تا آن را با myapp/ تطبیق دهد.

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

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

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

# urls.py

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

urlpatterns = patterns('',
    # ...
    (r'^somepage/$', views.some_page),
    # ...
)

# views.py

from django.http import Http404, HttpResponseRedirect
from django.shortcuts import render_to_response

def some_page(request):
    if request.method == 'POST':
        do_something_for_post()
        return HttpResponseRedirect('/someurl/')
    elif request.method == 'GET':
        do_something_for_get()
        return render_to_response('page.html')
    else:
        raise Http404()

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

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

# views.py

from django.http import Http404, HttpResponseRedirect
from django.shortcuts import render_to_response

def method_splitter(request, GET=None, POST=None):
    if request.method == 'GET' and GET is not None:
        return GET(request)
    elif request.method == 'POST' and POST is not None:
        return POST(request)
    raise Http404

def some_page_get(request):
    assert request.method == 'GET'
    do_something_for_get()
    return render_to_response('page.html')

def some_page_post(request):
    assert request.method == 'POST'
    do_something_for_post()
    return HttpResponseRedirect('/someurl/')

# urls.py

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

urlpatterns = patterns('',
    # ...
    (r'^somepage/$', views.method_splitter, {'GET': views.some_page_get, 'POST': views.some_page_post}),
    # ...
)

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

  • یک view جدید با نام method_splitter() نوشته شده است، که نماینده ی view های دیگر می باشد. این تابع دو آرگومان کیورد یعنی GET و POST که باید توابع view باشند را جستجو می کند. در صورتیکه request.method مقدارش 'GET' باشد، view مورد نظر یعنی GET را فراخوانی و در صورتیکه مقدار آن 'POST' باشد POST را فراخوانی می کند. همچنین در صورتیکه request.method چیزی غیر از موارد ذکر شده باشد (مانند HEAD و غیره ...) و یا در صورتیکه GET یا POST در تابع عرضه نشده باشند، سپس یک Http404 ایجاد خواهد شد.
  • درون URLconf به /somepage/ در method_splitter() اشاره شده و آرگومان های اضافه به آن ارسال شده اند – توابع view به ترتیب برای استفاده از GET و POST.
  • در پایان، تابع some_page() به دو تابع view تقسیم شده است – some_page_get() و some_page_post(). این روش بسیار زیباتر از نشان دادن تمام منطق درون یک view می باشد.
  • توجه داشته باشید این توابع view از نظر فنی دیگر لازم نیست request.method را بررسی کنند، زیرا method_splitter() این کار را انجام می دهد. (در آن زمان که some_page_post() فراخوانی شده است، می توان مطئمن بود که مقدار request.method، 'post' می باشد.)

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

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

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

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

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

برای مثال:

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

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

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

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

Wrapping View Functions

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

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

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

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

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

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

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

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

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

from django.conf.urls.defaults import *
from mysite.views import requires_login, my_view1, my_view2, my_view3

urlpatterns = patterns('',
    (r'^view1/$', requires_login(my_view1)),
    (r'^view2/$', requires_login(my_view2)),
    (r'^view3/$', requires_login(my_view3)),
)

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

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

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

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

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^weblog/', include('mysite.blog.urls')),
    (r'^photos/', include('mysite.photos.urls')),
    (r'^about/$', 'mysite.views.about'),
)

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

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

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^(\d\d\d\d)/$', 'mysite.blog.views.year_detail'),
    (r'^(\d\d\d\d)/(\d\d)/$', 'mysite.blog.views.month_detail'),
)

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

  • /weblog/2007/: در اولین URLconf، الگوی r'weblog/' تطبیق داده می شود. زیرا آن یک include() است، جنگو تمام متن مطابق را در می آورد، که در این مورد 'weblog/' می باشد. پاقیمانده ی بخش URL 2007/ می باشد، که اولین خط در URlconf مورد نظر یعنی mysite.blog.urls تطبیق داده می شود.
  • /weblog//2007/ (با دو علامت /) در اولین URLconf، الگوی r'weblog/' تطبیق داده می شود. زیرا یک include() می باشد، جنگو تمام متن مطابق را در می آورد، که در این مورد 'weblog/' می باشد. باقیمانده ی بخش URL /2007/ می باشد (با یک علامت / اولیه)، که هیچ خطی از URLconf مورد نظر یعنی mysite.blog.urls با آن تطبیق داده نمی شود.
  • /about/: این URL در URLconf اول و view مورد نظر یعنی mystie.views.about تطبیق داده می شود، نشان می دهد که می توان الگوهای include() را با الگوهای non-include() ترکیب کرد.

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

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

# root urls.py

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^(?P<username>\w )/blog/', include('foo.urls.blog')),
)

# foo/urls/blog.py

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^$', 'foo.views.blog_index'),
    (r'^archive/$', 'foo.views.blog_archive'),
)

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

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

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

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

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

دسته اول:

# urls.py

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^blog/', include('inner'), {'blogid': 3}),
)

# inner.py

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^archive/$', 'mysite.views.archive'),
    (r'^about/$', 'mysite.views.about'),
    (r'^rss/$', 'mysite.views.rss'),
)

دسته دوم:

# urls.py

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^blog/', include('inner')),
)

# inner.py

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^archive/$', 'mysite.views.archive', {'blogid': 3}),
    (r'^about/$', 'mysite.views.about', {'blogid': 3}),
    (r'^rss/$', 'mysite.views.rss', {'blogid': 3}),
)

 

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


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

Template های پیشرفته در جنگو

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

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

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

مرور زبان Template

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

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

{% if is_logged_in %}
    Thanks for logging in!
{% else %}
    Please log in.
{% endif %}

متغیر یک علامت داخل یک template می باشد که یک مقدار را به عنوان خروجی بر می گرداند.
تگ های متغیر با {{ و }} پوشیده شده اند.

My first name is {{ first_name }}. My last name is {{ last_name }}.

context یک نام -> مقدار مرتبط شده (همانند دیکشنری پایتون) است که به یک template ارسال می شود.
template یک context را با جابه جا کردن متغیر با مقادیر context و اجرای تمام تگ ها render می کند.
برای جزئیات بیشتر درباره ی اصول اولیه 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)

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

  • ابتدا، یک تابع به نام custom_proc تعریف شده است. این تابع یک پردازشگر context می باشد که یک شیء HttpRequest دریافت کرده و یک دیکشنری از متغیرها برای استفاده در template context بر می گرداند. این تمام کاری است که این تابع انجام می دهد.
  • دو تابع view دیگر برای استفاده از RequestContext به جای Context تغییر کرده اند. دو تفاوت در نحوه ی ساخته شدن context وجود دارد. اول، RequestContext نیاز به یک آرگومان اول برای شیء HttpRequest بودن می باشد که به داخل تابع view در اولین مکان ارسال شده است (request). دوم، RequestContext یک آرگومان اختیاری به نام processors دریافت می کند، که یک لیست یا تاپل از توابع پردازشگر context مورد استفاده می باشد که در کد فوق پردازشگری که در بالا تعریف کردیم را به آن ارسال کرده ایم.
  • هر view دیگر لازم نیست شامل app، user یا ip_addressدر ساخت context باشد، زیرا زیرا آن ها با تابع custom_proc تهیه شده اند.
  • هر view هنوز دارای انعطاف پذیری برای معرفی هر متغیر template در صورت نیاز می باشد. در این مثال، متغیر template مورد نظر یعنی message به صورت متفاوت در هر view در نظر گرفته شده است.


در آموزش 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 حاوی این متغیرها خواهد بود:

  • user: یک نمونه ی django.auth.models.User می باشد که کاربر فعلی وارد شده (یا در صورتی کاربری وارد نشده باشد یک کاربر بی نام) را نمایش می دهد.
  • messages: یک لیست از پیام ها (به صورت رشته) برای کاربر فعلی وارد شده، در پشت صحنه، این متغیر برای هر درخواست request.user.get_and_delete_messages() را فراخوانی می کند. این متد پیام های کاربر را جمع کرده و آن ها را از پایگاه داده حذف می کند.
  • perms: یک نمونه از django.core.context_processors.PermWrapper که حق دسترسی هایی که کاربر فعلی وارد شده دارا می باشد را نشان می دهد.

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

django.core.context_processors.debug

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

  • debug: مقدار تنظیم DEBUG (True or False). شما می توانید از این متغیر برای تست اینکه آیا در حالت debug می باشید یا خیر استفاده کنید.
  • sql_queries: لیست ('sql': ..., 'time': ...) دیکشنری هایی که هر کوئری SQL نشان می دهد که دارای اتفاقاتی که تاکنون در طول درخواست رخ داده اند و چه مدت طول کشیده اند می باشد. لیست به ترتیب کوتئری هایی که صادر شده اند می باشد.

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

  • تنظیم DEBUG، True باشد.
  • درخواست از یک آدرس IP در تنظیم INTERNAL_IPS آمده باشد.

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

django.core.context_processors.i18n

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

  • LANGUAGE: مقدار تنظیم LANGUAGE.
  • LANUAGE_CODE: در صورت وجود request.LANGUAGE_CODE؛ در غیر اینصورت، مقدار تنظیم LANGUAGE_CODE.

django.core.context_processors.request

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

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

{{ request.REMOTE_ADDR }}

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

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

  • هر پردازشگر context را مسئول کوچکترین زیر مجموعه ی قابلیت های ممکن کنید. استفاده از پردازشگرهای چندگانه ساده می باشد، بنابراین ممکن است عمکرد را به قسمت های منطقی برای استفاده ی دوباره در آینده تقسیم کنید.
  • به خاطر داشته باشید که هر پردازشگر context در TEMPLATE_CONTEXT_PROCESSORS در هر template ساخته شده با فایل تنظیمات قابل دسترس خواهد بود، بنابراین سعی کنید نام های متغیری انتخاب کنید که با نام متغیرهایی که به طور مستقل ممکن است استفاده کنید تداخل نداشته باشد. از آنجایی که نام های متغیر به حروف کوچک و بزرگ حساس می باشند، ایده ی بدی نیست که تمام متغیرهایی که یک پردازشگر تولید می کند با تماما با حروف بزرگ باشند.
  • تا زمانیکه آن ها در مسیر پایتون می باشند مشکلی نیست که filesystem باشند، بنابراین می توان به آن ها از تنظیم TEMPLATE_CONTEXT_PROCESSORS اشاره کرد. با در نظر گرفتن این، مناسب آن است که آن ها را در یک فایل با نام context_processors.py داخل app یا پروژه ذخیره کرد.

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) نامیده می شوند. (برای اطلاعات بیشتر در مورد امنیت، به آموزش امنیت مراجعه کنید.)

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

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

به طور پیشفرض، هر 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 می باشد:

  • django.template.loader.get_template (template_name): get_template، template کامپایل شده (یک شیء Template) برای template با نام داده شده را بر می گرداند. در صورتیکه template مورد نظر وجود نداشته باشد، خطای TemplateDoesNotExist رخ خواهد داد.
  • django.template.loader.select_template (template_name_list): select_template درست مثل get_template می باشد، با این تفاوت که select_template لیستی از نام template ها را دریافت می کند. از لیست موجود، اولین template موجود را بر می گرداند. در صورتیکه هیچکدام از template ها وجود نداشته باشند، یک خطای TemplateDoesNotexist رخ خواهد داد.

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

  • بعضی از loader ها به طور پیشفرض غیر فعال می باشند، ولی می توان با ویرایش تنظیم TEMPLATE_LOADER آن ها را فعال کرد. TEMPLATE_LOADERS باید یک تاپل از رشته ها باشد، به طوری که هر رشته یک template loader را نمایش می دهد. این template loader ها به همراه جنگو ارائه شده اند:
  • django.template.loaders.filesystem.load_template_source: این loader، template ها را از filesystem به همراه TEMPLATE_DIRS بارگذاری می کند.
  • django.template.loaders.app_directories.load_template_source: این loader، template ها را از برنامه های جنگو روی filesystem بارگذاری می کند. برای هر برنامه در INSTALLED_APPS، loader یک دایرکتوری زیرمجموعه ی templates را جستجو می کند. در صورتیکه دایرکتوری وجود داشته باشد، جنگو در آن جا به دنبال template ها می گردد.
  • این بدین معناست که می توان template ها را با برنامه های فردی خودتان ذخیره کنید، برای مثال، اگر INSTALLED_APPS حاوی ('myproject.polls'، 'myproject.music') باشد، در اینصورت get_template('foo.html') به شکل زیر template ها را بدین ترتیب جستجو می کند:
  • /path/to/myproject/polls/templates/foo.html
  • /path/to/myproject/music/templates/foo.html
  • توجه داشته باشید که loader یک بهینه سازی را اجرا می کند: loader یک لیست از پکیج های INSTALLED_APPS را که دارای دایرکتوری زیرمجموعه ی templates می باشد را cache می کند.
  • این loader به صورت پیشفرض فعال می باشد.
  • django.template.loaders.eggs.load_template_source: این loader درست مثل app_directories می باشد، با این تفاوت که template ها را به جای filesystem از egg های پایتون بارگذاری می کند. این loader به طور پیشفرض غیر فعال می باشد؛ در صورتیکه از egg ها برای توزیع برنامه ی خود استفاده می کنید نیاز خواهید داشت که آن را فعال کنید. (egg های پایتون روشی برای فشرده سازی کد پایتون داخل یک فایل تنها می باشد.)

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

گسترش Template System

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

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

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

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

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

  • مرحله اول، تصمیم گرفتن اینکه کدام برنامه ی جنگو باید به کتابخانه ی template جا دهد. در صورتیکه از طریق دستور manage.py startapp، یک app ساخته اید، می توانید کتابخانه ی خود را در آنجا قرار دهید، یا می توانید منحصرا یک app دیگر برای کتابخانه ی template بسازید. ما ساختن یک app جدا برای کتابخانه ی template را پیشنهاد می کنیم، زیرا فیلترهای شما می توانند در پروژه های آینده نیز برای شما مفید واقع شوند.
  • هر روشی را که انتخاب می کنید، اطمینان حاصل کنید که app مورد نظر را به تنظیم INSTALLED_APPS اضافه کرده اید. به طور کوتاه این موضوع را توضیح خواهیم داد.
  • مرحله دوم، ساختن یک دایرکتوری به نام templatetags در پکیج مناسب برنامه ی جنگو می باشد. این دایرکتوری باید از نظر مسیر هم سطح با models.py، views.py و غیره باشد. برای مثال:
books/
    __init__.py
    models.py
    templatetags/
    views.py

دو فایل خالی در دایرکتوری templatetags بسازید: یک فایل __init__.py (برای اینکه به پایتون نشان داده شود که این یک پکیج حاوی کد پایتون می باشد) و یک فایل که حاوی تعریف تگ ها/فیلترهای سفارشی شما خواهد بود. نام فایل دوم آن چیزی خواهد بود که شما برای بارگذاری تگ ها از آن استفاده خواهید کرد. برای مثال، در صورتیکه تگ ها یا فیلترهای سفارشی شما درون فایلی به نام poll_extras.py خواهد بود، شما کدی نظیر کد زیر را درون template خواهید داشت:

{% load poll_extras %}

تگ {% load %} تنظیم INSTALLED_APPS را مورد بررسی قرار داده و تنها اجازه به بارگذاری کردن کتابخانه های template داخل برنامه های نصب شده جنگو می دهد. این یک ویژگی امنیتی می باشد؛ این ویژگی اجازه می دهد تا مکانی برای بسیاری از کتابخانه های template در یک رایانه ی تنها را بدون فعال کردن دسترسی به تمام آنها برای هر نصب جنگو ایجاد کنید.

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

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

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

from django import template

register = template.Library()

نکته

برای یک انتخاب خوب از مثال ها، کد منبع فیلترها و تگ های پیشفرض جنگو را بخوانید. آن ها به ترتیب در django/template/defaultfilters.py و django/template/defaulttags.py می باشند. همچنین برخی برنامه های موجود در django.contrib حاوی کتابخانه های template می باشد.

هنگامی که متغیر 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 های زیر نشان داده شده است:

  • node متن: "Hello, "
  • node متغیر: person.name
  • node متن: ".\n\n"
  • ifEqual node: name.birthday and today

هنگامی که در یک 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/ را مشاهده کنید) باشد. در این مورد تصور می کنیم تگ باید به شکل زیر استفاده شود:

نکته

بله، این تگ template یک تگ اضافه و زائد می باشد – تگ پیشفرض {% now %} با روشی ساده تر همین کار را انجام می دهد. تگ template فوق تنها با هدف آشنایی با ساختن تگ های سفارشی ارائه شده است.

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 دو آرگومان دریافت می کند، parser و token. parser شیء template parser می باشد که در این مثال از آن استفاده نشده است. token علامتی است که در حال حاضر توسط parser تجزیه شده است.
  • token.contents یک رشته از محتویات خام تگ می باشد. در مثال فوق 'current_time "%Y‑%m‑%d %I:%M %p"' می باشد.
  • متد token.split_contents() بر حسب فاصله، تا زمانی که متن توسط کتیشن پوشیده شده است آن را جدا می کند. از token.contents.split() استفاده نکنید () در دست ترجمه ...
  • تابع فوق موظف است خطای django.template.TemplateSyntaxError با یک پیام مفید برای هر خطا ایجاد کند.
  • نام تگ را به طور مستقیم در پیام های خطا استفاده نکنید، زیرا این کار باعث می شود نام تگ به تابع شما وصل شود. token.split_contents()[0] همواره نام تگ شما خواهد بود – حتی هنگامی که تگ دارای هیچ آرگومانی نیست.
  • تابع فوق یک CurrentTimeNode (که کمی بعد آن را ایجاد خواهیم کرد) حاوی هرچیزی که node برای شناختن این تگ نیاز دارد بر می گرداند. در این مورد، تنها آرگومان "%Y‑%m‑%d %I:%M %p" ارسال می شود. کتیشن عقبی و جلویی تگ template توسط format_string[1:-1] حذف می شود.
  • توابع کامپایل تگ template باید یک کلاس فرزند Node بر گردانند؛ در غیر اینصورت مقدار برگشتی یک خطا است.

نوشتن 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() دو آرگومان دریافت می کند:

  • نام تگ template (رشته).
  • تابع کامپایل.

همانند معرفی فیلتر، امکان این وجود دارد که از 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 استفاده کرد:

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

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

تگ های 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 در حالت مستقل

نکته

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

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

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

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


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

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

شیء های مرتبط

مدل های book در آموزش مدل جنگو را بخاطر بیاورید:

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

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

>>> from mysite.books.models import Book
>>> b = Book.objects.get(id=50)
>>> b.title
u'The Django Book'

ولی چیزی که قبلا ذکر نشده است شیء های مرتبط می باشد – فیلدهای بیان شده به صورت یک ForeignKey یا ManyToManyField – که کمی متفاوت عمل می کنند.

دسترسی به مقادیر Foreign Key

هنگامی که به یک فیلد ForeignKey دسترسی پیدا می کنید، شما شیء مدل مرتبطی دریافت خواهید کرد. برای مثال:

>>> b = Book.objects.get(id=50)
>>> b.publisher
<Publisher: Apress Publishing>
>>> b.publisher.website
u'http://www.apress.com/'

با فیلدهای ForeignKey، با روش دیگری مواجه خواهیم بود، ولی این کمی متفاوت است، به دلیل اینکه ارتباط نامتقارن می باشد. جهت بدست آوردن لیست کتاب ها برای ناشر داده شده، از publisher.book_set.all() استفاده کنید:

>>> p = Publisher.objects.get(name='Apress Publishing')
>>> p.book_set.all()
[<Book: The Django Book>, <Book: Dive Into Python>, ...]

در پشت صحنه، book_set تنها یک QuerySet می باشد (همانطور که در آموزش مدل جنگو توضیح داده شد)، و می تواند همانند QuerySet های دیگر فیلتر یا برش داده شود. برای مثال:

>>> p = Publisher.objects.get(name='Apress Publishing')
>>> p.book_set.filter(name__icontains='django')
[<Book: The Django Book>, <Book: Pro Django>]

نام attribute فوق یعنی book_set با استفاده از اضافه کردن نام مدل به صورت حروف کوچک به _set تولید شده است.

دسترسی به مقادیر Many-to-Many

مقادیر Many-to-Many همانند مقادیر foreign-key کار می کنند، با این تفاوت که ما با مقادیر QuerySet به جای نمونه های مدل سر کار داریم. برای مثال، در اینجا نحوه ی مشاهده ی نویسندگان برای یک کتاب وجود دارد:

>>> b = Book.objects.get(id=50)
>>> b.authors.all()
[<Author: Adrian Holovaty>, <Author: Jacob Kaplan-Moss>]
>>> b.authors.filter(first_name='Adrian')
[<Author: Adrian Holovaty>]
>>> b.authors.filter(first_name='Adam')
[]

در کد فوق، همانند فیلدهای foreignKey، book_set با اضافه کردن نام مدل به صورت حروف کوچک به _set تولید شده است.

>>> a = Author.objects.get(first_name='Adrian', last_name='Holovaty')
>>> a.book_set.all()
[<Book: The Django Book>, <Book: Adrian's Other Book>]

Here, as with ForeignKey fields, the attribute name book_set is generated by appending the lower case model name to _set.

ایجاد تغییرات برای یک طرح پایگاه داده

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

هنگام سر و کار داشتن با تغییرات طرح، به خاطر داشتن چند نکته درباره نحوه ی کار لایه پایگاه داده اهمیت دارد:

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

ایجاد تغییرات schema موضوعی از تغییر قسمت های مختلف می باشد – کد پایتون و خود پایگاه داده.

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

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

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

راهکار برای این مشکل که کدام عمل اول انجام شود، استفاده از یک توسعه ی محیطی به جای ایجاد تغییرات در production سرور است. (شما در حال استفاده از یک testing/development محیطی می باشید، درست است؟) در اینجا مراحل دقیق وجود دارد.

ابتدا، این مراحل در development environment استفاده کنید (نه بر روی production server)

  • فیلد را به مدل خود اضافه کنید.
  • دستور manage.py sqlall [yourapp] را جهت مشاهده ی عبارت جدید CREATE TABLE برا مدل اجرا کنید. تعریف ستون برای فیلد جدید را توجه کنید.
  • interactive shell پایگاه داده ی خود را اجرا کرده (psql یا mysql و یا می توانید از manage.py dbshell استفاده کنید). یک عبارت ALTER TABLE را که ستون جدید شما را اضافه می کند اجرا کنید.
  • interactive shell پایتون را از طریق manage.py shell اجرا نموده و مطمئن شوید که فیلد جدید به درستی اضافه شده است از طریق import کردن مدل و واکشی از جدول (مانند MyModel.objects.all()[:5]). در صورتیکه پایگاده داده را به درستی به روز رسانی کرده باشید، عبارت باید بدون خطا کار کند.

سپس بر روی production server این مراحل را انجام دهید:

  • interactive shell پایگاه داده را اجرا کنید.
  • عبارت ALTER TABLE ای که در مرحله ی سوم development environment استفاده شد را اجرا کنید.
  • فیلد را به مدل خود اضافه کنید. در صورتیکه در دست ترجمه ...
  • وب سرور را برای اعمال تغییرات دوباره راه اندازی کنید.

برای مثال، تصور کنید یک فیلد با نام num_pages را به مدل Book که در آموزش مدل جنگو توضیح داده شد اضافه کرده ایم. ابتدا، درون development environment مدل خود را مانند زیر تغییر می دهیم:

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

    def __unicode__(self):
        return self.title

سپس دستور manage.py sqlall books را برای مشاهده ی عبارت CREATE TABLE اجرا می کنیم. بسته به پایگاه داده ی شما، خروجی چیزی شبیه به کد زیر است:

CREATE TABLE "books_book" (
    "id" serial NOT NULL PRIMARY KEY,
    "title" varchar(100) NOT NULL,
    "publisher_id" integer NOT NULL REFERENCES "books_publisher" ("id"),
    "publication_date" date NOT NULL,
    "num_pages" integer NULL
);

ستون جدید بدین شکل نشان داده شده است:

"num_pages" integer NULL

در قدم بعدی، interactive shell پایگاه داده را برای توسعه ی پایگاه داده با تایپ کردن psql (برایPostgreSQL) اجرا کرده، و عبارت زیر را درون آن اجرا می کنیم:

ALTER TABLE books_book ADD COLUMN num_pages integer;

اضافه کردن ستون های NOT NULL

در اینجا زیرکی قابل ذکری وجود دارد. هنگامی که فیلد num_pages را به مدل خود اضافه کردیم، امکان های blank=True و null=True را نیز استفاده کردیم. این کار به این دلیل انجام شده است که یک ستون پایگاه داده هنگام ساختن آن در با اول حاوی مقادیر NULL باشد.

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

BEGIN;
ALTER TABLE books_book ADD COLUMN num_pages integer;
UPDATE books_book SET num_pages=0;
ALTER TABLE books_book ALTER COLUMN num_pages SET NOT NULL;
COMMIT;

در صورتیکه از این روش استفاده می کنید، بخاطر داشته باشید که باید blank=True و null=True را در مدل خود حذف کنید.

بعد از عبارت ALTER TABLE، با اجرای shell پایتون و اجرای کد زیر در آن مطمئن می شویم که تغییر به درستی کار می کند:

>>> from mysite.books.models import Book
>>> Book.objects.all()[:5]

در صورتیکه کد فوق موجب بروز هیچ خطایی نشود، بر روی production server رفته و عبارت ALTER TABLE را در پایگاه داده ی production اجرا می کنیم. سپس، مدل موجود در production environment را به روز رسانی کرده و وب سرور را دوباره راه اندازی می کنیم.

حذف فیلدها

حذف یک فیلد از یک مدل بسیار ساده تر از اضافه کردن آن می باشد. برای حذف یک فیلد، تنها کافیست مراحل زیر را دنبال کنید:

  • فیلد مورد نظر را از مدل خود حذف کرده و وب سرور را دوباره راه اندازی کنید.
  • ستون مورد نظر را از پایگاه داده ی خود، با استفاده از دستور زیر حذف کنید:
ALTER TABLE books_book DROP COLUMN num_pages;

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

حذف فیلدهای Many-to-Many

به دلیل آنکه فیلدهای many-to-many متفاوت از فیلدهای عادی می باشند، روند حذف آن ها نیز متفاوت می باشد:

  • ManyToManyField را از مدل خود حذف کرده و وب سرور را دوباره راه اندازی کنید.
  • جدول many-to-many را از پایگاه داده ی خود حذف با استفاده از دستور زیر حذف کنید:
DROP TABLE books_book_authors;

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

حذف مدل ها

حذف کردن یک مدل کاملا ساده تر از حذف یک فیلد می باشد. برای حذف یک مدل، تنها کافیست مراحل زیر را دنبال کنید:

  • مدل مورد نظر را از فایل models.py خود حذف کرده و وب سرور را دوباره راه اندازی کنید.
  • جدول مورد نظر را از پایگاه داده ی خود به استفاده از کد زیر حذف کنید:
DROP TABLE books_book;

توجه داشته باشد که، ممکن است نیاز باشد تا هر جدول وابسته به این جدول را درون پایگاه داده حذف کنید – مانند، هر جدولی که به books_book دارای کلید خارجی باشد.

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

Managers

در عبارت Book.objects.all()، objects یک attribute ویژه از میان کوئری هایی که به پایگاه داده ارسال می شود می باشد. در آموزش مدل جنگو این attribute، به طور خلاصه به صورت manager مدل توضیح داده شد. حالا زمان آن است که کمی عمیق تر به هویت manager ها و اینکه چگونه می توان از آن ها استفاده کرد بپردازیم.

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

دو دلیل که ممکن است شما بخواهید یک manager سفارشی ایجاد کنید وجود دارد: برای اضافه کردن متدهای manager اضافه، و یا برای تغییر QuerySet اولیه ای که manager بر می گرداند.

اضافه کردن متدهای اضافه Manager

اضافه کردن متدهای اضافه ی manager روشی مقدم برای اضافه کردن عملکرد در سطح جدول برای مدل های شما می باشد. (برای عملکرد در سطح ردیف – مانند توابعی که بر روی یک نمونه ی تکی از یک شیء مدل عمل می کنند – از متدهای مدل استفاده کنید، که پیش تر در این فصل توضیح داده شده است.)

برای مثال، اجازه دهید برای مدل Book یک manager متد title_count() که که یک keyword دریافت کرده و تعداد کتاب هایی که عنوان آن ها حاوی keyword می باشد را بر می گرداند بنویسیم. (این مثال کمی ساختگی می باشد، ولی نحوه کار manager ها را نشان می دهد.)

# models.py

from django.db import models

# ... Author and Publisher models here ...

class BookManager(models.Manager):
    def title_count(self, keyword):
        return self.filter(title__icontains=keyword).count()

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

    def __unicode__(self):
        return self.title

با وجود manager فوق، می توان:

>>> Book.objects.title_count('django')
4
>>> Book.objects.title_count('python')
18

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

  • یک کلاس BookManager ایجاد شد که از کلاس django.db.models.Manager ارث بری کرده است. این کلاس دارای تنها یک متد title_count() می باشد که محاسبات را انجام می دهد. توجه داشته باشید که متد از self.filter() استفاده کرده است که self به خود manager مراجعه می کند.
  • BookManager() به objects در مدل اختصاص داده شده است. این دارای تاثیر از جابه جایی پیشفرض manager برای مدل می باشد که objects نامیده می شود و در صورتیکه یک manager سفارشی تعیین نشده باشد به طور خودکار ساخته می شود. ما آن را به جای هر چیز دیگری، objects می نامیم. در دست ترجمه ...

چرا می خواهیم یک متد مانند title_count() اضافه کنیم؟ برای ایجاد کدی عمومی برای اجرای کوئری ها که نیازی به کد تکراری نداشته باشیم.

تغییر QuerySet های اولیه manager

QuerySet پایه ی manager تمام شیء های سیستم را بر می گرداند. برای مثال، Book.objects.all() تمام کتاب های موجود در پایگاه داده ی book را بر می گرداند.

می توان از طریق بازنویسی متد Manager.get_query_set()، QuerySet پایه ی manager را بازنویسی کرد. get_query_set() باید یک QuerySet با ویژگی هایی که نیاز است را بر گرداند.

برای مثال، مدل زیر دارای دو manager می باشد – یکی تمام شیء ها را بر می گرداند و دیگری تنها کتاب های نوشته شده توسط Roald Dahl را بر می گرداند.

from django.db import models

# First, define the Manager subclass.
class DahlBookManager(models.Manager):
    def get_query_set(self):
        return super(DahlBookManager, self).get_query_set().filter(author='Roald Dahl')

# Then hook it into the Book model explicitly.
class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.CharField(max_length=50)
    # ...

    objects = models.Manager() # The default manager.
    dahl_objects = DahlBookManager() # The Dahl-specific manager.

با مدل نمونه فوق، Book.objects.all() تمام کتاب های موجود در پایگاه داده را بر خواهد گرداند، ولی Book.dahl_objects.all() تنها کتاب هایی که توسط Roald Dahl نوشته شده اند را بر خواهد گرداند. توجه داشته باشید که به طور صریح objects را در نمونه Manager عادی قرار داده ایم، زیرا در غیر اینصورت، تنها manager قابل دسترس dahl_objects خواهد بود.

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

Book.dahl_objects.all()
Book.dahl_objects.filter(title='Matilda')
Book.dahl_objects.count()

مثال فوق همچنین به تکنیک های جالب دیگری نیز اشاره می کند: استفاده از manager های چندگانه در یک مدل. می توان به صورت بسیاری از نمونه های Manager() به یک مدل همانطور که می خواهید attach کنید.

برای مثال:

class MaleManager(models.Manager):
    def get_query_set(self):
        return super(MaleManager, self).get_query_set().filter(sex='M')

class FemaleManager(models.Manager):
    def get_query_set(self):
        return super(FemaleManager, self).get_query_set().filter(sex='F')

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    sex = models.CharField(max_length=1, choices=(('M', 'Male'), ('F', 'Female')))
    people = models.Manager()
    men = MaleManager()
    women = FemaleManager()

مثال فوق به شما اجازه می دهد Person.men.all()، Person.women.all() و Person.people.all() را درخواست کنید. در دست ترجمه ...

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

متدهای Model

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

این یک تکنیک ارزشمند برای نگهداشتن business logic در یک مکان می باشد – مدل.

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

from django.contrib.localflavor.us.models import USStateField
from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    birth_date = models.DateField()
    address = models.CharField(max_length=100)
    city = models.CharField(max_length=50)
    state = USStateField() # Yes, this is U.S.-centric...

    def baby_boomer_status(self):
        "Returns the person's baby-boomer status."
        import datetime
        if datetime.date(1945, 8, 1) <= self.birth_date <= datetime.date(1964, 12, 31):
            return "Baby boomer"
        if self.birth_date < datetime.date(1945, 8, 1):
            return "Pre-boomer"
        return "Post-boomer"

    def is_midwestern(self):
        "Returns True if this person is from the Midwest."
        return self.state in ('IL', 'WI', 'MI', 'IN', 'OH', 'IA', 'MO')

    def _get_full_name(self):
        "Returns the person's full name."
        return u'%s %s' % (self.first_name, self.last_name)
    full_name = property(_get_full_name)

آخرین متد در مثال فوق یک "property" می باشد. برای اطلاعات بیشتر در مورد property ها به

محتوای مخفی

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

کاربرد مثال فوق:

>>> p = Person.objects.get(first_name='Barack', last_name='Obama')
>>> p.birth_date
datetime.date(1961, 8, 4)
>>> p.baby_boomer_status()
'Baby boomer'
>>> p.is_midwestern()
True
>>> p.full_name  # Note this isn't a method -- it's treated as an attribute
u'Barack Obama'

اجرای کوئری های خام SQL

گاهی اوقات می خواهید کوئری هایی را به صورت مستقیم در پایگاه داده ی خود اجرا کنید. به آسانی می توان این کار را از طریق دسترسی به شیء django.db.connection انجام داد که connection فعلی پایگاده داده را نشان می دهد. برای استفاده از آن، connection.cursor() را جهت بدست آوردن یک شیء cursor فراخوانی کنید، سپس جهت اجرای SQL و cursor.fetchone() یا cursor.fetchall برای برگرداندن نتیجه ی ردیف ها cursor.execute(sql, [params]) را فراخوانی کنید:

>>> from django.db import connection
>>> cursor = connection.cursor()
>>> cursor.execute("""
...    SELECT DISTINCT first_name
...    FROM people_person
...    WHERE last_name = %s""", ['Lennon'])
>>> row = cursor.fetchone()
>>> print row
['John']

connection و cursor غالبا "DB-API" استاندارد پایتون را اجرا می کنند که می توانید برا اطلاعات بیشتر در مورد آن به

محتوای مخفی

    برای مشاهده محتوای مخفی می بایست در انجمن ثبت نام کنید.
مراجعه کنید. در صورتیکه با DB-API پایتون آشنایی ندارید، توجه داشته باشید که عبارت SQL در cursor.execute() از حفره های "%s" به جای اضافه کردن پارامتر های به صورت مستقیم درون SQL استفاده می کند. در صورتیکه از این تکنیک استفاده می کنید، کتابخانه ی زیرین پایگاه داده به طور خودکار کتیشن هایی در صورت نیاز اضافه خواهد کرد. در دست ترجمه ...

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

from django.db import connection, models

class PersonManager(models.Manager):
    def first_names(self, last_name):
        cursor = connection.cursor()
        cursor.execute("""
            SELECT DISTINCT first_name
            FROM people_person
            WHERE last_name = %s""", [last_name])
        return [row[0] for row in cursor.fetchone()]

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    objects = PersonManager()

کاربرد مثال فوق:

>>> Person.objects.first_names('Lennon')
['John', 'Cynthia']

 

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


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

View های Generic در جنگو

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

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

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

جنگو view های generic را برای انجام کارهای زیر ارائه کرده است:

  • انجام وظایف ساده و مشترک: تغییر مسیر به یک صفحه ی متفاوت، یا ارائه ی یک template داده شده.
  • نمایش صفحات "list" و "detail" برای یک شیء تک. view های event_list و entry_list از آموزش view و urlconf پیشرفته مثال های از لیست view ها می باشند. یک صفحه ی تک event یک مثال از آنچه را که ما "detail" view می نامیم می باشد.
  • ارائه دادن شیء های بر اساس تاریخ در صفحات بایگانی سال/ماه/روز، همراه با جزئیات و صفحات "latest". وبلاگ سال، ماه و روز (http://www.djangoproject.com/weblog/) بایگانی با این ها ساخته شده اند، به صورت نوعی بایگانی روزنامه خواهد بود.

روی هم رفته، این view ها، رابط های ساده ای برای انجام رایج ترین وظایفی که توسعه دهندگان با آن روبرو هستند تهیه شده اند.

استفاده از View های Generic

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

برای مثال، در اینجا یک URLconf ساده وجود دارد که شما می توانید برای ارائه ی یک صفحه ی استاتیک "about" از آن استفاده کنید:

from django.conf.urls.defaults import *
from django.views.generic.simple import direct_to_template

urlpatterns = patterns('',
    (r'^about/$', direct_to_template, {
        'template': 'about.html'
    })
)

هر چند کد فوق ممکن است در نگاه اول کمی جادویی به نظر برسد – نگاه کنید، یک view بدون هیچ کدی! – کد فوق دقیقا با مثال آموزش view و urlconf پیشرفته یکی می باشد: view مورد نظر در آن مثال یعنی direct_to_template به سادگی اطلاعات را از پارامترهای اضافه ی دیکشنری دریافت کرده و زمان render شدن view از آن اطلاعات استفاده می کند.

به این دلیل که view های generic مانند توابع view دیگر یک تابع view عادی می باشد، می توان آن ها را، درون view های خودمان دوباره استفاده کنیم. به عنوان مثال، اجازه دهید مثال "about" را برای مرتبط ساختن URL ها از حالت /about/<whatever>/ برای about/<whatever>.html به طور ثابت render شده گسترش دهیم. ما این کار را با اولین اصلاح URLconf برای اشاره به تابع view انجام خواهیم داد:

from django.conf.urls.defaults import *
from django.views.generic.simple import direct_to_template
from mysite.books.views import about_pages

urlpatterns = patterns('',
    (r'^about/$', direct_to_template, {
        'template': 'about.html'
    }),
    (r'^about/(\w )/$', about_pages),
)

در قدم بعدی، view مورد نظر یعنی about_pages را خواهیم نوشت:

from django.http import Http404
from django.template import TemplateDoesNotExist
from django.views.generic.simple import direct_to_template

def about_pages(request, page):
    try:
        return direct_to_template(request, template="about/%s.html" % page)
    except TemplateDoesNotExist:
        raise Http404()

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

آیا از نظر امنیتی یک آسیب پذیری در اینجا وجود دارد؟

خوانندگان تیزبین ممکن است متوجه حفره ی امنیتی شده باشند: ما با استفاده محتویاتی که در میان عبارات دیگر جا داده شده اند استفاده کرده ایم (template="about/%s.html"). در نگاه اول، این شبیه به یک آسیب پذیری کلاسیک directory traversal می باشد (که در آموزش امنیت به تفصیل درباره ی آن بحث شده است). ولی آیا واقعا اینطور است؟

نه دقیقا. بله، یک مقدار با هدف مخرب ساخته شده از page می تواند موجب directory traversal شود، درست است که page از URL درخواست گرفته است، ولی نه هر مقداری که قبول شده خواهد بود. نکته ی کلیدی در URLconf این است که: در مثال فوق، از regular expression \w برای تطبیق بخش page از URL استفاده شده است، و \w تنها حروف الفبا و اعداد را قبول می کند. در نتیجه، هر حروف مخربی (مانند نقطه ها و علامت های \) قبل از اینکه به خود view برسند طرد خواهند شد.

View های Generic شیء ها

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

اجازه دهید نگاهی به یکی از این view های generic با نام "object list" بیاندازیم. ما از شیء Publisher که در آموزش مدل جنگو از آن استفاده شده است، در اینجا استفاده کرده ایم:

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

جهت ساختن یک صفحه ی لیست از تمام ناشران، ما از یک URLconf مانند زیر استفاده می کنیم:

from django.conf.urls.defaults import *
from django.views.generic import list_detail
from mysite.books.models import Publisher

publisher_info = {
    'queryset': Publisher.objects.all(),
}

urlpatterns = patterns('',
    (r'^publishers/$', list_detail.object_list, publisher_info)
)

تمام چیزی که برای نوشتن نیاز است کد پایتون می باشد. ولی هنوز نیاز به نوشتن یک template داریم. می توان یک کلید دیگر با نام template_name در آرگومان های اضافه دیکشنری که شامل template مورد نظر می باشد به publisher_info اضافه کرد:

from django.conf.urls.defaults import *
from django.views.generic import list_detail
from mysite.books.models import Publisher

publisher_info = {
    'queryset': Publisher.objects.all(),
    'template_name': 'publisher_list_page.html',
}

urlpatterns = patterns('',
    (r'^publishers/$', list_detail.object_list, publisher_info)
)

در صورت نبودن template_name، به هر حال generic view مورد نظر یعنی object_list از نام شیء یکی را استنباط خواهد کرد. در این مورد، template استنباط شده، "books/publisher_list.html" خواهد بود – جزء "books" از نام app گرفته شده است، هنگامی که جزء "publisher" تنها حروف کوچک از نام مدل است آن را تعریف می کند.

این template در مقابل یک context حاوی یک متغیر به نام object_list، render خواهد شد که حاوی تمام شیء های publisher است. یک template خیلی ساده ممکن است شبیه به چیزی مانند زیر باشد:

{% extends "base.html" %}

{% block content %}
    <h2>Publishers</h2>
    <ul>
        {% for publisher in object_list %}
            <li>{{ publisher.name }}</li>
        {% endfor %}
    </ul>
{% endblock %}

(توجه داشته باشید که template فوق فرض می کند یک base.html وجود دارد، همانطور که در آموزش template جنگو در یک مثال ایجاد کردیم.)

در دست ترجمه/تالیف ...

گسترش View های Generic

شکی وجود ندارد که استفاده از view های generic می تواند سرعت توسعه را به شکل قابل ملاحظه ای افزایش دهد. در اغلب پروژه ها، در دست ترجمه/تالیف .... در واقع، یکی از رایج ترین سوالات پاسخ داده شده توسط توسعه دهندگان جدید فریم ورک یا چارچوب جنگو، نحوه ی ایجاد کردن کنترل یک مجموعه ای از شرایط گسترده تر view های generic می باشد.

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

ایجاد Template Context های مساعد

ممکن است توجه کرده باشید لیست ناشران یعنی درون متغیری با نام object_list ذخیره شده است. زمانی این کد زیباتر خواهد شد که، این نام زمانی برای نویسندگان template مساعد خواهد بود که به جای نام object_list نام آن publisher_list باشد؛ محتویات این متغیر با این نام واضح تر خواهد بود.

نام این متغیر را می توان به سادگی با آرگومان template_object_name تغییر داد:

from django.conf.urls.defaults import *
from django.views.جنریک import list_detail
from mysite.books.models import Publisher

publisher_info = {
    'queryset': Publisher.objects.all(),
    'template_name': 'publisher_list_page.html',
    'template_object_name': 'publisher',
}

urlpatterns = patterns('',
    (r'^publishers/$', list_detail.object_list, publisher_info)
)

درون template، view جنریک یک _list به template_object_name اضافه می کند.

ایجاد یک template_object_name مفید همواره یک ایده ی خوب می باشد. همکاران شما کسانی که template ها را طراحی می کند از شما ممنون خواهند بود.

اضافه کردن context اضافه

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

ولی باید متذکر شد که راهی وجود دارد: تمام view های جنریک یک پارامتر اختیاری اضافه به نام extra_context دریافت می کنند. این که دیکشنری از شیء های اضافه می باشد که به context ارسال شده به template اضافه خواهد شد. بنابراین، جهت ایجاد لیست ناشران، از یک دیکشنری اطلاعات مانند زیر استفاده کرده ایم:

publisher_info = {
    'queryset': Publisher.objects.all(),
    'template_object_name': 'publisher',
    'extra_context': {'book_list': Book.objects.all()}
}

کد فوق یک متغیر {{ book_list }} در context موجود در template قرار می دهد. این الگو می تواند برای ارسال هر اطلاعاتی به درون template برای view جنریک استفاده شود که بسیار نیز مفید می باشد.

هر چند، در واقع یک اشکال ظریف در اینجا وجود دارد – می توانید آن را حدس بزنید؟

مشکل باید هنگامی که کوئری های درون extra_context ارزیابی شدند ایجاد شود. زیرا این مثال Book.objects.all() را درون URLconf قرار داده است، در این حالت تنها یک بار ارزیابی می شود (هنگامی که URLconf برای اولین بار بارگذاری می شود). هنگامی که شما ناشران را حذف یا اضافه می کنید، دقت خواهید داشت که view جنریک تا زمانی که وب سرور را دوباره بارگذاری نکرده اید، این تغییرات را منعکس نخواهد کرد.

نکته

این مشکل در مورد آرگومان view جنریک، queryset اعمال نمی شود. چرا که فریم ورک یا چارچوب جنگو می داند که QuerySet خاص نباید هرگز ذخیره سازی (cache) شود، view جنریک زمانی هر view می خواهد render شود cache مناسبی را انجام می دهد.

راه حل، استفاده از یک callback در extra_context به جای متغیر می باشد. هر چیز قابل فراخوانی (مانند یک تابع) ارسال شده به extra_context زمانی که view ارائه شود ارزیابی خواهد شد (به جای تنها یک بار). می توانید با تعریف یک تابع مشکل فوق را حل کنید:

def get_books():
    return Book.objects.all()

publisher_info = {
    'queryset': Publisher.objects.all(),
    'template_object_name': 'publisher',
    'extra_context': {'book_list': get_books}
}

یا می توانید از یک روش کوتاه تر که بر این واقعیت تکیه دارد که Book.objects.all خودش قابل فراخوانی می باشد استفاده کنید:

publisher_info = {
    'queryset': Publisher.objects.all(),
    'template_object_name': 'publisher',
    'extra_context': {'book_list': Book.objects.all}
}

به Book.objects.all بدون پرانتز پایانی توجه کنید. این حالت به تابع در واقع بدون فراخوانی آن رجوع می شود.

تماشای زیر مجموعه ای از شیء ها

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

برای برگزیدن یک مثال ساده، می خواهیم یک لیست از کتاب ها را از با تاریخ انتشار چیدمان کنیم.

book_info = {
    'queryset': Book.objects.order_by('-publication_date'),
}

urlpatterns = patterns('',
    (r'^publishers/$', list_detail.object_list, publisher_info),
    (r'^books/$', list_detail.object_list, book_info),
)

مثال فوق کاملا ساده می باشد، ولی یک با حالتی ظریف ایده ای را نشان می دهد. البته، معمولا بیشتر از چند بار شیء ها می خواهید که چیدمان کنید. در صورتی که می خواهید لیستی از کتاب ها با یک ناشر خواص ارائه دهید، می توانید از تکنیکی یکسان استفاده کنید:

apress_books = {
    'queryset': Book.objects.filter(publisher__name='Apress Publishing'),
    'template_name': 'books/apress_list.html'
}

urlpatterns = patterns('',
    (r'^publishers/$', list_detail.object_list, publisher_info),
    (r'^books/apress/$', list_detail.object_list, apress_books),
)

دقت داشته باشید که به همراه یک queryset فیلتر شده، همچنین از یک نام template سفارشی استفاده کرده ایم. در صورتی که این کار را انجام ندهیم، view جنریک از template همسان به صورت شیء لیست "vanilla" استفاده خواهد کرد، که ممکن است آنچه که می خواهید نباشد.

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

فیلتر کردن پیچیده با توابع wrapper

نیاز رایج دیگر، فیلتر کردن شیء های داده شده در یک صفحه ی لیست از طریق برخی کلیدها در URL می باشد. کمی قبل تر، نام ناشر را به طور مستقیم درون URLconf قرار می دادیم، ولی چه می شد اگر می خواستیم یک view بنویسیم که تمام کتاب ها را از طریق برخی ناشران دلخواه نمایش دهد؟ راهکار wrap کردن جنریک view مورد نظر یعنی object_list جهت اجتناب از نوشتن مقدار زیادی کد به صورت دستی می باشد. به طور معمول، با نوشتن یک URLconf شروع می کنیم:

urlpatterns = patterns('',
    (r'^publishers/$', list_detail.object_list, publisher_info),
    (r'^books/(\w )/$', books_by_publisher),
)

در قدم بعدی، view مورد نظر یعنی books_by_publisher را خواهیم نوشت:

from django.shortcuts import get_object_or_404
from django.views.جنریک import list_detail
from mysite.books.models import Book, Publisher

def books_by_publisher(request, name):

    # Look up the publisher (and raise a 404 if it can't be found).
    publisher = get_object_or_404(Publisher, name__iexact=name)

    # Use the object_list view for the heavy lifting.
    return list_detail.object_list(
        request,
        queryset = Book.objects.filter(publisher=publisher),
        template_name = 'books/books_by_publisher.html',
        template_object_name = 'book',
        extra_context = {'publisher': publisher}
    )

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

نکته

دقت داشته باشید که در مثال قبلی، publisher جاری در حال نمایش را در extra_context ارسال نمودیم. این معمولا فکر خوبی برای wrapper های از این نوع می باشد؛ که اجازه می دهد template شیء "parent" ای را که در حال حاضر در حال جستجو می باشد را بشناسد.

انجام کار اضافه

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

تصور کنید یک فیلد last_accessed در شیء Author موجود می باشد که برای پیگیری آخرین زمانی که کسی author را نگاه کرده است استفاده می شود. view جنریک object_dtail، البته هیچ چیزی درباره ی این فیلد نمی داند، ولی یک بار دیگر به سادگی یک view سفارشی، برای نگه داشتن فیلد به روز رسانی شده می نویسیم.

در ابتدا، نیاز به اضافه کردن یک بخش جزئیات نویسنده در URLconf برای اشاره به یک view سفارشی داریم:

from mysite.books.views import author_detail

urlpatterns = patterns('',
    # ...
    (r'^authors/(?P<author_id>\d )/$', author_detail),
    # ...
)

سپس تابع wrapper خودمان را می نویسیم:

import datetime
from django.shortcuts import get_object_or_404
from django.views.جنریک import list_detail
from mysite.books.models import Author

def author_detail(request, author_id):
    # Delegate to the view جنریک and get an HttpResponse.
    response = list_detail.object_detail(
        request,
        queryset = Author.objects.all(),
        object_id = author_id,
    )

    # Record the last accessed date. We do this *after* the call
    # to object_detail(), not before it, so that this won't be called
    # unless the Author actually exists. (If the author doesn't exist,
    # object_detail() will raise Http404, and we won't reach this point.)
    now = datetime.datetime.now()
    Author.objects.filter(id=author_id).update(last_accessed=now)

    return response

نکته

کد فوق در واقع کار نخواهد کرد مگر این که شما یک فیلد last_accessed به مدل Author اضافه کنید و یک template با نام books/author_detail.html بسازید.

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

def author_list_plaintext(request):
    response = list_detail.object_list(
        request,
        queryset = Author.objects.all(),
        mimetype = 'text/plain',
        template_name = 'books/author_list.txt'
    )
    response["Content-Disposition"] = "attachment; filename=authors.txt"
    return response

کد فوق جواب خواهد داد، چرا که view ها جنریک شیء های ساده ی HttpResponse را بر می گردانند که می توانند مانند دیکشنری برای مجموعه ای از HTTP header ها رفتار کنند. ضمنا Content-Disposition، به مرورگر یاد می دهد که صفحه به جای اینکه در مرورگر نمایش دهد، آن را دانلود و ذخیره کند.

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


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

تولید محتوای غیر html ای در جنگو

معمولا هنگامی که درباره ی deploy کردن وب سایت ها صحبت می شود، موضوع صحبت درباره ی تولید HTML می باشد. البته که موارد بسیار دیگری نیز وجود دارد؛ ما از وب برای توزیع داده در تمام قابل بندی ها استفاده می کنیم: RSS، PDF، عکس ها و غیره ....

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

جنگو دارای ابزار داخلی مناسبی می باشد که می توان برای تولید برخی محتوای به غیر از HTML از آن استفاده کرد:

  • Feed های پیوند RSS/Atom
  • Sitemap ها (یک قالب بندی XML توسعه داده شده توسط گوگل که به موتورهای جستجو راهنمایی هایی را می دهد)

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

اصول اولیه: view ها و MIME-type ها

آموزش view و urlconf جنگو را بخاطر بیاورید که یک تابع view به سادگی یک تابپ پایتون می باشد که یک درخواست وب را دریافت کرده و یک پاسخ وب را بر می گرداند. این پاسخ می تواند محتویات HTML از صفحه ی وب یا یک تغییر مسیر، یا یک خطای 404، یا یک سند XML، یا حتی یک تصویر ... و یا هر چیز دیگری باشد.

به طور رسمی تر، یک تابع view جنگو باید

  • قبول یک نمونه ی HttpRequest به صورت اولین آرگومان
  • برگرداندن یک نمونه ی HttpResponse

نکته ی کلیدی جهت برگرداندن محتوای غیر HTML ای از یک view درون کلاس HttpResponse قرار دارد، به ویژه آرگومان mimetype. با قرار دادن mimetype ، می توان به مرورگر نشان داد که یک پاسخ از قالب بندی متفاوت را بر مر گردانیم.

برای مثال، اجازه دهید به view زیر که یک تصویر با قالب بندی PNG را بر می گرداند را بررسی کنیم، تنها فایل را از حافظه خوانده ایم:

from django.http import HttpResponse

def my_image(request):
    image_data = open("/path/to/my/image.png", "rb").read()
    return HttpResponse(image_data, mimetype="image/png")

همین! در صورتی که شما مسیر تصویر را در فراخوانی open() با یک مسیر برای یک تصویر واقعی جا به جا کنید، می توانید از این view به سادگی برای نمایش یک تصویر استفاده کنید، و مرورگر به درستی آن را نمایش خواهد داد.

نکته ی مهم دیگر که باید در نظر داشت این است که، شیء های HttpResponse با API استاندارد پایتون یعنی "file like object" کار می کنند. این بدین معناست که می توان، از یک نمونه ی HttpResponse را در هر جای پایتون (یا یک کتابخانه ی third-party) که انتظار یک فایل را دارد استفاده کنید.

برای مثالی از نحوه ی کارکرد آن، اجازه دهید نگاهی به تولید CSV با جنگو بیاندازیم.

تولید CSV

CSV یک قالب بندی ساده می باشد که معمولا توسط نرم افزار spreadsheet استفاده می شود. اساسا یک سری از ردیف های جدول با هر سلول در ردیف جدا شده توسط یک علامت کاما می باشد (CVS مخفف comma‑seperated values می باشد). برای مثال، در زیر برخی داده های "unruly" مسافران هوایی در قالب بندی CSV می باشد.

Year,Unruly Airline Passengers
1995,146
1996,184
1997,235
1998,200
1999,226
2000,251
2001,299
2002,273
2003,281
2004,304
2005,203
2006,134
2007,147

نکته

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

هر چند CSV ساده به نظر می رسد، قالب بندی جزئیات آن دارای توافق جهانی نمی باشد. قسمت های مختلف نرم افزار CVS های مختلفی را تولید و استفاده می کند. خوشبختانه، پایتون دارای یک کتابخانه ی استاندار CSV با نام cvs می باشد.

به دلیل آنکه ماژول csv به صورت file-like objects کار می کند، استفاده از آن ساده تر از HttpResponse می باشد:

import csv
from django.http import HttpResponse

# Number of unruly passengers each year 1995 - 2005. In a real application
# this would likely come from a database or some other back-end data store.
UNRULY_PASSENGERS = [146,184,235,200,226,251,299,273,281,304,203]

def unruly_passengers_csv(request):
    # Create the HttpResponse object with the appropriate CSV header.
    response = HttpResponse(mimetype='text/csv')
    response['Content-Disposition'] = 'attachment; filename=unruly.csv'

    # Create the CSV writer using the HttpResponse as the "file."
    writer = csv.writer(response)
    writer.writerow(['Year', 'Unruly Airline Passengers'])
    for (year, num) in zip(range(1995, 2006), UNRULY_PASSENGERS):
        writer.writerow([year, num])

    return response

کد فوق باید بسیار واضح باشد، ولی یک نکاتی ویژه برای ذکر کردن وجود دارد:

  • Response به جای mimetype پیشفرض یعنی text/html با mimetype مورد نظر یعنی text/csv معین شده است.
  • متغیر response یک Content-Disposition header اضافه دریافت می کند که حاوی نام فایل CSV می باشد. این header (بخش "attachment") مرورگر را برای یک جا جهت ذخیره کردن فایل به جای نمایش آن بر می انگیزد. نام این فایل دلخواه می باشد؛ آن را هر چه که می خواهید نام گذاری کنید. این توسط مرورگر در دیالوگ "Save As" استفاده خواهد شد.
  • جهت اختصاص دادن یک header در یک HttpResponse ، تنها کافیست به صورت یک دیکشنری و مجموعه ای از کلید و ارزش ها با آن رفتار کنید.
  • در دست ترجمه/تالیف ... به داخل API، CSV_generation ساده می باشد: تنها response را به صورت اولین آرگومان به csv.writer ارسال کنید.
  • برای هر ردیف در فایل CSV، فراخوانی writer.writerow مانند شیءی به صورت یک لیست یا تاپل آن را ارسال می کند.
  • ماژول CSV برای گذاشتن کتیشن برای ما محتاط می باشد، بنابراین نگرانی ای درباره ی رد کردن رشته های با کتیشن یا کاما در آن ها نخواهید داشت. تنها اطلاعات را برای writerow() ارسال می کند.

این الگوی کلی ای می باشد که شما در هر زمان که نیاز به برگرداندن محتوای غیر HTML ای دارید از آن استفاده خواهید کرد: ساختن یک شیء HttpResponse (با یک mimetype ویژه)، ارسال کردن آن به چیزی که انتظار یک فایل را دارد، و سپس برگرداند یک response.

اجازه دهید به نگاهی تعداد بیشتری از مثال بیاندازیم.

ساختن PDF

Portable Document Format (PDF) یک قالب بندی توسعه یافته توسط Adobe می باشد که برای نمایش اسناد قابل چاپ استفاده می شود، قالب بندی pixel-perfect کامل، فونت های جاسازی شده، و گرافیک دو بعدی. می توان یک PDF را معادل دیجیتال یک سند چاپ شده دانست؛ در واقع، PDF ها اغلب در توزیع اسناد به قصد چاپ آن ها استفاده می شود.

می توان به سادگی PDF ها را با پایتون و جنگو، با تشکر از کتابخانه ی عالی منبع باز ReportLab تولید کرد (htt://www.reportlab.org/rl_toolkit.html). مزیت تولید فایل های PDF به طور پویا این است که، می توانید PDF های سفارشی را برای اهداف مختلف ایجاد کنید.

نصب ReportLab

قبل از تولید PDF نیاز می باشد ReportLab را نصب کنید که معمولا خیلی ساده می باشد: تنها کافیست کتابخانه ی آن را از

محتوای مخفی

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

نکته

در صورتیکه توزیع مدرن لینوک استفاده می کنید، ممکن است package management خود را قبل از نصب ReportLab بررسی کنید. اغلب repository های پکیج دارای ReportLab اضافه شده می باشند.

برای مثال، در صورتی که از ubuntu استفاده می کنید، به سادگی دستور apt-get install python-reportlab این کار را انجام خواهد داد.

راهنمای کاربر (به طور طبیعی تنها به صورت یک فایل PDF در دسترس می باشد) در

محتوای مخفی

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

می توان نصب بودن ReportLab را با import کردن آن در interactive interpreter پایتون آزمایش کرد:

>>> import reportlab

در صورتیکه دستور فوق هیچ خطایی را ایجاد نکند، نصب درست انجام شده است.

نوشتن view

همانند CSV، تولید PDF ها به صورت پویا با جنگو ساده می باشد، چرا که API مخصوص ReportLab به صورت file‑like object عمل می کند.

در زیر مثال "Hello World" را مشاهده می کنید:

from reportlab.pdfgen import canvas
from django.http import HttpResponse

def hello_pdf(request):
    # Create the HttpResponse object with the appropriate PDF headers.
    response = HttpResponse(mimetype='application/pdf')
    response['Content-Disposition'] = 'attachment; filename=hello.pdf'

    # Create the PDF object, using the response object as its "file."
    p = canvas.Canvas(response)

    # Draw things on the PDF. Here's where the PDF generation happens.
    # See the ReportLab documentation for the full list of functionality.
    p.drawString(100, 100, "Hello world.")

    # Close the PDF object cleanly, and we're done.
    p.showPage()
    p.save()
    return response

نکاتی در مورد کد فوق:

  • در کد فوق از mimetype مخصوص PDF یعنی application/pdf استفاده شده است. این حالت به مرورگرها می گوید که سند به جای یک فایل HTML، یک فایل PDF می باشد. در صورتی که این اطلاعات را جا بیاندازید، مرورگرها پاسخ را به صورت HTML تفسیر می کنند، نتیجه ی به هم ریخته و نا مفهومی در پنجره ی مرورگر ایجاد خواهد کرد.
  • در دست ترجمه/تالیف ...: تنها کافیست response را به صورت آرگومان اول برای canvas.Canvas ارسال کنید. کلاس Canvas انتظار یک file-like object را دارد، و شیء های HttpResponse نیز همینطور.
  • تمام متدهای در دست ترجمه/تالیف ...
  • در پایان، فراخوانی showPage() و save() در فایل PDF با اهمیت می باشد – در غیر این صورت در فایل PDF خراب خواهد شد.

PDF های پیچیده

در صورتی که یک سند پیچیده ی PDF (یا هر قطعه داده ی بزرگ) را ایجاد می کنید، استفاده کردن از کتابخانه ی cStringIO به صورت یک محل نگهداری موقت برای فایل PDF خودتان را بررسی کنید. کتابخانه ی cStringIO یک رابط file-like object را تهیه می کند که برای حداکثر بهره وری در C نوشته شده است.

در زیر مثال "Hello World" قبلی با استفاده از cStringIO باز نویسی شده است:

from cStringIO import StringIO
from reportlab.pdfgen import canvas
from django.http import HttpResponse

def hello_pdf(request):
    # Create the HttpResponse object with the appropriate PDF headers.
    response = HttpResponse(mimetype='application/pdf')
    response['Content-Disposition'] = 'attachment; filename=hello.pdf'

    temp = StringIO()

    # Create the PDF object, using the StringIO object as its "file."
    p = canvas.Canvas(temp)

    # Draw things on the PDF. Here's where the PDF generation happens.
    # See the ReportLab documentation for the full list of functionality.
    p.drawString(100, 100, "Hello world.")

    # Close the PDF object cleanly.
    p.showPage()
    p.save()

    # Get the value of the StringIO buffer and write it to the response.
    response.write(temp.getvalue())
    return response

امکانات دیگر

مجموعه ی کاملی از انواع دیگر محتویاتی که می توان در پایتون تولید کرد وجود دارد:

  • فایل های ZIP: کتابخانه ی استاندارد پایتون حاوی ماژول zipfile می باشد، که می تواند فایل های ZIP را هم بنویسد و هم بخواهند. می توان برای تولید بایگانی کردن تعدادی از فایل های مورد نیاز یا فشرده ساختن اسناد بزرگ از آن استفاده کرد. همچنین می توان فایل های TAR را با استفاده از ماژول tarfile در کتابخانه ی استاندارد پایتون تولید نمود.
  • تصاویر پویا: کتابخانه ی تصویر پایتون (PIL؛

    محتوای مخفی

      برای مشاهده محتوای مخفی می بایست در انجمن ثبت نام کنید.
    یک جعبه ابزار خارق العاده برای تولید تصاویر (PNG، JPEG، GIF و مقدار بیشتری) می باشد. شما می توانید برای کاهش اندازه ی تصاویر به عکس های ریز به طور خودکار، تصاویر چندگانه ی مرکب در یک فریم و یا حتی برای انجام پردازش تصویر تحت وب از آن استفاده کنید.
  • طرح ها و نمودارها: تعدادی کتابخانه ی قدرتمند برای طرح ها و نمودارها در پایتون وجود دارد که می توان برای تولید نقشه های مورد نیاز، نمودارها، طرح ها و گرافیک ها از آن استفاده می کرد. مسلما نمی توان تمام آن ها را در اینجا لیست کرد، بنابراین تعداد از موارد برجسته در زیر نام برده شده اند:
  • matplotlib (http://matplotlib.sourceforge.net/) می تواند برای تولید نوعی از طرح های با کیفیت بالا که معمولا توسط MatLab یا Matematica تولید شده اند استفاده شود.
  • pygraphviz (http://networkx.lanl.gov/pygraphviz/)، یک رابط برای لایه ی ابزار گرافیک Graphviz (http://graphviz.org/) می باشد، که می تواند برای تولید نمودارهای ساخت یافته از گرافیک ها و شبکه ها استفاده شود.

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

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

فریم ورک یا چارچوب Syndication Feed

جنگو یک فریم ورک یا چارچوب سطح بالا syndication-feed-generating را ارائه می کند که ایجاد RSS و Atom را ساده می کند.

RSS چیست؟ Atom جیست؟

RSS و Atom هر دو XML-based می باشند، که شما می توانید برای تهیه ی به روز رسانی به طور خودکار "feed" های محتویات سایت از آن استفاده کنید. برای اطلاعات بیشتر در مورد RSS می توانید به

محتوای مخفی

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

محتوای مخفی

    برای مشاهده محتوای مخفی می بایست در انجمن ثبت نام کنید.
نیر می توانید اطلاعاتی راجع به Atom بدست آوردید.

برای ایجاد هر syndication feed، همه باید یک کلاس کوچک پایتون بنویسند. می توانید هر مقدار "feed" که می خواهید بسازید.

فریم ورک یا چارچوب سطح بالای feed-generating یک view می باشد که به طور قرارداد به /feeds/ وصل شده است. جنگو از باقی مانده ی URL (هر چیزی بعد از /feeds/) برای تعیین feed برای برگرداندن استفاده می کند.

برای ایجاد یک feed، شما یک Feed Class خواهید نوشت و در URLconf خود به آن اشاره می کنید.

مقدار دهی اولیه

برای فعال کردن syndication feed ها روی سایت جنگوی خود، URLconf زیر را اضافه کنید:

(r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed',
    {'feed_dict': feeds}
),

این خط به جنگو می گوید؛ از فریم ورک RSS جهت کنترل تمام URL هایی که با "feeds/" شروع می شوند استفاده کند. (می توانید پیشوند "feeds/" را برای متناسب ساختن با نیازهای خودتان تغییر دهید.)

این خط URLconf دارای یک آرگومان اضافی می باشد: {'feed_dict': feeds}. از این آرگومان اضافه جهت ارسال feed هایی که باید تحت URL منتشر شده باشند استفاده کنید.

به طور خاص، feed_dict باید یک دیکشنری باشد که نام URL را به کلاس Feed مرتبط می کند. می توانید feed_dict را درون خود URLconf تعریف کنید. در زیر مثال کامل URLconf را ملاحظه می کنید:

from django.conf.urls.defaults import *
from mysite.feeds import LatestEntries, LatestEntriesByCategory

feeds = {
    'latest': LatestEntries,
    'categories': LatestEntriesByCategory,
}

urlpatterns = patterns('',
    # ...
    (r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed',
        {'feed_dict': feeds}),
    # ...
)

مثال قبلی دو feed را در نظر گرفته است:

  • feed اول که با LatestEntries نشان داده شده است و در feeds/latest/ قرار خواهد گرفت.
  • دومین feed که با latestEntriesByCategory نمایش داده شده و در feeds/categories قرار خواهد گرفت.

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

یک کلاس Feed یک کلاس ساده ی پایتون می باشد که یک syndication feed را نشان می دهد. یک feed می تواند ساده باشد (مانند یک feed "سایت اخبار"، یا یک feed اولیه که آخرین ورودی های یک بلاگ را نمایش می دهد) یا بسیار پیچیده (مانند یک feed که تمام ورودی های بلاگ در یک طبقه بندی خاص جایی که طبقه بندی متغیر می باشد نمایش می دهد).

کلاس های Feed باید از کلاس django.contrib.syndication.feeds.Feed مشتق شوند. آن ها می توانند در هر جایی از درخت کد شما قرار بگیرند.

یک Feed ساده

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

from django.contrib.syndication.feeds import Feed
from mysite.blog.models import Entry

class LatestEntries(Feed):
    title = "My Blog"
    link = "/archive/"
    description = "The latest news about stuff."

    def items(self):
        return Entry.objects.order_by('-pub_date')[:5]

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

  • کلاس فوق از کلاس django.contrib.syndication.feeds.Feed مشتق شده است.
  • title، link و description به ترتیب با المان های <title>، <link> و <description> برابر می باشند.
  • items() یک متد می باشد که به سادگی یک لیست از شیء هایی که باید به صورت المان های <item> شامل شده در feed باشند بر می گرداند. اگر چه مثال فوق شیء های Entry را که از API پایگاه داده ی جنگو استفاده می کنند بر می گرداند، item() نباید نمونه های مدل را بر گرداند.

تنها یک گام بیشتر وجود دارد. در یک RSS feed، هر <item> دارای یک <title>، <link> و <description> می باشد. نیاز است داده ای که قرار است برای درون آن المنت ها قرار گیرد، به فریم ورک گفته شود.

  • برای تعیین محتویات <title> و <description>، template های جنگو را با نام های feeds/latest_title.html و feeds/latest_description.html ایجاد کنید، جایی که latest، slug تعیین شده در URLconf برای feed داده شده می باشد. توجه داشته باشید که پسوند .html الزامی می باشد.
  • سیستم RSS آن template را برای هر آیتم render می کند، ارسال آن دو متغیر template:
  • obj: شیء فعلی (هر کدام از شیء هایی که در items() بر گردانده شده است).
  • site: نمایش یک شیء django.models.core.sites.Site سایت فعلی. این برای {{ site.domain }} یا {{ site.name }} مفید می باشد.
  • در صورتیکه یک template برای title یا description ایجاد نکنید، فریم ورک به صورت پیشفرض از template، {{ obj }} استفاده می کند – این template نمایش رشته ی معمولی شیء می باشد. (برای شیء های مدل، این متد __unicode__() خواهد بود.)
  • همچنین می توان نام این دو template را از طریق تعیین title_template و description_template به صورت attribute های کلاس Feed تغییر داد.
  • برای تعیین محتویات <link>، دو option وجود دارد. برای هر آیتم در items()، جنگو ابتدا سعی می کند یک متد get_absolute_url() روی آن شیء اجرا کند. در صورتی که متد وجود نداشته باشد، سعی می کند یک متد item_link() در کلاس Feed فراخوانی کند، ارسال آن یک پارامتر تنها، item، که خود شیء می باشد.
  • هر دوی get_absolute_url() و item_link() باید URL ایتم را به صورت یک رشته ی معمولی پایتون بر گردانند.
  • برای مثال LatestEntries قبلی، می توانیم template های بسیار ساده ی feed را داشته باشیم. latest_title.html حاوی:
{{ obj.title }}

و latest_description.html حاوی:

{{ obj.description }}

این اغلب بسیار ساده می باشد ...

یک Feed پیچیده تر

فریم ورک همچنین feed های پیچیده تر را نیز از طریق پارامترها پشتیبانی می کند.

برای مثال، تصور کنید بلاگ شما یک RSS feed برای هر تگ مجزایی که شما برای طبقه بندی ورودی های خودتان استفاده کرده اید ارائه می کند. ساختن یک کلاس Feed جدا برای هر تگ احمقانه می باشد؛ نقض قانون "Don't Repeat Yourself" (DRY) می باشد.

در عوض، چارچوب syndication به شما اجازه می دهد feed های جنریکی ایجاد کنید که آیتم های متکی بر اطلاعات در آدرس feed ها را گردانند.

feed های تعیین تگ شما می تواند مانند زیر URL ها را استفاده کنند:

  • محتوای مخفی

      برای مشاهده محتوای مخفی می بایست در انجمن ثبت نام کنید.
    ورودی های فعلی علامت زده شده با "python" را بر می گرداند
  • محتوای مخفی

      برای مشاهده محتوای مخفی می بایست در انجمن ثبت نام کنید.
    ورودی های فعلی علامت زده شده با "cats" را بر می گرداند

slug در اینجا "tags" می باشد. فریم ورک syndication، bit های URL اضافه ی بعد از slug را می بیند – 'python' و 'cats' – و به شما جهت گفتن معنی آن bit های URL و نحوه ی تاثیر آن ها در دست ترجمه/تالیف ...

یک مثال این موضوع را واضح تر می کند. در کد زیر feed های تگ تعیین را ملاحظه می کنید:

from django.core.exceptions import ObjectDoesNotExist
from mysite.blog.models import Entry, Tag

class TagFeed(Feed):
    def get_object(self, bits):
        # In case of "/feeds/tags/cats/dogs/mice/", or other such
        # clutter, check that bits has only one member.
        if len(bits) != 1:
            raise ObjectDoesNotExist
        return Tag.objects.get(tag=bits[0])

    def title(self, obj):
        return "My Blog: Entries tagged with %s" % obj.tag

    def link(self, obj):
        return obj.get_absolute_url()

    def description(self, obj):
        return "Entries tagged with %s" % obj.tag

    def items(self, obj):
        entries = Entry.objects.filter(tags__id__exact=obj.id)
        return entries.order_by('-pub_date')[:30]

در کد فوق اصل اولیه ی الگوریتم فریم ورک RSS وجود دارد، به توجه به این کلاس و یک درخواست برای URL مورد نظر یعنی /feeds/tags/python/:

  • فریم ورک آدرس /feeds/tags/python/ را دریافت می کند و ملاحظه می کند که یک تکه ی اضافی از URL بعد از slug وجود دارد. فریم ورک رشته ی باقی مانده را از توسط حرف ("/") جدا کرده و متد کلاس Feed یعنی get_object را فراخوانی کرده و bit ها را به آن ارسال می کند.
  • در این مورد، bits مورد نظر ['python'] می باشد. برای یک درخواست به /feeds/tags/python/django/، bit ها ['python', 'django'] می باشند.
  • get_object() مسئول بازیابی شیء Tag داده شده از bit های داده شده می باشد.
  • در این مورد، get_object() برای بازیابی Tag از API پایگاده داده ی جنگو استفاده می کند. توجه داشته باشید که get_object() در صورتیکه پارامتر های غیر معتبر داده شود، باید خطای django.core.exceptions.ObjectDoesNotExist ایجاد کند. هیچ try/except ای در اطراف فراخوانی Tag.DoesNotExist وجود ندارد، زیرا این کار ضروری نمی باشد. آن تابع زمان شکست Tag.DoesNotExist ایجاد می کند، و Tag.DoesNotexist از OjbectDoesNotexist مشتق شده است. بروز objectDoesNotexist در get_object()تولید یک خطای 404 برای آن درخواست را به جنگو می گوید.
  • برای تولید <title>، <link> و <description> مربوط به feed، جنگو متدهای title()، link() و Description() را مورد استفاده قرار می دهد. در مثال قبلی، آن ها attribute های ساده ی کلاس رشته بودند، ولی این مثال نشان می دهد که، آن ها می تواند هم رشته و هم متد باشند. برای هر title، link و description، جنگو الگوریتم زیر را دنبال می کند:

 

  1. سعی می کند یک متد را فراخوانی کند، ارسال آرگومان obj، جایی که obj شیءی می باشد که توسط get_object() بر گردانده شده است.
  2. در صورت شکست، جنگو تلاش می کند یک متد با هیچ آرگومانی را فراخوانی کند.
  3. در صورت شکست، جنگو attribute کلاس را استفاده می کند.
  • در پایان، توجه داشته باشید که items() در این مثال همچنین آرگومان obj را نیز دریافت می کند. الگوریتم برای items همانند الگوریتم توضیح داده شده در گام قبلی می باشد – ابتدا، سعی می کند items(obj)، سپس items() و در پایان یک attribute کلاس items (که باید یک لیست باشد).
  • مستندات کامل از تمام متدها و attribute ها از کلاس های Feed همواره از اسناد رسمی جنگو قابل دسترسی می باشد (http://docs.djangoproject.com/en/dev/ref/contrib/syndication)

تعیین نوع Feed

به طور پیشفرض، فریم ورک syndication، RSS 2.0 را تهیه می کند. برای تغییر آن، یک attribute، feed_type برای کلاس Feed خودتان اضافه کنید:

from django.utils.feedgenerator import Atom1Feed

class MyFeed(Feed):
    feed_type = Atom1Feed

توجه داشته باشید که feed_type را برای یک شیء کلاس در نظر گرفته اید، نه یک نمونه، در حال حاضر انواع feed های در دسترس در جدول زیر نشان داده شده اند.

کلاس Feed Format
Django.utils.feedgenerator.Rss201rev2Feed RSS 2.01 (default)
Django.utils.feedgenerator.RssUserland091Feed RSS 0.91
Django.utils.feedgenerator.Atom1Feed Atom 1.0

Enclosures

برای تعیین enclosure ها (مانند منابع media مرتبط با یک آیتم feed مانند feed های MP3 podcast)، item_enclosure_url، item_enclosure_length و item_enclosure_mime_type را استفاده کنید، به عنوان مثال:

from myproject.models import Song

class MyFeedWithEnclosures(Feed):
    title = "Example feed with enclosures"
    link = "/feeds/example-with-enclosures/"

    def items(self):
        return Song.objects.all()[:30]

    def item_enclosure_url(self, item):
        return item.song_url

    def item_enclosure_length(self, item):
        return item.song_length

    item_enclosure_mime_type = "audio/mpeg"

البته که شما یک شیء Song با فیلدهای song_url و song_length (مانند اندازه ی بایت ها) ساخته اید.

Language

Feed های ساخته شده توسط فریم ورک syndication به طور خودکار شامل تگ مناسب <language> (RSS 2.0) یا attribute، xml:lang (Atom) می باشند. این به طور مستقیم از تنظیم LANGUAGE_CODE آمده است.

URLs

لینک متد/attribute می تواند هم یک URL مستقل (مانند "/blog/") یا یک URL با آدرس کامل دامنه و پروتکل (مانند "http://www.example.com/blog/") باشد. در صورتی که link دامنه را بر نگرداند، فریم ورک syndication دامنه ی سایت فعلی را به همراه تنظیم SITE_ID درج خواهد کرد. (برای اطلاعات بیشتر در مورد SITE_ID و چارچوب سایت ها به پکیج django.contrib مراجعه کنید.)

feed های Atom نیازمند یک <link rel="self"> می باشند که مکان فعلی feed را تعریف کند. فریم ورک syndication به طور خودکار این را قرار می دهد.

انتشار feed های Atom و RSS پشت سر هم

برخی از توسعه دهندگان تمایل دارند، فیلد های هر دو نسخه ی Atom و RSS در دسترس قرار دهند. انجام این کار با جنگو بسیار آسان می باشد: تنها کافیست یک کلاس فرزند از کلاس feed خود ایجاد کرده و feed_type را برای چیزی متفاوت قرار دهید. سپس URLconf خود را جهت اضافه کردن نسخه های اضافه به روز رسانی کنید. در زیر یک مثال کامل را ملاحظه می کنید:

from django.contrib.syndication.feeds import Feed
from django.utils.feedgenerator import Atom1Feed
from mysite.blog.models import Entry

class RssLatestEntries(Feed):
    title = "My Blog"
    link = "/archive/"
    description = "The latest news about stuff."

    def items(self):
        return Entry.objects.order_by('-pub_date')[:5]

class AtomLatestEntries(RssLatestEntries):
    feed_type = Atom1Feed

و در زیر URL همراه:

from django.conf.urls.defaults import *
from myproject.feeds import RssLatestEntries, AtomLatestEntries

feeds = {
    'rss': RssLatestEntries,
    'atom': AtomLatestEntries,
}

urlpatterns = patterns('',
    # ...
    (r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed',
        {'feed_dict': feeds}),
    # ...
)

فریم ورک یا چارچوب نقشه ی سایت

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

به عنوان مثال، در زیر یک تکه از نقشه ی سایت برای وب سایت جنگو وجود دارد (http://www.djangoproject.com/sitemap.xml):

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>http://www.djangoproject.com/documentation/</loc>
    <changefreq>weekly</changefreq>
    <priority>0.5</priority>
  </url>
  <url>
    <loc>http://www.djangoproject.com/documentation/0_90/</loc>
    <changefreq>never</changefreq>
    <priority>0.1</priority>
  </url>
  ...
</urlset>

برای مشاهده ی نقشه ی سایت های بیشتر به

محتوای مخفی

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

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

نصب

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

  • 'django.contrib.sitemaps' را به تنظیم INSTALLED_APPS اضافه کنید.
  • 'django.template.loaders.app_directories.load_template_source'باید در تنظیم TEMPLATE_LOADERS وجود داشته باشد. این حالت به طور پیشفرض وجود دارد، بنابراین در صورتی که این تنظیم را تغییر داده اید، تنها کافیست آن را به حالت اول خود بر گردانید.
  • اطمینان حاصل کنید که سایت های چارچوب را نصب کرده اید (به پکیج django.contrib مراجعه کنید).

نکته

برنامه ی نقشه ی سایت در هر جدول دیتابیسی نصب نمی شود. تنها دلیلی که برای رفتن به داخل INSTALLED_APPS نیاز دارد این است که template loader مورد نظر یعنی load_template_source بتواند template های پیشفرض را پیدا کند.

مقدار دهی اولیه

جهت فعال کردن تولید نقشه ی سایت در سایت جنگو، خط زیر را درون URLconf اضافه کنید:

(r'^sitemap\.xml$', 'django.contrib.sitemaps.views.sitemap', {'sitemaps': sitemaps})

خط فوق برای ساختن یک نقشه ی سایت هنگامی که یک کلاینت به /sitemap.xml دسترسی پیدا می کند در نظر گرفته شده است. توجه داشته باشید که حرف نقطه در sitemap.xml با یک علامت ("\") میسر شده است، زیرا نقطه ها معنی خاصی در regular expression ها دارند.

نام فایل نقشه ی سایت اهمیتی ندارد، ولی مکان آن مهم می باشد. موتورهای جستجو تنها لینک ها را در نقشه ی سایت شما برای سطح URL فعلی و پایین فهرست می کنند. برای مثال، در صورتی که sitemap.xml در دایرکتوری ریشه ی شما می باشد، ممکن است به هر URL ای در سایت شما رجوع کند. در صورتی که نقشه ی سایت در /content/sitemap.xml باشد، ممکن است تنها به URL هایی که با /content/ شروع می شوند رجوع کند.

view نقشه ی سایت یک آرگومان اضافه ی الزامی دریافت می کند: {'sitemaps': sitemaps}. نقشه های سایت باید یک دیکشنری باشند که بخش کوتاه لیبلی (مانند blog یا news) را به کلاس Sitemap آن (مانند BlogSitemap یا NewsSitemap) مرتبط می سازد. همچنین ممکن است به یک instance از یک کلاس Sitemap (مانند BlogSitemap(some_var)) مرتبط سازد.

کلاس های Sitemap

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

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

کلاس های Sitemap باید از کلاس django.contrib.sitemaps.Sitemap مشتق شوند. آن ها می توانند هر جایی در درخت کد پایتون شما قرار بگیرند.

برای مثال، فرض می کنیم شما دارای یک سیستم بلاگ می باشید، با یک مدل Entry، و می خواهید نقشه ی سایت تمام لینک ها به ورودی منحصر به فرد بلاگ را شامل شود. در زیر کلاس Sitemap مورد نظر وجود دارد:

from django.contrib.sitemaps import Sitemap
from mysite.blog.models import Entry

class BlogSitemap(Sitemap):
    changefreq = "never"
    priority = 0.5

    def items(self):
        return Entry.objects.filter(is_draft=False)

    def lastmod(self, obj):
        return obj.pub_date

declare کردن نقشه ی سایت باید بسیار شبیه به یک Feed باشد. در دست ترجمه/تالیف ...

همانند کلاس های Feed، اعضای Sitemap می توانند متد یا attribute ها باشند. به بخش کمی قبل در این آموزش از کتاب، "یک مثال پیچیده" برای نحوه ی انجام این اعمال مراجعه کنید.

یک کلاس نقشه ی سایت می تواند متد/attribute های زیر را تعریف کند:

  • items(required): لیستی از شیء ها تهیه می کند. فریم ورک نوع شیء هایی که هستند را زیر نظر ندارد؛ تمام موضوع این است که این شیء ها به متدهای location()، lastmod()، changefreq() و priority() ارسال شده باشند.
  • location(optional): URL مستقل برای شیء داده شده را می دهد. در اینجا، "URL مستقل" به معنی یک URL می باشد که شامل پروتکل یا دامنه نباشد. تعدادی مثال را در زیر مشاهده می کنید:
  1. خوب: '/foo/bar/'
  2. بد: 'example.com/foo/bar/'
  3. بد: 'http://example.com/foo/bar/'
  4. در صورتیکه location تهیه نشده باشد، فریم ورک متد get_absolute_url() را در هر شیء به صورت برگشت داده شده توسط items() فراخوانی خواهد کرد.
  • lastmod(optional): شیء های "آخرین اصلاح" زمان و تاریخ، به صورت یک شیء datetime پایتون.
  • changefreq(optional): هر چند وقت یک بار شیء را تغییر می دهد. مقادیر ممکن (به صورت داده شده توسط تعیین نقشه های سایت) به قرار زیر می باشند:
  1. 'always'
  2. 'hourly'
  3. 'daily'
  4. 'weekly'
  5. 'monthly'
  6. 'yearly'
  7. 'never'
  • priority(optional): یک پیشنهاد اولویت فهرست سازی بین 0.0 و 1.0. اولویت پیشفرض یک صفحه 0.5 می باشد؛ برای اطلاعات بیشتر درباره ی عمکرد اولویت به

    محتوای مخفی

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

میانبرها

فریم ورک نقشه ی سایت کلاس های مناسبی برای موارد مشترک تهیه کرده است. این کلاس ها در بخش های زیر توضیح داده شده اند.

FlatPageSitemap
کلاس django.contrib.sitemaps.FlatPageSitemap به تمام صفحات مسطح تعریف شده برای سایت فعلی نگاه می کند و یک ورودی در نقشه ی سایت ایجاد می کند. این ورودی ها تنها شامل attribute مورد نظر یعنی location می شوند – نه lastmod، changefreq یا priority.

GenericSitemap

کلاس GenericSitemap با هر view جنریکی کار می کند که شما آن ها را فرا گرفته اید.

برای استفاده از آن، یک نمونه بسازید، ارسال در info_dict یکسان که شما به view های جنریک ارسال می کنید. تنها نیازمندی این است که دیکشنری یک ورودی queryset داشته باشد. ممکن است همچنین یک ورودی date_field داشته باشد که یک فیلد تاریخ برای شیء های بازیابی شده از queryset را تعیین می کند. این برای attribute، lastmode در نقشه ی سایت تولید شده مورد استفاده قرار خواهد گرفت. شما ممکن است آرگومان های کیورد priority و changefreq را برای سازنده ی GenericSitemap جهت تعیین این attribute ها برای همه ی URL ها ارسال کنید.

در زیر مثالی از یک URLconf که از هر دوی FlatPageSitemap و GenericSiteMap (با شیء فرضی Entry از پیش) استفاده می کند وجود دارد:

from django.conf.urls.defaults import *
from django.contrib.sitemaps import FlatPageSitemap, GenericSitemap
from mysite.blog.models import Entry

info_dict = {
    'queryset': Entry.objects.all(),
    'date_field': 'pub_date',
}

sitemaps = {
    'flatpages': FlatPageSitemap,
    'blog': GenericSitemap(info_dict, priority=0.6),
}

urlpatterns = patterns('',
    # some generic view using info_dict
    # ...

    # the sitemap
    (r'^sitemap\.xml$',
     'django.contrib.sitemaps.views.sitemap',
     {'sitemaps': sitemaps})
)

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

  • شما از دو view در URLconf خود استفاده می کنید: django.contrib.views.index و django.conrib.sitemaps.views.sitemap.
  • django.contrib.sitemaps.views.sitemap باید یک آرگومان کیورد section دریافت کند.

در زیر خطوط URLconf مربوط برای مثال قبلی وجود دارد:

(r'^sitemap.xml$',
 'django.contrib.sitemaps.views.index',
 {'sitemaps': sitemaps}),

(r'^sitemap-(?P<section>. ).xml$',
 'django.contrib.sitemaps.views.sitemap',
 {'sitemaps': sitemaps})

کد فوق به طور اتوماتیک یک فایل sitemap.xml تولید می کند که به هر دوی sitemap-flatpages.cml و sitemap-blog.xml رجوع می کند. کلاس های Sitemap و دیکشنری sitemaps هیچگاه تغییر نمی کنند.

پینگ کردن گوگل

ممکن است بخواهید گوگل را هنگامی که نقشه ی سایت شما تغییر می کند پینگ کنید، تا به آن اجازه دهید دوباره فهرست گذاری سایت شما را بداند. چارچوب یک تابع تنها برای فقط برای این کار تهیه کرده است: django.contrib.sitemaps.ping_google().

ping_google() یک آرگومان اختیاری دریافت می کند، sitemap_url، که باید URL مستقل از نقشه ی سایت سایت شما باشد (مانند '/sitemap.xml'). در صورتی که آرگومان تهیه نشده باشد، ping_google() تلاش می کند نقشه ی سایت شما را توسط انجام یک جستجوی بر عکس در URLconf بسنجد.

ping_google() در صورتی که نتواند URL نقشه ی سایت را تعیین کند، خطای django.contrib.sitemaps.SitemapNotFound را ایجاد خواهد کرد.

روش مفید برای فراخوانی ping_google به شکل یک متد save() مدل می باشد:

from django.contrib.sitemaps import ping_google

class Entry(models.Model):
    # ...
    def save(self, *args, **kwargs):
        super(Entry, self).save(*args, **kwargs)
        try:
            ping_google()
        except Exception:
            # Bare 'except' because we could get a variety
            # of HTTP-related exceptions.
            pass

راهکار موثر تر، فراخوانی ping_google() از یک اسکریپت cron یا برخی وظایف برنامه ریزی شده ی دیگر می باشد. تابع یک HTTP request برای سرور های گوگل ایجاد می کند، بنابراین شما ممکن نیست بخواهید برای معرفی در هر بار فراخوانی save() بار اضافه ی شبکه را داشته باشید.

در پایان، در صورتی که 'django.contrib.sitemaps' درون تنظیم INSTALLED_APPS می باشد، manage.py شما شامل یک دستور جدید، ping_google خواهد بود. این برای دسترسی به پینگ کردن درون خط فرمان مفید می باشد. برای مثال:

python manage.py ping_google /sitemap.xml

 

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


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

Session ها، کاربران، و عضویت

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

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

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

کوکی ها (Cookies)

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

اجازه دهید نگاهی به نحوه ای که این عمل ممکن است انجام شود بیاندازیم. هنگامی که شما مرورگر خود را باز می کنید و درون آن google.com را تایپ می کنید، مرورگر شما یک درخواست HTTP را به گوگل می فرستد که با چیزی شبیه به این شروع می شود:

GET / HTTP/1.1
Host: google.com
...

زمانی که گوگل پاسخ می دهد، یک پاسخ HTTP شبیه به پاسخ زیر خواهد بود:

HTTP/1.1 200 OK
Content-Type: text/html
Set-Cookie: PREF=ID=5b14f22bdaf1e81c:TM=1167000671:LM=1167000671;
            expires=Sun, 17-Jan-2038 19:14:07 GMT;
            path=/; domain=.google.com
Server: GWS/2.1
...

به هدر Set_Cookie دقت کنید. مرورگر شما مقدار کوکی که از این قرار است ذخیره خواهد کرد (PREF=ID=5b14f22bdafle81c:TM=1167000671:LM=1167000671) و در هر بار که شما به سایت دسترسی پیدا کنید آن را به گوگل بر می گرداند. بنابراین در مرتبه ی بعدی که به گوگل دسترسی پیدا می کنید، مرورگر شما یک درخواست مانند زیر را ارسال خواهد کرد:

GET / HTTP/1.1
Host: google.com
Cookie: PREF=ID=5b14f22bdaf1e81c:TM=1167000671:LM=1167000671
...

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

قرار دادن و گرفتن کوکی ها

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

خواندن کوکی ها ساده می باشد. هر شیء HttpRequest دارای یک شیء COOKIES می باشد که مانند دیکشنری عمل می کند؛ می توانید برای خواند هر کوکی که مرورگر به view ارسال می کند از آن استفاده کنید:

def show_color(request):
    if "favorite_color" in request.COOKIES:
        return HttpResponse("Your favorite color is %s" % \
            request.COOKIES["favorite_color"])
    else:
        return HttpResponse("You don't have a favorite color.")

نوشتن کوکی ها قدری پیچیده تر می باشد. نیاز است که از متد set_cookie() در یک شیء HttpResponse استفاده کنید. در زیر مثالی وجود دارد که کوکی favorite_color مستقر در یک پارامتر GET را قرار می دهد:

def set_color(request):
    if "favorite_color" in request.GET:

        # Create an HttpResponse object...
        response = HttpResponse("Your favorite color is now %s" % \
            request.GET["favorite_color"])

        # ... and set a cookie on the response
        response.set_cookie("favorite_color",
                            request.GET["favorite_color"])

        return response

    else:
        return HttpResponse("You didn't give a favorite color.")

می توانید همچنین یک تعداد از آرگومان های اختیاری را به response.set_cookie() که جنبه هایی از کوکی را کنترل می کند ارسال کنید. همانطور که در جدول زیر مشاهده می کنید.

پارامتر پایگاه داده توضیح
Max_age None عمر (بر حسب ثانیه) که کوکی باید زنده باشد. در صورتی که این پارامتر None باشد، کوکی تنها تا زمانی که مرورگر بسته شود زنده خواهد ماند.
expire None زمان و تاریخ واقعی ای، که کوکی باید از بین برود که باید در قالب بندی "Wdy, DD-Mth-YY HH:MM:SS GMT" باشد. در صورت داده این پارامتر، پارامتر max_age باز نویسی می شود.
path "/" پیشوند مسیری که کوکی برای آن معتبر است. مرورگرها کوکی را تنها به صفحاتی که تحت این مسیر پیشوند می باشد بر می گرداند، بنابراین شما می توانید از این برای جلوگیری از ارسال کوکی ها به بخش های دیگر سایت استفاده کنید.
این پارامتر به ویژه هنگامی که سطح زیادی زا دامنه های سایت را کنترل نمی کنید مفید است.
domain None دامنه ای که این کوکی برای آن معتبر است. می توانید از این پارامتر برای قرار دادن یک کوکی cross-domain استفاده کنید. برای مثال، domain=".example.com" یک کوکی قرار خواهد داد که توسط دامنه های www.example.com، www2.example.com و sub.domain.example.com قابل خواندن می باشد.
در صورتی که این پارامتر None قرار داده شود، یک کوکی تنها توسط دامنه ای که آن را قرار داده قابل خواندن خواهد بود.
secure False در صورتی که مقدار این پارامتر را True قرار داده شود، این پارامتر به مرورگر خواهد گفت که این کوکی را تنها برای صفحاتی که HTTPS می باشند در دسترس قرار بده.

The Mixed Blessing of Cookies

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

  • ذخیره سازی کوکی ها ارادی می باشد؛ یک کلاینت ملزم به قبول یا ذخیره کوکی نمی باشد. در واقع، تمام مرورگرها کاربران را قادر به کنترل سیاست خود برای قبول کردن کوکی ها می کند در صورتی که می خواهید تنها کوکی های واجبی که در وب وجود دارند را تماشا کنید، option مرورگر خود یعنی "prompt to accept every cookie" را روشن کنید.
  • با وجود تقریبا استفاده ی جهانی آنها، کوکی ها هنوز غیر قابل اطمینان تلقی می شوند. این بدان معنی است که توسعه دهندگان باید بررسی کنند که یک کاربر در واقع کوکی ها را قبل اعتماد به آن ها قبول کرده است.
  • کوکی ها (به ویژه آن هایی که در HTTPS ارسال نمی شوند) امن نمی باشند. چرا که داده ی HTTP به صورت متن ساده ارسال می شود، کوکی ها به شدت در برابر حملات جاسوسی آسیب پذیر می باشند. این بدین معنی است که، یک مهاجم جاسوس می تواند یک کوکی را قطع کرده و آن را بخواند. این بدان معنی است که نباید هرگز اطلاعات حساس را در یک کوکی ذخیره کرد.
  • حتی یک حمله ی مخرب تر که بک حمله ی man-in-the-middle معروف است وجود دارد، در جایی که یک مهاجم یک کوکی را قطع می کند و از برای نشان دادن خود به صورت یک کاربر دیگر استفاده می کند. آموزش امنیت به تفصیل این مسائل و راه ها جلوگیری از آن را توضیح داده است.
  • کوکی ها حتی از دریافت کنندگان در نظر گرفته ی خود نیز امن نمی باشد. اغلب مرورگر ها روشی ساده برای ویرایش محتوای کوکی های منحصر به فرد تهیه می کنند، و کاربران خبره می توانند همواره با استفاده از ابزاری مانند mechanize (http://wwwsearch.sourceforge.net/mechanize) درخواست های HTTP به صورت دستی بسازند.
  • بنابراین شما نمی توانید داده ی درون کوکی ها را که برای دستکاری حساس می باشند را ذخیره کنید. اشتباه رایج در این سناریو ذخیره چیزی شبیه به IsLoggedIn=1 در یک کوکی در هنگامی که کاربر وارد شده است می باشد. متعجب می شوید وقتی تعدادی از سایت ها این اشتباه طبیعی را انجام می دهند؛ فریب دادن سیستم های امنیتی این سایت ها تنها یک ثانیه طول می کشد.

فریم ورک یا چارچوب Session جنگو

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

این چارچوب session به شما اجازه می دهد داده ی دلخواه را بر اساس هر بازدید کننده ی سایت ذخیره و بازیابی کنید. این فریم ورک داده را در سمت سرور ذخیره می کند و ارسال و دریافت کوکی ها را حذف می کند. کوکی ها تنها از یک ID هش شده استفاده می کنند – نه خود داده – در نتیجه شما را از مشکلات رایج کوکی محافظت می کند.

اجازه دهید به نحوه ی فعال کردن session ها و استفاده کردن از آن ها در view ها بپردازیم.

فعال کردن Session ها

session ها از طریق قسمتی از middleware (به فرم مراجعه کنید) و مدل جنگو اجرا می شوند. جهت فعال کردن session ها، نیاز است مراحل زیر را انجام دهید:

  • تنظیم MIDDLEWARE_CLASSES را ویرایش کرده و اطمینان حاصل کنید که MIDDLEWARE_CLASSES حاوی 'django.contrib.sessions.middleware.SessionMiddleware'می باشد.
  • اطمینان حاصل کنید که 'django.contrib.sessions' درون تنظیم INSTALLED_APPS وجود دارد (و در صورتیکه ملزم به اضافه کردن آن هستید دستور manage.py syncdb را اجرا کنید).

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

در صورتی که نمی خواهید از session ها استفاده کنید، ممکن است بخواهید خط SessionMiddleware را از MIDDLEWARE_CLASSES و 'django.contrib.sessions'از INSTALLED_APPS خود حذف کنید. این شما را تنها از مقدار کمی از بار اضافی حفظ می کند، ولی هر قسمت کوچکی شمارش می شوند.

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

هنگامی که SessionMiddleware فعال شده است، هر شیء HttpRequest – اولین آرگومان برای هر تابع view جنگو – دارای یک attribute، seesion خواهد بود که یک شیء شبیه به دیکشنری می باشد. می تواند درست مثل یک دیکشنری معمولای از آن استفاده کنید. برای مثال در یک view می توانید کاری شبیه به زیر را انجام دهید:

# Set a session value:
request.session["fav_color"] = "blue"

# Get a session value -- this could be called in a different view,
# or many requests later (or both):
fav_color = request.session["fav_color"]

# Clear an item from the session:
del request.session["fav_color"]

# Check if the session has a given key:
if "fav_color" in request.session:
    ...

همچنین می توان از متدهای دیگر دیکشنری همانند keys() و items() در request.session استفاده کنید.

تعدادی قوانین ساده برای استفاده ی موثر از session های جنگو وجود دارد:

  • از رشته های معمولی پایتون به صورت کلیدهای دیکشنری در request.session (مخالف integer ها، شیء ها و غیره ...) استفاده کنید.
  • کلیدهای دیکشنری session که با یک خط تیره شروع می شوند، برای استفاده ی داخلی توسط جنگو رزرو شده اند. در عمل، فریم ورک تنها از تعداد کمی از متغیرهای session با خط تیره شروع شده استفاده می کند، ولی در صورتی که در مورد این قبیل متغیرهای اطلاعاتی ندارید (و اگر تمایل دارید با هر تغییراتی در خود جنگو مطابق باشید)، استفاده از پیشوندهای خط تیره، از تداخل جنگو با برنامه ی شما جلوگیری می کند.
  • برای مثال، از کلید session با نام _fav_color، مانند زیر استفاده نکنید:
request.session['_fav_color'] = 'blue' # Don't do this!

request.session را با یک شیء جدید جایگزین نکنید، و به attribute های آن دسترسی پیدا نکرده و چیزی در آن ها قرار ندهید. از آن مانند یک دیکشنری پایتون استفاده کنید. مثال:

request.session = some_other_object # Don't do this!

request.session.foo = 'bar' # Don't do this!

اجازه دهید مثال های کوچکی را ذکر کنیم. view ساده ی زیر بعد از این که کاربر یک کامنت را پست می کند مقدار True را در یک متغیر has_commented قرار می دهد. جلوگیری از پست کردن بیشتر از یک کامنت توسط کاربر ساده می باشد:

def post_comment(request):
    if request.method != 'POST':
        raise Http404('Only POSTs are allowed')

    if 'comment' not in request.POST:
        raise Http404('Comment not submitted')

    if request.session.get('has_commented', False):
        return HttpResponse("You've already commented.")

    c = comments.Comment(comment=request.POST['comment'])
    c.save()
    request.session['has_commented'] = True
    return HttpResponse('Thanks for your comment!')

view ساده ی ورودی یک عضو در سایت:

def login(request):
    if request.method != 'POST':
        raise Http404('Only POSTs are allowed')
    try:
        m = Member.objects.get(username=request.POST['username'])
        if m.password == request.POST['password']:
            request.session['member_id'] = m.id
            return HttpResponseRedirect('/you-are-logged-in/')
    except Member.DoesNotExist:
        return HttpResponse("Your username and password didn't match.")

و کد زیر خروج یک عضو از سایت که از طریق login() فوق وارد سایت شده است:

def logout(request):
    try:
        del request.session['member_id']
    except KeyError:
        pass
    return HttpResponse("You're logged out.")

نکته

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

آزمون کوکی های ارسال شده

همانطور که در بالا ذکر شد، نمی توان به هر کوکی ارسال شده ی مرورگر اطمینان کرد. بنابراین، برای راحتی کار، جنگو روشی ساده برای آزمون کوکی های ارسال شده ی مرورگر تهیه کرده است. تنها کافیست request.session.set_test_cookie() را در یک view فراخوانی کرده و request.session.test_cookie_worked() را در view بعدی بررسی کنید – نه در فراخوانی view همسان.

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

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

در زیر مثال کاربرد معمولی موضوع فوق وجود دارد:

def login(request):

    # If we submitted the form...
    if request.method == 'POST':

        # Check that the test cookie worked (we set it below):
        if request.session.test_cookie_worked():

            # The test cookie worked, so delete it.
            request.session.delete_test_cookie()

            # In practice, we'd need some logic to check username/password
            # here, but since this is an example...
            return HttpResponse("You're logged in.")

        # The test cookie failed, so display an error message. If this
        # were a real site, we'd want to display a friendlier message.
        else:
            return HttpResponse("Please enable cookies and try again.")

    # If we didn't post, send the test cookie along with the login form.
    request.session.set_test_cookie()
    return render_to_response('foo/login_form.html')

نکته

یک بار دیگر، توابع داخلی authentication این بررسی را برای شما انجام می دهند.

استفاده از Session ها خارج از view ها

به طور داخلی، هر session تنها یک مدل جنگوی معمولی تعریف شده در django.contrib.sessions.models می باشد. هر session توسط هش تصادفی کمتر یا بیشتر 32 حرفی در یک کوکی شناسایی می شود. به این دلیل که session یک مدل معمولی می باشد، می توان با استفاده از API معمولی پایگاه داده ی جنگو به session ها دسترسی پیدا کرد:

>>> from django.contrib.sessions.models import Session
>>> s = Session.objects.get(pk='2b1189a188b44ad18c35e113ac6ceead')
>>> s.expire_date
datetime.datetime(2005, 8, 20, 13, 35, 12)

برای بدست آوردن داده ی session واقعی، نیاز به فراخوانی get_decoded() می باشد. این موضوع ضروری می باشد، چرا که دیکشنری در قالب بندی رمزی شده ذخیره شده است:

>>> s.session_data
'KGRwMQpTJ19hdXRoX3VzZXJfaWQnCnAyCkkxCnMuMTExY2ZjODI2Yj...'
>>> s.get_decoded()
{'user_id': 42}

زمانی که Session ها ذخیره شده اند

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

# Session is modified.
request.session['foo'] = 'bar'

# Session is modified.
del request.session['foo']

# Session is modified.
request.session['foo'] = {}

# Gotcha: Session is NOT modified, because this alters
# request.session['foo'] instead of request.session.
request.session['foo']['bar'] = 'baz'

برای تغییر این رفتار پیشفرض، مقدار SESSION_SAVE_EVERY_REQUEST را True قرار دهید. در صورتی که SESSION_SAVE_EVERY_REQUEST، True باشد، جنگو در هر درخواست تنها درون پایگاه داده ذخیره می کند، حتی اگر تغییر نکرده باشد.

توجه داشته باشید که کوکی session تنها زمانی که یک session ساخته شده یا تغییر کرده باشد فرستاده می شود. در صورتی که SESSION_SAVE_EVERY_REQUEST، True باشد، کوکی session در هر درخواست فرستاده خواهد شد. به طور یکسان، بخش expires از یک کوکی session در هر بار که کوکی session فرستاده شود به روز رسانی می شود.

Session های Browser-Length در مقابل Session های مقاوم

ممکن است متوجه شده باشید که کوکی گوگل فرستاده شده به ما در ابتدای این آموزش حاوی expires=Sun، 17-Jan-2038 19:14:07 GMT; بود. کوکی ها به طور اختیاری می توانند حاوی یک تاریخ انقضا باشند که مرورگر را در هنگام حذف کوکی آگاه می سازد. در صورتی که یک کوکی حاوی مقدار انقضا نباشد، زمانی که کاربر پنجره ی مرورگر را ببندد از بین می رود. می توان رفتار فریم ورک یا چارچوب session را رابطه با تنظیم SESSION_EXPIRE_AT_BROWSER_CLOSE کنترل کرد.

به طور پیشفرض، مقدار SESSION_EXPIRE_AT_BROWSER_CLOSE، False در نظر گرفته شده است، که بدین معنی می باشد که کوکی های session در مرورگرهای کاربران برای SESSION_COOKIE_AGE ثانیه (که پیشفرض آن دو هفته یا 1,209,600 ثانیه) می باشد. در صورتی که نمی خواهید مردم در هر بار که مرورگر را باز می گنند به سایت log in نشوند از این تنظیم می توانید استفاده کنید.

در صورتی که مقدار SESSION_EXPIRE_AT_BROWSER_CLOSE، True باشد، جنگو از کوکی های browser-length استفاده خواهد کرد.

تنظیمات دیگر Session

در کنار تنظیمات ذکر شده، چند تنظیم دیگر بر نحوه ی استفاده ی فریم ورک session جنگو از کوکی ها تاثیر می گذارد، همانطور که در جدول زیر نشان داده شده است.

تنظیم توضیح پیشفرض
SESSION_COOKIE_DOMAIN دامنه ی مورد استفاده برای کوکی های session. برای این تنظیم یک رشته مانند ".example.com" برای کوکی های cross‑domain قرار دهید، یا برای یک کوکی استاندارد از None استفاده کنید. None
SESSION_COOKIE_NAME نام کوکی ای که برای session ها استفاده می شود. این می تواند هر رشته ای باشد. "sessionid"
SESSION_COOKIE_SECURE در دست ترجمه/تالیف .... در صورتی که این تنظیم True باید، کوکی به صورت "امن" علامت گذاری خواهد شد، که بدین معناست که مرورگر ها مطمئن خواهند بود که کوکی تنها از طریق HTTPS ارسال شده است. False

جزئیات فنی

محض کنجکاوی، در زیر چند نکته ی فنی درباره ی کار عملکرد داخلی فریم ورک session بیان شده است:

  • در دست ترجمه/تالیف .... برای کسب اطلاعات درمورد کارکرد ماژول داخلی پایتون یعنی pickle مستندات پایتون مراجعه کنید.
  • داده ی session در جدول پایگاه داده با نام django_session ذخیره می شود.
  • در دست ترجمه/تالیف .... در صورتی که هرگز به request.session دسترسی ندارید، جنگو به آن جدول پایگاه داده مراجعه می کند.
  • جنگو در صورت نیاز یک کوکی ارسال می کند. در صورتی که هیچ داده ی session ای قرار نگرفته باشد، جنگو کوکی session ای ارسال نخواهد کرد (مگر اینکه مقدار SESSION_SAVE_EVERY_REQUEST، True قرار داده شده باشد).
  • فریم ورک session جنگو به طور کلی و تنها بر اساس کوکی می باشد. در دست ترجمه/تالیف ....
  • این یک تصمیم طراحی بین المللی می باشد. قرار دادن session ها در URL ها نه تنها باعث زشت شدن URL ها می شود، بلکه همچنین سایت شما را برای یک فرم خاص از سرقت هدر Referer آسیب پذیر می سازد.

در صورتی که هنوز کنجکاو هستید، منبع بسیار آسان می باشد؛ برای اطلاعات بیشتر به django.contrib.sessions نگاهی بیاندازید.

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

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

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

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

در ادامه ی این نیازمندی ها، سیستم auth/auth جنگو حاوی تعدادی از بخش ها می باشد:

  • کاربران: افرادی که در سایت شما عضویت دارند
  • حق دسترسی ها: پرچم های دودویی (yes/no) طراحی شده برای مشخص کردن اینکه آیا یک کاربر می تواند یک وظیفه ی خاص را انجام دهد
  • گروه ها: روشی عمومی جهت بکار بردن لیبل ها و حق دسترسی به بیشتر از یک کاربر
  • پیام ها: روشی ساده برای به صف کردن و نمایش سیستم پیام ها به کاربران

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

فعال ساختن پشتیبانی تصدیق

همانند ابزار session، پشتیبانی تصدیق به صورت یک برنامه ی جنگو در django.contrib که نیاز به نصب شدن دارد همراه است. همچنین مانند ابزار session، باید به طور پیشفرض نصب شده باشد، ولی در صورتی که شما آن را حذف کرده باشید، نیاز است مراحل زیر را برای نصب آن دنبال کنید:

  • اطمینان حاصل کنید فریم ورک session همانطور که قبلا در این آموزش از کتاب توضیح داده شد نصب شده باشد. پیگیری کاربران بدیهی است که مستلزم کوکی ها می باشد، و در نتیجه در فریم ورک session ساخته می شود.
  • 'django.contrib.auth' را در تنظیم INSTALLED_APPS قرار داده و دستور manage.py syncdb را جهت نصب جداول پایگاه داده ی مناسب اجرا کنید.
  • از وجود 'django.contrib.auth.middleware.AuthenticationMiddleware'درون تنظیم MIDDLEWARE_CLASSES اطمینان حاصل کنید – بعد از SessionMiddleware.

با انجام مراحل فوق، همه چیز برای سر و کار داشتن با کاربران در توابع view آماده می باشد. رابط اصلی که شما برای دسترسی به کاربران در یک view از آن استفاده خواهید کرد request.user می باشد؛ این یک شیء است که کاربر فعلی وارد شده به سایت را نشان می دهد. در صورتی که کاربر وارد نشده باشد، به جای آن یک شیء AnonymousUser خواهد بود (برای جزئیات بیشتر به ادامه ی این بخش نگاه بیاندازید).

می توانید به سادگی در صورتی که یک کاربر وارد شده است، با متد is_authenticated() تشخیص دهید:

if request.user.is_authenticated():
    # Do something for authenticated users.
else:
    # Do something for anonymous users.

استفاده از کاربران

هنگامی که شما یک کاربر دارید – اغلب از request.user، ولی از طریق یکی از روش های مختصر توضیح داده شده – تعدادی از فیلدها و متدهای در دسترس در آن شیء دارید. شیء های AnonymousUser برخی از این رابط ها را تقلید کرده است، ولی نه تمام آن را، بنابراین باید همواره user.is_authenticated() را قبل از آنکه کاربری را که با آن سر و کار دارید را با حسن نیت تصور کنید بررسی کنید.. جدول 3-14 و 4-14 فیلد ها و متد ها را به ترتیب در شیء های User لیست کرده است.

فیلد توضیح
username الزامی؛ 30 حرف یا کمتر. تنها حروف الفبایی (حروف الفبا، عددها، و خط تیره)
First_name اختیاری؛ 30 حرف یا کمتر
Last_name اختیاری؛ 30 حرف یا کمتر
email اختیاری؛ آدرس پست الکترونیک
password الزامی؛ یک هش و ابر داده از رمز عبور (جنگو رمز عبور خام را ذخیره نمی کند). برای اطلاعات بیشتر به بخش "رمزهای عبور" مراجعه کنید.
Is_staff Boolean. اینکه کاربر می تواند به سایت مدیر دسترسی پیدا کند یا خیر را مشخص می کند.
Is_active Boolean. این که این حساب می تواند برای ورود استفاده شده باشد یا خیر را مشخص می کند. این پرچم را به جای حذف حساب ها مقدار False قرار دهید.
Is_superuser Boolean. اینکه این کاربر دارای تمام دسترسی ها بدون اختصاص دادن آن ها به طور واضح می باشد یا خیر را مشخص می کند.
Last_login یک datetime از آخرین ورود کاربر. به طور پیشفرض زمان/تاریخ فعلی در آن قرار دارد.
date_joined تشخیص datetime زمانی که حساب ساخته شده است. به طور پیشفرض زمانی که حساب ساخته شده است مقدار آن زمان/تاریخ فعلی می باشد.

 

 

متد توضیح
is_authenticated() همواره شیء های User "واقعی" مقدار True بر می گرداند. در صورتی که کاربر تصدیق شده باشد این روشی برای گفتن می باشد. این هیچ اشاره ای به حق دسترسی ها نداشته و فعال بودن کاربر را نیز بررسی نمی کند. تنها این را نشان می دهد که کاربر با موفقیت تصدیق شده است.
is_anonymous() تنها برای شیء های AnonylousUser مقدار True بر می گرداند (و مقدار False برای شیء های User "واقعی"). عموما، باید استفاده از is_authenticated() را برای این متد ترجیح دهید.
get_full_name() first_name را بعلاوه ی last_name، با یک فاصله در بین آن ها بر می گرداند.
set_password(passwd) رمز عبور کاربر را برای رشته ی خام داده شده قرار می دهد، در دست ترجمه/تالیف .... در واقع این متد شیء User را ذخیره نمی کند.
check_password(passwd) در صورتی که رشته ی خام داده شده رمز عبور درست کاربر باشد مقدار True بر می گرداند. در دست ترجمه/تالیف ....
get_group_permissions() لیستی از رشته های حق دسترسی که کاربر از طریق گروهی که به آن تعلق دارد در اختیار دارد را بر می گرداند.
get_all_permissions() لیستی از رشته ی حق دسترسی که کاربر دارد، هم حق دسترسی گروه هم کاربر.
has_perm(perm) در صورتی که کاربر دارای حق دسترسی مشخص شده باشد مقدار True بر می گرداند و perm قالب بندی "package.codename" می باشد. در صورتی که کاربر غیر فعال باشد، این متد همواره مقدار False بر می گرداند.
has_perms(perm_list) در صورتی که کاربر دارای تمام حق دسترسی های تعیین شده باشد مقدار True بر می گرداند. در صورتی که کاربر غیر فعال باشد، هموراه این متد مقدار False بر می گرداند.
has_module_perms(app_lable) در صورتی که کاربر هیچ حق دسترسی ای در app_lable داده شده نداشته باشد مقدار True بر می گرداند. در صورتی که کاربر غیر فعال باشد، این متد هموراه مقدار False بر می گرداند.
get_and_delete_messages() لیستی از شیء های Message در صف کاربر بر می گرداند و پیام های صف را حذف می کند.
email_user(subj, msg) یک پست الکترونیکی به کاربر می فرستد. این پست الکترونیکی از تنظیم DEFAULT_FROM_EMAIL فرستاده می شود. همچنین می توانید یک آرگومان سوم به نام from_email جهت override آدرس From در پست الکترونیکی ارسال کنید.

 

 

در پایان، شیء های User دارای دو فیلد many-to-many می باشند: groups و permissions. شیء های User می توانند همانند فیلدهای many-to-many دیگر به شیء های مربوط به خود دسترسی پیدا کنند:

# Set a user's groups:
myuser.groups = group_list

# Add a user to some groups:
myuser.groups.add(group1, group2,...)

# Remove a user from some groups:
myuser.groups.remove(group1, group2,...)

# Remove a user from all groups:
myuser.groups.clear()

# Permissions work the same way
myuser.permissions = permission_list
myuser.permissions.add(permission1, permission2, ...)
myuser.permissions.remove(permission1, permission2, ...)
myuser.permissions.clear()

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


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

وارد و خارج شدن از سایت

جنگو برای کنترل ورود و خروج از سایت، برخی توابع داخلی (و چند فوت و فن جذاب) را ارائه کرده است، ولی قبل از آن، اجازه دهید نگاهی به نحوه ی ورود و خروج از سایت را "به صورت دستی" بیاندازیم. جنگو جهت انجام این اعمال در django.contrib.auth دو تابع با نام های authenticate() و login() را ارائه کرده است.

جهت تصدیق یک نام کاربری و رمز عبور داده شده، از authenticate() استفاده کنید. این تابع دو آرگومان کیورد username و password را دریافت می کند، و در صورتی که رمز عبور برای نام کاربری داده شده معتبر باشد، یک شیء User بر می گرداند. در صورتی که رمز عبور معتبر نباشد، authenticate() مقدار None بر می گرداند:

>>> from django.contrib import auth
>>> user = auth.authenticate(username='john', password='secret')
>>> if user is not None:
...     print "Correct!"
... else:
...     print "Invalid password."

authenticate() تنها اعتبار کاربر را تایید می کند. جهت ورود کاربر، از تابع loging() استفاده کنید. این تابع یک شیء HttpRequest و یک شیء User دریافت کرده و با استفاده از فریم ورک session، ID کاربر را درون session ذخیره می کند.

مثال زیر نحوه ی استفاده از هر دوی تابع authenticate() و login() را درون یک تابع view نشان می دهد:

from django.contrib import auth

def login_view(request):
    username = request.POST.get('username', '')
    password = request.POST.get('password', '')
    user = auth.authenticate(username=username, password=password)
    if user is not None and user.is_active:
        # Correct password, and the user is marked "active"
        auth.login(request, user)
        # Redirect to a success page.
        return HttpResponseRedirect("/account/loggedin/")
    else:
        # Show an error page
        return HttpResponseRedirect("/account/invalid/")

جهت خروج یک کاربر، از django.contrib.auth.logout() داخل view خود استفاده کنید. این تابع یک شیء HttpRequest دریافت کرده و هیچ مقداری بر نمی گرداند:

from django.contrib import auth

def logout_view(request):
    auth.logout(request)
    # Redirect to a success page.
    return HttpResponseRedirect("/account/loggedout/")

دقت داشته باشید که auth.logout() در صورتی که کاربر وارد سایت نشده باشد، هیچ خطایی ایجاد نمی کند.

در عمل، نیازی به نوشتن توابع login/logout خودتان نخواهید داشت؛ سیستم تصدیق مجموعه ای از view ها برای کنترل ورود و خروج به طور عمومی ارائه کرده است. اولین گام در استفاده از این view های تصدیق، وصل کردن آن ها به URLconf می باشد. نیاز است تکه کد زیر را اضافه کنید:

from django.contrib.auth.views import login, logout

urlpatterns = patterns('',
    # existing patterns here...
    (r'^accounts/login/$',  login),
    (r'^accounts/logout/$', logout),
)

/accounts/login/ و /accounts/logout/، URL های پیشفرض می باشند که جنگو برای این view استفاده می کند.

به طور پیشفرض view، login یک template را در registeration/login.html، render می کند (می توانید نام این template را از طریق ارسال یک آرگومان view اضافه تغییر دهید، ``template_name``). این فرم نیاز دارد حاوی یک فیلد username و یک فیلد password باشد. یک template ساده ممکن است چیزی شبیه به کد زیر باشد:

{% extends "base.html" %}

{% block content %}

  {% if form.errors %}
    <p class="error">Sorry, that's not a valid username or password</p>
  {% endif %}

  <form action="" method="post">
    <label for="username">User name:</label>
    <input type="text" name="username" value="" id="username">
    <label for="password">Password:</label>
    <input type="password" name="password" value="" id="password">

    <input type="submit" value="login">
    <input type="hidden" name="next" value="{{ next|escape }}">
  </form>

{% endblock %}

در صورتی که وارد شدن موفقیت آمیز باشد، کاربر به طور پیشفرض به /accounts/profile/ تغییر مسیر داده خواهد شد. می توان این حالت را توسط یک فیلد hidden به نام next با URL جهت تغییر مسیر بعد از ورود override کرد. همچنین می تواند این مقدار را به صورت یک پارامتر GET به view، login ارسال کرده و آن به صورت خودکار به صورت متغیر next به context اضافه خواهد شد که می توانید درون آن فیلد hidden آن را درج کنید.

view، logout کمی متفاوت تر عمل می کند. به طور پیشفرض این view یک template در registration/loggedout.html (که معمولا حاوی یک پیام "you've successfully logged out" می باشد) را render می کند. می توان view را با یک آرگومان اضافه با نام next_page فراخوانی کرد، که view را جهت تغییر مسیر بعد از خروج راهنمایی خواهد کرد.

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

به طور ساده، راه خام جهت محدود کردن دسترسی برای صفحات، بررسی request.user.is_authenticated() و تغییر مسیر به یک صفحه ی ورود می باشد:

from django.http import HttpResponseRedirect

def my_view(request):
    if not request.user.is_authenticated():
        return HttpResponseRedirect('/accounts/login/?next=%s' % request.path)
    # ...

یا شاید نمایش یک پیام خطا:

def my_view(request):
    if not request.user.is_authenticated():
        return render_to_response('myapp/login_error.html')
    # ...

به صورت یک میانبر، می توان از decorator مناسب login_required استفاده کرد:

from django.contrib.auth.decorators import login_required

@login_required
def my_view(request):
    # ...

login_required به شکل زیر عمل می کند:

  • در صورتی که کاربر وارد نشده باشد، به /accounts/login/ تغییر مسیر داده می شود، ارسال شدن مسیر URL فعلی در رشته ی کوئری به صورت next، برای مثال: /accounts/login/?next=/polls/3/.
  • در صورتی که کاربر وارد شده باشد، view به صورت معمول اجرا می شود. کد view می تواند.

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

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

روش خام اجرای آزمون در request.user به طور مستقیم درون view می باشد. برای مثال، view زیر برای اطمینان از این که کاربر، وارد شده و دارای حق دسترسی polls.can_vote می باشد یا خیر:

def vote(request):
    if request.user.is_authenticated() and request.user.has_perm('polls.can_vote')):
        # vote here
    else:
        return HttpResponse("You can't vote in this poll.")

بار دیگر، جنگو یک میانبر با نام user_passes_test ارائه کرده است. این میانبر آرگومان هایی دریافت کرده و یک decorator تخصص یافته برای وضعیت خاص شما تولید می کند:

def user_can_vote(user):
    return user.is_authenticated() and user.has_perm("polls.can_vote")

@user_passes_test(user_can_vote, login_url="/login/")
def vote(request):
    # Code here can assume a logged-in user with the correct permission.
    ...

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

همچنین در این مثال آرگومان دوم (اختیاری) نشان داده شده است، که اجازه ی تعیین URL برای صفحه ی خودتان را می دهد (/accounts/login/ به طور پیشفرض). در صورتی که کاربر آزمون را نگذرانده باشد؛ سپس decorator، user_passes_test کاربر را به login_url تغییر مسیر خواهد داد.

به دلیل آن که بررسی این که یک کاربر دارای یک حق دسترسی خاص است یا خیر یک وظیفه ی نسبتا مشترک می باشد، جنگو یک میانبر برای آن ارائه کرده است که یک decorator با نام permission_required() می باشد.

from django.contrib.auth.decorators import permission_required

@permission_required('polls.can_vote', login_url="/login/")
def vote(request):
    # ...

دقت داشته باشد که همچنین permission_required() یک پارامتر اختیاری login_url دریافت می کند؛ که این نیز به طور پیشفرض '/accounts/login/' می باشد.

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

یکی از سوالات تکراری پرسیده شده در لیست کاربران جنگو در مورد محدود کردن دسترسی برای یک view جنریک می باشد. برای جواب به این سوال؛ نیاز به نوشتن یک thin wrapper در اطراف view و اشاره URLconf به wrapper خود به جای خود generic view خواهید داشت:

from django.contrib.auth.decorators import login_required
from django.views.generic.date_based import object_detail

@login_required
def limited_object_detail(*args, **kwargs):
    return object_detail(*args, **kwargs)

البته که می توانید، login_required را با هر decorator محدودیت دیگری جا به جا کنید.

مدیریت کاربران، حق دسترسی ها و گروه ها

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

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

ساختن کاربران

ساختن کاربران با تابع کمکی create_user:

>>> from django.contrib.auth.models import User
>>> user = User.objects.create_user(username='john',
...                                 email='jlennon@beatles.com',
...                                 password='glass onion')

در این نقطه، user یک رابط نمونه ی User حاضر برای ذخیره شدن در پایگاه داده (create_user() در واقع save() خودش را فراخوانی نمی کند) می باشد. همچنین می توانید قبل از ذخیره attribute های آن را تغییر دهید:

>>> user.is_staff = True
>>> user.save()

تغییر رمزهای عبور

می توان یک رمز عبور را با set_password() تغییر داد:

>>> user = User.objects.get(username='john')
>>> user.set_password('goo goo goo joob')
>>> user.save()

attribute، password را به طور مستقیم قرار ندهید، مگر اینکه کاملا بدانید که چه کار می کنید. رمز عبور در واقع به صورت یک salted hash دخیره شده و در نتیجه نمی تواند به طور مستقیم ویرایش شود.

به طور رسمی تر، attribute، password از یک شیء User یک رشته در این قالب بندی می باشد:

hashtype$salt$hash

آن یک نوع hash، salt و خود hash، جدا شده توسط حرف ($) می باشد.

hashtype همچنین sha1 (پیشفرض) یا md5 می باشد، الگوریتم استفاده شده برای انجام یک hash یک طرفه از رمز عبور. salt یک رشته ی تصادفی استفاده شده برای اضافه شدن به رمز عبور خام برای ساختن hash برای مثال:

sha1$a1976$a36cc8cbf81742a8fb52e221aaeab48ed7f58ab4

توابع User.set.password() و User.check_password() این مقادیر را در پشت صحنه بررسی کرده و قرار می دهند.

hash های اضافه شده

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

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

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

بدتر از آن rainbow table ها می باشند که به صورت عمومی در دسترس هستند، یا پایگاه های داده ی قبل از محاسبه ی hash ها از میلیون ها رمز عبور. با یک rainbow table، یک مهاجم با تجربه می تواند در چند ثانیه تمام رمزهای عبور را بشکند.

اضافه کردن یک salt – اساسا یک مقدار اولیه تصادفی – برای hash ذخیره شده یک لایه ی دیگری را جهت سختی در شکستن رمزهای عبور اضافه می کند. زیرا salt ها در هر رمزعبوری متفاوت می باشند، آن ها همچنین از استفاده ی rainbow table جلوگیری می کنند؛ در نتیجه مهاجمان مجبور به سقوط در یک حمله ی brute-force می شوند، در دست ترجمه/تالیف ....

هنگامی hash های salt شده امن ترین روش برای ذخیره رمزهای عبور نیستند، یک حد وسط بین امنیت و راحتی می باشند.

کنترل عضویت

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

در ساده طرین حالت، می توان یک view کوچک برای اطلاعات الزامی کاربر و ساختن آن کاربران تهیه کرد. جنگو یک فرم داخلی ارائه کرده است که می توان برای این منظور از آن استفاده کرد، که در مثال زیر استفاده خواهیم کرد:

from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response

def register(request):
    if request.method == 'POST':
        form = UserCreationForm(request.POST)
        if form.is_valid():
            new_user = form.save()
            return HttpResponseRedirect("/books/")
    else:
        form = UserCreationForm()
    return render_to_response("registration/register.html", {
        'form': form,
    })

این فرم یک template با نام registration/register.html را فرض می کند. در اینجا یک مثال از template مورد نظر وجود دارد:

{% extends "base.html" %}

{% block title %}Create an account{% endblock %}

{% block content %}
  <h1>Create an account</h1>

  <form action="" method="post">
      {{ form.as_p }}
      <input type="submit" value="Create the account">
  </form>
{% endblock %}

استفاده از تصدیق داده در Template ها

کاربر وارد شده ی فعلی و حق دسترسی های وی، زمانی که از RequestContext (آموزش template پیشرفته را نگاه کنید) استفاده می کنید در template context در دسترس می باشد.

نکته

از نظر فنی، این متغیرها تنها در صورتی که شما از RequestContext استفاده کنید و تنظیم TEMPLATE_CONTEXT_PROCESSORS حاوی "django.core.context_processors.auth" باشد (که به طور پیشفرض این طور است) در template context در دسترس می باشند. بار دیگر برای اطلاعات بیشتر می توانید به آموزش template پیشرفته مراجعه کنید.

هنگامی که از RequestContext استفاده می کنید، کاربر فعلی (که می تواند هم یک نمونه از User یا یک نمونه ی AnonymousUser باشد) در متغیر template {{ user }} ذخیره می شود:

{% if user.is_authenticated %}
  <p>Welcome, {{ user.username }}. Thanks for logging in.</p>
{% else %}
  <p>Welcome, new user. Please log in.</p>
{% endif %}

حق دسترسی های این کاربر در متغیر template {{ perms }} ذخیره شده اند. این یک پروکسی template‑friendly برای تعدادی از متدهای حق دسترسی می باشد که به طور خلاصه توضیح داده شده است.

برای استفاده از شیء perms دو روش وجود دارد. می توان در صورتی که کاربر دارای هیچ حق دسترسی برای برخی برنامه های داده شده نداشته باشد، برای بررسی آن از چیزی شبیه به این {% if perms.polls %} استفاده کرد، یا می توان در صورتی که کاربر دارای حق دسترسی خاصی می باشد برای بررسی آن از چیزی شبیه به این {% if perms.polls.can_vote %} استفاده کرد.

در نتیجه، می توان حق دسترسی ها را در عبارت template {% if %} بررسی کرد:

{% if perms.polls %}
  <p>You have permission to do something in the polls app.</p>
  {% if perms.polls.can_vote %}
    <p>You can vote!</p>
  {% endif %}
{% else %}
  <p>You don't have permission to do anything in the polls app.</p>
{% endif %}

حق دسترسی ها، گروه ها و پیام ها

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

حق دسترسی ها

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

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

  • دسترسی به view فرم "add"، و اضافه کردن یک شیء که محدود به کاربران با حق دسترسی add برای آن نوع از شیء.
  • دسترسی به view لیست تغییر، view فرم "change"، و تغییر یک شیء که به کاربران با حق دسترسی change برای آن نوع شیء محدود شده است.
  • دسترسی به حذف یک شیء محدود شده به کاربران با حق دسترسی delete برای آن نوع از شیء.

حق دسترسی ها به صورت globally برای هر نوع از آجکت، نه هر نمونه ی خاص از شیء قرار داده شده اند. برای مثال، می توان گفت "Mary قادر است اخبار را تغییر دهد" ولی حق دسترسی ها اجازه نمی دهد که به عنوان مثال بگویید "Mary قادر است اخبار تغییر دهید، ولی تنها آنهایی را که خود او ساخته است" یا "Mary قادر است تنها اخباری را تغییر دهید که دارای یک وضعیت خاصی، انتشار یا ID خاص باشد."

این ها سه حق دسترسی اساسی می باشد – اضافه کردن، تغییر دادن، و حذف کردن – که به طور خودکار برای هر مدل جنگو ایجاد شده اند. در پشت صحنه، این حق دسترسی ها هنگامی که شما دستور manage.py sycdb را اجرا می کنید درون جدول پایگاه داده با نام auth_permission اضافه می شوند.

این حق دسترسی ها فرمی از "._" می باشند. بدین معنی که اگر دارای یک برنامه ی polls (نظرسنجی) با یک مدل Choice می باشید، حق دسترسی های "polls.add_choice"، "polls.change_choice" و "polls.delete_choice" را بدست خواهید آورد.

درست مثل کاربران، حق دسترسی ها در یک مدل جنگو موجود در django.contrib.auth.models اجرا شده اند. این یعنی این که می توان در صورت تمایل ارتباط با permission ها، به طور مستقیم از API پایگاه داده ی جنگو استفاده کرد.

گروه ها

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

یک کاربر در یک گروه به طور خودکار دارای حق دسترسی های داده به آن گروه می باشد. برای مثال، در صورتی که گروه Site editors دارای حق دسترسی can_edit_home_page باشد، هر کاربر در آن گروه آن حق دسترسی را خواهد داشت.

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

همانند کاربران، ساده ترین روش برای مدیریت گروه ها، از طریق رابط مدیر می باشد. هر چند، گروه ها نیز تنها مدل های جنگو می باشند که در django.contrib.auth.models وجود دارند. بنابراین شما می توانید همواره برای سرو کار داشتن با گروه ها در سطح پایین از API پایگاه داده ی جنگو استفاده کنید.

پیام ها

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

پیام ها توسط رابط مدیر جنگو بعد از اعمال موفقیت آمیز استفاده می شوند. زمانی که شما یک آجکت ایجاد می کند، متوجه ی یک پیام "The object was created successfully" در بالای صفحه ی مدیر خواهید شد.

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

  • جهت ایجاد یک پیام جدید، از user.message_set.create(message='message_text') استفاده کنید.
  • جهت بازیابی/حدف پیام ها، از user.get_and_delete_messages() استفاده کنید، که یک لیست از شیء های Message در صف کاربران (در صورت وجود) بر می گرداند و پیام های از صف را حذف می کند.

در مثال view زیر، سیستم یک پیام برای کاربر بعد از ساختن یک playlist ذخیره می کند:

def create_playlist(request, songs):
    # Create the playlist with the given songs.
    # ...
    request.user.message_set.create(
        message="Your playlist was added successfully."
    )
    return render_to_response("playlists/create.html",
        context_instance=RequestContext(request))

هنگامی که شما از RequestContext استفاده می کند، کاربر وارد شده ی فعلی و پیام او در template context به صورت متغیر template {{ message }} در دسترس هستند. در زیر یک مثال از کد template وجود دارد که پیام ها را نمایش می دهد:

{% if messages %}
<ul>
    {% for message in messages %}
    <li>{{ message }}</li>
    {% endfor %}
</ul>
{% endif %}

دقت داشته باشید که RequestContext در پشت صحنه get_and_delete_messages را فراخوانی می کند، بنابراین هر پیامی حذف شده خواهد بود حتی اگر آن ها را نمایش ندهید.

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

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


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

Caching در جنگو

هر بار که یک کاربر یک صفحه را درخواست می کند، وب سرور تمام محاسبات را ایجاد می کند – از کوئری های پایگاه داده جهت render کردن template برای business logic – برای ساختن صفحه ای که بازدید کنندگان سایت می بینند. این حرکت از نظر بار اضافی بسیار پر خرج تر و سنگین تر از خواندن فایل از filesystem می باشد.

برای اغلب برنامه های وب، این بار اضافی یک درگیری بزرگی به حساب نمی آید. اغلب برنامه های وب washingtonpost.com یا slashdot.org نیستند؛ آن ها وب سایت هایی یا اندازهای کوچک و متوسط و با ترافیکی به همین شکل می باشند. ولی برای سایت های با ترافیک بالا، حذف بارهای اضافی تا حد ممکن یک ضرورت به حساب می آید.

در آنجا بود که cashing بوجود آمد.

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

given a URL, try finding that page in the cache
if the page is in the cache:
    return the cached page
else:
    generate the page
    save the generated page in the cache (for next time)
    return the generated page

فریم ورک یا چارچوب جنگو یک سیستم قدرتمند cache را ارائه می کند که اجازه می دهد صفحات پویا را به طوری که اجباری برای مورد محاسبه قرار دادن برای هر درخواست نداشته باشید ذخیره کنید. برای راحتی، جنگو (Django) سطح های متفاوتی از cache به صورت دانه دانه را ارائه می دهد: می توان خروجی view های خاص را cache کرد، می توان تنها قسمت هایی که برای تولید مشکل می باشند را cache کرد، یا می توان تمام سایت را cache کرد.

همچنین جنگو با cache ها "upstream" به خوبی کار می کند، مانند Squid (http://www.squid-cache) و cache های بر پایه ی مرورگر. این ها انواعی از cache هایی هستند که به طور مستقیم کنترل نمی شوند ولی می توان تذکراتی (از طریق HTTP headers) درباره ی قسمت هایی از سایت که باید cache شده باشد و نحوه ی آن تهیه کرد.

نصب کردن Cache

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

اولویت cache شما در تنظیم CACHE_BACKEND درون فایل تنظیمات می باشد. در زیر توضیحی از تمام مقادیر قابل دسترس برای CACHE_BACKEND وجود دارد.

Memcached

تاکنون سریع ترین، موثرترین نوع cache در دسترس برای جنگو، Memcached یک فریم ورک cache بر پایه ی حافظه می باشد که در ابتدا برای کنترل بارگذاری های بالا در LiveJournal.com و به دنبال آن (در دست ترجمه ...). این نوع cache توسط سایت هایی از قبیل Facebook و Wikipedia جهت کاهش دسترسی پایگاه داده و به طور چشمگیری افزایش کارایی سایت استفاده شده است.

Memcached به صورت آزاد و مجانی در

محتوای مخفی

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

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

  • سریع ترین آپشن در دسترس یک ماژول با نام cmemchache می باشد، که در لینک

    محتوای مخفی

      برای مشاهده محتوای مخفی می بایست در انجمن ثبت نام کنید.
    در دسترس می باشد.
  • در صورتی که نمی توانید cmemcache را نصب کنید، می توانید python‑memcached را که در لینک

    محتوای مخفی

      برای مشاهده محتوای مخفی می بایست در انجمن ثبت نام کنید.
    در دسترس می باشد را نصب کنید. در صورتی که URL دیگر معتبر نباشد، تنها کافیست به وب سایت Memcached مراجعه کرده (http://www.danga.com/memcached/) و اتصالات پایتون را از بخش "Client APIs" به دست آورید.

جهت استفاده Memcached با جنگو، CACHE_BACKEND را با مقدار memcached://ip:port/ تنظیم کنید، جایی که ip آدرس IP Memcached daemon و port، پورت Memcached ای می باشد که در حال اجرا است.

در مثال زیر، Memcached در localhost (127.0.0.1) پورت 11211 در حال اجرا می باشد:

CACHE_BACKEND = 'memcached://127.0.0.1:11211/'

یکی از ویژگی های بسیار خوب Memcached، توانایی آن برای به اشتراک گذاشتن cache در سرتاسر چندین سرور می باشد. بدین معنی که شما می توانید Memcached daemon ها را در چندین ماشین اجرا کرده و برنامه با گروهی از ماشین به صورت یک cache تنها رفتار خواهد کرد، بدون نیاز به مقادیر cache تکراری در هر ماشین. جهت بهره بردن از این خصوصیت، تمام آدرس های سرورها را در CACHE_BACKEND که با علامت (;) از هم جدا شده اند قرار دهید.

در مثال زیر، cache در سرتاسر نمونه های Memcachedd در حال اجرا در آدرس IP های 172.19.26.240 و 172.19.26.242 هر دو در پورت 11211 به اشتراک گذاشته شده اند.

CACHE_BACKEND = 'memcached://172.19.26.240:11211;172.19.26.242:11211/'

در مثال زیر، cache در سرتاسر نمونه های Memcached در حال اجرا در آدرس IP های 172.19.26.240 (پورت 11211) و 172.19.26.242 (پورت 11212) و 172.19.26.244 (پورت 11213) به اشتراک گذاشته شده است.

CACHE_BACKEND = 'memcached://172.19.26.240:11211;172.19.26.242:11212;172.19.26.244:11213/'

نکته آخر درباره ی Memcached این است که، cache بر پایه ی حافظه دارای یک اشکال نیز می باشد: به این دلیل که داده cache شده درون حافظه ذخیره می شود، در صورتی که سرور شما crash کند داده مورد نظر از بین خواهد رفت. واضح است که، حافظه برای ذخیره سازی داده به طور دائمی در نظر گرفته نشده است، بنابراین به cache کردن بر پایه حافظه برای تنها ذخیره داده اعتماد نکنید. بدون هیچ شکی، باطن هیچکدام از سیستم های cache جنگو برای ذخیره سازی دائمی در نظر گرفته نشده اند – آن ها به طور کلی راهکارهایی برای cache کردن داده می باشند، نه ذخیره سازی – ولی به این موضوع در اینجا اشاره کردیم، چرا که cache کردن بر پایه حافظه به طور خاص موقتی می باشد.

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

جهت استفاده از یک جدول پایگاه داده برای cache، ابتدا یک جدول cache درون پایگاه داده خود توسط اجرای دستور زیر ایجاد کنید:

python manage.py createcachetable [cache_table_name]

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

هنگامی که شما جدول پایگاه داده را ایجاد کردید، تنظیم CACHE_BACKEND را با "db://tablename" تنظیم کنید، جایی که tablename نام جدول پایگاه داده می باشد. در مثال زیر، نام جدول cache نام my_cache_table می باشد:

CACHE_BACKEND = 'db://my_cache_table'

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

cache پایگاه داده در صورتی که دارای یک پایگاه داده ی سریع باشید بسیار عالی کار خواهد کرد.

Cache کردن Filesystem

جهت ذخیره ی آیتم های cache شده در یک filesystem، از "file://" در CACHE_BACKEND استفاده کنید. به عنوان مثال، جهت ذخیره داده ی cache شده در /var/tmp/django_cache از تنظیم زیر استفاده کنید:

CACHE_BACKEND = 'file:///var/tmp/django_cache'

توجه داشته باشید که سه علامت (/) در شروع مثال فوق وجود دارد. دوتای اول برای file://، و سومی، اولین حرف مسیر دایرکتوری /var/tmp/django_cache می باشد. در صورتی که در سیستم عامل ویندوز هستید، حرف درایو را بعد از file:// مانند زیر قرار دهید:

file://c:/foo/bar

مسیر دایرکتوری باید کامل باشد – بدین بدان معنی است که، باید از ریشه filesystem شروع شود. گذاشتن یا نذاشتن علامت (/) در پایان تنظیم اهمیتی ندارد.

اطمینان حاصل کنید دایرکتوری اشاره شده توسط این تنظیم وجود داشته و قابل نوشتن و خواندن توسط کاربر سیستمی که وب سرور درون آن اجرا می شود باشد. در ادامه مثال فوق، در صورتی که سرور شما به صورت کاربر apache اجرا می شود، اطمینان حاصل کنید که دایرکتوری /var/tmp/django_cache وجود داشته و قابل نوشتن و خواندن توسط کاربر apache باشد.

هر مقدار cache ای به صورت یک فایل جدا ذخیره شده خواهد بود که محتویات داده cach ذخیره شده در یک قالب بندی سریال شده ("pickled") توسط ماژول pickle پایتون هستند. هر نام فایل کلید cache رها شده برای استفاده امن filesystem می باشد.

Cache کردن حافظه ی داخلی

اگر مزایای سرعت cache در حافظه را بدون قابلیت اجرای Memcached می خواهید، cache حافظه داخلی را ملاحظه کنید. این cache چند پردازشی و thread-safe می باشد. برای استفاده از آن، تنظیم CACHE_BACKEND را با "locmem:///" تنظیم کنید. برای مثال:

CACHE_BACKEND = 'locmem:///'

توجه داشته باشید که هر پردازش دارای نمونه cache خصوصی خود می باشد، که بدین معنی است که cache به صورت cross‑process ممکن خواهد بود. همچنین واضح است که حافظه ی داخلی cache منحصرا حافظه ی کار آمد به حساب نمی آید، بنابراین شاید برای محیط های تولید انتخاب مناسبی نباشد. این نوع cache برای توسعه عالی می باشد.

Cache کردن ساختگی (برای توسعه)

در پایان، جنگو یک cache با نام "dummy" ارائه کرده است که در واقع cache نیست – این تنها بدون انجام چیزی رابط cache را اجرا می کند.

این نوع cache در صورتی که شما دارای یک سایت تولید باشید که از cache سنگینی را در مکان های گوناگون استفاده کند مفید است. ولی مکان ها یک محیط توسعه/آزمون جایی که نمی خواهید cache انجام شود و نمی خواهید لزوما کد شما برای مورد خاص اخیر تغییر کند. جهت فعال کردن dummy cache، تنظیم CACHE_BACKEND را مانند زیر تنظیم کنید:

CACHE_BACKEND = 'dummy:///'

استفاده از یک Cache سفارشی

هنگامی که جنگو پشتیبانی از تعداد از cache ها بدون هیچ تنظیمی را ارائه می کند. ممکن است بخواهید از یک cache سفارشی شده استفاده کنید. برای استفاده از یک cache خارجی با جنگو، از مسیر import پایتون به صورت قسمت طرح (قسمتی قبل از تعریف علامت کالن ":") از URL، CACHE_BACKEND مانند زیر استفاده کنید:

CACHE_BACKEND = 'path.to.backend://'

در صورتی که cache مخصوص خود را می سازید، می توانید از cache استاندارد به صورت پیاده سازی مرجع استفاده کنید. درون دایرکتوری django/core/cache/backends/ از منبع جنگو کد را خواهید یافت.

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

آرگومان های CACHE_BACKEND

هر نوع cache ای ممکن است آرگومان هایی دریافت کند. آن ها به شکل query-string در تنظیم CACHE_BACKEND داده شده می باشند. آرگومان های معتبر از این قرار می باشند:

  • timeout: timeout پیشفرض بر حسب ثانیه، برای cache استفاده می شود. این آرگومان به طور پیشفرض 300 ثانیه (5 دقیقه) می باشد.
  • max_entries: برای cache های locmem، filesystem و پایگاه داده می باشد، حداکثر تعداد از ورودی های مجاز در cache قبل از مقادیر قدیمی که حذف شده اند. این آرگومان به صورت پیشفرض 300 می باشد.
  • cull_percentage: زمانی که max_entries برسد، درصد ورودی که جمع آوری شده اند می باشد. (در دست ترجمه ...).
  • یک مقدار از 0 برای cull_percentage بدین معنی است که تمام cache زمانی که max_entries برسد خالی شده خواهد بود. (در دست ترجمه ...).

در مثال زیر، timeout مقدار 60 می باشد:

CACHE_BACKEND = "memcached://127.0.0.1:11211/?timeout=60"

در مثال زیر، timeout مقدار 30 بوده و max_entires مقدار 400 می باشد:

CACHE_BACKEND = "locmem:///?timeout=30&max_entries=400"

آرگومان های نا معتبر بدون هیچ خطایی رد می شوند، به طوری که مقادیر نا معتبر آرگومان های شناخته شده رد می شوند.

Cache در هر سایت

هنگامی که cache راه اندازی شده است، ساده ترین روش جهت استفاده از cache، cache کردن کل سایت می باشد. نیاز به اضافه کردن 'django.middleware.cache.UpdateCacheMiddleware' و 'django.middleware.cache.FetchFromCacheMiddleware' به تنظیم مورد نظر یعنی MIDDLEWARE_CLASSES خواهید داشت، مانند مثال زیر:

MIDDLEWARE_CLASSES = (
    'django.middleware.cache.UpdateCacheMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.cache.FetchFromCacheMiddleware',
)

نکته

نه، این اشتباه تایپ نیست: middleware مربوط به "update" باید در ابتدای لیست باشد، و middleware مربوط به "fetch" باید آخرین باشد. جزئیات کمی مبهم می باشند، ولی در صورتی که می خواهید داستان کامل را بدانید ترتیب MIDDLEWARE_CLASSES زیر را ببینید.

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

  • CACHE_MIDDLEWARE_SECONDS – تعداد ثانیه هایی که هر صفحه باید cache شده باشد.
  • CACHE_MIDDLEWARE_KEY_PREFIX – در صورتی که cache در میان چندین سایت با استفاده از نصب جنگو یکسان به اشتراک گذاشته شده باشد. این تنظیم برای نام سایت قرار دهید، یا برخی رشته های دیگر که برای این نمونه جنگو منحصر به فرد می باشند، جهت جلوگیری برخوردهای کلید. در صورتی که اهمیتی نمی دهید از یک رشته ی خالی استفاده کنید.

middleware مربوط به cache، هر صفحه ای که دارای پارامتر GET یا POST نباشد را cache می کند. به طور اختیاری، در صورتی که تنظیم CACHE_MIDDLEWARE_ANONYMOUS_ONLY مقدار True را داشته باشد، تنها درخواست های anonymous (نه آن هایی که توسط یک کاربر وارد شده ساخته شده باشند) cache شده خواهند بود. این یک روش ساده و موثر از از کار انداختن عمل cache برای صفحات هر کاربر خاص (شمال رابط مدیر جنگو) می باشند. دقت داشته باشید، اگر از CACHE_MIDDLEWARE_ANONYMOUS_ONLY استفاده می کنید، باید اطمینان حاصل کنید AuthenticationMiddleware فعال کرده اید.

علاوه بر این، cache middleware به طور خودکار تعدادی header در هر HttpResponse قرار می دهد:

  • یک header به نام last-Modified برای تاریخ/زمان فعلی هنگامی که یک نسخه ی cache نشده از صفحه درخواست شده است قرار می دهد.
  • header ای با نام Expires برای تاریخ/زمان فعلی به علاوه ی CACHE_MIDDLEWARE_SECONDS تعریف شده قرار می دهد.
  • header ای با نام Cache-Control جهت دادن یک حداکثر عمر برای صفحه – بار دیگر، از تنظیم CACHE_MIDDLEWARE_SECONDS.

برای اطلاعات بیشتر در مورد middleware به مبحث middleware مراجعه کنید.

در صورتی که یک view زمان انقضای (به عنوان مثال دارای یک بخش max-age در هدر Cache-Control خود باشد) خود را قرار دهد، سپس صفحه تا زمان انقضا cache شده خواهد بود، به جای CACHE_MIDDLEWARE_SECONDS. استفاده از decorator ها در django.views.decorators.cache می توان به سادگی یک زمان انقضای view (با استفاده از decorator، cache_control) قرار داد یا cache برای یک view را غیر فعال کرد (با استفاده از decorator، never_cache). برای اطلاعات بیشتر در مورد این decorator ها به بخش "استفاده از header های دیگر مراجعه کنید.

Cache در ازای هر View

یک روش cache در مقیاس کوچک تر برای استفاده از فریم ورک یا چارچوب cache به شکل cache کردن خروجی view های منحصر به فرد می باشد. django.views.decorators.cache یک decorator با نام cache_page تعریف می کند که به طور خودکار پاسخ view را برای شما cache می کند. این روش برای استفاده ساده می باشد:

from django.views.decorators.cache import cache_page

def my_view(request):
    # ...

my_view = cache_page(my_view, 60 * 15)

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

@cache_page(60 * 15)
def my_view(request):
    # ...

cache_page یک آرگومان تنها دریافت می کند: timeout مربوط به cache، بر حسب ثاینه. در مثال بالا، نتیجه view مورد نظر یعنی my_view() برای 15 دقیقه cache شده خواهد بود. (توجه داشته باشید که جهت خوانایی بیشتر به صورت 60 * 15 نوشته شده است. 60 * 15 به صورت 900 ارزیابی خواهد شد – این بدان معنی است که، 15 دقیقه توسط ضرب 60 ثانیه در هر دقیقه بدست می آید.)

cache به ازای هر view، مانند cache به ازای هر سایت، (در دست ترجمه ...). در صورتی که چندین URL به یک view همسان اشاره کنند، هر URL به صورت جداگانه cache خواهد شد. در ادامه مثال my_view، در صورتی که URLconf شما مانند زیر باشد:

urlpatterns = ('',
    (r'^foo/(\d{1,2})/$', my_view),
)

سپس درخواست های به /foo/1/ و /foo/23/ به طور جداگانه cache خواهند شد، به صورتی که ممکن است انتظار داشته باشید. ولی هنگامی که یک URL خاص (مانند /foo/23/) درخواست شده باشد، درخواست های بعدی به آن URL از cache استفاده خواهند کرد.

تعیین به ازای هر Cache View در URLconf

مثال های بخش قبلی دارای کد مسقیم زده شده در view ای که مورد cache قرار می گرفت بودند، زیرا cache_page تابع my_view را در محل تغییر می دهد. این رویکرد view شما را به سیستم cache جفت می کند، که به دلایلی ایده آل نمی باشد. به عنوان مثال، ممکن است بخواهید از توابع view در جایی دیگر، سایت بدون cache استفاده کنید، یا ممکن است view ها را به افرادی توزیع کنید که ممکن است بخواهند از آن ها بدون cache شدن استفاده کنند. راهکار برای این مشکلات، تعیین cache به ازای هر view به جای قرار گرفتن در خود توابع view درون URLconf می باشد.

انجام این کار ساده می باشد: به سادگی عبارت cache_page که درون تابع view قرار گرفته است را درون URLconf اشاره کننده به این تابع view قرار دهید. در زیر URLconf قبلی را مشاهده می کنید:

urlpatterns = ('',
    (r'^foo/(\d{1,2})/$', my_view),
)

در زیر کدی همسان وجود دارد، با این تفاوت که cache_page درون URLconf قرار گرفته است:

from django.views.decorators.cache import cache_page

urlpatterns = ('',
    (r'^foo/(\d{1,2})/$', cache_page(my_view, 60 * 15)),
)

در صورتی که از این رویکرد استفاده می کنید، قرار داده cache_page را درون URLconf فراموش نکنید.

Template Fragment Caching

در صورتی که خواستار کنترل بیشتر می باشید، همچنین می توانید قطعه های template را با استفاده تگ template ای با نام cache، cache کنید. جهت دادن دسترسی templateبه این تگ، {% load cache %} را در بالای template خود قرار دهید.

تگ {% cache %} محتویات بلاک برای مقدار زمان داده شده را cache می کند. این تگ حداقل دو آرگومان دریافت می کند: cache timeout بر حسب ثانیه، و نام برای دادن قطعه cache. برای مثال:

{% load cache %}
{% cache 500 sidebar %}
    .. sidebar ..
{% endcache %}

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

{% load cache %}
{% cache 500 sidebar request.user.username %}
    .. sidebar for logged in user ..
{% endcache %}

تعیین بیشتر از یک آرگومان برای تشخیص قطعه کاملا خوب می باشد. به سادگی برخی آرگومان ها را به {% cache %} همان طور که نیاز دارید ارسال کنید.

cache timeout می تواند یک متغیر template باشد، تا زمانی که متغیر template یک مقدار integer باشد. برای مثال، در صورتی که متغیر my_timeout مقدار 600 برایش قرار گرفته باشد، سپس دو مثال زیر با هم برابر هستند:

{% cache 600 sidebar %} ... {% endcache %}
{% cache my_timeout sidebar %} ... {% endcache %}

این خصوصیت برای اجتناب از تکرار در template ها مفید می باشد. می توان timeoutرا درون یک متغیر قرار داد، در یک جا، و تنها از آن مقدار دوباره استفاده کرد.

API سطح پایین Cache

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

ممکن است، برای مثال، سایت شما حاوی یک view باشد که بسته به چندین کوئری پر خرج نتیجه دهد، نتایج از آن تغییر در فواصل مختلف. در این مورد، استفاده از cache تمام صفحه ایده آل نمی باشد که به ازای هر سایت یا هر view استراتژی های ارائه شده cache، زیرا شما نمی خواهید تمام نتیجه (از آنجایی که برخی از داده ها اغلب تغییر می کنند) را cache کنید، ولی همچنان می خواهید نتایجی که به ندرت تغییر می کنند را cache کنید.

برای موارد شبیه به این، جنگو یک cache API سطح پایین ساده را ارائه می کند. می توان از این API جهت ذخیره ی شیء هایی در cache با هر سطحی که می خواهید استفاده کرد. می توان هر شیء پایتونی که می تواند به طور امن pickled باشد را cache کرد: رشته ها، دیکشنری ها، لیست شیء های مدل، و غیره ... (اغلب شیء های رایج پایتون می توانند pickled باشند؛ برای اطلاعات بیشتر درباره pickling به مستندات پایتون مراجعه کنید.)

ماژول cache، django.core.cache دارای یک شیء cache می باشد که به طور خودکار از تنظیم CACHE_BACKEND ساخته شده است:

>>> from django.core.cache import cache

رابط اصلی set(key, value, timeout_seconds) و get(key):

>>> cache.set('my_key', 'hello, world!', 30)
>>> cache.get('my_key')
'hello, world!'

آرگومان timeout_seconds اختیاری می باشد و به آرگومان timeout در تنظیم CACHE_BACKEND بر می گردد.

در صورتی که شیء در cache مورد نظر یعنی cache.get() وجود نداشته باشد None بر می گرداند:

# Wait 30 seconds for 'my_key' to expire...

>>> cache.get('my_key')
None

توصیه می شود مقدار واقعی None را در cache ذخیره نکنید، چرا که قادر به تشخیص مقدار ذخیره کرده ی None خود و دیگر مقادیر None نخواهید بود.

cache.get() می تواند یک آرگومان default دریافت کند. این آرگومان مقدار برگشت داده شده، در صورت عدم وجود شیء در cache را تعیین می کند:

>>> cache.get('my_key', 'has expired')
'has expired'

جهت اضافه کردن یک کلید تنها در صورتی که وجود نداشته باشد، از متد add() استفاده کنید. این متد پارامترهایی همانند set() دریافت می کند، ولی این متد در صورتی که کلید تعیین شده حاضر باشد (وجود داشته باشد) تلاشی برای به روز رسانی نخواهد کرد:

>>> cache.set('add_key', 'Initial value')
>>> cache.add('add_key', 'New value')
>>> cache.get('add_key')
'Initial value'

در صورتی که نیاز به دانستن این موضوع دارید که آیا add() یک مقدار در cache ذخیره کرده است، می توانید مقدار برگشتی را بررسی کنید. این مقدار در صورتی که مقدار ذخیره شده باشد True و در غیر این صورت مقدار False بر می گرداند.

همچنین یک رابط بانام get_many() وجود دارد که تنها یک بار (در دست ترجمه ...). get_many() یک دیکشنری با تمام کلیدهایی که خواسته اید و در واقع در cache وجود داشته باشد بر می گرداند.

>>> cache.set('a', 1)
>>> cache.set('b', 2)
>>> cache.set('c', 3)
>>> cache.get_many(['a', 'b', 'c'])
{'a': 1, 'b': 2, 'c': 3}

در پایان، می توان به طور واضح با delete() کلیدها را حذف کرد. این یک روش ساده از حذف cache برای یک شیء خاص می باشد:

>>> cache.delete('a')

همچنین می توان با استفاده از متدهای incr() و decr() یک کلید موجود را به ترتیب افزایش و کاهش داد. به طور پیشفرض، مقدار cache موجود توسط مقدار 1، افزایش یا کاهش داده خواهد شد. مقادیر دیگر افزایش/کاهش می توانند توسط تهیه ی یک آرگومان برای فراخوانی افزایش/کاهش تعیین شده باشند. در صورتی که تلاش کنید یک کلید cache ای که وجود ندارد را افزایش یا کاهش دهید یک خطا ایجاد خواهد شد.:

>>> cache.set('num', 1)
>>> cache.incr('num')
2
>>> cache.incr('num', 10)
12
>>> cache.decr('num')
11
>>> cache.decr('num', 5)
6

نکته

متدهای incr()/decr() برای atomic بودن تضمین نشده اند. در آن cache ها که افزایش/کاهش atomic را پشتیبانی می کنند (که مهمترین آن ها، memcached می باشد)، اعمال افزایش و کاهش atomic خواهند بود. هر چند، در صورتی که cache یک عمل افزایش/کاهش را ذاتا تهیه نکند، با استفاده از یک پروسه ی دو مرحله ای بازیابی/به روز رسانی انجام شده خواهد بود.

Cache های بالا دست

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

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

  • ISP شما ممکن است بعضی صفحات را cache کند، بنابراین اگر یک صفحه را از

    محتوای مخفی

      برای مشاهده محتوای مخفی می بایست در انجمن ثبت نام کنید.
    درخواست کرده باشید، ISP شما صفحه را بدون داشتن دسترسی مستقیم به example.com به شما ارسال می کند. maintainer های example.com دارای هیچ دانشی از این cache نمی باشند؛ ISP بین example.com و مرورگر وب شما نشسته و تمام cache را به طور روشن کنترل می کند.
  • وب سایت جنگوی شما ممکن است، پشت یک cache پروکسی از قبیل وب پروکسی Squid (http://www.squid‑cache.org/) نشسته و صفحات را برای نمایش cache نماید. در این مورد، هر درخواستی ابتدا توسط پروکسی کنترل می شده و تنها در صورت لزوم به برنامه ی شما ارسال می شود.
  • مرورگر وب شما نیز همچنین صفحات را cache می کند. در صورتی که یک صفحه ی وب header های مناسب را ارسال کند، مرورگر شما، کپی cache های داخلی را برای درخواست های بعدی به آن صفحه استفاده می کند، بدون حتی اتصال دوباره به صفحه ی وب جهت دیدن این که آیا تغییر کرده است یا خیر.

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

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

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

Using Vary Headers

(در دست ترجمه ...). برای مثال، در صورتی که محتویات یک صفحه ی به زبان مورد ترجیح کاربر وابستگی داشته باشد، صفحه "vary on language" گفته می شود.

به طور پیشفرض، سیستم cache جنگو کلیدهای cache خود را با استفاده از مسیر درخواست شده (مانند "/stories/2005/jun/23/bank_robbed/") ایجاد می کنید. این یعنی هر درخواست به آن URL از یک نسخه cache همسان استفاده خواهد کرد، بدون در نظر گرفتن تفاوت های user-agent از قبیل کوکی ها یا تنظیمات زبان. هر چند، اگر این صفحه محتویات متفاوتی بر اساس آن تفاوت در header های درخواست تولید کند – از قبیل یک کوکی، یا یک زبان، یا یک user-agent – شما نیاز خواهید داشت جهت گفتن مکانیسم های cache که خروجی صفحه به آن چیزها بستگی دارد، از Vary header استفاده کنید.

برای انجام این کار در جنگو، از decorator برای view با نام vary_on_headers مانند زیر استفاده کنید:

from django.views.decorators.vary import vary_on_headers

# Python 2.3 syntax.
def my_view(request):
    # ...
my_view = vary_on_headers(my_view, 'User-Agent')

# Python 2.4  decorator syntax.
@vary_on_headers('User-Agent')
def my_view(request):
    # ...

در این مورد، یک مکانیسم cache (مانند cache middleware خود جنگو) یک نسخه ی جدا از صفحه را برای هر user-agent منحصر به فرد cache خواهد کرد.

مزیت استفاده از vary_on_headers به جای دستی قرار دادن Vary header (با استفاده از چیزی شبیه به response['Vary'] = 'user-agent') این است که decorator به Vary header اضافه می کند (که ممکن وجود داشته باشد)، به جای (در دست ترجمه ...) و به طور بالقوه هر چیزی که در آن جا وجود داشته باشد را override می کند.

می توان چندین header را به vary_on_headers() ارسال کرد:

@vary_on_headers('User-Agent', 'Cookie')
def my_view(request):
    # ...

این به cache های بالا دست تغییر کردن هر دو را می گوید، که یعنی ترکیب user-agent و cookie مقدار cache خود را بدست خواهند آورد. برای مثال، یک درخواست با user-agent ای مانند Mozilla و مقدار کوکی foo=bar از یک درخواست با user-agent ای با نام Mozilla و مقدار کوکی foo=ham متفاوت در نظر گرفته خواهند شد.

به این دلیل که vary در کوکی بسیار رایج می باشد، یک decorator با نام vary_on_cookie وجود دارد. این دو view برابر می باشند:

@vary_on_cookie
def my_view(request):
    # ...

@vary_on_headers('Cookie')
def my_view(request):
    # ...

header هایی که به vary_on_header ارسال می شوند به حروف بزرگ و کوچک حساس نیستند؛ "User‑gent" هیچ فرقی با "user-agent" نخواهد داشت.

همچنین می توان از یک تابع کمکی با نام django.utils.cache.patch_var_headers به طور مستقیم استفاده کرد. این تابع Vary header را قرار داده یا اضافه می کند. برای مثال:

from django.utils.cache import patch_vary_headers

def my_view(request):
    # ...
    response = render_to_response('template_name', context)
    patch_vary_headers(response, ['Cookie'])
    return response

patch_vary_headers یک نمون ی HttpResponse به صورت اولین آرگومان و یک لیست/تاپل از نام های header حساس به حروف بزرگ و کوچک به عنوان آرگومان دوم دریافت می کند.

کنترل Cache: با استفاده از Header ها

مشکلات دیگر cache حریم شخصی داده و سوال از جایی که داده باید در یک آبشاری از cache ها در آن ذخیره شده باشد.

یک کاربر معمولا با دو نوع از cache ها رو به رو می باشد: cache مرورگر خود کاربر (cache خصوصی) و ارائه دهنده ی cache کاربر (یک cache عمومی). cache عمومی توسط چندین کاربر و استفاده می شود و توسط برخی دیگر کنترل می شود. (در دست ترجمه ...). بنابراین برنامه های وب نیاز به یک روش برای گفتن cache ها دارند که کدام داده خصوصی و بوده و کدام عمومی می باشد.

راهکار، نشان دادن cache صفحه باید "خصوصی" باشد. برای انجام این کار در جنگو، از decorator مورد نظر برای view با نام cache_control استفاه کنید:

from django.views.decorators.cache import cache_control

@cache_control(private=True)
def my_view(request):
    # ...

این decorator مراقب فرستادن HTTP header مناسب در پشت صحنه می باشد.

چند روش دیگر برای کنترل پارامترهای cache وجود دارد. برای مثال، HTTP به برنامه ها اجازه ی انجام کارهای زیر را می دهد:

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

در جنگو، از decorator، cache_control برای تعیین این پارامترهای cache استفاده کنید. در این مثال، cache_control جهت دوباره معتبر ساختن cache در هر دسترسی و جهت ذخیره ی نسخه های cache برای حداکثر 3600 ثانیه به cache ها می گوید:

from django.views.decorators.cache import cache_control

@cache_control(must_revalidate=True, max_age=3600)
def my_view(request):
    # ...

هر رهنمود HTTP کنترل cache معتبری در cache_control() معتبر می باشد. در زیر لیست کامل وجود دارد:

public=True
private=True
no_cache=True
no_transform=True
must_revalidate=True
proxy_revalidate=True
max_age=num_seconds
s_maxage=num_seconds
(توجه داشته باشید که caching middleware پیش از این cache هدر max-age را با مقدار تنظیم CACHE_MIDDLEWARE_SETTINGS قرار داده شده است. در صورتی که از یک max_age سفارشی در یک decorator، cache_control استفاده می کنید، decorator اولیت خواهد گرفت، و مقادیر header به درستی ادغام خواهند شد.)

در صورتی که می خواهید جهت غیر فعال کردن الگوریتم cache کردن از هدرها استفاده کنید، django.view.decorators.cache.never_cache یک decorator برای view می باشد که جهت اطمینان از پاسخی که توسط مرورگر یا دیگر cache ها cache نخواهد شد هدرها را اضافه می کند. مثال:

from django.views.decorators.cache import never_cache

@never_cache
def myview(request):
    # ...

بهینه سازی های دیگر

جنگو چند قسمت دیگر از middleware را که می تواند کارایی app های شما را بهینه کند ارائه می کند.

django.middleware.http.ConditionalGetMiddleware مرورگرهای مدرن را برای پاسخ های GET بر پایه ی هدرهای ETag و Last-Modified پشتیبانی می کند.
django.middleware.gzip.GZipMiddleware پاسخ های تمام مرورگرها را فشرده کرده و پنهای باند و زمان انتقال را ذخیره می کند.

ترتیب MIDDLEWARE_CLASSES

در صورتی که از cashing middleware استفاده می کنید، قرار دادن هر نیمه در جای راست داخل تنظیم MIDDLEWARE_CLASSES اهمیت دارد. چرا که cache middleware نیاز دارد بداند هدرها توسط کدام ذخیره سازی vary cache می شود. middleware همواره هنگامی که بتواند چیزی را به پاسخ هدر Vary اضافه می کند.

UpdateCacheMiddleware فاز پاسخ را اجرا می کند، جایی که middleware به طور برعکس می باشد، بنابراین یک آیتم در بالای لیست آخرین فاز پاسخ را اجرا می کند. در نتیجه، نیاز می باشد اطمینان حاصل کنید که UpdateCacheMiddleware قبل از هر middleware دیگر ظاهر می شود که ممکن است چیزی را به هدر Vary اضافه کند. ماژول های middleware این کار را انجام می دهند:

  • SessionMiddleware، Cookie اضافه می کند
  • GZipMiddleware، Accept-Encoding اضافه می کند
  • LocaleMiddleware، Accept-Language اضافه می کند

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

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


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

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

مهمان
ارسال پاسخ به این موضوع ...

×   شما در حال چسباندن محتوایی با قالب بندی هستید.   حذف قالب بندی

  تنها استفاده از ۷۵ اموجی مجاز می باشد.

×   لینک شما به صورت اتوماتیک جای گذاری شد.   نمایش به عنوان یک لینک به جای

×   محتوای قبلی شما بازگردانی شد.   پاک کردن محتوای ویرایشگر

×   شما مستقیما نمی توانید تصویر خود را قرار دهید. یا آن را اینجا بارگذاری کنید یا از یک URL قرار دهید.


×
×
  • جدید...