کانتست تمام گشت و به پایان رسید المپیک ما همچنان در اوّلِ وصفِ سوال ماندهایم...
با رسیدن به آخرین سوال از مسابقات پایتون جنگوی سری دوم #المپیکفناوری پردیس، علی غرغرو (Ali GhorGhorooo) که از ماهها قبل از شروع این سری مسابقات، با غرهای فراوانی مانند "من میدونمممممممممممممممم ما موفق نمیشیممممممممممممم"، نقش بسزایی در پیشبرد این سری از مسابقات داشته است، میخواهد تا با توسعهی یک فرمساز پویا (Dynamic Form Builder) جدید برای کوئرا، نظرسنجی جامع و کاملی از تمام شرکتکنندگان این مسابقات در مورد تمام بخشها، از جمله محتوا و سطح سوالات تا داستانهای عجیب غریب و طولانی سوالات مسابقه و کیفیت برگزاری مسابقه حضوری فینال انجام دهد.
فرمهایی که این فرمسازی پویا قرار است تا برای نظرسنجی از شرکتکنندگان بسازد، باید قابلیتهای پیشرفتهای مانند ثبت زمان پاسخدهی کاربران، تولید فرمها از روی مدل دادهها و پردازش پاسخها را داشته باشد تا علی غرغرو در نهایت بتواند با تحلیل و بررسی دادههای نظرسنجی انجام شده و زدن غرهای بهتر و بیشتر، سری سوم مسابقات #المپیکفناوری پردیس که قرار است در سال آینده برگزار شود را به صورت هر چه بهتری برگزار کند. از آنجایی که سر او همچنان با گفتن "من میدونمممممممممممممممم ما موفق نمیشیممممممممممممم" برای تیم برگزاری مسابقه فینال، حسابی شلوغ است، شما قرار است تا در این سوال به پیادهسازی بخشهایی از این فرمساز جدید کوئرا بر اساس توضیحات داده شده بپردازید.

