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

تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models


Ola Abbas

يوضح هذا المقال كيفية تعريف النماذج Models لموقع المكتبة المحلية LocalLibrary، ويشرح ما هو النموذج وكيفية التصريح عنه وبعض أنواع الحقول الرئيسية، ويعرض بإيجاز بعض الطرق الرئيسية التي يمكنك من خلالها الوصول إلى بيانات النموذج.

يمكن لتطبيقات جانغو Django الوصول إلى البيانات وإدارتها من خلال كائنات لغة بايثون Python المُشار إليها بالنماذج Models، إذ تعرِّف النماذج بنية البيانات المخزنة بما في ذلك أنواع الحقول وربما حجمها الأقصى وقيمها الافتراضية وخيارات قائمة الاختيار ونص التعليمات للتوثيق ونص التسمية للاستمارات Forms وغير ذلك. يُعَد تعريف النموذج مستقلًا عن قاعدة البيانات الأساسية، إذ يمكنك اختيار قاعدة بيانات واحدة من عدة قواعد بيانات بوصفها جزءًا من إعدادات مشروعك. لن تحتاج أبدًا إلى التواصل مع قاعدة البيانات التي تريد استخدامها مباشرةً بمجرد اختيارها، فما عليك سوى كتابة بنية نموذجك وشيفرة برمجية أخرى، وسيتولى إطار عمل جانغو كل الأعمال الأخرى للتواصل مع قاعدة البيانات نيابةً عنك.

يوضح هذا المقال كيفية تعريف النماذج والوصول إليها في مثال موقع المكتبة المحلية LocalLibrary.

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

تصميم نماذج المكتبة المحلية LocalLibrary

يُفضَّل أخذ بضع دقائق للتفكير في البيانات التي يجب تخزينها والعلاقات بين الكائنات قبل الانتقال والبدء في كتابة شيفرة النماذج البرمجية. نعلم أننا نحتاج إلى تخزين معلومات حول الكتب (العنوان والملخص والمؤلف واللغة المكتوبة والفئة ورقم ISBN)، وأنه يمكن أن يكون لدينا نسخ متعددة متاحة (مع معرّف فريد عام وحالة التوفر وغير ذلك). يمكن أن نحتاج إلى تخزين مزيد من المعلومات حول المؤلف أكثر من مجرد اسمه، ويمكن أن يكون هناك عدة مؤلفين لهم الاسم نفسه أو أسماء متشابهة. نريد أن نكون قادرين على فرز المعلومات بناءً على عنوان الكتاب والمؤلف واللغة المكتوبة والفئة، إذ يُفضَّل أن يكون لديك نماذج منفصلة لكل كائن (مجموعة من المعلومات المتعلقة ببعضها) عند تصميم نماذجك، والأشياء الواضحة في هذه الحالة هي الكتب ونسخ الكتب والمؤلفون.

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

يجب التفكير في العلاقات بمجرد أن نقرر ما هي النماذج والحقول، إذ يسمح جانغو بتعريف العلاقات التي تكون من نوع واحد لواحد "OneToOneField" وواحد إلى متعدد "ForeignKey" ومتعدد إلى متعدد "ManyToManyField".

يوضح مخطط الارتباط التالي باستخدام لغة UML النماذج التي سنعرّفها في هذه الحالة (على شكل مربعات):

01_local_library_model_uml.png

أنشأنا نماذجًا للكتاب (التفاصيل العامة للكتاب)، ونسخة الكتاب (حالة النسخ الحقيقية المُحدَّدة للكتاب المتاح في النظام)، والمؤلف، وقررنا أن يكون لدينا نموذج للنوع، بحيث يمكن إنشاء أو تحديد القيم من خلال واجهة المدير، وقررنا عدم وجود نموذج لحالة نسخة الكتاب BookInstance:status، إذ كتبنا شيفرة ثابتة للقيم (LOAN_STATUS) لأننا لا نتوقع تغييرها. يمكنك رؤية اسم النموذج وأسماء الحقول وأنواعها والتوابع وأنواع قيمها المُعادة ضمن كل مربع من المربعات.

يوضح المخطط العلاقات بين النماذج بما في ذلك درجة تعدّدها Multiplicities، وهي الأعداد الموجودة على المخطط والتي توضح أعداد (الحد الأقصى والحد الأدنى) كل نموذج والتي يمكن أن تكون موجودةً في العلاقة، فمثلًا يوضّح الخط المتصل بين المربعات أن الكتاب Book والنوع Genre مرتبطان، وتوضح الأعداد القريبة من نموذج النوع أن الكتاب يجب أن يحتوي على نوع واحد أو أكثر (بقدر ما تريد)، بينما توضح الأعداد الموجودة على الطرف الآخر من الخط بجوار نموذج الكتاب أن النوع يمكن ألّا يكون له أيّ نوع مرتبط بالكتاب أو يمكن أن يكون له العديد من الأنواع المرتبطة بالكتب.

