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

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

هذه المقالة جزء من سلسلة من المقالات تشرح جانغو للمبتدئين على النحو التالي:

  • الجزء الأول: البدء في إنشاء مدونة بسيطة
  • الجزء الثاني: استخدام بنية MTV لإنشاء مدونة بسيطة
  • الجزء الثالث: استخدام عمليات CRUD لإدارة المدونة
  • الجزء الرابع: تطبيق المدونة الكامل
  • الجزء الخامس: إضافة بعض الميزات المتقدمة إلى تطبيق المدونة

مفهوم النماذج Models

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

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

يوضح المثال التالي نموذجًا في الملف blog/models.py:

from django.db import models

class Person(models.Model):
   first_name = models.CharField(max_length=30)
   last_name = models.CharField(max_length=30)

يمكنك بعد ذلك توليد ملف التهجير باستخدام الأمر التالي:

python manage.py makemigrations

ويجب أن يبدو ملف التهجير الناتج blog/migrations/0001_initial.py كما يلي:

# Generated by Django 4.1.2 on 2022-10-19 23:13

from django.db import migrations, models

class Migration(migrations.Migration):

   initial = True

   dependencies = []

   operations = [
       migrations.CreateModel(
           name="Person",
           fields=[
               (
                   "id",
                   models.BigAutoField(
                       auto_created=True,
                       primary_key=True,
                       serialize=False,
                       verbose_name="ID",
                   ),
               ),
               ("first_name", models.CharField(max_length=30)),
               ("last_name", models.CharField(max_length=30)),
           ],
       ),
   ]

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

python manage.py migrate

يجب أن تبدو قاعدة بياناتك db.sqlite3 كما يلي:

01 db blog person

لا تحاول تعديل أو حذف ملفات التهجير إذا كنت مبتدئًا، ودع جانغو يطبّق كل شيء نيابةً عنك إلّا إن كنت تعرف ما تفعله تمامًا. يمكن لجانغو في معظم الحالات اكتشاف التغييرات التي أجريتها في النماذج حتى إذا حذفتَ شيئًا وتوليد ملفات الترحيل وفقًا لذلك.

سينشئ النموذج في مثالنا جدول قاعدة بيانات person، وسيحتوي هذا الجدول على ثلاثة أعمدة اسمها id و first_name و last_name، حيث يُنشأ العمود id تلقائيًا كما هو موضح في ملف التهجير، ويُستخدم هذا العمود كمفتاح رئيسي Primary Key للفهرسة Indexing افتراضيًا.

يسمَّىCharField()‎ بنوع الحقل ويعرِّف نوع العمود، ويسمَّى max_length بخيار الحقل ويحدّد معلومات إضافية حول هذا العمود.

أهم نواع حقول النموذج وخياراتها

يوضّح الجدول التالي بعض أنواع الحقول الأكثر استخدامًا وننصحك بالاطلاع على جميع أنواع الحقول وخياراتها من توثيق جانغو الرسمي.

نوع الحقل وصف عنه
BigAutoField ينشئ عمودًا نوعه عدد صحيح يتزايد تلقائيًا، يُستخدم عادةً مع العمود id.
BooleanField ينشئ عمودًا قيمه منطقية True أو False.
DateField و DateTimeField يُستخدم لإضافة تواريخ وأوقات كما يوحي اسمه.
FileField و ImageField ينشئ عمودًا لتخزين المسار الذي يؤشّر إلى الملف أو الصورة المرفوعة.
IntegerField و BigIntegerField تتراوح قيم الأعداد الصحيحة Integer من ‎-2147483648 إلى 2147483647، وتتراوح قيم الأعداد الصحيحة الكبيرة Big Integer من ‎-9223372036854775808 إلى 9223372036854775807
SlugField الاسم المختصر Slug هو نسخة بسيطة من عنوان URL للاسم أو العنوان.
CharField و TextField ينشئ كل من CharField و TextField عمودًا لتخزين السلاسل النصية Strings، ولكن يقابل TextField مربع نص أكبر في صفحة مدير جانغو Django Admin التي سنتحدث عنها لاحقًا في هذه السلسلة من المقالات.

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

خيار الحقل وصف عنه
blank يسمح للحقل بأن يحتوي على إدخال فارغ.
choices يمنح الحقل خيارات متعددة، حيث سنوضّح ذلك لاحقًا عندما نصل إلى مدير جانغو.
default يعطي الحقل قيمةً افتراضية.
unique يتأكد من أن كلّ عنصر في العمود فريد، ويُستخدَم عادةً لتحديد الاسم المختصر Slug والحقول الأخرى التي يُفترَض أن تحتوي على قيم فريدة.

