oruji.github.io
oruji.github.ioPersian Tutorials
ویرایش: 1398/2/28 15:23
A A

آموزش فرم ها در جنگو (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 هایی بستگی دارد که کاربر فرستاده و وب سرور قرار داده است. برخی از کلید های در دسترس رایج در این دیکشنری:

توجه داشته باشید، چراکه 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 دسترسی پیدا کرد.

داده ی 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)

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

  1. <form> یک متغیر q تعریف می کند. هنگامی که فرم تایید می شود، ارزش q از طریق GET (method="get") به آدرس /search/ فرستاده می شود.
  2. 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)

داده ی پست نیر مانند داده ی 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.')

نکاتی که در کد فوق وجود دارد:

اصلاح نمودن مثال ساده ی کنترل فرم

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

ابتدا، کنترل کردن یک ورودی خالی توسط تابع 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 حاوی یک سطح تایید اعتبار می باشند که پیچیده تر از اطمینان حاصل کردن از مقدار غیر خالی است. همه ی پیام های خطایی به این شکل درون وب دیده ایم:

اجازه دهید 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 خواهد داشت.)

چند نکته ی جدید که در مثال فوق اتفاق افتاده است:

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 می باشد، که بسیاری از مسائل بررسی شده در این فصل را کنترل می کند – از نمایش فرم تا تایید اعتبار. اجازه دهید برنامه ی فرم تماس را با استفاده از فرم فریم ورک یا چارچوب جنگو دوباره بنویسیم.

روش اصلی برای استفاده از فریم ورک یا چارچوب فرم ها تعریف یک کلاس 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>