ملاحظة: يوفر القسم التالي تمهيدًا يشرح كيفية تعريف النماذج واستخدامها، وضع في بالك أثناء قراءته كيفية بناء كل نموذج من النماذج الموضحة في المخطط السابق.

مدخل إلى النماذج

يقدم هذا القسم نظرةً عامة موجزة عن كيفية تعريف النموذج وبعضًا من أهم الحقول والوسطاء.

تعريف النموذج

تُعرَّف النماذج عادةً في الملف models.py الخاص بالتطبيق، وتُقدَّم بوصفها صنفًا فرعيًا من django.db.models.Model، ويمكن أن تشمل الحقول والتوابع والبيانات الوصفية. يُظهِر جزء الشيفرة البرمجية التالي نموذجًا قياسيًا بالاسم MyModelName:

from django.db import models
from django.urls import reverse

class MyModelName(models.Model):
    """A typical class defining a model, derived from the Model class."""

    # حقول
    my_field_name = models.CharField(max_length=20, help_text='Enter field documentation')
    # …

    # بيانات وصفية
    class Meta:
        ordering = ['-my_field_name']

    # توابع
    def get_absolute_url(self):
        """Returns the URL to access a particular instance of MyModelName."""
        return reverse('model-detail-view', args=[str(self.id)])

    def __str__(self):
        """String for representing the MyModelName object (in Admin site etc.)."""
        return self.my_field_name

سنستكشف في الأقسام التالية كل ميزة في النموذج بالتفصيل.

الحقول Fileds

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

my_field_name = models.CharField(max_length=20, help_text='Enter field documentation')

يحتوي هذا المثال على حقل واحد يسمى my_field_name من النوع models.CharField، مما يعني أن هذا الحقل سيحتوي على سلاسل نصية من المحارف الأبْجَعَددية alphanumeric. تُسنَد أنواع الحقول باستخدام أصناف Classes معينة تحدد نوع السجل المُستخدَم لتخزين البيانات في قاعدة البيانات مع معايير التحقق لاستخدامها عند استلام القيم من استمارة HTML (أي ما يشكّل قيمة صالحة). يمكن أن تأخذ أنواع الحقول وسطاء تحدّد كيفية تخزين الحقل أو كيفية استخدامه، إذ سنعطي الحقل في حالتنا وسيطين، هما:

  • max_length=20، الذي يشير إلى أن أقصى طول لقيمةٍ ما في هذا الحقل وهو 20 محرفًا.

  • help_text='Enter field documentation'‎، وهو نص مفيد يمكن عرضه في استمارة لمساعدة المستخدمين على فهم كيفية استخدام الحقل.

يُستخدَم اسم الحقل للإشارة إليه في الاستعلامات والقوالب، وتحتوي الحقول على تسمية Label يحدّدها الوسيط verbose_name (بقيمة افتراضية هي None). إذا لم يُضبَط الوسيط verbose_name، فستُنشَأ التسمية من اسم الحقل من خلال استبدال أيّ شرطات سفلية بمسافة وجعل الحرف الأول حرفًا كبيرًا، فمثلًا سيكون للحقل my_field_name تسمية افتراضية هي "My field name" عند استخدامه في الاستمارات.

يؤثر ترتيب التصريح عن الحقول على ترتيبها الافتراضي إذا عُرِض نموذج ضمن استمارة (في موقع المدير مثلًا) بالرغم من أن هذا الترتيب يمكن تجاهله.