خيارات الصنف Meta

يمكنك أيضًا إضافة الصنف Class الذي هو Meta في صنف النموذج، والذي يحتوي على معلومات إضافية حول هذا النموذج مثل اسم جدول قاعدة البيانات وخيارات الترتيب والأسماء المفردة وجمعها التي يمكن أن يقرأها الإنسان كما يلي:

class Category(models.Model):
   priority = models.IntegerField()

   class Meta:
       ordering = ["priority"]
       verbose_name_plural = "categories"

توابع النموذج

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

class Person(models.Model):
   first_name = models.CharField(max_length=50)
   last_name = models.CharField(max_length=50)
   birth_date = models.DateField()

   def baby_boomer_status(self):
       "Returns the person's baby-boomer status."
       import datetime
       if self.birth_date < datetime.date(1945, 8, 1):
           return "Pre-boomer"
       elif self.birth_date < datetime.date(1965, 1, 1):
           return "Baby boomer"
       else:
           return "Post-boomer"

في الكود السابق، يفحص جانغو تاريخ ميلاد الشخص ويعيد حالة تمثل إن كانت فترة ولادته قبل زيادة المواليد التي حدثت بعد الحرب العالمية الثانية Pre-boomer، أم خلالها Baby-Boomer أم بعدها Post-boomer للشخص عند استدعاء التابع baby_boomer_status()‎.

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

وراثة النماذج

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

class CommonInfo(models.Model):
   name = models.CharField(max_length=100)
   age = models.PositiveIntegerField()

   class Meta:
       abstract = True

class Student(CommonInfo):
   home_group = models.CharField(max_length=5)

لاحظ أن النموذج CommonInfo نموذج مجرد Abstract، مما يعني أنه لا يقابل نموذجًا فرديًا فعليًا، بل يستخدم كأب لنماذج أخرى.

لنولّد الآن ملف تهجير جديد blog/migrations/0002_student.py باستخدام الأمر التالي للتحقق من ذلك:

python manage.py makemigrations

سيتولّد ملف التهجير التالي:

# Generated by Django 4.1.2 on 2022-10-19 23:28

from django.db import migrations, models

class Migration(migrations.Migration):

   dependencies = [
       ("blog", "0001_initial"),
   ]

   operations = [
       migrations.CreateModel(
           name="Student",
           fields=[
               (
                   "id",
                   models.BigAutoField(
                       auto_created=True,
                       primary_key=True,
                       serialize=False,
                       verbose_name="ID",
                   ),
               ),
               ("name", models.CharField(max_length=100)),
               ("age", models.PositiveIntegerField()),
               ("home_group", models.CharField(max_length=5)),
           ],
           options={
               "abstract": False,
           },
       ),
   ]

لاحظ إنشاء الجدول Student فقط.

علاقات قاعدة البيانات

تحدثنا عن كيفية إنشاء جداول فردية، ولكن لا تكون هذه الجداول مستقلة تمامًا في معظم التطبيقات، بل توجد علاقات بينها، فمثلًا قد يكون لديك فئة Category تنتمي إليها منشورات متعددة، ويكون كل منشور في المدونة تابع لمستخدم معين وما إلى ذلك، لذا توجد ثلاثة أنواع أساسية للتعبير عن العلاقات بين جداول قاعدة البيانات وهي: علاقة واحد إلى واحد One-to-one وعلاقة متعدد إلى واحد Many-to-one وعلاقة متعدد إلى متعدد Many-to-many وسنشرح تاليًا المزيد حول هذه العلاقات وطريقة وصفها ضمن نماذج جانغو.

علاقة واحد إلى واحد

علاقة واحد إلى واحد هي العلاقة الأسهل، فمثلًا يمكن أن يكون لكل شخص هاتف واحد، ويمكن أن يعود كل هاتف إلى شخص واحد، حيث يمكننا وصف هذه العلاقة في النماذج كما يلي:

class Person(models.Model):
   name = models.CharField(max_length=100)

class Phone(models.Model):
   person = models.OneToOneField('Person', on_delete=models.CASCADE)

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

يمكنك الآن إنشاء عمليات تهجير وتطبيقها لهذه النماذج ومعرفة ما يحدث، وإذا واجهتك مشاكل أثناء تشغيل الأوامر التالية، فاحذف الملف db.sqlite3 وملفات التهجير للبدء من جديد:

python manage.py makemigrations
python manage.py migrate

02 one to one

لاحظ أن نوع الحقل OneToOneField أنشأ العمود person_id في الجدول blog_phone، وسيخزّن هذا العمود معرّف id الشخص الذي يمتلك هذا الهاتف.

علاقة متعدد إلى واحد

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

class Category(models.Model):
   name = models.CharField(max_length=100)

class Post(models.Model):
   category = models.ForeignKey('Category', on_delete=models.CASCADE)

ينشئ نوع الحقل ForeignKey العمود category_id في الجدول blog_post، والذي يخزن معرّف id الفئة التي ينتمي إليها هذا المنشور.

علاقة متعدد إلى متعدد

تكون علاقة متعدد إلى متعدد أكثر تعقيدًا بعض الشيء، فمثلًا يمكن أن يكون لكل مقال وسوم Tags متعددة، ويمكن أن يكون لكل وسم مقالات متعددة:

class Tag(models.Model):
   name = models.CharField(max_length=100)

class Post(models.Model):
   tags = models.ManyToManyField('Tag')

ستنشئ الشيفرة البرمجية السابقة جدولًا جديدًا بالاسم post_tags بدلًا من إنشاء عمود جديد، وسيحتوي هذا الجدول الجديد على عمودين هما post_id و tag_id، مما يتيح لك تحديد جميع الوسوم المرتبطة بمنشور معين والعكس صحيح.

لنفترض أن لدينا الجدول التالي مثلًا لتوضيح الأمور:

post_id tag_id
1 1
2 1
3 2
1 2
2 3

يمتلك المنشور الذي له المعرّف id=1 وسمين tags لهما المعرّف id=1 والمعرّف id=2. إذا أردنا عكس الأمور للعثور على المنشورات باستخدام الوسم، فيمكننا أن نرى منشورين لهما المعرّف id=3 والمعرّف id=1 بالنسبة للوسم ذي المعرّف id=2.

طبقة العرض View

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

لا تعمل دالة العرض View Function على استرداد البيانات فقط، إذ توجد أربع عمليات أساسية يمكن تطبيقها على البيانات في معظم تطبيقات الويب، وهي الإنشاء Create والقراءة Read والتحديث Update والحذف Delete، ويشار إلى هذه العمليات مجتمعة بالاسم CRUD، والتي سنوضّحها في مقال لاحق.

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

لنفترض أن لدينا النموذج blog/models.py التالي:

class Category(models.Model):
   name = models.CharField(max_length=100)

class Tag(models.Model):
   name = models.CharField(max_length=200)

class Post(models.Model):
   title = models.CharField(max_length=255)
   content = models.TextField()
   pub_date = models.DateField()
   category = models.ForeignKey(Category, on_delete=models.CASCADE)
   tags = models.ManyToManyField(Tag)

يمكنك معالجة البيانات بمساعدة QuerySet من خلال هذا النموذج ضمن دوال العرض التي توجد في الملف blog/views.py.

إنشاء البيانات وحفظها

لنفترض أنك تريد إنشاء فئة جديدة كما يلي:

# استيراد نموذج الفئة‫ Category
from blog.models import Category

# إنشاء نسخة جديدة من النموذج‫ Category
category = Category(name="New Category")

# حفظ الفئة التي أنشأناها في قاعدة البيانات
category.save()

يجب أن يكون ما سبق سهلًا إذا كنت على دراية بمفهوم البرمجة كائنية التوجه، حيث أنشأنا في المثال السابق نسخة جديدة من الكائن Category واستخدمنا التابع save()‎ الذي ينتمي إلى هذا الكائن لحفظ المعلومات في قاعدة البيانات.

توجد علاقة متعدد إلى واحد بين الفئة والمنشور، وقد عرفناها باستخدام الحقل ForeignKey()‎، مع افتراض أن لدينا عدد كافي من السجلات في قاعدة البيانات.

from blog.models import Category, Post

# ‫Post.objects.get(pk=1)‎ هي الطريقة التي نسترجع بها المنشور الذي له المفتاح الرئيسي pk=1، 
# حيث يرمز‫ pk إلى المفتاح الرئيسي Primary Key الذي يمثّل المعرّف id عادةً إن لم نحدّد خلاف ذلك.
post = Post.objects.get(pk=1)

# استرداد الفئة التي اسمها‫ "New Category"
new_category = Category.objects.get(name="New Category")

# إسناد المتغير‫ new_category إلى حقل فئة المنشور وحفظه
post.category = new_category
post.save()

