سنوضح في هذا المقال كيفية العمل مع استمارات HTML في إطار العمل جانغو Django، خاصةً الطريقة الأسهل لكتابة الاستمارات لإنشاء نسخ من النموذج وتحديثها وحذفها، إذ سنوسّع موقع المكتبة المحلية LocalLibrary بحيث يمكن لأمناء المكتبة تجديد الكتب وإنشاء وتحديث وحذف المؤلفين باستخدام استماراتنا الخاصة عوضًا عن استخدام تطبيق المدير.
- المتطلبات الأساسية: اطّلع على جميع المقالات السابقة من هذه السلسلة، بما في ذلك المقال الخاص باستيثاق المستخدمين وأذوناتهم.
- الهدف: فهم كيفية كتابة الاستمارات للحصول على معلومات من المستخدمين وتحديث قاعدة البيانات، وفهم كيف يمكن لعروض التعديل المُعمَّمة المستندة إلى الأصناف تبسيط إنشاء الاستمارات للعمل مع نموذج واحد فقط.
تُعَد استمارة HTML مجموعةً مكونةً من حقل واحد أو عنصر واجهة مستخدم widget واحدة أو أكثر على صفحة الويب، والتي يمكن استخدامها لجمع المعلومات من المستخدمين لإرسالها إلى الخادم، فالاستمارات هي آلية مرنة لتجميع دخل المستخدم، نظرًا لوجود عناصر واجهة مستخدم مناسبة لإدخال العديد من أنواع البيانات المختلفة، بما في ذلك مربعات النص ومربعات الاختيار وأزرار الاختيار ومنتقي التواريخ وما إلى ذلك. تُعَد الاستمارات طريقةً آمنة نسبيًا لمشاركة البيانات مع الخادم، لأنها تسمح بإرسال البيانات في طلبات POST
مع الحماية من هجمات تزوير الطلبات عبر المواقع.
لم ننشِئ أيّ استمارات في هذه السلسلة من المقالات حتى الآن، ولكننا صادفناها في موقع مدير جانغو، فمثلًا تُظهِر لقطة الشاشة التالية استمارةً لتعديل أحد نماذج الكتاب Book، والذي يتكون من عدد من قوائم الاختيار ومحرّري النصوص.
يمكن أن يكون العمل مع الاستمارات معقدًا، إذ يجب أن يكتب المطورون شيفرة HTML للاستمارة، ويجب التحقق من صحة البيانات المدخلة وتعقيمها Sanitization (أي التخلص من البيانات الحساسة أو غير الضرورية) على الخادم وربما في المتصفح أيضًا، وإعادة نشر الاستمارة مع رسائل خطأ لإبلاغ المستخدمين بأيّ حقول غير صالحة، والتعامل مع البيانات عند إرسالها بنجاح، وأخيرًا الرد على المستخدم بطريقة ما للإشارة إلى النجاح.
تقلّل استمارات جانغو الكثير من العمل المبذول في كل من هذه الخطوات من خلال توفير إطار عمل يتيح لك تعريف الاستمارات وحقولها برمجيًا، ثم استخدام هذه الكائنات لتوليد شيفرة استمارة HTML والتعامل مع الكثير من عمليات التحقق من الصحة وتفاعل المستخدم.
سنعرض في هذا المقال بعض الطرق التي يمكنك من خلالها إنشاء الاستمارات والعمل معها، وخاصة كيف يمكن لعروض التعديل المُعمَّمة أن تقلل من حجم العمل الواجب تطبيقه لإنشاء الاستمارات بهدف معالجة النماذج Models، إذ سنوسّع تطبيق المكتبة المحلية LocalLibrary من خلال إضافة استمارة للسماح لأمناء المكتبة بتجديد الكتب، وسننشئ صفحات لإنشاء وتعديل وحذف الكتب والمؤلفين، أو إعادة إنتاج نسخة أساسية من الاستمارة التي وضحناها سابقًا لتعديل الكتب.
تتألف هذه السلسلة الفرعية من السلسلة الأشمل تعلم تطوير الويب من المقالات التالية:
- مدخل إلى إطار عمل الويب جانغو Django
- إعداد بيئة تطوير تطبيقات جانغو
- تطبيق عملي لتعلم جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية
- تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models
- تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin
- تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية
- تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية
- تطبيق عملي لتعلم جانغو - الجزء السادس: إدارة الجلسات Sessions
- تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم
- تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms
- تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو
- تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج
- تعرف على أمان تطبيقات جانغو
استمارة HTML
أولًا، اطّلع على مفهوم استمارات HTML، ثم أنشئ استمارة HTML بسيطة، مع حقل نصي واحد لإدخال اسم الفريق والتسمية Label المرتبطة به كما يلي:
تُعرَّف الاستمارة Form في HTML بوصفها مجموعة من العناصر ضمن وسوم <form>…</form>
التي تحتوي على عنصر إدخال input
واحد على الأقل من النوع type="submit"
.
<form action="/team_name_url/" method="post"> <label for="team_name">Enter name: </label> <input id="team_name" type="text" name="name_field" value="Default name for team." /> <input type="submit" value="OK" /> </form>
لدينا حقل نصي واحد فقط لإدخال اسم الفريق في مثالنا، ولكن يمكن أن تحتوي الاستمارة على أيّ عدد من عناصر الإدخال الأخرى والتسميات المرتبطة بها. تحدّد السمة type
الخاصة بالحقل نوع عنصر واجهة المستخدم الذي سيُعرض، ويُستخدَم اسم name
ومعرّف id
الحقل لتحديد الحقل في شيفرة JavaScript/CSS/HTML، بينما يحدد value
القيمة الأولية للحقل عند عرضه لأول مرة. تُحدَّد تسمية الفريق المطابق باستخدام الوسم label
(لاحظ التسمية "أدخِل اسمًا Enter name") مع الحقل for
الذي يحتوي على قيمة معرّف id
حقل الإدخال input
المرتبط به.
سيُعرَض حقل الإدخال submit
بوصفه زرًا افتراضيًا، ويمكن الضغط عليه لتحميل البيانات من جميع عناصر الإدخال الأخرى في الاستمارة إلى الخادم (وهي في حالتنا حقل اسم الفريق team_name
فقط). تحدّد سمات الاستمارة نوع طلب HTTP المُستخدَم لإرسال البيانات (عبر method
) ووِجهة البيانات على الخادم (action
) كما يلي:
-
action
: المورد أو عنوان URL لمكان إرسال البيانات لمعالجتها عند إرسال الاستمارة. إذا لم تُضبَط هذه السمة، أو ضُبطت على أنها سلسلة فارغة)، ستُعاد الاستمارة إلى عنوان URL للصفحة الحالية. -
method
: نوع طلب HTTP المُستخدَم لإرسال البيانات وهو إما من النوعPOST
أوGET
.-
يجب دائمًا استخدام النوع
POST
إذا كانت البيانات ستؤدي إلى تغيير في قاعدة بيانات الخادم، لأنه يمكن جعلها أكثر مقاومة لهجمات تزوير الطلبات عبر المواقع. -
يجب استخدام النوع
GET
فقط للاستمارات التي لا تغير بيانات المستخدم (مثل استمارة البحث)، ويوصَى به عندما تريد أن تكون قادرًا على وضع إشارة مرجعية على عنوان URL أو مشاركته.
-
يجب دائمًا استخدام النوع
يتمثل دور الخادم server أولًا في عرض حالة الاستمارة الأولية، فإما أن تحتوي على حقول فارغة، أو تكون مملوءةً مسبقًا بقيم أولية. سيتلقى الخادم بعد أن يضغط المستخدم على زر الإرسال بيانات الاستمارة مع قيم من متصفح الويب ويجب عليه التحقق من صحة المعلومات، فإذا احتوت الاستمارة على بيانات غير صالحة، فيجب أن يعرض الخادم الاستمارة مرةً أخرى، ولكن مع بيانات أدخلها المستخدم ضمن حقول صالحة ورسائل لوصف مشكلة الحقول غير الصالحة. يمكن للخادم تطبيق الإجراء المناسب، مثل حفظ البيانات وإعادة نتيجة البحث وتحميل ملف وغير ذلك بمجرد أن يتلقى الخادم طلبًا بجميع بيانات الاستمارة الصالحة، ومن ثم إعلام المستخدم.
يمكن أن يكون إنشاء شيفرة HTML والتحقق من صحة البيانات المُعادة وإعادة عرض البيانات المُدخَلة مع تقارير الأخطاء إن لزم الأمر وتطبيق العملية المطلوبة على البيانات الصالحة أمرًا متعبًا جدًا ليكون صحيحًا، ويجعل جانغو هذا الأمر أسهل بكثير من خلال التخلص من بعض الشيفرة البرمجية الثقيلة والمتكررة.
عملية معالجة استمارة جانغو
تستخدم معالجة الاستمارات في جانغو الأساليب نفسها التي تعلمناها في مقالات سابقة (لعرض معلومات حول نماذجنا)، إذ يتلقى العرض طلبًا، ويطبّق أي إجراءات مطلوبة بما في ذلك قراءة البيانات من النماذج، ثم يولّد ويعيد صفحة HTML من قالب إلى آخر نمرر إليه سياقًا يحتوي على البيانات المراد عرضها، ولكن ما يعقّد الأمورهو أن الخادم يحتاج أيضًا إلى أن يكون قادرًا على معالجة البيانات التي يقدّمها المستخدم، ويجب أن يعيد عرض الصفحة في حالة وجود أيّ أخطاء.
يوضَح المخطط التالي كيفية معالجة جانغو لطلبات الاستمارة، بدءًا من طلب صفحة تحتوي على استمارة والموضح باللون الأخضر:
تطبّق عملية معالجة استمارة جانغو الأمور الرئيسية التالية بناءً على المخطط السابق:
-
عرض الاستمارة الافتراضية في المرة الأولى التي يطلبها المستخدم.
- يمكن أن تحتوي الاستمارة على حقول فارغة إذا أردتَ إنشاء سجل جديد، أو يمكن ملؤها مسبقًا بالقيم الأولية إذا غيّرت سجلًا أو كان لديك قيم أولية افتراضية مفيدة مثلًا.
- يُشار إلى الاستمارة إلى أنها غير مرتبطة Unbound في هذه المرحلة، لأنها غير مرتبطة بأيّ بيانات أدخلها المستخدم، بالرغم من أنه يمكن أن تحتوي على قيم أولية.
-
تلقي البيانات من طلب الإرسال وربطها بالاستمارة.
- يعني ربط Binding البيانات بالاستمارة أن البيانات التي أدخلها المستخدم والأخطاء تكون متاحة عندما نحتاج إلى إعادة عرض الاستمارة.
-
تنظيف البيانات والتحقق من صحتها.
- يؤدي تنظيف البيانات إلى تعقيم حقول الإدخال مثل إزالة المحارف غير الصالحة التي يمكن استخدامها لإرسال محتوًى ضار إلى الخادم، وتحويلها إلى أنواع بايثون متناسقة.
- تفحص عملية التحقق من صحة البيانات أن القيم مناسبة للحقل، مثل أن تكون موجودة في مجال البيانات الصحيح، أو أنها ليست قصيرة جدًا أو طويلة جدًا وما إلى ذلك.
- إذا وُجدت بيانات غير صالحة، فأعِد عرض الاستمارة، ولكن هذه المرة مع أيّ قيمٍ يملؤها المستخدم ورسائل خطأ لحقول المشكلة.
- إذا كانت جميع البيانات صالحة، فطبّق الإجراءات المطلوبة، مثل حفظ البيانات وإرسال بريد إلكتروني وإعادة نتيجة البحث وتحميل ملف وغير ذلك.
- أعِد توجيه المستخدم إلى صفحة أخرى عند اكتمال جميع الإجراءات.
يوفر جانغو عددًا من الأدوات والأساليب لمساعدتك في المهام السابقة، والأكثر أهمية هو الصنف Form
الذي يبسّط توليد شيفرة HTML للاستمارة وتنظيف أو التحقق من صحة البيانات. سنشرح في القسم التالي كيفية عمل الاستمارات باستخدام تطبيق عملي لصفحة بهدف السماح لأمناء المكتبة بتجديد الكتب.
ملاحظة: سيساعدك فهم كيفية استخدام الصنف Form
عندما نناقش أصناف إطار عمل الاستمارة عالية المستوى الخاصة بجانغو.
استمارة تجديد الكتب Renew-book باستخدام الصنف Form والعرض المستند إلى الدوال
سنضيف صفحةً للسماح لأمناء المكتبة بتجديد الكتب المستعارة من خلال إنشاء استمارة تسمح للمستخدمين بإدخال قيمة تاريخ، إذ سنعطي الحقل قيمة أولية هي 3 أسابيع من التاريخ الحالي (فترة الاستعارة العادية)، وسنضيف تحققًا من صحة البيانات للتأكد من أنّ أمين المكتبة لا يمكنه إدخال تاريخ في الماضي، أو تاريخ بعيد جدًا في المستقبل. إذا أُدخِل تاريخ صالح، فسنكتبه في الحقل BookInstance.due_back
الخاص بالسجل الحالي.
سيستخدم مثالنا عرضًا مستندًا إلى الدوال والصنف Form
، إذ توضح الأقسام التالية كيفية عمل الاستمارات والتغييرات التي يجب إجراؤها على مشروع المكتبة المحلية LocalLibrary.
صنف الاستمارة Form
يُعَد الصنف Form
قلب نظام معالجة الاستمارات في جانغو؛ فهو يحدّد الحقول في الاستمارة وتنسيقها وعناصر واجهة المستخدم للعرض والتسميات والقيم الأولية والقيم الصالحة ورسائل الخطأ (بمجرد التحقق من صحتها) المرتبطة بالحقول غير الصالحة. يوفّر هذا الصنف أيضًا توابعًا لعرض نفسه في القوالب باستخدام تنسيقات مُعرَّفة مسبقًا، مثل الجداول والقوائم وغيرها، أو للحصول على قيمة أيّ عنصر، مثل تفعيل عرض يدوي دقيق.
التصريح عن الصنف Form
تُعَد صياغة التصريح عن الصنف Form
مشابهة جدًا للصياغة المُستخدَمة في التصريح عن الصنف Model
، وتتشاركان في أنواع الحقول نفسها وبعض المعاملات المماثلة، وهذا أمرٌ منطقي لأنه في كلتا الحالتين يجب التأكد من أن كل حقل يتعامل مع الأنواع الصحيحة من البيانات، وهو مقيدٌ بالبيانات الصالحة وله وصف description للعرض أو التوثيق.
تُخزَّن بيانات الاستمارة في الملف "forms.py" الخاص بالتطبيق ضمن مجلد التطبيق، لذا أنشئ وافتح الملف locallibrary/catalog/forms.py. يمكن إنشاء Form
من خلال استيراد المكتبة forms
، واشتقاق الصنف Form
، والتصريح عن حقول الاستمارة. يوضح المثال البسيط التالي صنف استمارة تجديد كتب المكتبة، لذا أضفه إلى ملفك الجديد:
from django import forms class RenewBookForm(forms.Form): renewal_date = forms.DateField(help_text="Enter a date between now and 4 weeks (default 3).")
حقول الاستمارة
لدينا في مثالنا حقل تاريخ واحد DateField
لإدخال تاريخ التجديد الذي سيُعرَض في صفحة HTML بقيمة فارغة، والتسمية الافتراضية ":Renewal date"، وبعض نصوص الاستخدام المفيدة مثل: "أدخِل تاريخًا بين الوقت الحالي و4 أسابيع (القيمة الافتراضية 3 أسابيع).".
سيقبل الحقل التواريخ باستخدام تنسيق input_formats مثل:
- YYYY-MM-DD (2016-11-06)
- MM/DD/YYYY (02/26/2016)
- MM/DD/YY (10/25/16)
وذلك نظرًا لعدم تحديد أي من الوسطاء الاختيارية الأخرى، وستُعرَض باستخدام عنصر واجهة المستخدم الافتراضية: DateInput
.
هناك العديد من الأنواع الأخرى لحقول الاستمارة، والتي سنلاحظ تشابهها مع أصناف حقول النموذج المكافئة لها، وهي:
-
BooleanField
-
CharField
-
ChoiceField
-
TypedChoiceField
-
DateField
-
DateTimeField
-
DecimalField
-
DurationField
-
EmailField
-
FileField
-
FilePathField
-
FloatField
-
ImageField
-
IntegerField
-
GenericIPAddressField
-
MultipleChoiceField
-
TypedMultipleChoiceField
-
NullBooleanField
-
RegexField
-
SlugField
-
TimeField
-
URLField
-
UUIDField
-
ComboField
-
MultiValueField
-
SplitDateTimeField
-
ModelMultipleChoiceField
-
ModelChoiceField
إليك الوسائط الشائعة في معظم الحقول مع قيمها الافتراضية:
-
required
: إذا كانت قيمتهTrue
، فلا يجوز ترك الحقل فارغًا أو إعطاءه قيمةNone
. تكون الحقول مطلوبة افتراضيًا، لذلك يجب أن تضبطrequired=False
للسماح بالقيم الفارغة في الاستمارة. -
label
: التسمية التي ستُستخدَم عند عرض الحقل بتنسيق HTML. إذا لم تُحدَّد تسمية، فسينشئ جانغو تسمية من اسم الحقل من خلال كتابة الحرف الأول بأحرف كبيرة ووضع مسافات مكان الشرطات السفلية، مثل Renewal date. -
label_suffix
: تُعرَض نقطتان بعد التسمية افتراضيًا، مثل Renewal date:، إذ يسمح لك هذا الوسيط بتحديد لاحقة مختلفة تحتوي على محرف أو محارف أخرى. -
initial
: قيمة الحقل الأولية عند عرض الاستمارة. -
widget
: عنصر واجهة المستخدم للعرض المُراد استخدامه. -
help_text
: نص إضافي يمكن عرضه في الاستمارة لشرح كيفية استخدام الحقل. -
error_messages
: قائمة برسائل الخطأ الخاصة بالحقل، ويمكنك تعديلها لتحتوي على رسائلك الخاصة إن لزم الأمر. -
validators
: قائمة بالدوال التي ستُستدعَى في الحقل عند التحقق من صحتها. -
localize
: يفعّل توطين Localization إدخال بيانات الاستمارة. -
disabled
: يُعرَض الحقل ولكن لا يمكن تعديل قيمته إذا كان هذا الوسيطTrue
، والقيمة الافتراضية هيFalse
.
التحقق من صحة البيانات
يوفر جانغو العديد من الأماكن التي يمكنك من خلالها التحقق من صحة بياناتك، ولكن أسهل طريقة للتحقق من صحة حقل واحد هي تعديل التابع clean_<fieldname>()
للحقل الذي تريد التحقق منه، لذلك مثلًا يمكننا التحقق من أن قيم renewal_date
المُدخلة تتراوح بين الآن (الوقت الحالي) و 4 أسابيع من خلال تقديم التابع clean_renewal_date()
، لذا حدّث الملف "forms.py" ليبدو كما يلي:
import datetime from django import forms from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ class RenewBookForm(forms.Form): renewal_date = forms.DateField(help_text="Enter a date between now and 4 weeks (default 3).") def clean_renewal_date(self): data = self.cleaned_data['renewal_date'] # تحقق مما إذا كان التاريخ ليس في الماضي if data < datetime.date.today(): raise ValidationError(_('Invalid date - renewal in past')) # تحقق مما إذا كان التاريخ يقع ضمن النطاق المسموح به (+4 أسابيع من اليوم) if data > datetime.date.today() + datetime.timedelta(weeks=4): raise ValidationError(_('Invalid date - renewal more than 4 weeks ahead')) # تذكّر دائمًا أن تعيد البيانات النظيفة return data
هناك شيئان مهمان يجب ملاحظتهما، الأول هو أننا نحصل على بياناتنا باستخدام self.cleaned_data['renewal_date']
وأننا نعيد هذه البيانات سواءً عدّلناها أم لا في نهاية الدالة، إذ تمنحنا هذه الخطوة بيانات "نظيفة" ومُعقمة من المدخلات التي يمكن أن تكون غير آمنة باستخدام أدوات التحقق الافتراضية، ومُحوَّلة إلى النوع المعياري الصحيح للبيانات، وهو في حالتنا كائن بايثون datetime.datetime
.
النقطة الثانية هي أنه إذا كانت القيمة واقعةً خارج نطاقنا، فسنصدّر خطأ ValidationError
مع تحديد نص الخطأ الذي نريد عرضه في الاستمارة إذا أُدخِلت قيمة غير صالحة، إذ يغلّف المثال السابق هذا النص في إحدى دوال الترجمة Translation Functions الخاصة بجانغو هي gettext_lazy()
(مستوردة بالشكل ()_
)، وهي ممارسة جيدة إذا أردتَ ترجمة موقعك لاحقًا.
ملاحظة: هناك العديد من التوابع والأمثلة الأخرى للتحقق من صحة الاستمارات في توثيق جانغو، فمثلًا يمكنك تعديل الدالة Form.clean()
وإصدار خطأ ValidationError
مرةً أخرى في الحالات التي يكون فيها لديك حقول متعددة تعتمد على بعضها بعضًا.
وهذا كل ما نحتاجه للاستمارات في مثالنا.
ضبط عناوين URL
لنضِف ضبط عنوان URL لصفحة تجديد الكتب قبل إنشاء العرض، لذا انسخ الضبط التالي وضعه في نهاية الملف locallibrary/catalog/urls.py:
urlpatterns += [ path('book/<uuid:pk>/renew/', views.renew_book_librarian, name='renew-book-librarian'), ]
سيعيد ضبط URL توجيه عناوين URL بالتنسيق "/catalog/book/<bookinstance_id>/renew/" renew_book_librarian()
في الملف views.py، ويرسل معرّف نسخة الكتاب BookInstance
بوصفه معاملًا بالاسم pk
، إذ يتطابق النمط فقط إذا كان pk
مُنسَّقًا بتنسيق uuid
بصورة صحيحة.
ملاحظة: يمكننا تسمية بيانات URL المأخوذة "pk
" بأيّ شيء نريده، لأن لدينا تحكمًا كاملًا بدالة العرض فنحن لا نستخدم صنف عرض تفصيلي مُعمَّم يتوقع معاملات باسم معين، ولكن يُعَد pk
اختصارًا للمفتاح الرئيسي "primary key"، وهو اصطلاح مناسب لاستخدامه.
العرض
يجب أن يخرج render العرض الاستمارة الافتراضية عند استدعائه لأول مرة، ثم يعيد إخراجها مع رسائل خطأ إذا كانت البيانات غير صالحة، أو يعالج البيانات ويعيد التوجيه إلى صفحة جديدة إذا البيانات صالحة، لذا يجب أن يكون العرض قادرًا على معرفة ما إذا كان مُستدعًى لأول مرة لعرض الاستمارة الافتراضية، أو لمرة تالية للتحقق من صحة البيانات.
النمط الأكثر شيوعًا بالنسبة للاستمارات التي تستخدم طلب POST
لإرسال معلومات إلى الخادم هو اختبار العرض للطلب من النوع POST
، أي if request.method == 'POST':
، لتحديد طلبات التحقق من صحة الاستمارة، والنوع GET
(باستخدام تعليمة else
) لتحديد طلب إنشاء الاستمارة الأولية؛ فإذا أدرتَ إرسال بياناتك باستخدام طلب GET
، فيمكنك تحديد ما إذا كان هذا الاستدعاء هو استدعاء العرض الأول أو التالي من خلال قراءة بيانات الاستمارة، مثل قراءة قيمة مخفية في الاستمارة.
ستُكتَب عملية تجديد الكتاب في قاعدة البيانات، لذلك نستخدم أسلوب طلب POST
اصطلاحيًا، إذ يُظهِر جزء الشيفرة البرمجية التالي النمط المعياري لهذا النوع من العرض المستند إلى الدوال:
import datetime from django.shortcuts import render, get_object_or_404 from django.http import HttpResponseRedirect from django.urls import reverse from catalog.forms import RenewBookForm def renew_book_librarian(request, pk): book_instance = get_object_or_404(BookInstance, pk=pk) # إذا كان هذا الطلب من النوع POST، فعالج بيانات الاستمارة if request.method == 'POST': # أنشئ نسخة من الاستمارة واملأها ببيانات من الطلب (الربط Binding): form = RenewBookForm(request.POST) # تحقق مما إذا كانت الاستمارة صالحة: if form.is_valid(): # معالجة البيانات في form.cleaned_data كما هو مطلوب (نكتبها هنا فقط في الحقل due_back الخاص بالنموذج) book_instance.due_back = form.cleaned_data['renewal_date'] book_instance.save() # إعادة التوجيه إلى عنوان URL جديد: return HttpResponseRedirect(reverse('all-borrowed')) # إذا كان تابع GET (أو أي تابع آخر)، فأنشئ الاستمارة الافتراضية else: proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3) form = RenewBookForm(initial={'renewal_date': proposed_renewal_date}) context = { 'form': form, 'book_instance': book_instance, } return render(request, 'catalog/book_renew_librarian.html', context)
أولًا، نستورد الاستمارة RenewBookForm
وعددًا من الكائنات والتوابع المفيدة الأخرى المُستخدَمة في متن دالة العرض وهي:
-
get_object_or_404()
: يعيد كائنًا محددًا من نموذج بناءً على قيمة مفتاحه الرئيسي، ويرفع استثناءHttp404
(غير موجود) إذا كان السجل غير موجود. -
HttpResponseRedirect
: ينشئ إعادة توجيه إلى عنوان URL محدد (رمز حالة HTTP هو 302). -
reverse()
: يولّد عنوان URL من اسم ضبط URL ومجموعة من الوسائط، وهو مكافئ لغة بايثون للوسمurl
الذي استخدمناه في قوالبنا. -
datetime
: مكتبة بايثون لمعالجة التواريخ والأوقات.
نستخدم أولًا في العرض الوسيط pk
ضمن التابع get_object_or_404()
للحصول على نسخة الكتاب BookInstance
الحالية، وإذا لم تكن موجودة، فسيُنهَى العرض مباشرةً وستعرض الصفحة خطأ "غير موجود". إذا لم يكن هذا الطلب من النوع POST
(تعالجه تعليمة else
)، فسننشئ الاستمارة الافتراضية التي تمرّر القيمة الأولية initial
لحقل renewal_date
، وهي 3 أسابيع من التاريخ الحالي.
book_instance = get_object_or_404(BookInstance, pk=pk) # إذا كان تابع GET (أو أي تابع آخر)، فأنشئ الاستمارة الافتراضية else: proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3) form = RenewBookForm(initial={'renewal_date': proposed_renewal_date}) context = { 'form': form, 'book_instance': book_instance, } return render(request, 'catalog/book_renew_librarian.html', context)
نستدعي الدالة render()
بعد إنشاء الاستمارة لإنشاء صفحة HTML، مما يؤدي إلى تحديد القالب والسياق الذي يحتوي على الاستمارة، إذ يحتوي السياق في هذه الحالة على نسخة الكتاب BookInstance
التي سنستخدمها في القالب لتقديم معلومات حول الكتاب الذي نجدّده، لكن إذا كان هذا الطلب من النوع POST
، فسننشئ كائن form
ونملؤه ببيانات من الطلب، وتسمَّى هذه العملية "بالربط Binding" وتسمح لنا بالتحقق من صحة الاستمارة.
نتحقق بعد ذلك ما إذا كانت الاستمارة صالحة، مما يؤدي إلى تشغيل شيفرة التحقق من صحة البيانات في جميع الحقول، بما في ذلك الشيفرة المُعمَّمة generic code للتحقق من أن حقل التاريخ هو تاريخ صالح ودالة clean_renewal_date()
للتحقق من أن التاريخ ضمن النطاق الصحيح.
book_instance = get_object_or_404(BookInstance, pk=pk) # إذا كان هذا الطلب من النوع POST، فعالج بيانات الاستمارة if request.method == 'POST': # أنشئ نسخة من الاستمارة واملأها ببيانات من الطلب (الربط Binding): form = RenewBookForm(request.POST) # تحقق ما إذا كانت الاستمارة صالحة: if form.is_valid(): # معالجة البيانات في form.cleaned_data كما هو مطلوب (نكتبها هنا فقط في الحقل due_back الخاص بالنموذج) book_instance.due_back = form.cleaned_data['renewal_date'] book_instance.save() # إعادة التوجيه إلى عنوان URL جديد: return HttpResponseRedirect(reverse('all-borrowed')) context = { 'form': form, 'book_instance': book_instance, } return render(request, 'catalog/book_renew_librarian.html', context)
نستدعي الدالة render()
مرةً أخرى إذا لم تكن الاستمارة صالحة، ولكن ستتضمن هذه المرة قيمة الاستمارة المُمرَّرة في السياق رسائل خطأ؛ أما إذا كانت الاستمارة صالحة، فيمكننا البدء في استخدام البيانات والوصول إليها من خلال السمة form.cleaned_data
، مثل data = form.cleaned_data['renewal_date']
، إذ نحتفظ في مثالنا فقط بالبيانات ضمن قيمة due_back
الخاصة بالكائن BookInstance
المرتبط بها.
تحذير: يمكنك الوصول إلى بيانات الاستمارة مباشرةً من خلال الطلب، مثل request.POST['renewal_date']
أو request.GET['renewal_date']
إذا استخدمتَ طلبًا من النوع GET
، ولكن لا ينصح بذلك، إذ تُطهَّر البيانات المُنظَّفة ويُتحقَّق من صحتها وتُحوَّل إلى أنواع متوافقة مع لغة بايثون.
تتمثل الخطوة الأخيرة في جزء معالجة الاستمارة ضمن العرض في إعادة التوجيه إلى صفحةٍ أخرى وهي صفحة "النجاح" عادةً، إذ نستخدم في هذه الحالة HttpResponseRedirect
و reverse()
لإعادة التوجيه إلى العرض الذي اسمه 'all-borrowed'
(وهو تحدي المقال السابق)؛ فإذا لم تنشئ هذه الصفحة، ففكّر في إعادة التوجيه إلى الصفحة الرئيسية ذات العنوان '/'
هذا كل ما نحتاجه لمعالجة الاستمارة، لكننا ما زلنا بحاجة إلى تقييد الوصول إلى العرض لأمناء المكتبة الذين سجلوا الدخول فقط والذين لديهم إذن لتجديد الكتب، لذا نستخدم المزخرف decorator @login_required
لمتطلب تسجيل دخول المستخدم، ومزخرف الدالة @permission_required
مع الإذن can_mark_returned
الحالي للسماح بالوصول. تُعالَج المزخرفات بالترتيب.
لاحظ أنه ربما كان يجب إنشاء إعداد إذن جديد في BookInstance
هو "can_renew
"، لكننا سنعيد استخدام الإعداد الحالي لتبسيط مثالنا.
يكون العرض النهائي كما هو موضح فيما يلي، لذا انسخ الشيفرة التالية في نهاية الملف locallibrary/catalog/views.py:
import datetime from django.contrib.auth.decorators import login_required, permission_required from django.shortcuts import get_object_or_404 from django.http import HttpResponseRedirect from django.urls import reverse from catalog.forms import RenewBookForm @login_required @permission_required('catalog.can_mark_returned', raise_exception=True) def renew_book_librarian(request, pk): """View function for renewing a specific BookInstance by librarian.""" book_instance = get_object_or_404(BookInstance, pk=pk) # إذا كان هذا الطلب من النوع POST، فعالج بيانات الاستمارة if request.method == 'POST': # أنشئ نسخة من الاستمارة واملأها ببيانات من الطلب (الربط Binding): form = RenewBookForm(request.POST) # تحقق مما إذا كانت الاستمارة صالحة: if form.is_valid(): # معالجة البيانات في form.cleaned_data كما هو مطلوب (نكتبها هنا فقط في الحقل due_back الخاص بالنموذج) book_instance.due_back = form.cleaned_data['renewal_date'] book_instance.save() # إعادة التوجيه إلى عنوان URL جديد: return HttpResponseRedirect(reverse('all-borrowed')) # إذا كان تابع GET (أو أي تابع آخر)، فأنشئ الاستمارة الافتراضية else: proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3) form = RenewBookForm(initial={'renewal_date': proposed_renewal_date}) context = { 'form': form, 'book_instance': book_instance, } return render(request, 'catalog/book_renew_librarian.html', context)
القالب
أنشئ القالب المُشار إليه في العرض "/catalog/templates/catalog/book_renew_librarian.html" وانسخ الشيفرة التالية إليه:
{% extends "base_generic.html" %} {% block content %} <h1>Renew: {{ book_instance.book.title }}</h1> <p>Borrower: {{ book_instance.borrower }}</p> <p {% if book_instance.is_overdue %} class="text-danger"{% endif %} >Due date: {{ book_instance.due_back }}</p> <form action="" method="post"> {% csrf_token %} <table> {{ form.as_table }} </table> <input type="submit" value="Submit"> </form> {% endblock %}
ستكون غالبية هذه الشيفرة البرمجية مألوفةً من المقالات السابقة، إذ نوسّع القالب الأساسي ثم نعيد تعريف كتلة المحتوى content
، ويمكننا الإشارة إلى {{ book_instance }}
ومتغيراته لأنه مُمرَّر إلى كائن السياق في الدالة render()
، ونستخدمه لسرد عنوان الكتاب والمستعير، وتاريخ الاسترجاع الأصلي.
شيفرة الاستمارة بسيطة نسبيًا، إذ نصرّح أولًا عن وسوم form
، التي تحدّد مكان إرسال الاستمارة action
وتابع method
لإرسال البيانات (في هذه الحالة هو "POST
")، إذ يعني الإجراء action
الفارغ إرسال بيانات الاستمارة إلى عنوان URL الحالي للصفحة وهو ما نريده. نعرّف ضمن الوسوم عنصرَ الإدخال submit
الذي يمكن للمستخدم الضغط عليه لإرسال البيانات، ويُعَد {% csrf_token %}
المُضَاف ضمن وسوم الاستمارة جزءًا من حماية جانغو من هجمات التزوير عبر المواقع، انظر مقال رفع مستوى أمان تطبيقات جانغو في بيئة الإنتاج.
ملاحظة: ضِف {% csrf_token %}
إلى كل قالب جانغو تنشئه والذي يستخدم طلب POST
لإرسال البيانات، لان ذلك سيؤدي إلى تقليل فرصة اختطاف المستخدمين الضارين للاستمارات.
بقي متغير القالب {{ form }}
الذي مرّرناه إلى القالب في قاموس السياق، وليس مستغربًا أنه يوفر الإخراج الافتراضي لجميع حقول الاستمارة عند استخدامه، بما في ذلك التسميات وعناصر واجهة المستخدم ونص التعليمات، ويكون الإخراج كما هو موضح فيما يلي:
<tr> <th><label for="id_renewal_date">Renewal date:</label></th> <td> <input id="id_renewal_date" name="renewal_date" type="text" value="2016-11-08" required /> <br /> <span class="helptext"> Enter date between now and 4 weeks (default 3 weeks). </span> </td> </tr>
ملاحظة: يمكن ألّا يكون الأمر واضحًا لأن لدينا حقلًا واحدًا فقط، ولكن يُعرَّف كل حقل في صف الجدول الخاص به افتراضيًا، ويمكن توفير الإخراج نفسه إذا أشرت إلى متغير القالب {{ form.as_table }}
.
إذا أدخلتَ تاريخًا غير صالح، فستحصل على قائمة بالأخطاء المعروضة على الصفحة، مثل errorlist
كما يلي:
<tr> <th><label for="id_renewal_date">Renewal date:</label></th> <td> <ul class="errorlist"> <li>Invalid date - renewal in past</li> </ul> <input id="id_renewal_date" name="renewal_date" type="text" value="2015-11-08" required /> <br /> <span class="helptext"> Enter date between now and 4 weeks (default 3 weeks). </span> </td> </tr>
طرق أخرى لاستخدام متغير قالب الاستمارة
يُخرج كل حقل بوصفه صفًا في جدول باستخدام {{ form.as_table }}
، ويمكنك إخراج كل حقل بوصفه عنصر قائمة باستخدام {{ form.as_ul }}
، أو فقرة باستخدام {{ form.as_p }}
.
يمكن التحكم الكامل في إخراج rendering كل جزء من الاستمارة من خلال فهرسة خاصياته باستخدام الصيغة النقطية، لذا يمكننا مثلًا الوصول إلى عدد من العناصر المنفصلة للحقل renewal_date
كما يلي:
-
{{ form.renewal_date }}
: كامل الحقل. -
{{ form.renewal_date.errors }}
: قائمة الأخطاء. -
{{ form.renewal_date.id_for_label }}
: معرّف التسمية. -
{{ form.renewal_date.help_text }}
: حقل نص التعليمات.
اختبار الصفحة
إذا قبلت التحدي الموجود في المقال السابق، فستحصل على قائمة بجميع الكتب المُعارة في المكتبة، والتي تكون مرئية فقط لموظفي المكتبة، إذ سيكون العرض مشابهًا لما يلي:
{% extends "base_generic.html" %} {% block content %} <h1>All Borrowed Books</h1> {% if bookinstance_list %} <ul> {% for bookinst in bookinstance_list %} <li class="{% if bookinst.is_overdue %}text-danger{% endif %}"> <a href="{% url 'book-detail' bookinst.book.pk %}">{{ bookinst.book.title }}</a> ({{ bookinst.due_back }}) {% if user.is_staff %}- {{ bookinst.borrower }}{% endif %} </li> {% endfor %} </ul> {% else %} <p>There are no books borrowed.</p> {% endif %} {% endblock %}
يمكننا إضافة رابط إلى صفحة تجديد الكتاب بجانب كل عنصر من خلال إلحاق شيفرة القالب التالية بنص عنصر القائمة السابق. لاحظ أنه لا يمكن تشغيل شيفرة القالب هذه إلا داخل حلقة {% for %}
، لأنه مكان تعريف قيمة bookinst
.
{% if perms.catalog.can_mark_returned %}- <a href="{% url 'renew-book-librarian' bookinst.id %}">Renew</a>{% endif %}
ملاحظة: تذكّر أن تسجيل الدخول التجريبي سيحتاج الحصول على الإذن "catalog.can_mark_returned
" لرؤية رابط "التجديد Renew" الجديد والوصول إلى الصفحة المرتبطة بهذا الارتباط، وذلك ربما باستخدام حساب مستخدمك المميز.
يمكنك بدلًا من ذلك بناء عنوان URL تجريبي يدويًا مثل "http://127.0.0.1:8000/catalog/book/<bookinstance_id>/renew/"bookinstance_id
صالح بالانتقال إلى صفحة تفاصيل الكتاب في مكتبتك، ونسخ الحقل id
.
إذا نجحت في كل ما سبق، فستبدو الاستمارة الافتراضية كما يلي:
وستبدو الاستمارة التي تحتوي على قيمة مُدخَلة غير صالحة كما يلي:
وستبدو قائمة جميع الكتب التي تحتوي على روابط التجديد كما يلي:
الصنف ModelForm
يُعَد إنشاء الصنف Form
باستخدام الأسلوب الموضح سابقًا مرنًا جدًا، مما يسمح لك بإنشاء أيّ نوع من صفحات الاستمارات التي تريدها وربطها بأيّ نموذج Model أو عدة نماذج، ولكن إذا احتجت استمارة لربط Map حقول نموذج واحد، فسيعرّف نموذجك معظم المعلومات التي تحتاجها في استمارتك، مثل الحقول والتسميات ونص التعليمات وغير ذلك.
يُعَد استخدام الصنف المساعد ModelForm
لإنشاء الاستمارة من نموذجك أسهل من إعادة إنشاء تعريفات النموذج في استمارتك، ثم يمكن استخدام الصنف ModelForm
ضمن العروض Views بطريقة الصنف Form
نفسها.
إليك فيما يلي صنف ModelForm
يحتوي على حقل الصنف RenewBookForm
الأصلي نفسه، وكل ما عليك تطبيقه لإنشاء الاستمارة هو إضافة class Meta
مع النموذج model
المرتبط به (BookInstance
) وقائمة بحقول fields
النموذج المُراد تضمينها في الاستمارة:
from django.forms import ModelForm from catalog.models import BookInstance class RenewBookModelForm(ModelForm): class Meta: model = BookInstance fields = ['due_back']
ملاحظة: يمكنك أيضًا تضمين جميع الحقول في الاستمارة باستخدام fields = '__all__'
، أو يمكنك استخدام exclude
بدلًا من fields
لتحديد الحقول التي لا يجب تضمينها من النموذج. لا يوصَى بأيٍّ من الأسلوبين لأنه ستُضمَّن بعد ذلك الحقول الجديدة المُضافة إلى النموذج في الاستمارة تلقائيًا دون أن يأخذ المطور بالضرورة في حساباته الآثار الأمنية المحتملة.
ملاحظة: يمكن أن تعتقد أن هذه الطريقة ليست أبسط بكثير من استخدام الصنف Form
، إذ لن تلاحظ ذلك في حالتنا لأن لدينا حقلًا واحدًا فقط، ولكن إذا كان لديك الكثير من الحقول، فيمكن أن يقلل ذلك من مقدار الشيفرة البرمجية بصورة كبيرة.
تأتي بقية المعلومات من تعريفات حقول النموذج، مثل التسميات وعناصر واجهة المستخدم ونص التعليمات ورسائل الخطأ، فإن لم تكن هذه المعلومات صحيحة، فيمكننا تعديلها في class Meta
، وتحديد قاموس يحتوي على الحقل المطلوب تغييره وقيمته الجديدة.
يمكن أن نرغب في هذه الاستمارة مثلًا باستخدام تسميةٍ لحقلنا هي "تاريخ التجديد Renewal date" بدلًا من التسمية الافتراضية التي تعتمد على اسم الحقل "Due Back"، ويمكن أن نرغب أيضًا في أن يكون نص التعليمات محددًا لحالة الاستخدام هذه. يوضح الصنف Meta
التالي كيفية تعديل هذه الحقول، ويمكنك ضبط عناصر واجهة المستخدم widgets
ورسائل الخطأ error_messages
إن لم تكن الإعدادات الافتراضية كافية.
class Meta: model = BookInstance fields = ['due_back'] labels = {'due_back': _('New renewal date')} help_texts = {'due_back': _('Enter a date between now and 4 weeks (default 3).')}
يمكن إضافة التحقق من صحة البيانات من خلال استخدام الأسلوب المتبع نفسه في الصنف Form
العادي، إذ يمكنك تعريف دالة بالاسم clean_<field_name>()
ورفع استثناءات ValidationError
للقيم غير الصالحة. الاختلاف الوحيد فيما يتعلق بالاستمارة الأصلية هو أن حقل النموذج يسمى due_back
وليس "renewal_date
"، وهذا التغيير ضروري لأن الحقل المقابل في BookInstance
يسمى due_back
.
from django.forms import ModelForm from catalog.models import BookInstance class RenewBookModelForm(ModelForm): def clean_due_back(self): data = self.cleaned_data['due_back'] # تحقق مما إذا كان التاريخ ليس في الماضي if data < datetime.date.today(): raise ValidationError(_('Invalid date - renewal in past')) # تحقق مما إذا كان التاريخ يقع ضمن النطاق المسموح به (+4 أسابيع من اليوم) if data > datetime.date.today() + datetime.timedelta(weeks=4): raise ValidationError(_('Invalid date - renewal more than 4 weeks ahead')) # تذكّر دائمًا أن تعيد البيانات النظيفة return data class Meta: model = BookInstance fields = ['due_back'] labels = {'due_back': _('Renewal date')} help_texts = {'due_back': _('Enter a date between now and 4 weeks (default 3).')}
يُعَد الصنف RenewBookModelForm
السابق مكافئًا وظيفيًا للصنف RenewBookForm
الأصلي، ويمكنك استيراده واستخدامه في أيّ مكان تستخدم فيه الصنف RenewBookForm
حاليًا طالما أنك تحدّث اسم متغير الاستمارة المقابل من renewal_date
إلى due_back
كما في التصريح عن الاستمارة الثانية:
RenewBookModelForm(initial={'due_back': proposed_renewal_date}
عروض التعديل المعممة Generic Editing Views
تمثل خوارزمية معالجة الاستمارة التي استخدمناها في مثال العرض المستند إلى الدوال السابق نمطًا شائعًا جدًا في عروض تعديل الاستمارة، إذ يجرّد جانغو كثيرًا من هذه "الشيفرة المتداولة Boilerplate" من خلال إنشاء عروض التعديل المُعمَّمة لإنشاء وتعديل وحذف العروض بناءً على النماذج، فهي لا تعالج سلوك "العرض View" فحسب، بل تنشئ تلقائيًا صنف الاستمارة ModelForm
من النموذج.
ملاحظة: هناك أيضًا -إضافةً إلى عروض التعديل التي وضحناها سابقًا- الصنف FormView
الذي يقع في مكانٍ ما بين العرض المستند إلى الدوال والعروض المُعمَّمة الأخرى من حيث المرونة والجهد المبذول لكتابة الشيفرة البرمجية. ما زلت بحاجة إلى إنشاء الصنف Form
لاستخدام الصنف FormView
، ولكن لا يتوجّب عليك تقديم جميع أنماط معالجة الاستمارة المعيارية، بل يجب فقط توفير تقديم للدالة التي ستُستدعَى بمجرد معرفة أن الإرسال صالح.
سنستخدم في هذا القسم عروض التعديل المُعمَّمة لإنشاء صفحات لإضافة وظائف إنشاء سجلات Author
وتعديلها وحذفها من المكتبة، مما يوفر إعادة تقديم أساسية لأجزاءٍ من موقع المدير بفعالية. يمكن أن يكون ذلك مفيدًا إذا كنت بحاجة توفير وظائف المدير بطريقة أكثر مرونة مما يمكن توفيره باستخدام موقع المدير.
العروض
افتح ملف العروض "locallibrary/catalog/views.py" وضع كتلة الشيفرة البرمجية التالية في نهايته:
from django.views.generic.edit import CreateView, UpdateView, DeleteView from django.urls import reverse_lazy from catalog.models import Author class AuthorCreate(CreateView): model = Author fields = ['first_name', 'last_name', 'date_of_birth', 'date_of_death'] initial = {'date_of_death': '11/06/2020'} class AuthorUpdate(UpdateView): model = Author fields = '__all__' # غير موصَى به، إذ يمكن أن يسبب مشكلة أمنية محتملة في حالة إضافة مزيد من الحقول class AuthorDelete(DeleteView): model = Author success_url = reverse_lazy('authors')
يمكنك إنشاء أو تحديث أو حذف العروض التي تريد اشتقاقها من CreateView
و UpdateView
و DeleteView
على التوالي، ثم تعريف النموذج المرتبط بها.
يجب أيضًا -بالنسبة لحالات "الإنشاء create" و"التحديث update"- تحديد الحقول المراد عرضها في الاستمارة باستخدام صياغة ModelForm
نفسها، لذا نظهر في هذه الحالة كيفية سرد الحقول بصورة فردية وكيفية صياغة سردها جميعًا. يمكنك تحديد القيم الأولية لكل حقل باستخدام قاموس أزواج اسم الحقل/قيمته field_name/value، وقد ضبطنا هنا تاريخ نهايتها عشوائيًا للتوضيح، إذ يمكن أن ترغب في حذفها. ستعيد هذه العروض توجيهك افتراضيًا عند النجاح إلى صفحة تعرض عنصر النموذج المُنشَأ أو المُعدَّل، والذي سيكون في حالتنا عرض تفاصيل المؤلف الذي أنشأناه في مقال سابق، ويمكنك تحديد موقع إعادة توجيه بديل من خلال التصريح عن المعامل success_url
كما حدث للصنف AuthorDelete
.
لا يحتاج الصنف AuthorDelete
إلى عرض أيٍّ من الحقول، فلا حاجة لتحديدها، ولكن يجب تحديد المعامل success_url
، لأنه لا توجد قيمة افتراضية واضحة ليستخدمها جانغو، لذا نستخدم في هذه الحالة الدالة reverse_lazy()
لإعادة التوجيه إلى قائمة المؤلفين بعد حذف المؤلف، إذ تُعَد الدالة reverse_lazy()
نسخةً بطيئة من الدالة reverse()
، واستخدمناها هنا لأننا نقدم عنوان URL لسمة عرض مستند إلى الأصناف.
القوالب
تستخدم عروض "الإنشاء create" و"التحديث update" القالب نفسه افتراضيًا، والذي سيُسمَّى بعد نموذجك وفقًا التنسيق: model_name_form.html
. يمكنك تغيير اللاحقة إلى شيء آخر غير ""_form باستخدام الحقل template_name_suffix
في عرضك مثل template_name_suffix = '_other_suffix'
.
أنشئ ملف القالب "locallibrary/catalog/templates/catalog/author_form.html" وانسخ النص التالي:
{% extends "base_generic.html" %} {% block content %} <form action="" method="post"> {% csrf_token %} <table> {{ form.as_table }} </table> <input type="submit" value="Submit" /> </form> {% endblock %}
يُعَد ذلك مشابهًا للاستمارات السابقة ويعرض الحقول باستخدام جدول. لاحظ أيضًا كيف نصرّح مرةً أخرى عن {% csrf_token %}
للتأكد من أن الاستمارات مقاومة لهجمات CSRF.
يتوقع عرض "الحذف delete" العثور على قالب مسمًّى بالتنسيق"_" مثل model_name_confirm_delete.html
. يمكنك تغيير اللاحقة باستخدام template_name_suffix
في عرضك.
أنشئ ملف القالب التالي وانسخ النص التالي:
- الملف "locallibrary/catalog/templates/catalog/author_confirm_delete.html":
{% extends "base_generic.html" %} {% block content %} <h1>Delete Author</h1> <p>Are you sure you want to delete the author: {{ author }}?</p> <form action="" method="POST"> {% csrf_token %} <input type="submit" value="Yes, delete." /> </form> {% endblock %}
ضبط عناوين URL
افتح ملف ضبط عناوين URL الخاص بك "locallibrary/catalog/urls.py" وضِف الضبط التالي إلى نهاية الملف:
urlpatterns += [ path('author/create/', views.AuthorCreate.as_view(), name='author-create'), path('author/<int:pk>/update/', views.AuthorUpdate.as_view(), name='author-update'), path('author/<int:pk>/delete/', views.AuthorDelete.as_view(), name='author-delete'), ]
لا يوجد شيء جديد هنا، إذ يمكنك أن ترى أن العروض ما هي إلا أصناف، وبالتالي يجب استدعاؤها باستخدام .as_view()
، ويجب أن تكون قادرًا على التعرّف على أنماط URL في كل حالة، ويجب أن نستخدم pk
بوصفه اسمًا لقيمة المفتاح الرئيسي الملتقطة، لأنه اسم المعامل الذي تتوقعه أصناف العرض.
أصبحت صفحات إنشاء وتحديث وحذف المؤلفين جاهزةً الآن للاختبار، ولن نربطها بالشريط الجانبي للموقع في هذه الحالة، بالرغم من أنه يمكنك ذلك إذا أردت.
ملاحظة: سيلاحظ المستخدمون شديدو الانتباه أننا لم نفعل أيّ شيء لمنع المستخدمين غير المُصرَّح لهم من الوصول إلى الصفحات، إذ سنترك ذلك بمثابة تمرين لك (تلميح: يمكنك استخدام PermissionRequiredMixin
وإنشاء إذن جديد أو إعادة استخدام الإذن can_mark_returned
).
اختبار الصفحة
أولًا، سجّل الدخول إلى الموقع بحساب لديه أيّ أذونات قررت أنها ضرورية للوصول إلى صفحات تعديل المؤلف، ثم انتقل إلى صفحة إنشاء المؤلف "http://127.0.0.1:8000/catalog/author/create/"، والتي يجب أن تبدو كما يلي:
أدخِل قيم الحقول ثم اضغط على إرسال Submit لحفظ سجل المؤلف، ويجب أن تُنقَل الآن إلى عرض تفصيلي لمؤلفك الجديد، مع عنوان URL مثل العنوان "http://127.0.0.1:8000/catalog/author/10".
يمكنك اختبار تعديل السجلات من خلال إلحاق "/update/" بنهاية عنوان URL للعرض التفصيلي، مثل "http://127.0.0.1:8000/catalog/author/10/update/". لن نعرض لقطة شاشة لأنها تبدو تمامًا مثل صفحة "الإنشاء create".
أخيرًا، يمكننا حذف الصفحة من خلال إلحاق "delete" بنهاية عنوان URL لعرض المؤلف التفصيلي، مثل "http://127.0.0.1:8000/catalog/author/10/delete/"، ويجب أن يعرض جانغو صفحة الحذف الآتية. اضغط على "Yes, delete." لإزالة السجل والانتقال إلى قائمة جميع المؤلفين.
تحدى نفسك
أنشئ بعض الاستمارات لإنشاء وتعديل وحذف سجلات Book
، إذ يمكنك استخدام بنية Authors
نفسها تمامًا. إذا كان القالب book_form.html مجرد نسخة مُعاد تسميتها من القالب author_form.html، فستظهر صفحة "إنشاء كتاب" الجديدة مثل لقطة الشاشة التالية:
الخلاصة
يمكن أن يكون إنشاء الاستمارات والتعامل معها عملية معقدة، ولكن يسهّل جانغو هذا الأمر من خلال توفير آليات برمجية للتصريح عن الاستمارات وعرضها والتحقق من صحتها، ويوفر عروضًا معمَّمة لتعديل الاستمارة ويمكنها إنجاز كل العمل تقريبًا لتعريف الصفحات التي يمكنها إنشاء وتعديل وحذف السجلات المرتبطة بنسخة نموذج واحدة.
هناك الكثير من الأمور التي يمكن إنجازها باستخدام الاستمارات، ولكن يجب أن تفهم كيفية إضافة الاستمارات الأساسية وشيفرة معالجتها إلى موقعك.
ترجمة -وبتصرُّف- للمقال Django Tutorial Part 9: Working with forms.
اقرأ المزيد
- المقال التالي: تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو
- المقال السابق: تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم
- التعامل مع الاستمارات
- كتابة أول تطبيق دانغو - الجزء الرابع: كتابة استمارة بسيطة (توثيق جانغو)
- واجهة برمجة تطبيقات الاستمارة (توثيق جانغو)
- حقول الاستمارة (توثيق جانغو)
- التحقق من صحة الاستمارة والحقل (توثيق جانغو)
- معالجة الاستمارة باستخدام العروض المستندة إلى الأصناف (توثيق جانغو)
- إنشاء الاستمارات من النماذج (توثيق جانغو)
- عروض التعديل المعمَّمة (توثيق جانغو)
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.