يمكن استخدام الوسطاء الشائعة التالية عند التصريح عن أنواع الحقول:

  • help_text: يوفر هذا الوسيط تسميةً نصيةً لاستمارات HTML (في موقع المدير مثلًا) كما وضّحنا سابقًا.

  • verbose_name: اسم يمكن أن يقرأه الإنسان للحقل المُستخدَم في تسميات الحقل، وإذا لم يُحدَّد، فسيستنتج جانغو الاسم المُطوَّل Verbose Name الافتراضي من اسم الحقل.

  • default: القيمة الافتراضية للحقل، ويمكن أن يكون هذا الوسيط قيمةً أو كائنًا يمكن استدعاؤه، إذ سيُستدعَى الكائن في هذه الحالة في كل مرة يُنشَأ فيها سجل جديد.

  • null: إذا كانت قيمة هذا الوسيط True، فسيخزّن جانغو القيم الفارغة على أنها NULL في قاعدة البيانات للحقول التي يكون ذلك مناسبًا لها، وسيخزّن الحقل من النوع CharField سلسلة نصية فارغة بدلًا من ذلك. القيمة الافتراضية لهذا الوسيط هي False.

  • blank: إذا كانت قيمة هذا الوسيط True، فسيُسمَح للحقل بأن يكون فارغًا في استماراتك. القيمة الافتراضية لهذا الوسيط هي False، أي سيجبرك التحقق من صحة استمارة جانغو على إدخال قيمة. يُستخدَم هذا الحقل عادةً مع القيمة null=True، لأنك إذا أردتَ السماح بقيم فارغة، فأنت تحتاج أيضًا أن تكون قاعدة البيانات قادرة على تمثيلها بصورة مناسبة.

  • choices: مجموعة من الاختيارات للحقل، بحيث إذا توفّرت هذه الاختيارات، فستكون أداة الاستمارة المقابلة الافتراضية هي مربع تحديد لهذه الاختيارات بدلًا من حقل النص المعياري.

  • primary_key: إذا كانت قيمة هذا الوسيط True، فسيُضبط الحقل الحالي بوصفه مفتاحًا رئيسيًا للنموذج؛ والمفتاح الرئيسي هو عمود خاص في قاعدة البيانات مخصَّص لتعريف جميع سجلات الجدول المختلفة بطريقة فريدة. إذا لم يُحدَّد أيّ حقل بوصفه مفتاحًا رئيسيًا، فسيضيف جانغو تلقائيًا حقلًا لهذا الغرض. يمكن تحديد نوع حقول المفاتيح الرئيسية المُنشَأة تلقائيًا لكل تطبيق في AppConfig.default_auto_field أو بصورة عامة في إعداد DEFAULT_AUTO_FIELD.

ملاحظة: تضبط التطبيقات المُنشَأة باستخدام manage.py نوعَ المفتاح الرئيسي على النوع BigAutoField. يمكنك رؤية ما يلي في الملف catalog/apps.py الخاص بموقع المكتبة المحلية:

class CatalogConfig(AppConfig):
  default_auto_field = 'django.db.models.BigAutoField'

هناك العديد من الخيارات الأخرى، إذ يمكنك الاطلاع القائمة الكاملة لخيارات الحقول في توثيق جانغو.

توضح القائمة التالية بعض أنواع الحقول الأكثر استخدامًا:

  • يُستخدَم النوع CharField لتعريف سلاسل نصية ذات طول ثابت قصير إلى متوسط الحجم. يجب عليك تحديد الطول الأقصى max_length للبيانات المراد تخزينها.

  • يُستخدَم النوع TextField للسلاسل النصية الكبيرة ذات الطول العشوائي. يمكنك تحديد الطول الأقصى max_length للحقل، ولكنه لا يُستخدَم إلا عند عرض الحقل في استمارات (ليس إجباريًا على مستوى قاعدة البيانات).

  • النوع IntegerField هو حقل لتخزين القيم الصحيحة (عدد صحيح)، وللتحقق من صحة القيم المدخَلة بوصفها أعدادًا صحيحة في الاستمارات.

  • يُستخدَم النوعان DateField و DateTimeField لتخزين أو تمثيل التواريخ ومعلومات التاريخ/الوقت، مثل كائنات بايثون datetime.date و datetime.datetime على التوالي. يمكن أن تصرِّح هذه الحقول إضافةً لما سبق عن المعاملات (الحصرية فيما بينها) auto_now=True (لضبط الحقل على التاريخ الحالي في كل مرة يُحفَظ فيها النموذج) و auto_now_add (لضبط التاريخ عند إنشاء النموذج لأول مرة فقط) و default (لضبط تاريخ افتراضي يمكن للمستخدم تجاهله).

  • يُستخدَم النوع EmailField لتخزين عناوين البريد الإلكتروني والتحقق من صحتها.

  • يُستخدَم النوعان FileField و ImageField لتحميل الملفات والصور على التوالي، إذ يضيف الحقل ImageField تحققًا إضافيًا من أن الملف المُحمَّل هو صورة. يمتلك هذان النوعان من الحقول معاملات لتحديد كيفية ومكان تخزين الملفات التي جرى تحميلها.

  • النوع AutoField هو نوع خاص من الحقل IntegerField الذي يزداد تلقائيًا. يُضاف مفتاح رئيسي من هذا النوع تلقائيًا إلى نموذجك إذا لم تحدده صراحةً.

  • يُستخدَم النوع ForeignKey لتحديد علاقة واحد إلى متعدد بنموذج قاعدة بيانات آخر، فمثلًا للسيارة مُصنِّع واحد، ولكن يمكن للمصنِّع صنع العديد من السيارات. الجانب "واحد" من العلاقة هو النموذج الذي يحتوي على المفتاح، وتشير النماذج التي تحتوي على مفتاح خارجي Foreign Key إلى ذلك المفتاح، إذ تكون هذه النماذج في الجانب "متعدد" من هذه العلاقة.

  • يُستخدَم النوع ManyToManyField لتحديد علاقة متعدد إلى متعدد، فمثلًا يمكن أن يكون للكتاب عدة أنواع ويمكن أن يحتوي كل نوع على عدة كتب. سنستخدم هذا النوع في تطبيق المكتبة بطريقة مشابهة جدًا للنوع ForeignKeys، ولكن يمكن استخدامه بطرق أكثر تعقيدًا لوصف العلاقات بين المجموعات. يحتوي هذا النوع على المعامل on_delete لتحديد ما يحدث عند حذف السجل المرتبط به مثل قيمة models.SET_NULL التي تضبط القيمة على NULL.

