آموزش جنگو (Basic) : جلسه نه | فرم ها

در این جلسه به بررسی فرم ها در جنگو می پردازیم و آنها را در برنامه خود ایجاد می کنیم . با ما همراه باشید .آموزش جنگو : جلسه نه | فرم ها فرم هافرم ها در فضای وب برای دریافت اطلاعاتی از سوی کاربر استفاده میشوند . این اطلاعات هر چیزی میتواند باشند. برای مثال هنگامی که دیجی کالا می خواهد یک کاربر را لاگین کند و یا ثبت نام کند ,برای دریافت شماره تلفن از فرم ها استفاده میکند . یا هنگامی که کاربر بخواهد پستی را در اینستاگرام لایک کند , اینستاگرام از فرم ها برای این منظور استفاده میکند . در واقع فرم ها پلی میان سرور و کلاینت هستند که ارتباط مستقیم را با کاربر برای ما برقرار میکنند .ایجاد یک فرم (فرم رای گیری)برای یادگیری نحوه کار با فرم ها بهتر است تا یکی از آنها را در پروژه خود ایجاد کنیم. بنابراین بیایید به کامل کردن پروژه جلسه قبل بپردازیم.ابتدا باید فرم را با زبان html در فایل polls/detail.html ایجاد کنید. فایل مربوطه را به صورت زیر تغییر دهید . (برای این بخش شما نیاز دارید تا با انواع متود های وب و کمی زبان html اشنا باشید که در این مقاله مباحث آنها بررسی نمی شود )<form action='{% url 'polls:vote' question.id %}' method='post'> {% csrf_token %} <fieldset> <legend> <h1> {{ question.question_text }} </h1> </legend> {% if error_message %} <p> <strong> {{ error_message }} </strong> </p> {% endif %} {% for choice in question.choice_set.all %} <input type='radio' name='choice' id='choice{{ forloop.counter }}' value='{{ choice.id }}'> <label for='choice{{ forloop.counter }}'>{{ choice.choice_text }}</label><br> {% endfor %} </fieldset> <input type='submit' value='Vote'> </form>بیایید به بررسی کد بالا بپردازیم :1-تمپلیت بالا برای هر گزینه (choice) از هر سوال یک radio button قرار میدهد (radio button =دکمه گرد شکل در سند های html) . هنگامی که کاربر یک دکمه را از میان گزینه ها انتخاب کند , درخواستی از نوع POST و همراه با id گزینه انتخاب شده برای جنگو ارسال میشود (در واقع مقدار name تگ در سرور پردازش خواهد شد). این کاری است که یک فرم html انجام میدهد .2-ما مقدار method را روی POST تنظیم کردیم . این فقط مختص به جنگو نیست و برای تمام وب این گونه است . هرگاه که شما نیاز داشته باشید اطلاعاتی که از فرم دریافت میکنید را در سمت سرور پردازش کنید و روی آن عملیاتی انجام بدهید (در اینجا میخواهیم در دیتابیس آن را ثبت کنیم) باید متود را برابر با POST قرار دهیم .3-کد forloop.counter تعداد بار هایی را هر بار به شما می دهد که حلقه for خودش را تکرار کرده است . برای مثال , در پیمایش چهارم حلقه , این کد برابر 4 خواهد بود .4-مقدار action فرم , روی {% url ‘polls:vote’ question.id %} تنظیم شده است که در واقع اشاره به همان ادرس http://127.0.0.1:8000/<int:question_id>/vote دارد . (ادرسی است که در آن رای ها ثبت میشوند و وظیفه ویو مربوط به آن ثبت رای ها در دیتابیس است . همچنین بجای question_id مقدار id همان سوال و رای مربوط به آن قرار میگیرد)5-هرگاه شما از متود POST استفاده میکنید ,شما باید حواس خود را به باگ cross site request forgeries جمع کنید . خوشبختانه جنگو برای شما این مشکل را رفع کرده است . جنگو از متد csrf استفاده میکند تا فرم ها و ورودی ها را کنترل کند . شما فقط کافیست در هر فرمی که ایجاد میکنید مقدار {% csrf_token%} را قرار دهید تا به صورت اتوماتیک جنگو بقیه کار ها را برای شما انجام بدهد .حال بیایید به سراغ نوشتن ویو vote برویم . قبلا ادرسی با نام vote به صورت زیر ایجاد کرده بودیم (urls.py) :path('<int:question_id>/vote/', views.vote, name='vote'),همچنین ویو مربوط به این ادرس را ایجاد کرده بودیم که کار خاصی را انجام نمیداد . اکنون زمان اصلاح آن است. تابع vote را در فایل polls/views.py به صورت زیر تغییر دهید .from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import get_object_or_404, render from django.urls import reverse from .models import Choice, Question # ... def vote(request, question_id): question = get_object_or_404(Question, pk=question_id) try: selected_choice = question.choice_set.get(pk=request.POST['choice']) except (KeyError, Choice.DoesNotExist): # Redisplay the question voting form. return render(request, 'polls/detail.html', { 'question': question, 'error_message': &quotYou didn't select a choice.&quot, }) else: selected_choice.votes += 1 selected_choice.save() return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))اکنون شاید کمی از حجم بالای این قطعه کد ترسیده باشید :)نگران نباشید ,بیشتر کد را اکنون میتوانید بخوانید . فقط نکاتی باقی مانده است که باید به آنها توجه کنید :1-مقدار request.POST یک دیکشنری است که می توانید به وسیله key , به مقادیر داخل آن دسترسی پیدا کنید . درون آن اطلاعات فرم ثبت شده وجود دارد . همچنین مقادیر آن همیشه str است . (نوع داده ای آنها) و در این مورد request.POST[‘choice’] به id گزینه یا گزینه انتخاب شده توسط کاربر اشاره دارد . (مقدار choice در واقع مقدار name در عنصر input موجود در فرم است)2-در بدنه try ما تلاش کردیم تا مقدار گزینه را از request.POST بگیریم . اگر کاربر گزینه ای را انتخاب نکرده باشد ,این کد به شما keyError خواهد بازگرداند . زیرا این key در دیکشنری request.POST وجود ندارد , پس در بدنه except ما فرم را همراه با یک ارور به کاربر دوباره نمایش میدهیم تا گزینه ای را انتخاب کند .3-در آخر نیز اگر بدنه try با موفقیت اجرا شد ,مقدار vote آن choice را یک عدد اضافه میکنیم و این گونه آن را در دیتابیس ثبت میکنیم . گرچه ما باید پاسخی را به کاربر بازگردانیم , پس کاربر را ریدایرکت میکنیم . (برای اشنایی بیشتر با redirect یا ریدایرکت کمی در گوگل جستجو کنید اما به طور خلاصه به هدایت کاربر از صفحه به صفحه دیگر ریدایرکت گفته میشود )4-تابع redirect به عنوان ورودی مقدار ادرسی که کاربر قرار است به آن ریدایرکت شود را می پذیرد , اما در اینجا ما از تابع reverse بجای آدرس مورد نظر استفاده کردیم (ادرس مورد نظر در واقع صفحه نتایج یا همان results است).تابع redirect برای شما ادرس را به صورت خودکار ایجاد میکند . فقط کافیست تا مقادیری را به آن بدهید . دلیل استفاده ما از این تابع نیز این است که از نامفهوم نوشتن کد جلوگیری کنیم و کد تمیزی را داشته باشیم . ما در اینجا برای مقدار ,ابتدا به ادرس results اشاره کردیم که در صفحات پیشین در فایل polls/urls.py آن را نوشتیم. اشاره کردن به این ادرس نیز مانند کاری بود که در تگ {% url %} انجام دادیم (ابتدا اسم اپ و سپس name تعیین شده در ()path ). در ادامه به عنوان args مقدار question.id را ارسال کردیم . این مقدار در واقع همان question_id است که در ورودی تابع results دریافت میشود و تابع reverse به صورت اتوماتیک برای ما آن را ارسال میکند . اکنون تابع reverse به ادرس زیر اشاره دارد :'/polls/1/results/'حال به مرحله اخر میرسیم که کاربر رای خود را ثبت کرده است و به صفحه results هدایت شده است . ویو resluts را در فایل polls/views.py بنویسید :from django.shortcuts import get_object_or_404, render def results(request, question_id): question = get_object_or_404(Question, pk=question_id) return render(request, 'polls/results.html', {'question': question})حال زمان آن است تمپلیت مربوط به آن را ایجاد کنید . در مسیر polls/results.html کد زیر را بنویسید .<h1>{{ question.question_text }}</h1> <ul> {% for choice in question.choice_set.all %} <li>{{ choice.choice_text }} -- {{ choice.votes }}vote{{ choice.votes|pluralize }}</li> {% endfor %} </ul> <a href='{% url 'polls:detail' question.id %}'>Vote again?</a>اکنون , برای مثال , در ادرس http://127.0.0.1:8000/1 میتوانید سوالی را ببینید و به آن رای بدهید و کار اصلی ما تا اینجا تمام شده است .نکته : ویو vote یک مشکل کوچک دارد . این ویو ابتدا گزینه انتخاب شده را از درون دیتابیس پیدا میکند و سپس مقدار جدید آن را حساب میکند و درون دیتابیس ذخیره میکند . مشکل زمانی ایجاد می شود که دو کاربر همزمان با هم رای بدهند . در اینجا باید 2 رای برای گزینه موردنظر ثبت شود , اما این ایراد باعث می شود تا فقط یک رای بجای هر دوی آنها ثبت شود . راه حل این است که از تابع ()F استفاده کنیم که اینجا جای بررسی آن وجود ندارد . در بخش های بعدی با این تابع اشنا خواهید شد .در این جلسه پروژه جلسه قبل خود را تکمیل تر از قبل کردیم و با فرم ها در جنگو و نحوه هندل آنها آشنا شدیم . البته مباحث بیشتری از فرم ها (مانند اعتبارسنجی) بررسی نشد که بسیار مباحث مهمی هستند . در جلسه بعدی به بررسی generic view در جنگو خواهیم پرداخت .