اذهب إلى المحتوى

تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms


Ola Abbas

سنوضح في هذا المقال كيفية العمل مع استمارات HTML في إطار العمل جانغو Django، خاصةً الطريقة الأسهل لكتابة الاستمارات لإنشاء نسخ من النموذج وتحديثها وحذفها، إذ سنوسّع موقع المكتبة المحلية LocalLibrary بحيث يمكن لأمناء المكتبة تجديد الكتب وإنشاء وتحديث وحذف المؤلفين باستخدام استماراتنا الخاصة عوضًا عن استخدام تطبيق المدير.

  • المتطلبات الأساسية: اطّلع على جميع المقالات السابقة من هذه السلسلة، بما في ذلك المقال الخاص باستيثاق المستخدمين وأذوناتهم.
  • الهدف: فهم كيفية كتابة الاستمارات للحصول على معلومات من المستخدمين وتحديث قاعدة البيانات، وفهم كيف يمكن لعروض التعديل المُعمَّمة المستندة إلى الأصناف تبسيط إنشاء الاستمارات للعمل مع نموذج واحد فقط.

تُعَد استمارة HTML مجموعةً مكونةً من حقل واحد أو عنصر واجهة مستخدم widget واحدة أو أكثر على صفحة الويب، والتي يمكن استخدامها لجمع المعلومات من المستخدمين لإرسالها إلى الخادم، فالاستمارات هي آلية مرنة لتجميع دخل المستخدم، نظرًا لوجود عناصر واجهة مستخدم مناسبة لإدخال العديد من أنواع البيانات المختلفة، بما في ذلك مربعات النص ومربعات الاختيار وأزرار الاختيار ومنتقي التواريخ وما إلى ذلك. تُعَد الاستمارات طريقةً آمنة نسبيًا لمشاركة البيانات مع الخادم، لأنها تسمح بإرسال البيانات في طلبات POST مع الحماية من هجمات تزوير الطلبات عبر المواقع.

لم ننشِئ أيّ استمارات في هذه السلسلة من المقالات حتى الآن، ولكننا صادفناها في موقع مدير جانغو، فمثلًا تُظهِر لقطة الشاشة التالية استمارةً لتعديل أحد نماذج الكتاب Book، والذي يتكون من عدد من قوائم الاختيار ومحرّري النصوص.

01_admin_book_add.png

يمكن أن يكون العمل مع الاستمارات معقدًا، إذ يجب أن يكتب المطورون شيفرة HTML للاستمارة، ويجب التحقق من صحة البيانات المدخلة وتعقيمها Sanitization (أي التخلص من البيانات الحساسة أو غير الضرورية) على الخادم وربما في المتصفح أيضًا، وإعادة نشر الاستمارة مع رسائل خطأ لإبلاغ المستخدمين بأيّ حقول غير صالحة، والتعامل مع البيانات عند إرسالها بنجاح، وأخيرًا الرد على المستخدم بطريقة ما للإشارة إلى النجاح.

تقلّل استمارات جانغو الكثير من العمل المبذول في كل من هذه الخطوات من خلال توفير إطار عمل يتيح لك تعريف الاستمارات وحقولها برمجيًا، ثم استخدام هذه الكائنات لتوليد شيفرة استمارة HTML والتعامل مع الكثير من عمليات التحقق من الصحة وتفاعل المستخدم.

سنعرض في هذا المقال بعض الطرق التي يمكنك من خلالها إنشاء الاستمارات والعمل معها، وخاصة كيف يمكن لعروض التعديل المُعمَّمة أن تقلل من حجم العمل الواجب تطبيقه لإنشاء الاستمارات بهدف معالجة النماذج Models، إذ سنوسّع تطبيق المكتبة المحلية LocalLibrary من خلال إضافة استمارة للسماح لأمناء المكتبة بتجديد الكتب، وسننشئ صفحات لإنشاء وتعديل وحذف الكتب والمؤلفين، أو إعادة إنتاج نسخة أساسية من الاستمارة التي وضحناها سابقًا لتعديل الكتب.

تتألف هذه السلسلة الفرعية من السلسلة الأشمل تعلم تطوير الويب من المقالات التالية:

استمارة HTML

أولًا، اطّلع على مفهوم استمارات HTML، ثم أنشئ استمارة HTML بسيطة، مع حقل نصي واحد لإدخال اسم الفريق والتسمية Label المرتبطة به كما يلي:

02_form_example_name_field.png

تُعرَّف الاستمارة 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 من قالب إلى آخر نمرر إليه سياقًا يحتوي على البيانات المراد عرضها، ولكن ما يعقّد الأمورهو أن الخادم يحتاج أيضًا إلى أن يكون قادرًا على معالجة البيانات التي يقدّمها المستخدم، ويجب أن يعيد عرض الصفحة في حالة وجود أيّ أخطاء.