هناك العديد من الأنواع الأخرى من الحقول بما في ذلك حقول أنواع مختلفة من الأعداد (الأعداد الصحيحة الكبيرة والأعداد الصحيحة الصغيرة والأعداد العشرية) والقيم المنطقية وعناوين URL والعناوين الفرعية Slugs والمعرّفات الفريدة وغيرها من المعلومات المتعلقة بالوقت (المدة والوقت وغير ذلك)، ويمكنك الاطلاع على القائمة الكاملة في توثيق جانغو.

البيانات الوصفية Metadata

يمكنك التصريح عن البيانات الوصفية على مستوى النموذج من خلال التصريح عن الصنف class Meta كما يلي:

class Meta:
    ordering = ['-my_field_name']

تتمثل إحدى الميزات الأكثر فائدة لهذه البيانات الوصفية في التحكم في الترتيب الافتراضي للسجلات المُعادة عند الاستعلام عن نوع النموذج، ويمكنك تطبيق ذلك من خلال تحديد ترتيب المطابقة في قائمة أسماء الحقول مع السمة ordering كما وضّحنا سابقًا. يعتمد الترتيب على نوع الحقل، إذ تُفرَز الحقول المحرفية أبجديًا، بينما تُفرَز حقول التاريخ بترتيب زمني، كما يمكنك أن تسبق اسم الحقل بإشارة ناقص (-) لعكس ترتيب الفرز.

إذا اخترنا مثلًا فرز الكتب افتراضيًا كما يلي:

ordering = ['title', '-pubdate']

فستُفرَز الكتب أبجديًا حسب العنوان من A إلى Z، ثم حسب تاريخ النشر ضمن كل عنوان من الأحدث إلى الأقدم.

هناك سمة شائعة أخرى هي verbose_name، وهي اسم مطوَّل للصنف بصيغة المفرد والجمع:

verbose_name = 'BetterName'

تسمح لك السمات المفيدة الأخرى بإنشاء وتطبيق أذونات وصول جديدة للنموذج (تُطبَّق الأذونات الافتراضية تلقائيًا)، أو السماح بالترتيب بناءً على حقل آخر، أو التصريح عن أن الصنف "مجرد Abstract"، أي صنف أساسي لا يمكنك إنشاء سجلات له، وستُشتَق بدلًا من ذلك لإنشاء نماذج أخرى.

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

التوابع Methods

يمكن أن يحتوي النموذج أيضًا على توابع، إذ يجب عليك في كل نموذج على الأقل تعريف تابع لصنف بايثون المعياري ‎__str__()‎ لإعادة سلسلة نصية يمكن يقرأها الإنسان لكل كائن، إذ تُستخدَم هذه السلسلة النصية لتمثيل السجلات الفردية في موقع المدير وفي أيّ مكان آخر تحتاج فيه للإشارة إلى نسخة من النموذج. يعيد هذا التابع غالبًا حقل العنوان أو الاسم من النموذج.

def __str__(self):
    return self.field_name

هناك تابع آخر شائع لتضمينه في نماذج جانغو وهو التابع get_absolute_url()‎ الذي يعيد عنوان URL لعرض سجلات النماذج على موقع الويب. إذا حدّدتَ هذا التابع، فسيضيف جانغو تلقائيًا زر "عرض على الموقع View on Site" إلى شاشات تعديل سجل النموذج في موقع المدير. يوضّح ما يلي نمطًا معياريًا للتابع get_absolute_url()‎:

def get_absolute_url(self):
    """Returns the URL to access a particular instance of the model."""
    return reverse('model-detail-view', args=[str(self.id)])

ملاحظة: يجب إنشاء رابط Mapper عنوان URL لتمرير الاستجابة والمعرّف إلى "عرض النموذج التفصيلي" (الذي سينفّذ العمل المطلوب لعرض السجل) بافتراض أنك ستستخدم عناوين URL، مثل "‎/myapplication/mymodelname/2" لعرض سجلات نموذجك، إذ يمثل "2" معرّف id سجل معين. الدالة reverse()‎ السابقة قادرة على عكس رابط عنوان URL (المسماة في الحالة المذكورة سابقًا باسم نموذج-تفاصيل-عرض 'model-detail-View') لإنشاء عنوان URL بالتنسيق الصحيح، ويجب عليك كتابة ربط عنوان URL والعرض والقالب.

يمكنك تعريف أيّ توابع أخرى تريدها واستدعاؤها من شيفرتك البرمجية أو قوالبك بشرط ألّا تأخذ أيّ معاملات.

إدارة النموذج

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

إنشاء وتعديل السجلات

يمكنك إنشاء سجل من خلال تعريف نسخة من النموذج ثم استدعاء الدالة save()‎.

# ‫أنشِئ سجلًا جديدًا باستخدام باني النموذج
record = MyModelName(my_field_name="Instance #1")

# احفظ الكائن في قاعدة البيانات
record.save()

ملاحظة: إذا لم تصرّح عن أي حقل primary_key، فسيُمنَح السجل الجديد حقلًا من هذا النوع تلقائيًا مع معرّف id اسم الحقل، إذ يمكنك الاستعلام عن هذا الحقل بعد حفظ السجل السابق، وسيكون له القيمة 1. يمكنك الوصول إلى الحقول في هذا السجل الجديد باستخدام الصيغة النقطية وتغيير القيم، ويجب عليك استدعاء الدالة save()‎ لتخزين القيم المُعدَّلة في قاعدة البيانات.

# الوصول إلى قيم حقل النموذج باستخدام سمات بايثون
print(record.id) # يجب أن تعيد القيمة 1 للسجل الأول
print(record.my_field_name) # ‫يجب أن تطبع 'Instance #1'

# ‫غيّر السجل من خلال تعديل الحقول ثم استدعِ save()‎
record.my_field_name = "New Instance Name"
record.save()

البحث عن السجلات

يمكنك البحث عن السجلات التي تطابق معايير معينة باستخدام سمة النموذج objects، والتي يوفّرها الصنف الأساسي.

ملاحظة: يمكن أن يكون شرح كيفية البحث عن السجلات باستخدام أسماء الحقول والنماذج المجردة مربكًا قليلًا، إذ سنشير في المناقشة الآتية إلى النموذج Book باستخدام الحقلين title و genre، بحيث يكون النوع genre نموذجًا له حقل name واحد.

يمكننا الحصول على جميع سجلات النموذج بوصفها كائن QuerySet باستخدام الدالة objects.all()‎، إذ يُعَد QuerySet كائنًا تكراريًا، مما يعني أنه يحتوي على عدد من الكائنات الممكن تكرارها.

all_books = Book.objects.all()

تسمح لنا الدالة filter()‎ من جانغو بترشيح الكائن QuerySet المُعاد لمطابقة نص محدّد، أو حقل عددي مع معايير معينة، فمثلًا يمكننا تطبيق ما يلي لترشيح الكتب التي تحتوي على الكلمة "wild" في عنوانها ثم عَدّها:

wild_books = Book.objects.filter(title__contains='wild')
number_wild_books = wild_books.count()

تُعرَّف الحقول المراد مطابقتها ونوع التطابق في اسم معامل الترشيح باستخدام التنسيق field_name__match_type، ويمكنك ملاحظة الشرطة السفلية المزدوجة بين title و contains. رشّحنا title باستخدام مطابقة حساسة لحالة الأحرف، وهناك العديد من أنواع التطابقات الأخرى الممكن إجراؤها، مثل icontains (غير حساسة لحالة الأحرف) و iexact (مطابقة تامة غير حساسة لحالة الأحرف) و exact (مطابقة تامة حساسة لحالة الأحرف) و in و gt (أكبر من) و startswith وغير ذلك. يمكنك الاطلاع على القائمة الكاملة في توثيق جانغو.