توجد أيضًا علاقة متعدد إلى متعدد بين المنشورات والوسوم كما يلي:

from blog.models import Tag, Post

post1 = Post.objects.get(pk=1) # استرداد المنشور 1

tag1 = Tag.objects.get(pk=1) # استرداد الوسم 1
tag2 = Tag.objects.get(pk=2) # استرداد الوسم 2
tag3 = Tag.objects.get(pk=3) # استرداد الوسم 3
tag4 = Tag.objects.get(pk=4) # استرداد الوسم 4
tag5 = Tag.objects.get(pk=5) # استرداد الوسم 5

post.tags.add(tag1, tag2, tag3, tag4, tag5) # إضافة الوسوم من 1 إلى 5 إلى المنشور 1

استرداد البيانات

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

توابع QuerySet

تتيح توابع QuerySet استرداد البيانات بناءً على معايير معينة، ويمكن الوصول إليها باستخدام السمة Attribute التي هي objects، حيث يُستخدَم التابع get()‎ الذي رأيناه سابقًا لاسترداد سجل معين كما يلي:

first_tag = Tag.objects.get(pk=1)
new_category = Category.objects.get(name="New Category")

ويمكن أيضًا استرداد جميع السجلات باستخدام التابع all()‎:

Post.objects.all()

يعيد التابع all()‎ مجموعة من السجلات والتي نسميها مجموعة الاستعلام QuerySet، ويمكنك تحسين هذه المجموعة من خلال سَلسَلة التابع filter()‎ أو التابع exclude()‎ مع التابع all()‎ كما يلي:

Post.objects.all().filter(pub_date__year=2024)

سيؤدي ذلك إلى إعادة جميع المنشورات المنشورة في عام 2024، ويُسمَّى pub_date__year بوسيط البحث في الحقول Field Lookup، وسنناقش هذا الموضوع بالتفصيل لاحقًا.

يمكننا أيضًا استبعاد المنشورات المنشورة عام 2024 كما يلي:

Post.objects.all().exclude(pub_date__year=2024)

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

وسطاء عمليات البحث في الحقول Field Lookups

وسطاء عمليات البحث في الحقول هي وسطاء الكلمات المفتاحية للتوابع get()‎ و filter()‎ و exclude()‎، والتي تعمل بطريقة مشابهة لتعليمة WHERE  في لغة SQL، وتأخذ الصيغة fieldname__lookuptype=value مع وجود شرطة سفلية مزدوجة.

Post.objects.all().filter(pub_date__lte='2024-01-01')

pub_date هو اسم الحقل و lte هو نوع البحث الذي يرمز إلى أقل من أو يساوي، وستُعاد جميع المنشورات التي يكون فيها تاريخ النشر pub_date أقل من أو يساوي ‎2024-01-01‎.

يمكن أيضًا استخدام وسطاء عمليات البحث في الحقول للعثور على السجلات التي لها علاقة بالسجل الحالي كما في المثال التالي، وستُعاد جميع المنشورات التي تنتمي إلى الفئة التي اسمها "Django":

Post.objects.filter(category__name='Django')

يمكننا تطبيق ذلك عكسيًا مثل إعادة جميع الفئات التي تحتوي على منشورٍ واحد على الأقل الذي يحتوي عنوانه على الكلمة "Django" كما يلي:

Category.objects.filter(post__title__contains='Django')

يمكننا أيضًا المرور عبر علاقات متعددة كما يلي:

Category.objects.filter(post__author__name='Admin')

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

حذف الكائنات

نستخدم التابع delete()‎ لحذف سجلٍ ما، حيث ستحذف الشيفرة البرمجية التالية المنشور الذي له المفتاح الرئيسي pk=1:

post = Post.objects.get(pk=1)
post.delete()

ويمكننا أيضًا استخدامه لحذف سجلات متعددة معًا كما يلي:

Post.objects.filter(pub_date__year=2022).delete()

سيؤدي ذلك إلى حذف جميع المنشورات في عام 2022، ولكن قد يتعلّق السجل الذي نحذفه بسجل آخر مثل محاولة حذف فئة تحتوي على منشورات متعددة كما يلي:

category = Category.objects.get(pk=1)
category.delete()

يحاكي جانغو سلوك قيد SQL التالي ON DELETE CASCADE، مما يعني حذف جميع المنشورات التي تنتمي إلى هذه الفئة أيضًا، ولكن إذا أردتَ تغيير ذلك، فيمكنك تغيير خيار on_delete إلى شيء آخر، لذا اطّلع على جميع خيارات on_delete المتاحة في توثيق جانغو الرسمي.