پروژهی اولیه
برای دانلود پروژهی اولیه روی این لینک کلیک کنید.
ساختار فایلها
form-builder/
├── config
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── forms
│ ├── migrations
│ │ ├── __init__.py
│ │ └── 0001_initial.py
│ ├── templatetags
│ │ ├── __init__.py
│ │ └── form_tags.py
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── forms.py
│ ├── middleware.py
│ ├── models.py
│ ├── urls.py
│ ├── utils.py
│ └── views.py
├── static
│ ├── css
│ │ └── style.css
│ └── js
│ └── main.js
├── templates
│ ├── forms
│ │ ├── field_create.html
│ │ ├── field_delete.html
│ │ ├── field_edit.html
│ │ ├── form_analytics.html
│ │ ├── form_builder.html
│ │ ├── form_create.html
│ │ ├── form_delete.html
│ │ ├── form_detail.html
│ │ ├── form_edit.html
│ │ ├── form_list.html
│ │ ├── form_responses.html
│ │ ├── form_success.html
│ │ ├── response_delete.html
│ │ └── response_detail.html
│ └── base.html
├── manage.py
└── requirements.txt
راهاندازی پروژه
برای اجرای پروژه، باید پایتون و ابزار pip را از قبل نصب کرده باشید.
-
ابتدا پروژهی اولیه را دانلود و از حالت فشرده خارج کنید.
-
در پوشهی اصلی پروژه، یک محیط مجازی پایتون (venv) ایجاد و فعال کنید:
python -m venv venv
source venv/bin/activate # در ویندوز: venv\Scripts\activate
- دستور زیر را برای نصب نیازمندیها در پوشهی اصلی پروژه اجرا کنید:
pip install -r requirements.txt
- برای اجرای پروژه Django دستور زیر را در مسیر پوشهی اصلی پروژه اجرا کنید:
python manage.py runserver
در صورت اجرای موفق، یک لینک در خروجی نمایش داده میشود که میتوانید آن را در مرورگر باز کنید.
- برای اجرای مایگریشنها و ایجاد جداول پایگاه داده، دستور زیر را اجرا کنید:
python manage.py migrate
- برای بارگذاری دادههای نمونه از فایل fixture، دستور زیر را اجرا کنید:
python manage.py loaddata data.json
جزئیات پروژه
فایل models.py
models.pyدر این فایل تعدادی مدل وجود دارد که دو تا از آنها را شما باید پیادهسازی کنید؛ در تصویر زیر نمودار ER مربوط به آن جهت درک بهتر روابط میان این مدلها نمایش داده شده است.
| نمودار Entity Rlationship Diagram (ERD) مربوط به مدلها |
در این قسمت باید دو مدل Form و Field را به شکل گفته شده پیادهسازی کنید.
مدل Form
Formاین مدل وظیقهی نگهداری فرمهای ساخته شده توسط کاربران را دارد و اطلاعات مربوط به هر فرم را در خود ذخیره کرده تا در ادامه با استفاده از مدل Field بتوانیم سوالات برای این فرم ایجاد کنیم و آن را برای پاسخدهی به اشتراک بگذاریم.
| فیلد | نوع داده | توضیحات |
|---|---|---|
title |
CharField |
بیشترین طول مجاز ۲۰۰ کاراکتر |
description |
TextField |
توضیحات فرم (اختیاری) |
success_message |
TextField |
پیام موفقیت بعد از ارسال |
allow_multiple_submissions |
BooleanField |
اجازه چند بار پاسخ دادن به یک کاربر (پیشفرض: False) |
submission_limit |
PositiveIntegerField |
حداکثر تعداد پاسخهای مجاز (اختیاری) |
expires_at |
DateTimeField |
تاریخ انقضای فرم (اختیاری) |
is_active |
BooleanField |
فعال یا غیرفعال بودن فرم (پیشفرض True) |
created_by |
ForeignKey |
کاربری (User) که فرم را ساخته |
created_at |
DateTimeField |
تاریخ ایجاد فرم (خودکار) |
updated_at |
DateTimeField |
آخرین بروزرسانی (خودکار) |
- باید بتوان از طریق مدل
Userبا استفاده از ویژگیcreated_formsبه فرمهای ساخته شده توسط کاربر دسترسی داشت و همچنین در صورت حذف کاربر فرمهای مربوط به آن نیز حذف شوند. - مقدار پیشفرض برای فیلد
success_messageبرابر با رشتهی زیر است:
Thank you for your submission!
ویژگی is_expired
بررسی میکند آیا فرم منقضی شده است؛ برای اینکار باید بررسی کنید زمان فعلی از زمان منقضی شدن فرم کمتر باشد.
ویژگی is_submission_limit_reached
بررسی میکند آیا محدودیت پاسخها پر شده است یا خیر؛ برای اینکار باید از ارتباط میان این مدل با مدل Response استفاده کنید و در صورتی که متغیر submission_limit برای این فرم مقداردهی شده است، چک کنید آیا تعداد پاسخها بیشتر مساوی مقدار این فیلد باشد؛ در غیر اینصورت False برگردانید.
ویژگی can_accept_submissions
یک مقدار بولی برمیگرداند که نشان میدهد فرم هنوز میتواند پاسخ جدید بگیرد یا خیر؛ برای پیادهسازی باید چک کنید فرم فعال باشد و منقضی نشده باشد و محدودیت پاسخ آن پر نشده باشد؛ در غیر اینصورت مقدار False برگردانده شود.
متد __str__
خروجی این متد رشتهای به فرمت زیر میباشد:
TITLE
متد get_response_count()
در این متد باید تعداد پاسخهای مرتبط با فرم را از طریق رابطهی میان مدلهای Form و Response برگردانید.
متد get_completion_rate()
نرخ تکمیل فرم را برمیگرداند؛ نرخ تکمیل که عددی ببین 0 و 100 است، از تقسیم تعداد پاسخها بر تعداد بازدیدهای فرم ضربدر 100 بدست میآید. همچنین برای دریافت تعداد ویوهای مربوط به هر فرم باید از رابطهی موجود میان مدلهای Form و FormView استفاده کرد.
مدل Field
Fieldاین مدل وظیفهی نگهداری اطلاعات مربوط به سوالات یک فرم را دارد و باید حاوی فیلدهای زیر باشد.
| فیلد | نوع داده | توضیحات |
|---|---|---|
form |
ForeignKey |
ارجاع به فرم (Form) که این فیلد متعلق به آن است |
label |
CharField |
عنوان فیلد (حداکثر ۲۰۰ کاراکتر) |
field_type |
CharField |
نوع فیلد |
help_text |
TextField |
متن راهنما برای فیلد (اختیاری) |
placeholder |
CharField |
متن پیشفرض داخل فیلد (اختیاری) |
is_required |
BooleanField |
آیا پر کردن فیلد اجباری است یا خیر (پیشفرض: False) |
order |
PositiveIntegerField |
ترتیب نمایش فیلد در فرم (پیشفرض: 0) |
options |
JSONField |
گزینهها برای فیلدهای چند گزینهای (پیشفرض: لیست خالی) |
min_length |
PositiveIntegerField |
حداقل طول متن (اختیاری) |
max_length |
PositiveIntegerField |
حداکثر طول متن (اختیاری) |
min_value |
FloatField |
حداقل مقدار (اختیاری) |
max_value |
FloatField |
حداکثر مقدار (اختیاری) |
regex_pattern |
CharField |
الگوی regex برای اعتبارسنجی (اختیاری) |
custom_error_message |
CharField |
پیام خطای دلخواه (اختیاری) |
- فیلد
field_typeمیتواند از یک فیلد انتخابی است که مقادیر مجاز برای آن به شکل زیر است:
FIELD_TYPES = [
('text', 'Text'),
('email', 'Email'),
('number', 'Number'),
('textarea', 'Textarea'),
('select', 'Select'),
('radio', 'Radio'),
('checkbox', 'Checkbox'),
('file', 'File Upload'),
('date', 'Date'),
('url', 'URL'),
('phone', 'Phone'),
]
optionsفقط برای فیلدهای چند گزینهای (select,radio,checkbox) استفاده میشود و انتخابهای مجاز آن را نشان میدهد.min_lengthوmax_lengthبرای فیلدهای متنی کاربرد دارند و درصورت مقدار دهی یک بازهی مشخص برای طول کاراکترهای آن فیلد تعیین میشود.min_valueوmax_valueبرای فیلدهای عددی کاربرد دارند و درصورت مقدار دهی یک بازهی مشخص برای مقدار آن فیلد تعیین میشود.
متد __str__
خروجی این متد رشتهای به فرمت زیر میباشد:
FORM_TITLE - LABEL
متد clean()
در این متد باید صحت مقادیر داده شده را برای ایجاد یک نمونه از مدل Field را بررسی کنید.
برای این کار ابتدا بررسی کنید اگر این فیلد از یکی از انواع فیلدهای انتخابی است و مقدار options خالی یا برابر لیست خالی است، یک ValidationError با پیغام زیر پرتاب کنید:
Choice fields must have at least one option.
در مرحلهی بعد، در صورت مقداردهی فیلدهای min_value و max_value بایستی بررسی کنید مقدار max_value همواره بزرگتر مساوی مقدار min_value باشد و در غیر این صورت یک ValidationError با پیغام زیر پرتاب کنید:
Minimum value cannot be greater than maximum value.
در ادامه همین اعتبارسنجی را برای فیلدهای min_legth و max_length نیز انجام دهید و در صورت وجود خطا باید حاوی پیغام زیر باشد:
Minimum length cannot be greater than maximum length.
در نهایت در صورت مقداردهی فیلد regex_pattern، بررسی کنید آیا الگوی وارد شده یک الگوری Regex معتبر است یا خیر. در صورت نامعتبر بودن یک ValidationError با پیغام زیر پرتاب کنید:
Invalid regex pattern.
متد validate_value(value)
این متد وظیفهی اعتبارسنجی مقدار ورودی (value) برای یک نمونه از مدل Field را دارد. خروجی این متد یک لیست خواهد بود که در صورت وجود خطا حاوی این خطاها میباشد.
در صورتی که فیلد اجباری است اما مقدار ورودی خالی است (یعنی مقدار دهی نشده است) یک لیست تکعضوی حاوی رشتهی زیر را برگردانید:
"{label} is required."
اگر پاسخ به فیلد اختیاری است و مقدار ورودی نیز خالی داده نشده، یک لیست خالی برگردانید و اجرای تابع را متوقف کنید تا سایر اعتبارسنجی ها روی این فیلد انجام نشود.
اما در غیر این صورت بر اساس نوع فیلد (field_type) اعتبارسنجیهای زیر را انجام دهید:
email: مقدار ورودی را با الگوی[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$تطابق داده و در صورت عدم تطابق خطای زیر را به لیست خطاها اضافه کنید:
Please enter a valid email address.
number: در اینصورت سه مرحله اعتبارسنجی باید انجام دهید و هر کجا به خطا برخوردید دیگر ادامه ندهید؛ در مرحلهی اول، چک کنید مقدار داده شده از نوع عددی باشد و در غیر این صورت ارور زیر را به لیست ارورها اضافه کنید:
Please enter a valid number.
اگر ورودی از نوع عددی بود و فیلد min_value مقداردهی شده بود باید بررسی شود مقدار ورودی بیشتر مساوی مقدار این فیلد باشد و در غیر اینصورت خطای زیر به لیست خطاها اضافه شود:
Value must be at least {min_value}.
و اگر ورودی از نوع عددی بود و از مراحل قبل عبور کردید، این بار همین کار را برای فیلد max_value انجام دهید و بررسی کنید مقدار ورودی کمتر مساوی مقدار این فیلد باشد و در غیر این صورت خطای زیر را اضافه کنید:
Value must be at most {max_value}.
url: باید مقدار آدرس داده شده را بررسی کنیم تا یک URL معتبر باشد؛ برای اینکار با استفاده از الگوی زیر این مورد را بررسی کنید:
^https?://(?:[-\w.])+(?:[:\d]+)?(?:/(?:[\w/_.])*(?:\?(?:[\w&=%.])*)?(?:#(?:\w*))?)?$
در صورت عدم تطابق پیغام خطای زیر را به لیست خطاها اضافه کنید:
Please enter a valid URL.
phone: برای بررسی معتبر بود شماره تلفن وارد شده باید ابتدا آن را با الگوی^\+?[1-9][0-9]{7,14}$تطبیق داده و در صورت عدم مطابقت پیغام خطای زیر را اضافه کنید:
Please enter a valid phone number.
selectیاradio: در صورتی که مقدار ورودی دادهشده در فیلدoptionsکه مقادیر مجاز برای فیلدهای انتخابی است وجود نداشت، پیغام خطای زیر را اضافه کنید:
Please select a valid option.
checkbox: برای فیلدهای از نوع چند انتخابی که ورودی از نوع لیست است، باید تمام اعضای لیست را بررسی کنید و در صورتی که حداقل یکی از اعضای لیست ورودی جز مقادیر مجاز برای این فیلد نبود خطای زیر را به لیست خطاها اضافه کنید:
Please select valid options.
در ادامه باید مقادیر max_length و min_length را بررسی کنید؛ در صورت مقداردهی باید مقدار ورودی را با توجه به مقادیر موجو در این متغیر ها بررسی کنید تا در بازهی مورد نظر باشند. در صورتی که طول کاراکترهای ورودی از min_legth کمتر است خطای زیر را اضافه کنید:
Must be at least {min_length} characters long.
در غیر اینصورت اگر طول کاراکترها از max_length بیشتر است پیغام خطای زیر را اضافه کنید:
Must be at most {max_length} characters long.
در آخر، در صورت مقداردهی فیلد regex_pattern باید الگوی موجود در این فیلد را با مقدار ورودی تطبیق داده و در صورت عدم تطابق، اگر فیلد custom_error_message مقداردهی شده بود آن را به لیست ارورها اضافه کنید و در غیر اینصورت خطای زیر را اضافه کنید:
Invalid format.
در نهایت پس از انجام اعتبارسنجیهای بالا لیست خطاها را برگردانید.
فایل forms.py
forms.pyدر این فایل شما وظیفهی تکمیل یک کلاس به نام DynamicForm را دارید.
این فرم وظیفه دارد از روی مدلهای ذخیرهشده (Form و Field) بهطور پویا یک فرم HTML مناسب برای نمایش به کاربر بسازد. تابع سازندهی کلاس (__init__()) از قبل پیادهسازی شده و شما تنها کافیست منطق مربوط به اعتبارسنجی و ذخیرهی مدل در پایگاهداده را در متدهای clean() و save() پیادهسازی کنید.
متد clean
از این متد برای اعتبارسنجی دادههای فرم بعد از پر شدن توسط کاربر استفاده میکنیم.
در این متد ابتدا با صدا زدن متد clean کلاس والد اعتبارسنجی پیشفرض را انجام دهید و دیکشنری cleaned_data که برمیگرداند را در یک متغیر ذخیره کنید. در مرحلهی بعد روی همهی فیلدهای مرتبط با فرم پیمایش کرده و مقدار هر فیلد را از cleaned_data بگیرید؛ سپس با استفاده از متد validate_value(...) که پیشتر در مدل Field پیادهسازی کردید، ابتدا ورودی کاربر را بررسی کرده و در صورتی که لیست خطای برگردانده شده خالی نبود آن را با به لیست خطاهای فرم فعلی به همراه نام فیلد اضافه کنید:
self.add_error(field_name, error)
در انتها دیکشنری cleaned_data را برگردانید.
متد save
از این متد برای ذخیرهسازی پاسخهای ارسالشده توسط کاربر در مدل Response استفاده میکنیم و هدف از آن جداسازی منطق ذخیرهی این شیء از ویوهاست. خروجی این متد در صورت معتبر بودن دادهها و ذخیرهی اطلاعات کاربر، شیء Response ساخته شده است؛ و در غیر این صورت خروجی آن None خواهد بود.
ابتدا بررسی کنید که فرم معتبر است یا نه. اگر معتبر نبود، هیچ چیزی ذخیره نکنید و یک مقدار None برگردانید. در صورتی که فرم معتبر باشد، یک شیء Response جدید با استفاده از پارامترهای ارسالی برای فرم فعلی ایجاد کنید و آن را ذخیره کنید.
سپس باید روی تمام اشیاء فیلدهای مربوط به فرم فعلی پیمایش کنید و دادههای هر فیلد را ذخیره کنید؛ برای انجام اینکار ابتدا باید دادهها را از دیکشنری cleaned_data بخوانید؛ برای اینکار نیز به مقدار name هر فیلد در متد __init__() نوشته شده بود نیاز دارید. پس ابتدا نامی که مقدار فیلد با آن در این دیشکنری نگهداری میشود را تشکیل دهید و سپس از دیکشنری مقدار آن را بخوانید.
در صورتی که مقداری برای آن فیلد وجود داشت بسته به نوع فیلد یک شیء از مدل ResponseData ایجاد کنید و در پایگاهداده ذخیره کنید:
- اگر فیلد از نوع
fileبود آن را در ستونfileاز شیءResponseDataذخیره کنید. - در غیر اینصورت آن را به رشته تبدیل کرده و در ستون
valueذخیره کنید (توجه کنید که اگر فیلد از نوعcheckboxو مقدار آن از نوع لیست باشد باید آن را به JSON String تبدیل کنید)
در نهایت شیء Response ایجادشده را برگردانید.
فایل middlewares.py
middlewares.pyشما باید یک میدلور (Middleware) بنویسید که زمان شروع و پایان پر کردن فرم را ثبت کرده و در نهایت مدت پاسخدهی کاربر را ذخیره کند تا علی غرغرو بتواند آمار زمان پاسخدهی کاربران را دریافت کند.
کلاس FormAccessMiddleware
این کلاس وظیفه دارد مدت زمان اجرای هر درخواست را محاسبه کند و علاوه بر آن، مدت زمانی که یک کاربر برای پر کردن و ارسال فرم صرف میکند را نیز ثبت نماید. برای رسیدن به این هدف، سه متد اصلی باید پیادهسازی شوند: process_request، process_response و _calculate_completion_time.
متد process_request
این متد پیش از آنکه درخواست به view برسد اجرا میشود. در اولین گام باید زمان شروع پردازش درخواست در ویژگیای به نام _form_access_start_time در شیء request ذخیره شود. این زمان بعداً برای محاسبهی مدت کل اجرای درخواست مورد استفاده قرار خواهد گرفت.
در ادامه لازم است بررسی شود که آیا درخواست مربوط به مشاهدهی یک فرم است یا خیر. این حالت زمانی رخ میدهد که نام ویو برابر form_detail باشد و متد درخواست نیز GET باشد. اگر چنین شرایطی برقرار بود، باید زمان شروع پر کردن فرم در شیء session ذخیره شود تا بتوان در لحظهی ارسال فرم، مدت زمان تکمیل آن را محاسبه کرد. برای این منظور ابتدا باید کلیدی به نام form_start_times در session ایجاد شود (در صورتی که وجود نداشت) و مقدار اولیهی آن یک دیکشنری خالی قرار گیرد. سپس شناسهی فرم از پارامترهای URL استخراج شده و پس از تبدیل به رشته، به عنوان کلید در این دیکشنری قرار داده میشود. مقدار متناظر با آن نیز برابر زمان فعلی خواهد بود.
در نهایت اگر هرگونه خطایی در طول اجرای این بخش رخ داد، باید با استفاده از logger پیام خطا (error) در لاگ ثبت شود تا فرآیند قابل پیگیری باشد.
Error in FormAccessMiddleware: {error}
متد process_response
این متد پس از اجرای view و پیش از ارسال پاسخ به مرورگر فراخوانی میشود. در این مرحله ابتدا باید مدت زمان اجرای کل درخواست محاسبه شود. این کار از طریق مقایسهی زمان فعلی با مقداری که در متد process_request ذخیره شده انجام میشود. اگر این مدت زمان بیش از مقدار آستانهای باشد که در تنظیمات پروژه و در متغیر FORM_REQUEST_SLOW_THRESHOLD مشخص شده است، لازم است یک پیام هشدار در سطح warning در لاگ ثبت شود.
گام بعدی مربوط به حالتی است که کاربر یک فرم را ارسال کرده است. اگر نام ویو برابر form_submit، متد درخواست POST و وضعیت پاسخ نیز 302 باشد (که نشاندهندهی موفقیت و ریدایرکت است)، در این صورت باید متد _calculate_completion_time فراخوانی شود تا زمان تکمیل فرم محاسبه و در session ذخیره گردد.
در پایان، صرفنظر از نوع درخواست، لازم است جزئیات مربوط به آن از جمله مسیر، وضعیت کاربر، مدت زمان اجرا و دیگر اطلاعات مرتبط با استفاده از متد کمکی _log_form_access در لاگ ثبت شود.
همچنین اگر هرگونه خطایی در طول اجرای این بخش رخ داد، باید با استفاده از logger پیام خطا (error) در لاگ ثبت شود تا فرآیند قابل پیگیری باشد.
Error in FormAccessMiddleware: {error}
متد _calculate_completion_time
این متد وظیفهی محاسبهی مدت زمانی را بر عهده دارد که کاربر صرف پر کردن یک فرم کرده است. برای انجام این کار، ابتدا شناسهی فرم از پارامترهای URL استخراج میشود. سپس در session به دنبال زمانی که به عنوان شروع پر کردن همان فرم ثبت شده است جستجو میکنیم. در صورت وجود، اختلاف بین زمان فعلی و آن زمان به عنوان مدت زمان تکمیل فرم (بر حسب ثانیه) محاسبه میشود. این مقدار در session و تحت کلید form_completion_time ذخیره خواهد شد تا در بخشهای بعدی سیستم مورد استفاده قرار گیرد. در نهایت برای جلوگیری از باقی ماندن دادههای غیرضروری، مقدار ذخیرهشدهی اولیهی شروع فرم از دیکشنری form_start_times حذف میشود.
فایل views.py
views.pyدر این فایل شما وظیفهی پیادهسازی کلاس FormSubmissionView را دارید که وظیفهی مدیریت ارسالهای کاربران برای پاسخدهی به یک فرم را دارد.
کلاس FormSubmissionView
این کلاس برای مدیریت فرآیند ارسال فرم توسط کاربر طراحی شده است. هر بار که کاربری یک فرم را پر کرده و دکمهی ارسال را میزند، منطق پردازش دادهها از طریق این ویو انجام میشود. از آنجا که کلاس از DetailView ارثبری میکند، وظیفهی نمایش جزئیات یک فرم (از جمله فیلدهای پویا) را بر عهده دارد، اما بخش اصلی و مهم آن در متد post پیادهسازی شده است.
متد post
این متد زمانی اجرا میشود که یک درخواست POST به سمت سرور ارسال گردد، یعنی همان لحظهای که کاربر دادههای فرم را برای ذخیرهسازی ارسال میکند. در ادامه گامهای مهم این متد شرح داده شده است.
ابتدا شیء فرم مورد نظر از دیتابیس واکشی میشود تا بتوان بررسیها و اعتبارسنجیها روی آن اعمال گردد. سپس اولین شرط مهم این است که بررسی شود آیا فرم همچنان امکان دریافت پاسخ دارد یا خیر. اگر فرم بسته شده باشد (از طریق ویژگی can_accept_submissions)، کاربر با پیام خطای زیر مواجه شده و دوباره به صفحهی جزئیات همان فرم هدایت خواهد شد:
This form is no longer accepting submissions.
در گام بعدی لازم است اطمینان حاصل شود که کاربر یک شناسهی سشن معتبر دارد. این شناسه برای کاربرانی که وارد حساب نشدهاند اهمیت ویژهای دارد، زیرا به کمک آن میتوان تشخیص داد چه کسی (ولو ناشناس) فرم را ارسال کرده است. اگر کلید سشن هنوز ساخته نشده باشد، باید آن را ایجاد کنیم و سپس مقدار آن در متغیر session_key ذخیره میشود.
مسئلهی بعدی مربوط به جلوگیری از ارسال چندبارهی یک فرم توسط یک کاربر است. اگر فرم به گونهای تنظیم شده باشد که فقط یک بار قابلیت ارسال داشته باشد (allow_multiple_submissions=False)، آنگاه باید بررسی کنیم آیا کاربر (چه لاگین کرده و چه ناشناس) قبلاً پاسخی برای این فرم ثبت کرده است یا خیر.
- اگر کاربر وارد شده باشد، جستجو بر اساس فیلد
submitted_byانجام میشود. - اگر کاربر ناشناس باشد، بررسی با استفاده از کلید سشن (
session_key) صورت میگیرد.
اگر چنین پاسخی وجود داشته باشد، ارسال مجدد متوقف میشود و پیامی به کاربر نمایش داده خواهد شد:
You have already submitted this form.
در ادامه یک نمونه از DynamicForm ساخته میشود که با توجه به مدل فرم و دادههای ارسالی (POST و FILES) مقداردهی شده است. این فرم پویا وظیفهی اعتبارسنجی مقادیر هر فیلد را بر عهده دارد. اگر دادهها معتبر باشند، مرحلهی ذخیرهسازی شروع میشود.
در زمان ذخیرهسازی، ابتدا مقدار مدت زمانی که کاربر صرف تکمیل فرم کرده از سشن خوانده میشود (کلیدی به نام form_completion_time که در Middleware محاسبه شده بود). اگر چنین مقداری وجود داشته باشد، به صورت یک timedelta به تابع save فرستاده میشود. متد save در نهایت یک پاسخ جدید در دیتابیس ایجاد کرده و دادههای مربوط به هر فیلد را ذخیره میکند.
پس از ذخیره موفق، دادهی form_completion_time از سشن حذف میشود تا برای درخواستهای بعدی باقی نماند. سپس اطلاعاتی دربارهی referrer (یعنی صفحهای که کاربر از آنجا به فرم آمده است) جمعآوری شده و در جدول مربوط به بازدید فرمها (FormView) ذخیره میشود. در اینجا منطق ثبت بازدید به گونهای است که اگر کاربر ناشناس باشد، بازدید بر اساس کلید سشن منحصربهفرد در نظر گرفته میشود و اگر کاربر وارد سیستم شده باشد، بر اساس حساب کاربری او ذخیره خواهد شد.
در پایان کاربر به صفحهی موفقیت (form_success) هدایت میشود. اما اگر دادههای فرم معتبر نباشند (یعنی is_valid=False برگردد)، همان صفحهی فرم دوباره رندر میشود و خطاهای اعتبارسنجی در کنار فیلدها نمایش داده خواهند شد تا کاربر بتواند اصلاحات لازم را انجام دهد.
فایل templatetags/form_tags.py
templatetags/form_tags.pyدر این فایل یک سری فیلترهای سفارشی (Custom Template Filters) برای جنگو تعریف شدهاند. این فیلترها را میتوان در تمپلیتها استفاده کرد تا دادهها را قبل از نمایش به کاربر پردازش و تغییر دهند. در ادامه به معرفی و شرح عملکرد هر یک از این فیلترها میپردازیم.
فیلتر render_markdown(text)
این فیلتر برای تبدیل متنهایی که به فرمت Markdown نوشته شدهاند به کد HTML استفاده میشود. زمانی که کاربر یک متن را با نشانهگذاریهای مارکداون وارد میکند، این فیلتر باید به کمک کتابخانهی markdown آن را پردازش کرده و نسخهی HTML آن را تولید کند. در نهایت متن تبدیلشده با mark_safe علامتگذاری شود تا جنگو آن را به عنوان HTML معتبر رندر کند.
فیلتر extract_field_id(field_name)
این فیلتر وظیفه دارد شناسهی عددی یک فیلد را از نام آن (که در فرم DynamicForm ایجاد میکردیم) استخراج کند.
به عنوان مثال، اگر نام فیلد چیزی شبیه به field_123 باشد، این فیلتر مقدار 123 را برمیگرداند. برای انجام این کار باید به دنبال الگوی field_ به همراه عدد بگردید. اگر چنین الگویی پیدا شد، همان بخش عددی استخراج و بازگردانده شود؛ در غیر این صورت یک رشتهی خالی برگرداندید.
فیلتر get_field_type_icon(field_type)
این فیلتر به ازای هر نوع فیلد، یک کلاس آیکون از کتابخانهی Font Awesome برمیگرداند. ورودی این تابع یک رشته خواهد بود که برای هر یک از انواع مجاز برای فیلدها (field_type) یک آیکون منحصر به فرد در نظر گرفته شده است:
{
'text': 'fas fa-font',
'email': 'fas fa-envelope',
'number': 'fas fa-hashtag',
'textarea': 'fas fa-align-left',
'select': 'fas fa-list',
'radio': 'fas fa-dot-circle',
'checkbox': 'fas fa-check-square',
'file': 'fas fa-file-upload',
'date': 'fas fa-calendar',
'url': 'fas fa-link',
'phone': 'fas fa-phone',
}
اگر هم ورودی تابع یکی از موارد بالا نبود، مقدار پیشفرض 'fas fa-question' بازگردانده شود.

زیرمسئلهها
سیستم داوری برای این سوال به زیرمسئلههای زیر برای نمرهدهی تقسیمبندی شده است که میتوانید امتیاز مربوط به هر کدام را در جدول زیر مشاهده کنید. زیرمسئلههای این جدول ابتدا بر اساس اولویت و پیشنیازی پیادهسازی و سپس بر اساس امتیاز آنها مرتبسازی شدهاند. لذا پیشنهاد میشود در پیادهسازی از زیرمسئلهی ابتدایی آغاز کنید.
| زیرمسئله | امتیاز |
|---|---|
مدل Form |
50 |
مدل Field |
55 |
فرم DynamicForm |
90 |
میدلور FormAccessMiddleware |
90 |
ویو FormSubmissionView |
135 |
| فیلترها | 30 |
آنچه باید آپلود کنید
-
توجه: پس از اعمال تغییرات، کل پروژه را Zip کرده و آپلود کنید. همانند پروژهی اولیه در فایل زیپشده نباید کد در پوشهی دیگری قرار بگیرد در غیر این صورت سیستم داوری فایل را شناسایی نکرده و نمرهای دریافت نخواهید کرد.
-
توجه: تنها فایلهایی که در ساختار پروژه مشخص شدهاند، در سیستم داوری مورد پذیرش قرار خواهد گرفت و سایر تغییرات در سایر فایلها بیتأثیر خواهند بود.
-
توجه: متنهای نمونه در مدلها و فرمها باید دقیقاً برابر مقادیر گفتهشده باشند؛ در غیر این صورت نمرهی کامل دریافت نخواهید کرد.
ارسال پاسخ برای این سؤال