ستحتاج في بعض الحالات إلى ترشيح حقل يحدّد علاقة واحد إلى متعدد مع نموذج آخر، مثل ForeignKey، إذ يمكنك في هذه الحالة "فهرسة Index" الحقول ضمن النموذج المتعلق بها باستخدام شرطات سفلية مزدوجة إضافية، لذلك سيتعين عليك فهرسة الاسم name من خلال الحقل genre لترشيح كتب ذات نمط نوع معين كما يلي:

# Will match on: Fiction, Science fiction, non-fiction etc.
books_containing_genre = Book.objects.filter(genre__name__icontains='fiction')

ملاحظة: يمكنك استخدام الشرطين السفليتين __ للتنقل عبر العديد من مستويات العلاقات ForeignKey/ManyToManyField كما تريد، فمثلًا يمكن أن يكون للكتاب Book الذي له أنواع مختلفة ومُعرَّف باستخدام علاقة "cover" معامل اسمٍ هو type__cover__name__exact='hard'‎.

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

تعريف نماذج المكتبة المحلية LocalLibrary

سنبدأ في هذا القسم بتعريف نماذج المكتبة، لذا افتح الملف "models.py" ضمن المجلد "/locallibrary/catalog/". تستورد الشيفرة البرمجية المتداولة الموجودة في أعلى الصفحة الوحدة models التي تحتوي على صنف النموذج الأساسي models.Model الذي سترثه نماذجنا.

from django.db import models

# أنشِئ نماذجك هنا

النموذج Genre

انسخ شيفرة النموذج Genre البرمجية التالية والصقه في نهاية الملف "models.py" الخاص بك. يُستخدَم هذا النموذج لتخزين المعلومات حول فئة الكتاب مثل كونه خياليًا أو غير خيالي، أو عاطفيًا أو تاريخًا وغير ذلك. أنشأنا النوع genre بوصفه نموذجًا وليس نصًا حرًا أو قائمة اختيار بحيث يمكن إدارة القيم الممكنة عبر قاعدة البيانات بدلًا من أن تكون شيفرة ثابتة.

class Genre(models.Model):
    """Model representing a book genre."""
    name = models.CharField(max_length=200, help_text='Enter a book genre (e.g. Science Fiction)')

    def __str__(self):
        """String for representing the Model object."""
        return self.name

يحتوي النموذج على حقل واحد من النوع CharField هو name، بحيث يُستخدَم لوصف نوع الكتاب وهو مُحدَّد ليحتوي على 200 محرف ولديه بعض نصوص التعليمات help_text. صرّحنا في نهاية النموذج عن التابع ‎__str__()‎ الذي يعيد اسم النوع الذي يحدّده سجل معين. لم يُحدَّد أيّ اسم مطوَّل لذلك سيُسمَّى الحقل بالاسم Name في الاستمارات.

النموذج Book

انسخ النموذج Book التالي والصقه في نهاية ملفك، إذ يمثل هذا النموذج جميع المعلومات حول الكتاب المتاح بالمعنى العام، وليس مثيلًا أو نسخةً ماديةً معينة متاحة للإعارة. يستخدم النموذجُ النوعَ CharField لتمثيل الحقلين title و isbn الخاصَين بالكتاب، ولاحظ كيف يضبط المعامل الأول غير المُسمَّى للحقل isbn التسميةَ بصورة صريحة على القيمة "ISBN" وإلّا فستُضبَط افتراضيًا على القيمة "Isbn". ضبطنا المعامل unique على القيمة true لضمان حصول جميع الكتب على رقم ISBN فريد، إذ يجعل المعامل unique قيمة الحقل فريدةً بصورة عامة في الجدول. يستخدم النموذج النوع TextField للحقل summary، لأن هذا النص يمكن أن يكون طويلًا جدًا.

# يُستخدَم لإنشاء عناوين‫ URL من خلال عكس أنماط عنوان URL
from django.urls import reverse 