دالة العرض View Function

وضّحنا ما يمكن فعله داخل دالة العرض، وسنوضّح كيف تبدو دالة العرض الكاملة كما في المثال التالي، حيث تُعرَّف جميع العروض ضمن الملف views.py:

from django.shortcuts import render
from blog.models import Post

# أنشئ عروضك الخاصة هنا
def my_view(request):
   posts = Post.objects.all()

   return render(request, 'blog/index.html', {
       'posts': posts,
   })

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

يحتوي request على الكثير من المعلومات حول طلب HTTP الحالي، فمثلًا يمكننا الوصول إلى تابع طلب HTTP وكتابة شيفرات برمجية مختلفة لتوابع مختلفة كما يلي:

if request.method == 'GET':
   do_something()
elif request.method == 'POST':
   do_something_else()

ملاحظة: اطّلع على جميع المعلومات التي يمكنك الوصول إليها من كائن request في توثيق جانغو الرسمي.

والشيء الآخر الذي يجب الانتباه إليه هو استيراد اختصار Shortcut اسمه render()‎، ثم استخدامه لتمرير المتغير posts إلى القالب blog/index.html. يُطلق عليه اختصار لأنه من المفترض تحميل القالب افتراضيًا باستخدام التابع loader()‎ وعرض هذا القالب مع البيانات المُسترَدة وإعادة كائن HttpResponse، ولكن بسّط جانغو هذه العملية باستخدام الاختصار render()‎. لن نتحدث عن الطريقة المعقدة لذلك، لأننا لن نستخدمها في هذا المقال ويمكنك مطالعة جميع دوال الاختصار Shortcut Functions في توثيق جانغو الرسمي.

نظام قوالب جانغو

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

عمليات الضبط Configurations

يجب أولًا تغيير شيء ما في الملف settings.py، إذ يجب أن تخبر جانغو بمكان وضع ملفات القوالب، لذا لننشئ المجلد templates، حيث اخترنا وضعه ضمن المجلد الجذر للمشروع، ولكن يمكنك نقله لمكان آخر.

.
├── blog
├── db.sqlite3
├── djangoBlog
├── env
├── manage.py
├── mediafiles
├── staticfiles
└── templates

انتقل إلى الملف settings.py وابحث عن TEMPLATES.

TEMPLATES = [
   {
       'BACKEND': 'django.template.backends.django.DjangoTemplates',
       'DIRS': [
           'templates',
       ],
       'APP_DIRS': True,
       'OPTIONS': {
           'context_processors': [
               'django.template.context_processors.debug',
               'django.template.context_processors.request',
               'django.contrib.auth.context_processors.auth',
               'django.contrib.messages.context_processors.messages',
           ],
       },
   },
]

لنعدّل الخيار DIRS الذي يؤشر إلى المجلد templates، ولنتحقق الآن من عمل هذا الإعداد، لذا ننشئ نمط عنوان URL جديد يؤشّر إلى العرض test()‎ في الملف djangoBlog/urls.py كما يلي:

from django.urls import path
from blog import views

urlpatterns = [
   path('test/', views.test),
]

ولننشئ الآن العرض test()‎ في الملف blog/views.py كما يلي:

def test(request):
   return render(request, 'test.html')

انتقل إلى المجلد templates وأنشئ القالب test.html كما يلي:

<!doctype html>
<html lang="en">
 <head>
   <meta charset="UTF-8" />
   <meta http-equiv="X-UA-Compatible" content="IE=edge" />
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
   <title>Test Page</title>
 </head>
 <body>
   <p>This is a test page.</p>
 </body>
</html>

شغّل خادم التطوير وانتقل إلى العنوان http://127.0.0.1:8000/‎، وستظهر الصفحة التالية:

03 django template

لغة قوالب جانغو

لنناقش الآن محرّك قوالب جانغو بالتفصيل، وتذكّر أنه يمكننا إرسال البيانات من العرض إلى القالب كما يلي:

def test(request):
   return render(request, 'test.html', {
       'name': 'Jack'
   })

تُسنَد السلسلة النصية 'Jack' إلى المتغير name وتمرَّر إلى القالب، ويمكننا عرض المتغير name ضمن القالب باستخدام أقواس معقوصة مزدوجة {{ }} كما يلي:

<p>Hello, {{ name }}</p>

حدّث المتصفح، وسترى النتيجة التالية:

04 display data

ولكن لا تكون البيانات المُمرَّرة إلى القالب سلسلة نصية بسيطة في أغلب الحالات كما في المثال التالي:

def test(request):
   post = Post.objects.get(pk=1)

   return render(request, 'test.html', {
       'post': post
   })

المتغير post في المثال السابق هو قاموس Dictionary، حيث يمكنك الوصول إلى العناصر الموجودة في هذا القاموس في القالب كما يلي:

{{ post.title }}
{{ post.content }}
{{ post.pub_date }}

المرشحات Filters

تحوّل المرشحات قيم المتغيرات، فمثلًا إذا كان لدينا المتغير django الذي قيمته 'the web framework for perfectionists with deadlines' ووضعنا المرشح title مع هذا المتغير كما يلي:

{{ django|title }}

فسيُحوَّل القالب إلى ما يلي:

The Web Framework For Perfectionists With Deadlines

اطّلع على جميع المرشحات المُضمَّنة في جانغو من توثيق جانغو الرسمي.

الوسوم Tags

تضيف الوسوم ميزات لغات البرمجة مثل التحكم في التدفق والحلقات إلى شيفرة HTML، مما يوفر الكثير من الوقت والموارد، إذ لن نضطر إلى كتابة الشيفرة البرمجية نفسها مرارًا وتكرارًا. تُعرَّف جميع الوسوم باستخدام {% %} مثل حلقة for التالية:

<ul>
   {% for athlete in athlete_list %}
       <li>{{ athlete.name }}</li>
   {% endfor %}
</ul>

وتكون تعليمة if كما يلي:

{% if somevar == "x" %}
 This appears if variable somevar equals the string "x"
{% endif %}

وتكون تعليمة if-else كما في المثال التالي:

{% if athlete_list %}
   Number of athletes: {{ athlete_list|length }}
{% elif athlete_in_locker_room_list %}
   Athletes should be out of the locker room soon!
{% else %}
   No athletes.
{% endif %}

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

نظام الوراثة Inheritance

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

لننشئ الملف layout.html في المجلد templates، ويمثّل هذا الملف المكان الذي نعرّف فيه تخطيط القالب الخاص بنا كما يلي، ولكننا لم نضع الشيفرة البرمجية الخاصة بالتذييل وشريط التنقل لتسهيل القراءة:

<!DOCTYPE html>
<html>
<head>
   {% block meta %} {% endblock %}
   ‏<-- ‫استورد شيفرة CSS هنا –!>
</head>
<body>

<div class="container">
   <!-- ضع شيفرة شريط التنقل هنا -->

   {% block content %} {% endblock %}

   <!-- ضع شيفرة تذييل الصفحة هنا -->
</div>
</body>
</html>

لاحظ أننا عرّفنا كتلتين في هذا الملف هما meta و content باستخدام الوسم ‎{% block ... %}‎، وعرّفنا القالب home.html التالي لاستخدام هذا التخطيط:

{% extends 'layout.html' %}

{% block meta %}
   <title>Page Title</title>
   <meta charset="UTF-8">
   <meta name="description" content="Free Web tutorials">
   <meta name="keywords" content="HTML, CSS, JavaScript">
   <meta name="author" content="John Doe">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
{% endblock %}

{% block content %}
<p>This is the content section.</p>
   {% include 'vendor/sidebar.html' %}
{% endblock %}

سيجد جانغو أولًا الملف layout.html عند استدعاء القالب السابق، ويملأ الكتلتين meta و content بالمعلومات الموجودة في صفحة home.html.

لاحظ وجود شيء آخر في هذا القالب، حيث تخبر التعليمة ‎{% include 'vendor/sidebar.html' %}‎ جانغو بالبحث عن القالب templates/vendor/sidebar.html ووضعه في الصفحة، حيث يتضمن القالب sidebar.html المحتويات التالية مثلًا:

<p>This is the sidebar.</p>

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

from django.shortcuts import render

def home(request):
   return render(request, 'home.html')

وتأكّد من أن موجّه إرسال عنوان URL الخاص بك يؤشّر إلى هذا العرض كما يلي:

path('home/', views.home),

افتح متصفحك وانتقل إلى العنوان http://127.0.0.1:8000/home، ويجب أن ترى الصفحة التالية:

05 template layout

ترجمة -وبتصرّف- للمقال Django for Beginners #2 - The MTV Structure لصاحبه Eric Hu.

اقرأ أيضًا


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

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

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



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

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

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

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


×
×
  • أضف...