آموزش فرم ها در جنگو (Django)
فرم های HTML ستون فقرات تعامل وب سایت ها می باشند، برای فرم های ساده می توان جعبه ی جستجوی گوگل را مثال زد و برای فرم های پیچیده می توان رابط های ثبت نام برای سایت های بزرگ را مثال زد. در این آموزش از کتاب به نحوه ی دسترسی به داده های فرم که توسط کاربر ارائه می شود، تایید کردن آن و انجام برخی کارها به روی آن ها خواهیم پرداخت. در طول فصل، HttpRequest و شیء های Form را توضیح خواهیم داد.
دریافت داده از شیء Request
شیء های HttpRequest در آموزش view و urlconf جنگو هنگامی که اولین توابع view را ایجاد کردیم معرفی شده است، ولی صحبت زیادی درباره ی آن ها در آن زمان صورت نگرفت. یادآوری می کنیم که هر تابع view یک شیء HttpRequest به عنوان اولین پارامتر دریافت می کند، همانطور که در تابع hello() مشاهده می کنید:
from django.http import HttpResponse
def hello(request):
return HttpResponse("Hello world")
شیء های HttpRequest مانند متغیر request فوق، دارای attribute های و متدهای جالبی می باشند که می بایست با آن ها آشنا شوید. می توان از این attribute ها برای به دست آوردن اطلاعات درباره ی درخواست فعلی استفاده کرد (مانند مرورگری که در حال بارگذاری صفحه ی فعلی می باشد)، در زمانی که تابع view اجرا شده است.
اطلاعات درباره ی آدرس
شیء های HttpRequest حاوی چندین بخش از اطلاعات درباره ی آدرس فعلی درخواست شده می باشد:
جدول ۱-۷
متد/Attribute | توضیح | مثال |
---|---|---|
request.path | مسیر کامل، بدون شامل بودن دامین ولی با علامت (/) پیشین | "/hello/" |
Request.get_host() | میزبان (مانند دامین در گفتگوی رایج) | "127.0.0.1:8000" یا "www.example.com" |
Request.get_full_path() | مسیر به همراه query string (اگر در دسترس باشد) | "/hello/?print=true" |
Request.is_secure() | در صورتیکه درخواست از طریق HTTPS ساخته شده باشد True و در غیر اینصورت False بر می گرداند. | True یا False |
همواره از attribute ها/متدها به جای کد مستقیم آدرس درون view ها استفاده کنید. حرکت مذکور انعطاف پذیری کد را جهت استفاده در مکان های دیگر بیشتر می کند. یک مثال ساده:
# BAD!
def current_url_view_bad(request):
return HttpResponse("Welcome to the page at /current/")
# GOOD
def current_url_view_good(request):
return HttpResponse("Welcome to the page at %s" % request.path)
اطلاعات دیگر درباره ی Request
request.META یک دیکشنری پایتون است که تمام HTTP header های درخواست داده شده را شامل شده است – مانند آدرس IP و همچنین نام و نسخه ی مرورگر کاربر. توجه داشته باشید که لیست header های در دسترس به آن header هایی بستگی دارد که کاربر فرستاده و وب سرور قرار داده است. برخی از کلید های در دسترس رایج در این دیکشنری:
- HTTP_REFERER - اشاره کردن به آدرس، در صورت وجود.
- HTTP_USER_AGENT – رشته ی مرورگر کاربر، در صورت وجود. که چیزی شبیه به این می باشد:
"Mozilla/5.0 (X11; U; Linux i686; fr-FR; rv:1.8.1.17) Gecko/20080829 Firefox/2.0.0.17". - REMOTE_ADDR – آدرس IP کلاینت مانند "12.345.67.89". (در صورتیکه درخواست از میان پروکسی ارسال شده باشد، ممکن است با یک جدا کننده ی کاما نشان داده شود مانند "12.345.67.89,23.456.78.90")
توجه داشته باشید، چراکه request.META یک دیکشنری پایتون می باشد، در صورتیکه بخواهید به کلیدی که وجود ندارد دسترسی پیدا کنیدخطای KeyError رخ خواهد داد. (زیرا HTTP header داده ی خارجی می باشد – که توسط مرورگر کاربر شما تایید شده است – نباید به آن ها اعتماد شود، و باید همواره برنامه ی خود را طوری طراحی کنید که در صورت خالی بودن یک header خاص یا عدم وجود به شکل درستی آن را رد کند.) می بایست از یک عبارت try/except یا متد get() برای مواردی که یک کلید تعریف نشده است استفاده کرد:
# BAD!
def ua_display_bad(request):
ua = request.META['HTTP_USER_AGENT'] # Might raise KeyError!
return HttpResponse("Your browser is %s" % ua)
# GOOD (VERSION 1)
def ua_display_good1(request):
try:
ua = request.META['HTTP_USER_AGENT']
except KeyError:
ua = 'unknown'
return HttpResponse("Your browser is %s" % ua)
# GOOD (VERSION 2)
def ua_display_good2(request):
ua = request.META.get('HTTP_USER_AGENT', 'unknown')
return HttpResponse("Your browser is %s" % ua)
شما را تشویق می کنیم تا یک view کوچک بنویسید که تمام داده های request.META را نمایش دهد بنابراین می توانید با دلیل وجود آن ها را آشنا شوید:
def display_meta(request):
values = request.META.items()
values.sort()
html = []
for k, v in values:
html.append('<tr><td>%s</td><td>%s</td></tr>' % (k, v))
return HttpResponse('<table>%s</table>' % '\n'.join(html))
همچنین می توان view فوق را با استفاده از سیستم template جنگو به جای نوشتن کد مستقیم HTML تبدیل کنید. همچنین سعی کنید request.path و دیگر متدهای HttpRequest را از فصل قبل اضافه کنید.
اطلاعات درباره ی داده ی تایید (submit) شده
خارج از ابر داده های پایه درباره ی درخواست، شیء های HttpRequest دارای دو attribute هستند که حاوی اطلاعات تایید شده توسط کاربر می باشند: request.GET و request.POST. هر دوی این ها شیء های دیکشنری مانند می باشند که از طریق آن ها می توان به داده های نوع GET و POST دسترسی پیدا کرد.
شیء های دیکشنری مانند
هنگامی که گفته می شود request.GET و request.POST شیء های دیکشنری مانند می باشند، منظور این است که آن ها مانند دیکشنری های استاندارد پایتون رفتار می کنند ولی از نظر فنی دیکشنری پایتون نمی باشند. به عنوان مثال، request.GET و request.POST دارای متدهای get()، keys() و value() هستند، می توان درون کلیدها با for key in request.GET به جستجو پرداخت.
بنابراین چرا با یکدیگر فرق می کنند؟ زیرا هر دوی request.GET و request.POST دارای متدهای اضافی ای می باشند که دیکشنری های معمولی فاقد آن متدها می باشند.
شما ممکن است با اصطلاح شبیه به اصطلاح قبلی یعنی شیء های فایل مانند مواجه شده باشید – شیء های پایتون دارای تعدادی متد اولیه می باشند، مانند read() که اجازه می دهد به صورت جانشین برای شیء های واقعی فایل ایفای نقش کنند.
داده ی POST عموما تایید شده از <form> HTML می باشد، در حالی که داده ی GET می تواند از یک <form> یا query string در آدرس صفحه آمده باشد.
یک مثال ساده ی کنترل فرم
مثال books کتاب را با کتاب ها، نویسندگان و ناشران دنبال می کنیم، اجازه دهید یک view ساده را که به کاربران اجازه ی جستجو کتاب ها را بر اساس عنوان می دهد، بسازیم.
معمولا، دو بخش برای توسعه ی یک فرم وجود دارد: رابط کاربر HTML و کد view که در بطن کار قرار دارد و داده ی تایید شده توسط کاربر را پردازش می کند. بخش اول ساده می باشد؛ اجازه دهید یک view را که فرم جستجو را نمایش می دهد راه اندازی کنیم:
from django.shortcuts import render_to_response
def search_form(request):
return render_to_response('search_form.html')
همانطور که در آموزش view و urlconf جنگو یاد گرفتیم، view فوق را می تواند هر جایی در مسیر پایتون قرار دهیم. در اینجا آن را درون books/views.py قرار می دهیم.
template همراه، search_form.html چیزی شبیه به کد زیر خواهد بود:
<html>
<head>
<title>Search</title>
</head>
<body>
<form action="/search/" method="get">
<input type="text" name="q">
<input type="submit" value="Search">
</form>
</body>
</html>
URLpattern در urls.py نیز می تواند چیزی شبیه به کد زیر باشد:
from mysite.books import views
urlpatterns = patterns('',
# ...
(r'^search-form/$', views.search_form),
# ...
)
(توجه داشته باشید که ماژول views به جای آنکه به این شکل from mysite.views imort search_form به درون برنامه import شود، به طور مستقیم import شده است، زیرا این روش کوتاه تر می باشد، این روش import در آموزش view و urlconf پیشرفته با جزئیات بیشتری بحث خواهد شد.)
حالا، در صورتیکه دستور runserver را اجرا کنید و آدرس http://127.0.0.1:8000/search‑form/ درون مرورگر ملاحظه کنید، رابط جستجو را ملاحظه خواهید کرد که به اندازه ی کافی ساده می باشد.
سعی کنید که فرم را تایید کنید، که در اینصورت خطای 404 جنگو را مشاهده خواهید کرد. فرم به آدرس /search/ اشاره می کند، که هنوز اجرا نشده است. اجازه دهید این مشکل را با view دوم حل کنیم:
# urls.py
urlpatterns = patterns('',
# ...
(r'^search-form/$', views.search_form),
(r'^search/$', views.search),
# ...
)
# views.py
def search(request):
if 'q' in request.GET:
message = 'You searched for: %r' % request.GET['q']
else:
message = 'You submitted an empty form.'
return HttpResponse(message)
در حال حاضر، کد فوق تنها یک صفحه ی جستجو را نمایش می دهد، ولی می توان مطمئن شد که داده برای جنگو ارسال شده است، و می توان نحوه ی جریان جستجو را در سیستم متوجه شد. به طور خلاصه:
- <form> یک متغیر q تعریف می کند. هنگامی که فرم تایید می شود، ارزش q از طریق GET (method="get") به آدرس /search/ فرستاده می شود.
- view جنگو که آدرس /search/ (search()) را کنترل می کند به ارزش q در request.GET دسترسی دارد.
نکته مهم برای اشاره در اینجا این است که، به طور صریح بررسی شده است که 'q'درون request.GET وجود دارد یا خیر. همانطور که در بخش گذشته به request.META اشاره شد، شما نباید به هرچیزی که توسط کاربر فرستاده می شود اعتماد کنید و یا حتی فرض کنید که در وهله ی اول چیزی ارسال نمی شود. در صورتیکه این بررسی صورت نگیرد، هر ارسالی از فرم خالی باعث بروز خطای KeyError خواهد شد:
# BAD!
def bad_search(request):
# The following line will raise KeyError if 'q' hasn't
# been submitted!
message = 'You searched for: %r' % request.GET['q']
return HttpResponse(message)
پارامترهای query string
داده ی GET در query string ارسال می شود (مانند /search/?q=django)، می توان برای دسترسی به متغیرهای query string از request.GET استفاده کرد. در آموزش view و urlconf جنگو در بخش معرفی سیستم URLconf جنگو، آدرس های زیبای جنگو با آدرس های قدیمی PHP/Java مانند /time/plus?hours=3 مقایسه شد و همچنین گفته شد که در فرم نحوه ی دسترسی به به این آدرس های قدیمی نشان داده خواهد شد. حالا نحوه ی دسترسی به پارامترهای query string در view (مانند hours=3 در مثال فوق) را می دانید – با استفاده از request.GET.
داده ی پست نیر مانند داده ی GET عمل می کند – تنها از request.POST به جای request.GET استفاده می کند. تفاوت بین GET و POST در چیست؟ از روش GET هنگامی که تنها یک درخواست برای بدست آوردن داده می باشد استفاده می شود. از داده ی پست هر زمان که ارسال درخواست دارای برخی تاثیرات زیان بار باشد استفاده می شود – تغییر دادن داده، اراسل یک پست الکترونیک، و یا چیز دیگر که ورای نمایش ساده ی داده باشد. در مثال جستجوی ما، از روش GET استفاده شده است که زیرا هیچ داده ای قرار نیست درون سرور تغییر کند. (در صورتیکه می خواهید اطلاعات بیشتری در رابطه با روش های GET و POST به دست آورید می توانید به این آدرس مراجعه کنید: use of HTTP GET and POST).
اکنون که از روش GET استفاده شده است، اجازه دهید درون پایگاه داده به جستجوی کتاب مورد نظر بگردیم:
from django.http import HttpResponse
from django.shortcuts import render_to_response
from mysite.books.models import Book
def search(request):
if 'q' in request.GET and request.GET['q']:
q = request.GET['q']
books = Book.objects.filter(title__icontains=q)
return render_to_response('search_results.html',
{'books': books, 'query': q})
else:
return HttpResponse('Please submit a search term.')
نکاتی که در کد فوق وجود دارد:
- گذشته از بررسی اینکه 'q' درون request.GET وجود دارد یا خیر، همچنین در کد فوق قبل از اینکه مقادی به پایگاه داده ارسال شود اطمینان حاصل شده است که request.GET['q'] مقدار خالی نداشته باشد.
- درون کد فوق از Book.objects.filter(title__icontains=q) جهت پرسیدن اینکه آیا عنوانی شامل 'q' درون جدول کتاب ها وجود دارد یا خیر استفاده شده است. icontains یک نوع جستجو (که در آموزش مدل جنگو توضیح داده شده است) می باشد، عبارت می تواند تقریبا به این حالت ترجمه شود که "کتاب هایی که حاوی q بدون حساسیت به حروف بزرگ و کوچک می باشند را جستجو کن".
این یک روش بسیار ساده برای جستجوی کتاب می باشد. البته پیشنهاد نمی شود که از icontains در پایگاه داده های بزرگ محصولات استفاده شود زیرا در این حالت می تواند بسیار کند عمل کند.
- books (یک لیست از شیء های کتاب) به template ارسال شده است. کد template برای search_results.html ممکن است شامل چیزی شبیه به این باشد:
<p>You searched for: <strong>{{ query }}</strong></p> {% if books %} <p>Found {{ books|length }} book{{ books|pluralize }}.</p> <ul> {% for book in books %} <li>{{ book.title }}</li> {% endfor %} </ul> {% else %} <p>No books matched your search criteria.</p> {% endif %}
به کاربرد فیلتر template فوق یعنی pluralize توجه کنید، در صورتیکه مقدار لیست books بیشتر از یکی باشد حرف "s" را بر می گرداند.
اصلاح نمودن مثال ساده ی کنترل فرم
همانند فصل گذشته، یک فرم ساده از موضوع مورد بحث را نشان دادیم، حالا به برخی از مشکلات اشاره کرده و نحوه ی اصلاح آن را نشان خواهیم داد.
ابتدا، کنترل کردن یک ورودی خالی توسط تابع search() بسیار ضعیف می باشد – تنها نمایش داده یک پیام "Please submit a search term."، نیازمند این است که کاربر دکمه ی برگشت مرورگر را برای بازگشت به صفحه فشار دهد. این یک حالت نامطلوب و غیر حرفه ای می باشد، و در صورتیکه همیشه به این شکل کار می کنید، شما از امتیازات ویژه ی جنگو خود را محروم خواهید کرد.
اینکه در هنگام بروز خطا فرم نیز دوباره نمایش داده شود خیلی روش بهتری می باشد، به طوری که خطا در همان صفحه و در بالای فرم نمایش داده شود و کاربر بتواند به سرعت دوباره فرم را پر کرده و ارسال کند، چیزی مانند کد زیر:
from django.http import HttpResponse
from django.shortcuts import render_to_response
from mysite.books.models import Book
def search_form(request):
return render_to_response('search_form.html')
def search(request):
if 'q' in request.GET and request.GET['q']:
q = request.GET['q']
books = Book.objects.filter(title__icontains=q)
return render_to_response('search_results.html',
{'books': books, 'query': q})
else:
return render_to_response('search_form.html', {'error': True})
(توجه داشته باشید که تابع search_form() در کد فوق ایجاد شده است، بنابراین شما می توانید هر دوی view را در یک مکان مشاهده کنید.)
در مثال فوق تابع search() در صورتیکه فرم ارسالی خالی باشد، برای ارائه ی دوباره ی template مورد نظر یعنی search_form.html اصلاح شده است. و بدلیل نمایش یک پیام خطا در آن template، یک متغیر template ارسال شده است. حالا می توان search_form.html را جهت بررسی متغیر error بدین شکل ویرایش کرد:
<html>
<head>
<title>Search</title>
</head>
<body>
{% if error %}
<p style="color: red;">Please submit a search term.</p>
{% endif %}
<form action="/search/" method="get">
<input type="text" name="q">
<input type="submit" value="Search">
</form>
</body>
</html>
می توان همچنان از این template برای view اصلی یعنی search_form() استفاده کرد، زیرا search_form() هیچ متغیر error ای به template ارسال نمی کند – بنابراین پیام خطا در این مورد نشان داده نخواهد شد.
با تغییر فوق در اینجا، برنامه ی فعلی بهتر شده است، ولی این سوال پیش می آید: آیا وجود تابع search_form() واقعا ضروری است؟ یک درخواست به آدرس /search/ (بدون هیچ پارامتری از نوع GET) یک فرم خالی را نمایش خواهد داد (ولی با یک خطا). می توان تابع search_form() را به همراه URLpattern های همراه آن، تا زمانیکه کسی با هیچ پارامتر GET ای از /search/ بازدید می کند حذف نمود:
def search(request):
error = False
if 'q' in request.GET:
q = request.GET['q']
if not q:
error = True
else:
books = Book.objects.filter(title__icontains=q)
return render_to_response('search_results.html',
{'books': books, 'query': q})
return render_to_response('search_form.html',
{'error': error})
با تغییر فوق، در صورتیکه یک کاربر آدرس /search/ را با هیچ پارامتر GET ای بازدید کند، فرم جستجو را با هیچ پیام خطایی مشاهده خواهد کرد. در صورتیکه یک کاربر فرم را با یک مقدار خالی برای 'q' ارسال کند، صفحه ی جستجو را با یک پیام خطا مشاهده خواهد کرد. و در پایان، در صورتیکه یک کاربر فرم را به یک مقدار پر برای 'q' اراسل کند، نتایج جستجو در پایگاه داده را مشاده خواهد کرد.
می توان یک اصلاح پایانی جهت حذف یک قسمت زائد برای برنامه ی فوق ایجاد نمود. اکنون که دو view و آدرس را یکی شد و /search/ هر دوی نمایش فرم جستجو و نمایش نتیجه را کنترل می کند، فرم HTML در search_form.html نباید یک آدرس مستقیم داشته باشد. بجای کد زیر:
<form action="/search/" method="get">
می توان بدین شکل عمل کرد:
<form action="" method="get">
action="" یعنی "فرم را به آدرس همین صفحه ی فعلی ارسال کن." با تغییر فوق در اینجا، در صورتیکه همیشه تابع search() را به آدرس دیگر ارسال می کرده اید، دیگر لازم نیست تغییر دادن action را بیاد بیاورید.
تایید اعتبار آسان
مثال جستجوی بخش قبلی از نظر منطقی هنوز هم ساده است، مخصوصا در مورد تایید اعتبار داده؛ تنها اطمینان حاصل شد که مقدار ارسالی خالی نباشد. بسیاری از فرم های HTML حاوی یک سطح تایید اعتبار می باشند که پیچیده تر از اطمینان حاصل کردن از مقدار غیر خالی است. همه ی پیام های خطایی به این شکل درون وب دیده ایم:
- "لطفا یک آدرس الکترونیکی معتبر وارد کنید. 'foo' یک آدرس پست الکترونیکی نیست."
- "لطفا یک کد پستی پنج رقمی معتبر وارد کنید. '123' پست الکترونیکی نیست."
- "لطفا یک تاریخ معتبر با قالب بندی YYYY-MM-DD وارد کنید."
- لطفا رمز عبوری وارد کنید که حداقل هشت حرف داشته و حاوی حداقل یک عدد باشد.
یک نکته در تایید اعتبار جاوا اسکریپت
این موضوع خارج از حوصله ی این کتاب می باشد، ولی می توان از جاوا اسکریپت برای تایید اعتبار داده در سمت کلاینت به طور مستقیم در مرورگر استفاده کرد. اما آگاه باشید: حتی اگر این کار انجام دهید، باید در سمت سرور نیز داده را تایید اعتبار کنید. برخی افراد جاوا اسکریپت را خاموش می کنند، و برخی کاربران مخرب ممکن است برخی داده های غیر معتبر را برای مشاهده ی اینکه آیا می توانند تخریبی ایجاد کنند یا خیر درون کنترل کننده ی فرم ارسال می کنند.
هیچ کاری نمی توانید انجام دهید، غیر از اینکه همواره داده های ارسالی از سمت کاربر را در سمت سرور (مانند view های جنگو) تایید اعتبار کنید. باید اینگونه تصور کرد که تایید اعتبار جاوا اسکریپت نوعی ویژگی قابل استفاده اضافی می باشد، نه تنها به معنی یک تایید اعتبار.
اجازه دهید view قبلی یعنی search() را کمی پیچیده کنیم، به طوری که کاربر تنها بتواند داده ی ورودی برای جستجو را کمتر یا مساوی با بیست حرف وارد کند. (برای مثال، می توان این چنین گفت که هر چیزی طولانی تر مقدار گفته شده می تواند باعث کندی جستجو شود.) چه طور می توان آن را انجام داد؟ ساده ترین روش ممکن است قرار دادن منطق مورد نظر به طور مستقیم درون view می باشد، مانند زیر:
def search(request):
error = False
if 'q' in request.GET:
q = request.GET['q']
if not q:
error = True
elif len(q) > 20:
error = True
else:
books = Book.objects.filter(title__icontains=q)
return render_to_response('search_results.html',
{'books': books, 'query': q})
return render_to_response('search_form.html',
{'error': error})
حالا، در صورتیکه سعی کنید یک داده را برای جستجو با بیشتر از بیست حرف ارسال کنید، اجازه ی جستجو داده نخواهد شد؛ یک پیام خطا دریافت خواهید کرد. ولی پیام خطا در search_form.html در حال حاضر "Please submit a search term." می باشد. – بنابراین باید طوری آن را تغییر داد که برای هر دو مورد صحیح باشد:
<html>
<head>
<title>Search</title>
</head>
<body>
{% if error %}
<p style="color: red;">Please submit a search term 20 characters or shorter.</p>
{% endif %}
<form action="/search/" method="get">
<input type="text" name="q">
<input type="submit" value="Search">
</form>
</body>
</html>
در کد فوق نکته ای غیر زیبا وجود دارد. پیام خطای واحد برای همه کمی گیج کننده است. چرا باید پیام خطا برای یک فرم خالی ارسالی محدودیت بیست حرف را بیان کند؟ پیام خطا باید خاص، غیر مبهم بوده و گیج کننده نباشد.
در واقع مشکل آن است که از یک مقدار Boolean سایده برای خطا استفاده شده است، در حالیکه باید از یک لیست رشته های پیام خطا استفاده شود، در زیر نحوه ی حل این مشکل نشان داده شده است:
def search(request):
errors = []
if 'q' in request.GET:
q = request.GET['q']
if not q:
errors.append('Enter a search term.')
elif len(q) > 20:
errors.append('Please enter at most 20 characters.')
else:
books = Book.objects.filter(title__icontains=q)
return render_to_response('search_results.html',
{'books': books, 'query': q})
return render_to_response('search_form.html',
{'errors': errors})
سپس نیاز به ایجاد یک پیچیدگی کوچک درون فایل search_form.html برای بیان اینکه بجای یک مقدار Boolean یک لیست از error ها ارسال شده است.
<html>
<head>
<title>Search</title>
</head>
<body>
{% if errors %}
<ul>
{% for error in errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
<form action="/search/" method="get">
<input type="text" name="q">
<input type="submit" value="Search">
</form>
</body>
</html>
ساخت یک فرم تماس
اگرچه در سرتاسر مثال فرم جستجوی کتاب بررسی کرده و به طور ظریف آن را اصلاح نمودیم، ولی اساسا هنوز این مثال ساده می باشد: زیرا تنها دارای یک فیلد 'q' می باشد. به دلیل سادگی بیش از حد مثال مذکور حتی از کتابخانه ی فرم جنگو برای آن استفاده ای نشد. ولی فرم های پیچیده تر برای رفتار پیچیده تر فراخوانی می شوند – و حالا، چیزی پیچده تر را توسعه خواهیم داد: یک سایت فرم تماس.
این مثال یک فرم خواهد بود که به کاربران سایت اجازه اصلاح و وارد کردن اطلاعات را به همراه یک فیلد اختیاری آدرس پست الکترونیکی می دهد. بعد از آنکه فرم ارسال و داده تایید اعتبار شد، به طور خودکار یک پیام از طریق آدرس پست الکترونیک به کارمندان سایت ارسال خواهد شد.
با template مورد نظر contact_form.html شروع می کنیم:
<html>
<head>
<title>Contact us</title>
</head>
<body>
<h1>Contact us</h1>
{% if errors %}
<ul>
{% for error in errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
<form action="/contact/" method="post">
<p>Subject: <input type="text" name="subject"></p>
<p>Your e-mail (optional): <input type="text" name="email"></p>
<p>Message: <textarea name="message" rows="10" cols="50"></textarea></p>
<input type="submit" value="Submit">
</form>
</body>
</html>
سه فیلد تعریف شده است: موضوع، آدرس پست الکترونیک و پیام. دومین فیلد اختیاری می باشد، ولی دو فیلد دیگر الزاما باید پر شوند. توجه داشته باشید که به جای method="get" در اینجا از method="POST" استفاده شده است، زیرا ارسال فرم ممکن است دارای یک اثر زیان بار باشد – فرستادن یک پست الکترونیکی. همچنین، کد نمایش خطا از template قبلی search_form.html کپی شده است.
در صورتیکه کار را با view بخش قبلی search() ادامه دهیم، یک نسخه ی خام از contact() ممکن است چیزی شبیه به کد زیر باشد:
from django.core.mail import send_mail
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
def contact(request):
errors = []
if request.method == 'POST':
if not request.POST.get('subject', ''):
errors.append('Enter a subject.')
if not request.POST.get('message', ''):
errors.append('Enter a message.')
if request.POST.get('email') and '@' not in request.POST['email']:
errors.append('Enter a valid e-mail address.')
if not errors:
send_mail(
request.POST['subject'],
request.POST['message'],
request.POST.get('email', 'noreply@example.com'),
['siteowner@example.com'],
)
return HttpResponseRedirect('/contact/thanks/')
return render_to_response('contact_form.html',
{'errors': errors})
(در صورتیکه مثال های کتاب را دنبال می کنید، ممکن است مردد باشید که آیا view فوق را درون فایل books/views.py قرار دهید یا خیر. کد فوق هیچ کاری با برنامه ی books انجام نمی دهد، بنابراین آیا می توان جای دیگری آن را قرار داد؟ این کاملا به تصمیم شما بستگی دارد؛ جنگو هیچ توجهی به این موضوع ندارد، البته تا زمانیکه شما درون URLconf به درستی به view مورد نظر اشاره کنید. ترجیح شخصی ما بر این است که یک دایرکتوری جدا به نام contact هم سطح با دایرکتوری books بسازید. این دایرکتوری یک __init__.py و views.py خواهد داشت.)
چند نکته ی جدید که در مثال فوق اتفاق افتاده است:
- بررسی شده است که request.method مقدار 'POST' باشد. تنها در صورتیکه فرم ارسال شده باشد مقدار آن True خواهد بود؛ در صورتیکه شخصی تنها فرم تماس را مشاهده کند مقدار آن True نخواهد بود. (در مورد مشاهده ی فرم تماس request.method مقدار 'GET' خواهد داشت، زیرا در حالت عادی جستجو در وب، مرورگرها از GET استفاده می کنند نه از POST. این حالت روش زیبایی را برای مجزا کردن مورد نمایش فرم از مورد پردازش فرم ایجاد می کند.)
- بجای request.GET، برای دسترسی به داده های ارسال شده ی فرم از request.POST استفاده شده است. این حرکت بسیار ضروری می باشد، زیرا تگ <form> در contact_form.html از method="post" استفاده کرده است. در صورتیکه view از طریق POST قابل دسترسی باشد، بنابراین request.GET خالی خواهد بود.
- در مثال فوق، دو فیلد subject و message به صورت الزامی وجود دارد، بنابراین باید هر دوی آن ها تایید اعتبار شوند، توجه داشته باشید که به جای request.POST[] از request.POST.get() استفاده شده است، هنگام استفاده از request.POST[] در صورتیکه کلید مورد نظر وجود نداشته باشد موجب بروز خطای MultiValueDictKeyError خواهد شد، برای جلوگیری از بروز خطا در صورت نبود کلید از request.POST.get() استفاده شده است که آرگومان دوم مقدار پیشفرضی می باشد که در صورت نبود کلید این مقدار برگردانده می شود.
- اگرچه فیلد email یک فیلد الزامی نمی باشد، ولی در صورت وارد کردن مقداری برای این فیلد تایید اعتبار شده است. الگوریم تایید اعتبار در اینجا بسیار شکننده است، زیرا تنها بررسی شده است که رشته ی مورد نظر حاوی علامت (@) باشد. در دنیای واقعی، شما تایید اعتبارهایی قویتر نیاز دارید (جنگو همچنین تایید اعتباری را ارائه کرده است، که کمی بعد توضیح داده خواهد شد.)
- از تابع django.core.mail.send_mail برای فرستادن یک پست الکترونیکی استفاده شده است. این تابع دارای چهار آرگومان الزامی می باشد: موضوع پست الکترونیکی، بدنه ی پست الکترونیکی، آدرس فرستنده و یک لیستی از آدرس های گیرندگان. send_mail یک wrapper مناسب در اطراف کلاس EmailMessage می باشد، که ویژگی های پیشرفته ای مانند ضمیمه ها (attachments)، پست های الکترونیک چند قسمتی و کنترل کامل سرتاسر header های پست الکترونیک را ارائه می دهد.
توجه داشته باشید که به منظور فرستاند پست الکترونیک با استفاده از send_mail()، سرور شما باید برای فرستاده mail، پیکربندی شده باشد و همچنین تنظیمات خاصی برای این منظور نیز لازم می باشد که برای اطلاعات بیشتر در این باره می توانید به آدرس Sending email مراجعه کنید.
- بعد از فرستادن پست الکترونیک، با برگرداند یک شیء HttpResponseRedirect به یک صفحه ی "success" تغییر مسیر داده شده است. ولی باید دلیل استفاده از redirect را به جای برای مثال render_to_response() را توضیح دهیم.
دلیل: در صورتیکه یک کاربر دکمه ی "Refresh" را در آن صفحه ای که از طریق روش POST بارگذاری شده است فشار دهد، آن درخواست تکرار خواهد شد. این می تواند اغلب باعث بروز رفتاری ناخواسته شود، مانند اضافه شدن چندین بار یک رکورد تکراری در پایگاه داده – یا در مثال فوق، پست الکترونیکی دوباره فرستاده می شود. در صورتیکه کاربر بعد از POST به صفحه ی دیگری redirect شود، بنابراین هیچ شانسی برای تکرار شدن درخواست وجود ندارد.
شما باید همواره یک redirect برای نتایج POST موفقیت آمیز ایجاد کنید.
view فوق کار می کند، ولی توابع تایید اعتبار از توع ضعیفی می باشند. پردازش کردن یک فرم با ده ها فیلد را تصور کنید؛ آیا واقعا می خواهید برای همه ی آن ها عبارت های if بنویسید؟
مشکل دیگر نمایش دوباره ی فرم می باشد. در زمان ایجاد خطاهای اعتبار، بهترین تمرین نمایش دوباره ی فرم با داده های قبلی ارسال شده می باشد، به طوری که کاربر بتواند چیزی را که به طور اشتباه پر کرده است را مشاهده کند (و همچنین کاربر نیازی به دوباره پر کردن فیلدهایی که درست بوده اند پیدا نکند). می توان به صورت دستی داده های POST را به به سمت template پس فرستاد، ولی لازم است هر فیلد HTML را برای درج کردن مقدار مناسب در مکان مناسب ویرایش کرد:
# views.py
def contact(request):
errors = []
if request.method == 'POST':
if not request.POST.get('subject', ''):
errors.append('Enter a subject.')
if not request.POST.get('message', ''):
errors.append('Enter a message.')
if request.POST.get('email') and '@' not in request.POST['email']:
errors.append('Enter a valid e-mail address.')
if not errors:
send_mail(
request.POST['subject'],
request.POST['message'],
request.POST.get('email', 'noreply@example.com'),
['siteowner@example.com'],
)
return HttpResponseRedirect('/contact/thanks/')
return render_to_response('contact_form.html', {
'errors': errors,
'subject': request.POST.get('subject', ''),
'message': request.POST.get('message', ''),
'email': request.POST.get('email', ''),
})
# contact_form.html
<html>
<head>
<title>Contact us</title>
</head>
<body>
<h1>Contact us</h1>
{% if errors %}
<ul>
{% for error in errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
<form action="/contact/" method="post">
<p>Subject: <input type="text" name="subject" value="{{ subject }}"></p>
<p>Your e-mail (optional): <input type="text" name="email" value="{{ email }}"></p>
<p>Message: <textarea name="message" rows="10" cols="50">**{{ message }}**</textarea></p>
<input type="submit" value="Submit">
</form>
</body>
</html>
کد فوق بسیار ضعیف و غیر ضروری می باشد، و فرصت های زیادی را برای خطاهای فردی را ایجاد می کند.
اولین کلاس Form
جنگو دارای یک کتابخانه ی فرم با نام django.forms می باشد، که بسیاری از مسائل بررسی شده در این فصل را کنترل می کند – از نمایش فرم تا تایید اعتبار. اجازه دهید برنامه ی فرم تماس را با استفاده از فرم فریم ورک یا چارچوب جنگو دوباره بنویسیم.
کتابخانه ی "newforms" جنگو
ممکن است تاکنون چیزی درباره ی django.newforms شنیده باشید. هنگامی که درباره ی django.newforms صحبت می شود، صحبت درباره ی چیزی است که حالا django.forms نام دارد – که در این فصل به پرداخته شده است.
دلیل تغییر نام این کتابخانه تاریخی می باشد. هنگامی که جنگو برای عموم منتشر شد، دارای سیستم فرم های پیچیده و گیج کننده به نام django.forms بود. این کتابخانه دوباره به صورت کامل بازنویسی شد، و نسخه ی جدید django.newforms نامیده شد به طوری که افراد هنوز می توانستند از نسخه ی قدیمی استفاده کنند. هنگامی که جنگو 1.0 منتشر شد، django.forms کنار گذاشته شد، و django.newforms به django.forms تغییر نام داد.
روش اصلی برای استفاده از فریم ورک یا چارچوب فرم ها تعریف یک کلاس Form برای هر <form> HTML می باشد که با آن سرکار دارید. در مثال ما، تنها یک <form> داریم، بنابراین یک کلاس Form هم خواهیم داشت. این کلاس می تواند در هرجایی که می خواهید قرار بگیرد – حتی به طور مستقیم درون فایل views.py – ولی مانند یک قرارداد کلاس های Form درون یک فایل جدا به نام forms.py قرار می گیرند. این فایل را در مسیر فایل views.py بسازید، و کد زیر را درون آن وارد کنید:
from django import forms
class ContactForm(forms.Form):
subject = forms.CharField()
email = forms.EmailField(required=False)
message = forms.CharField()
کد فوق همانند مدل های جنگو می باشد. هر فیلد در فرم با یک نوع از کلاس فیلد – CharField و EmailField نوع هایی هستند که در کد فوق استفاده شده اند – به صورت attribute های کلاس Form نشان داده می شود. هر فیلد به صورت پیشفرض الزامی (required) می باشد، بنابراین برای اختیاری کردن فیلد email، required=False تعیین شده است.
اجازه دهید از طریق interactive interpreter پایتون کاری را که این کلاس انجام می دهد را با هم مشاهده کنیم، اولین کاری که می تواند انجام دهد نمایش خودش به صورت HTML می باشد:
>>> from contact.forms import ContactForm
>>> f = ContactForm()
>>> print f
<tr><th><label for="id_subject">Subject:</label></th><td><input type="text" name="subject" id="id_subject" /></td></tr>
<tr><th><label for="id_email">Email:</label></th><td><input type="text" name="email" id="id_email" /></td></tr>
<tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" /></td></tr>
جنگو یک label برای هر فیلد اضافه کرده است. هدف ایجاد رفتار تا حد ممکن بهینه به طور پیشفرض می باشد.
کد فوق خروجی پیشفرض در قالب بندی <table> HTML می باشد، ولی خروجی های داخلی دیگری نیز وجود دارند:
>>> print f.as_ul()
<li><label for="id_subject">Subject:</label> <input type="text" name="subject" id="id_subject" /></li>
<li><label for="id_email">Email:</label> <input type="text" name="email" id="id_email" /></li>
<li><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" /></li>
>>> print f.as_p()
<p><label for="id_subject">Subject:</label> <input type="text" name="subject" id="id_subject" /></p>
<p><label for="id_email">Email:</label> <input type="text" name="email" id="id_email" /></p>
<p><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" /></
توجه داشته باشید که تگ های باز و بسته ی <table>، <ul> و <form> درون خروجی وجود ندارند، به طوری که می توان هر ردیف اضافه ای را در صورت نیاز اضافه کرد.
این متدها تنها میانبرهای برای موارد رایج نمایش تمام فرم می باشند. همچنین می توان برای هر فیلد خاص نیز فرم را نمایش داد:
>>> print f['subject']
<input type="text" name="subject" id="id_subject" />
>>> print f['message']
<input type="text" name="message" id="id_message" />
دومین کاری که شیء های Form می توانند انجام دهند تایید اعتبار داده می باشد. برای تایید اعتبار داده، یک شیء Form جدید ایجاد کرده و یک دیکشنری از داده هایی که نام فیلد را به داده ی مورد نظر مربوط می کند به آن ارسال کنید.
>>> f = ContactForm({'subject': 'Hello', 'email': 'adrian@example.com', 'message': 'Nice site!'})
هنگامی که داده ها را با نمونه ی Form مربوط ساختید، شما یک "bound" فرم ساخته اید:
>>> f.is_bound
True
متد is_valid() را روی هر bound Form برای یافتن اینکه داده معتبر است یا خیر فراخوانی کنید. ما برای هر فیلد یک مثدار معتبر ارسال کردیم، بنابراین Form به کلی معتبر می باشد:
>>> f.is_valid()
True
در صورتیکه فیلد email را ارسال نکنیم، همچنان معتبر خواهد ماند، زیرا این فیلد را به صورت required=False تعیین کرده ایم:
>>> f = ContactForm({'subject': 'Hello', 'message': 'Nice site!'})
>>> f.is_valid()
True
ولی، در صورتیکه subject یا message را در نظر نگیریم، فرم دیگر معتبر نخواهد بود:
>>> f = ContactForm({'subject': 'Hello'})
>>> f.is_valid()
False
>>> f = ContactForm({'subject': 'Hello', 'message': ''})
>>> f.is_valid()
False
می توان اطلاعات بیشتری را از طریق پیام های خطا بدست آورد:
>>> f = ContactForm({'subject': 'Hello', 'message': ''})
>>> f['message'].errors
[u'This field is required.']
>>> f['subject'].errors
[]
>>> f['email'].errors
[]
هر نمونه ی bound Form دارای attribute های خطا می باشد که یک دیکشنری با نام فیلد های مرتبط شده با لیست های پیام خطا ارائه می دهد:
>>> f = ContactForm({'subject': 'Hello', 'message': ''})
>>> f.errors
{'message': [u'This field is required.']}
در پایان، برای نمونه هایی Form ای که دارای داده های معتبر می باشند، یک attribute با نام cleaned_data در دسترس می باشد. این attribute یک دشکنری از داده های ارسال (submit) شده می باشد. فریم ورک یا چارچوب فرم جنگو نه تنها داده را تایید اعتبار می کند، بلکه آن را به یک نوع مناسب پایتون تبدیل می کند.
>>> f = ContactForm({'subject': 'Hello', 'email': 'adrian@example.com', 'message': 'Nice site!'})
>>> f.is_valid()
True
>>> f.cleaned_data
{'message': u'Nice site!', 'email': u'adrian@example.com', 'subject': u'Hello'}
فرم تماس ما تنها با رشته ها سر کار دارد که به شیء های یونیکد تبدیل شده اند – ولی در صورتیکه از یک IntegerField یا dateField استفاده کنیم، فریم ورک یا چارچوب فرم از شیء های مناسب integer یا datetime.date برای فیلد داده شده استفاده خواهد کرد.
استفاده کردن از شیء های فرم درون view
در زیر نحوه ی استفاده از کلاس های فرم به جای روش قبلی را مشاهده خواهید کرد:
# views.py
from django.shortcuts import render_to_response
from mysite.contact.forms import ContactForm
def contact(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
send_mail(
cd['subject'],
cd['message'],
cd.get('email', 'noreply@example.com'),
['siteowner@example.com'],
)
return HttpResponseRedirect('/contact/thanks/')
else:
form = ContactForm()
return render_to_response('contact_form.html', {'form': form})
# contact_form.html
<html>
<head>
<title>Contact us</title>
</head>
<body>
<h1>Contact us</h1>
{% if form.errors %}
<p style="color: red;">
Please correct the error{{ form.errors|pluralize }} below.
</p>
{% endif %}
<form action="" method="post">
<table>
{{ form.as_table }}
</table>
<input type="submit" value="Submit">
</form>
</body>
</html>
نگاه کنید که تا چه مقدار قادر به حذف کردن کدهای اضافه ی قبلی می باشیم! فریم ورک یا چارچوب فرم جنگو نمایش HTML، تایید اعتبار، تبدیل داده ها و نمایش دوباره ی فرم با خطاها را کنترل می کند.
سعی کنید به صورت محلی روش فوق را امتحان کنید، با فیلدها خالی فرم را ارسال کنید، با فیلد نا معتبر برای email فرم را ارسال کنید، و در پایان فرم را با داده ی معتبر ارسال کنید. (البته بسته پیکربندی mail-server، ممکن است هنگام فراخوانی send_mail() دریافت کنید، که بحث آن جدا می باشد.)
تغییر نحوه ی ارائه ی فیلدها
ممکن است اولین چیزی که مورد توجه شما قرار گیرد این باشد که فیلد message به صورت <input type="text"> نمایش داده شده است، و باید یک <textarea> باشد. می توان این مشکل را به شکل زیر حل کرد:
from django import forms
class ContactForm(forms.Form):
subject = forms.CharField()
email = forms.EmailField(required=False)
message = forms.CharField(widget=forms.Textarea)
هر نوع فیلد دارای حالت پیشفرض خود می باشد، ولی می توان به سادگی این پیشفرض را تغییر داد، یا یک حالت مورد دلخواه را ایجاد نمود.
Think of the Field classes as representing validation logic, while widgets represent presentation logic.
تنظیم حداکثر طول
یک از رایج ترین نیازهای تایید اعتبار بررسی اندازه ی قطعی یک فیلد می باشد. برای اندازه گیری خوب، باید کلاس ContactForm را محدود به subject ای با صد حرف کرد. برای انجام چنین کاری، تنها یک max_length برای CharField قرار می دهیم:
from django import forms
class ContactForm(forms.Form):
subject = forms.CharField(max_length=100)
email = forms.EmailField(required=False)
message = forms.CharField(widget=forms.Textarea)
همچنین یک آرگومان اختیاری min_length نیز در دسترس می باشد.
تنظیم مقدار اولیه
به منظور بهبود این فرم، اجازه دهید یک مقدار اولیه برای فیلد subject اضافه کنیم: "I love your site". برای انجام چنین کاری می توان از آرگومان initial هنگامی که یک نمونه Form را می سازیم استفاده کرد.
def contact(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
send_mail(
cd['subject'],
cd['message'],
cd.get('email', 'noreply@example.com'),
['siteowner@example.com'],
)
return HttpResponseRedirect('/contact/thanks/')
else:
form = ContactForm(
initial={'subject': 'I love your site!'}
)
return render_to_response('contact_form.html', {'form': form})
حالا، فیلد subject با مقدار از پیش پر شده ی مورد نظر نمایش داده خواهد شد.
سفارشی کردن قوانین تایید اعتبار
تصور کنید یک فرم را برای پست الکترونیک راه اندازی کرده اید. ولی یک مشکلی وجود دارد: برخی از پیام های ارسال شده دارای تنها یک یا دو کلمه می باشند، که برای فهمیدن موضوع کافی نمی باشد. در این مورد تصمیم می گیرید یک روش تایید اعتبار جدید را برگزینید: لطفا چهار کلمه یا بیشتر.
تعدادی روش برای سفارشی ساختن تایید اعتبار فرم جنگو وجود دارد. در صورتیکه قانون جدید چیزی باشد که از آن همواره استفاده می کنیم، می توان یک نوع فیلد سفارشی ساخت.
می خواهیم یک تایید اعتبار اضافه در فیلد message ایجاد کنیم، بنابراین یک متد clean_message() به کلاس فرم خود اضافه می کنیم:
from django import forms
class ContactForm(forms.Form):
subject = forms.CharField(max_length=100)
email = forms.EmailField(required=False)
message = forms.CharField(widget=forms.Textarea)
def clean_message(self):
message = self.cleaned_data['message']
num_words = len(message.split())
if num_words < 4:
raise forms.ValidationError("Not enough words!")
return message
سیستم فرم جنگو به طور خودکار هر متدی را که با clean_ شروع و با نام یک فیلد تمام شود جستجو می کند. در صورتیکه همچین متدی وجود داشته باشد، در مدت تایید اعتبار فراخوانی خواهد شد.
به طور خاص، متد clean_message() بعد از تایید اعتبار منطقی پیشفرض برای فیلد داده شده فراخوانی می شود (در این مورد، تایید اعتبار منطقی برای یک CharField الزامی، می باشد.) زیرا داده ی فیلد در اینصورت تا حدی پردازش شده خواهد بود، این داده ی پردازش شده را از self.cleaned_data بدست می آوریم. همچنین، در این صورت لازم نیست درباره ی بررسی کردن اینکه مقدار وجود دارد و خالی نمی باشد نگران باشیم؛ این کار با تایید کننده ی اعتبار پیشفرض انجام شده است.
بسادگی از ترکیب len() و split() برای شمارش کلمات استفاده کرده ایم. در صورتیکه کاربر کلمات کمی وارد کند، خطای forms.ValidationError ایجاد خواهد شد. رشته ی اضافه شده به این خطا به صورت یک آیتم اضافه شده به لیست خطاها به کاربر نمایش داده خواهد شد.
مهم است که به طور صریح یک مقدار تمیز شده (cleaned) برای فیلد در پایان متد برگردانده شده است. این کار به ما اجازه می دهد مقدار را درون متد تایید اعتبار سفارشی خودمان تغییر دهیم (یا به نوع های دیگر پایتون تبدیل کنیم).
تعیین label ها
به طور پیشفرض، label ها در در جنگو به طور خودکار از طریق جا به جا کردن خط تیره با فاصله و همچنین تبدیل حرف اول به حرف بزرگ ساخته می شوند – بنابراین label برای فیلد email به این شکل خواهد بود: "Email". (این روش آشنا نیست؟ این الگوریتم همان الگوریتمی می باشد که مدل های جنگو از آن برای محاسبه ی مقدار پیشفرض verbose_name برای فیلدها از آن استفاده می کردند که در آموزش مدل جنگو توضیح داده شده است.)
ولی، همانند مدل های جنگو، می توان label فیلد داده شده را سفارشی کرد. تنها کافیست مانند زیر از label استفاده شود:
class ContactForm(forms.Form):
subject = forms.CharField(max_length=100)
email = forms.EmailField(required=False, label='Your e-mail address')
message = forms.CharField(widget=forms.Textarea)
سفارشی کردن طرح فرم
template ما یعنی contact_form.html از {{ form.as_table }} برای نمایش فرم استفاده کرده است، ولی می توان فرم را به روش های دیگری نیز برای کنترل ذره به ذره نمایش داد.
سریع ترین روش برای سفارشی کردن نمایش فرم ها استفاده از CSS می باشد. تولید کننده ی خودکار لیست های خطا دقیقا از <ul class="errorlist"> استفاده می کنند به طوری که می توان با استفاده از CSS آن ها را علامت گذاری کرد:
<style type="text/css">
ul.errorlist {
margin: 0;
padding: 0;
}
.errorlist li {
background-color: red;
color: white;
display: block;
font-size: 10px;
margin: 0 0 3px;
padding: 4px 5px;
}
</style>
زمانی این روش مناسب می باشد که بخواهیم حالت پیشفرض ارائه شده را تغییر دهیم. {{ form.as_table }} و ... میانبرهای مفیدی برای توسعه ی برنامه می باشند، ولی هرچیزی درباره ی روش نمایش یک فرم می تواند غالبا درون خود template دوباره نویسی شده و تغییر کند، و شما احتمالا انجام اینکار را برای خودتان خواهید یافت.
هر فیلدی (<input type="text">، <select>، <textarea> و ...) می تواند به صورت جداگانه با دسترسی داشتن به {{ form.fieldname }} در template ارائه شده باشد، و هر خطای مربوط به آن با یک فیلد به صورت {{ form.fieldname.errors }} قابل دسترسی می باشد. با در نظر گرفتن نکات فوق، می توان یک template سفارشی برای فرم تماس با کد زیر ایجاد نمود:
<html>
<head>
<title>Contact us</title>
</head>
<body>
<h1>Contact us</h1>
{% if form.errors %}
<p style="color: red;">
Please correct the error{{ form.errors|pluralize }} below.
</p>
{% endif %}
<form action="" method="post">
<div class="field">
{{ form.subject.errors }}
<label for="id_subject">Subject:</label>
{{ form.subject }}
</div>
<div class="field">
{{ form.email.errors }}
<label for="id_email">Your e-mail address:</label>
{{ form.email }}
</div>
<div class="field">
{{ form.message.errors }}
<label for="id_message">Message:</label>
{{ form.message }}
</div>
<input type="submit" value="Submit">
</form>
</body>
</html>
{{ form.message.errors }} در صورتیکه خطاها موجود باشند رشته ی خالی در صورتیکه فیلد معتبر باشد یک <ul class="errorlist"> را نمایش می دهد. همچنین می توان با form.message.errors به صورت یک Boolean رفتار کنید و یا حتی درون آن مثل یک لیست جستجو کنید. برای مثال:
<div class="field{% if form.message.errors %} errors{% endif %}">
{% if form.message.errors %}
<ul>
{% for error in form.message.errors %}
<li><strong>{{ error }}</strong></li>
{% endfor %}
</ul>
{% endif %}
<label for="id_message">Message:</label>
{{ form.message }}
</div>