class Book(models.Model):
    """Model representing a book (but not a specific copy of a book)."""
    title = models.CharField(max_length=200)

    # اُستخدِم المفتاح الخارجي لأنه لا يمكن أن يكون للكتاب سوى مؤلف واحد، ولكن يمكن أن يكون للمؤلفين عدة كتب
    # المؤلف هو سلسلة نصية وليس كائنًا بسبب عدم النصريح عنه بعد في الملف
    author = models.ForeignKey('Author', on_delete=models.SET_NULL, null=True)

    summary = models.TextField(max_length=1000, help_text='Enter a brief description of the book')
    isbn = models.CharField('ISBN', max_length=13, unique=True,
                             help_text='13 Character <a href="https://www.isbn-international.org/content/what-isbn">ISBN number</a>')

    # استُخدم حقل‫ ManyToManyField لأن النوع genre يمكن أن يحتوي على العديد من الكتب، ويمكن أن تغطي الكتب العديد من الأنواع.‫
    # ‫عُرِّف الصنف Genre، وبالتالي يمكننا تحديد الكائن السابق.
    genre = models.ManyToManyField(Genre, help_text='Select a genre for this book')

    def __str__(self):
        """String for representing the Model object."""
        return self.title

    def get_absolute_url(self):
        """Returns the URL to access a detail record for this book."""
        return reverse('book-detail', args=[str(self.id)])

يكون الحقل genre من النوع ManyToManyField، بحيث يمكن أن يكون للكتاب أنواع متعددة ويمكن أن يحتوي النوع على العديد من الكتب. صُرِّح عن المؤلف على أنه من النوع ForeignKey، لذلك سيكون لكل كتاب مؤلف واحد فقط، ولكن يمكن أن يكون للمؤلف العديد من الكتب. يمكن أن يكون للكتاب مؤلفِين متعددين عمليًا، ولكن هذا غير ممكن في هذا التطبيق.

يُصرَّح عن صنف النموذج ذي الصلة في كلا هذين النوعين من الحقول بوصفه أول معامل غير مسمًى باستخدام صنف النموذج أو سلسلة نصية تحتوي على اسم النموذج المرتبط بها، إذ يجب استخدام اسم النموذج بوصفه سلسلة نصية إذا لم يُعرَّف الصنف المرتبط به في هذا الملف قبل الإشارة إليه. المعاملات الأخرى ذات الأهمية في حقل المؤلف author هي null=True الذي يسمح لقاعدة البيانات بتخزين قيمة Null عند عدم تحديد أي مؤلف، و on_delete=models.SET_NULL الذي سيحدد قيمة حقل مؤلف الكتاب إلى Null عند حذف سجل المؤلف المرتبط به.

تحذير: يكون المعامل on_delete=models.CASCADE افتراضيًا، مما يعني أنه إذا حُذِف المؤلف، فسيُحذَف هذا الكتاب أيضًا. استخدمنا SET_NULL هنا، ولكن يمكننا أيضًا استخدام PROTECT أو RESTRICT لمنع حذف المؤلف أثناء استخدام أيّ كتاب له.

يعرّف النموذج أيضًا التابعَ ‎__str__()‎ باستخدام حقل عنوان الكتاب title لتمثيل سجل Book، إذ يعيد التابع الأخير get_absolute_url()‎ عنوان URL يمكن استخدامه للوصول إلى سجل تفصيلي لهذا النموذج، إذ يجب لتحقيق ذلك تعريف ربط لعنوان URL له الاسم book-detail وتعريف عرض وقالب مرتبط به.

النموذج BookInstance

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

ستصبح بعض هذه الحقول والتوابع مألوفةً الآن، إذ يستخدم النموذج BookInstance ما يلي:

  • حقلًا من النوع ForeignKey لتحديد النموذج Book المرتبط به (يمكن أن يكون لكل كتاب نسخ متعددة، ولكن يمكن أن يكون للنسخة كتاب Book واحد فقط). يحدِّد المفتاح on_delete=models.RESTRICT لضمان عدم إمكانية حذف النموذج Book عندما يشير إليه النموذج BookInstance.

  • حقلًا من النوع CharField لتمثيل الطبعة (إصدار محدد) للكتاب.

import uuid # مطلوب لنسخ الكتاب الفريدة

class BookInstance(models.Model):
    """Model representing a specific copy of a book (i.e. that can be borrowed from the library)."""
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, help_text='Unique ID for this particular book across whole library')
    book = models.ForeignKey('Book', on_delete=models.RESTRICT, null=True)
    imprint = models.CharField(max_length=200)
    due_back = models.DateField(null=True, blank=True)

    LOAN_STATUS = (
        ('m', 'Maintenance'),
        ('o', 'On loan'),
        ('a', 'Available'),
        ('r', 'Reserved'),
    )

    status = models.CharField(
        max_length=1,
        choices=LOAN_STATUS,
        blank=True,
        default='m',
        help_text='Book availability',
    )

    class Meta:
        ordering = ['due_back']

    def __str__(self):
        """String for representing the Model object."""
        return f'{self.id} ({self.book.title})'