يوضَح المخطط التالي كيفية معالجة جانغو لطلبات الاستمارة، بدءًا من طلب صفحة تحتوي على استمارة والموضح باللون الأخضر:

03_form_handling_-_standard.png

تطبّق عملية معالجة استمارة جانغو الأمور الرئيسية التالية بناءً على المخطط السابق:

  1. عرض الاستمارة الافتراضية في المرة الأولى التي يطلبها المستخدم.
    • يمكن أن تحتوي الاستمارة على حقول فارغة إذا أردتَ إنشاء سجل جديد، أو يمكن ملؤها مسبقًا بالقيم الأولية إذا غيّرت سجلًا أو كان لديك قيم أولية افتراضية مفيدة مثلًا.
    • يُشار إلى الاستمارة إلى أنها غير مرتبطة Unbound في هذه المرحلة، لأنها غير مرتبطة بأيّ بيانات أدخلها المستخدم، بالرغم من أنه يمكن أن تحتوي على قيم أولية.
  2. تلقي البيانات من طلب الإرسال وربطها بالاستمارة.
    • يعني ربط Binding البيانات بالاستمارة أن البيانات التي أدخلها المستخدم والأخطاء تكون متاحة عندما نحتاج إلى إعادة عرض الاستمارة.
  3. تنظيف البيانات والتحقق من صحتها.
    • يؤدي تنظيف البيانات إلى تعقيم حقول الإدخال مثل إزالة المحارف غير الصالحة التي يمكن استخدامها لإرسال محتوًى ضار إلى الخادم، وتحويلها إلى أنواع بايثون متناسقة.
    • تفحص عملية التحقق من صحة البيانات أن القيم مناسبة للحقل، مثل أن تكون موجودة في مجال البيانات الصحيح، أو أنها ليست قصيرة جدًا أو طويلة جدًا وما إلى ذلك.
  4. إذا وُجدت بيانات غير صالحة، فأعِد عرض الاستمارة، ولكن هذه المرة مع أيّ قيمٍ يملؤها المستخدم ورسائل خطأ لحقول المشكلة.
  5. إذا كانت جميع البيانات صالحة، فطبّق الإجراءات المطلوبة، مثل حفظ البيانات وإرسال بريد إلكتروني وإعادة نتيجة البحث وتحميل ملف وغير ذلك.
  6. أعِد توجيه المستخدم إلى صفحة أخرى عند اكتمال جميع الإجراءات.

يوفر جانغو عددًا من الأدوات والأساليب لمساعدتك في المهام السابقة، والأكثر أهمية هو الصنف 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.

إذا نجحت في كل ما سبق، فستبدو الاستمارة الافتراضية كما يلي:

04_forms_example_renew_default.png

وستبدو الاستمارة التي تحتوي على قيمة مُدخَلة غير صالحة كما يلي:

05_forms_example_renew_invalid.png

وستبدو قائمة جميع الكتب التي تحتوي على روابط التجديد كما يلي:

06_forms_example_renew_allbooks.png

الصنف 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/‎"، والتي يجب أن تبدو كما يلي:

07_forms_example_create_author.png

أدخِل قيم الحقول ثم اضغط على إرسال 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.‎" لإزالة السجل والانتقال إلى قائمة جميع المؤلفين.

08_forms_example_delete_author.png

تحدى نفسك

أنشئ بعض الاستمارات لإنشاء وتعديل وحذف سجلات Book، إذ يمكنك استخدام بنية Authors نفسها تمامًا. إذا كان القالب book_form.html مجرد نسخة مُعاد تسميتها من القالب author_form.html، فستظهر صفحة "إنشاء كتاب" الجديدة مثل لقطة الشاشة التالية:

09_forms_example_create_book.png

الخلاصة

يمكن أن يكون إنشاء الاستمارات والتعامل معها عملية معقدة، ولكن يسهّل جانغو هذا الأمر من خلال توفير آليات برمجية للتصريح عن الاستمارات وعرضها والتحقق من صحتها، ويوفر عروضًا معمَّمة لتعديل الاستمارة ويمكنها إنجاز كل العمل تقريبًا لتعريف الصفحات التي يمكنها إنشاء وتعديل وحذف السجلات المرتبطة بنسخة نموذج واحدة.

هناك الكثير من الأمور التي يمكن إنجازها باستخدام الاستمارات، ولكن يجب أن تفهم كيفية إضافة الاستمارات الأساسية وشيفرة معالجتها إلى موقعك.

ترجمة -وبتصرُّف- للمقال Django Tutorial Part 9: Working with forms.

اقرأ المزيد


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...