نصرّح إضافةً إلى ذلك عن بعض الأنواع الجديدة من الحقول وهي:

  • يُستخدَم النوع UUIDField للحقل id لضبطه بوصفه مفتاحًا رئيسيًا primary_key لهذا النموذج. يخصِّص هذا النوع من الحقول قيمةً فريدةً عامة لكل نسخة (قيمة لكل كتاب تجده في المكتبة).

  • يُستخدَم النوع DateField للتاريخ due_back، أي التاريخ المتوقع ليصبح فيه الكتاب متاحًا بعد استعارته أو صيانته، إذ يمكن أن تكون القيمة blank أو null (مطلوبة عندما يكون الكتاب متاحًا). تستخدم بيانات النموذج الوصفية (Class Meta) هذا الحقل لترتيب السجلات عند إعادتها ضمن استعلام.

  • الحقل status من النوع CharField الذي يعرّف قائمة الاختيار أو الخيارات. نعرّف كما ترى صفًا يحتوي على صفوف من أزواج مفتاح-قيمة ونمرّرها إلى وسيط الاختيارات. تُعَد القيمة في زوج مفتاح/قيمة قيمة عرض display value يمكن للمستخدم تحديدها، بينما تكون المفاتيح هي القيم المحفوظة عند تحديد الخيار. ضبطنا قيمة افتراضية هي "m" (للصيانة Maintenance) عند إنشاء الكتب في البداية إذ تكون غير متوفرة قبل تخزينها على الرفوف.

يمثل التابع ‎__str__()‎ كائن BookInstance باستخدام مجموعة من المعرّف الفريد وعنوان الكتاب Book المرتبط به.

ملاحظة: إليك بعض المعلومات عن لغة بايثون:

  • يمكنك بدءًا من الإصدار 3.6 للغة بايثون استخدام صيغة توليد السلاسل النصية (المعروفة أيضًا باسم f-strings) بالشكل التالي:
f'{self.id} ({self.book.title})'

النموذج Author

انسخ نموذج Author التالي بعد الشيفرة البرمجية الموجودة في الملف models.py:

class Author(models.Model):
    """Model representing an author."""
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    date_of_birth = models.DateField(null=True, blank=True)
    date_of_death = models.DateField('Died', null=True, blank=True)

    class Meta:
        ordering = ['last_name', 'first_name']

    def get_absolute_url(self):
        """Returns the URL to access a particular author instance."""
        return reverse('author-detail', args=[str(self.id)])

    def __str__(self):
        """String for representing the Model object."""
        return f'{self.last_name}, {self.first_name}'

يجب أن تكون جميع الحقول والتوابع مألوفةً الآن، إذ يعرِّف هذا النموذج مؤلفًا يحمل اسمًا أول واسم عائلة وتاريخ ميلاد ووفاة (كلاهما اختياري)، ويحدّد أنّ التابع ‎__str__()‎ افتراضيًا يعيد الاسم بالترتيب: اسم العائلة last name ثم الاسم الأول firstname. يعكس التابع get_absolute_url()‎ ربط عنوان URL لتفاصيل المؤلف author-detail للحصول على عنوان URL لعرض مؤلف واحد.

إعادة تشغيل عمليات تهجير قاعدة البيانات

أنشأتَ حتى الآن جميع نماذجك، لذا أعِد تشغيل عمليات تهجير قاعدة البيانات لإضافتها إلى قاعدة بياناتك كما يلي:

python3 manage.py makemigrations
python3 manage.py migrate

النموذج Language- تحدي

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

إليك بعض الأشياء التي يجب مراعاتها:

  • هل يجب ربط اللغة بالنموذج Book أو BookInstance أو أيّ كائن آخر؟
  • هل يجب تمثيل اللغات المختلفة باستخدام نموذج، أم حقل نص حر، أم قائمة اختيار ثابتة؟

أضف الحقل بعد أن تقرر ما تريده، ويمكنك أن ترى ما قررناه على GitHub.

لا تنسَ أنه يجب إعادة تشغيل عمليات تهجير قاعدة البيانات مرة أخرى لإضافة التغييرات بعد إجراء تغيير على نموذجك.

python3 manage.py makemigrations
python3 manage.py migrate

الخلاصة

تعلمنا في هذا المقال كيفية تعريف النماذج، ثم استخدمنا هذه المعلومات لتصميم وتقديم النماذج المناسبة لموقع المكتبة المحلية LocalLibrary.

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

ترجمة -وبتصرُّف- للمقال Django Tutorial Part 3: Using models.

اقرأ أيضًا


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

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



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

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

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

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   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.


×
×
  • أضف...