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

النماذج Models والاستعلام عن البيانات في Django


محمد طاهر5

تحدثنا في الدرسين السابقين عن كيفية تنصيب وإعداد إطار العمل Django على أنظمة التشغيل المختلفة، وتعرفنا كذلك على مفهومي المشروع والتطبيق، وقمنا بكتابة العرض الأول وتعرفنا بشكل مختصر على المساراتUrls.

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

django-3.png

النماذج Models

أشرنا في الدروس السابقة إلى أن إطار العمل Django يتبع مبدأ العمل MVT، ويشير الحرف M هنا إلى النماذج Models، ويمكن تعريف النماذج بشكل مبسط على أنها وصف للبيانات الموجودة في قاعدة البيانات باستخدام لغة Python، وبمعنى آخر تمثّل النماذج في Django بنية قاعدة البيانات، أي ما ستحصل عليه من إجراء الأمر CREATE TABLE ولكن بلغة Python بدلًا من SQL.

يستخدم Django النماذج في التواصل مع قواعد البيانات من خلال تنفيذ أوامر SQL خلف الكواليس وتقديم النتائج التي ترد من هذه الأوامر على هيئة بنية من البيانات تمثل الصفوف في جداول قاعدة البيانات.

ولهذا الأسلوب في التعامل مع قواعد البيانات بعض الفوائد، فكتابة النماذج بلغة Python يزيد في الانتاجية، إذ لن يكون المبرمج مضطرًا إلى اتباع قواعد لغة أخرى أثناء العمل على التطبيق ويكون العمل مقتصرًا على لغة python، كما يسهل هذا الأسلوب متابعة النماذج وتتبع التغييرات الحاصلة عليها من خلال أنظمة تتبع الإصدارات كـ Git وغيرها، إضافة إلى ذلك تقدم نماذج Python بعض أنواع البيانات غير المتوفرة في SQL كالبريد الإلكتروني وعناوين URL وغيرها.

ربط Django مع قواعد البيانات

يستطيع Django التعامل مع أنواع مختلفة من أنظمة قواعد البيانات، ومن أشهرها SQLite، MySQL، PostgreSQL و Oracle. وفي مشروعنا هذا سنستخدم قواعد بيانات SQLite والتي تعتبر الخيار الأبسط والأسهل في حالة المشاريع البسيطة والصغيرة ويتم تثبيتها عند تثبيت Python وهذا يعني عدم الحاجة إلى تثبيت أي حزم إضافية، كما أنها ليست بحاجة إلى خادوم خاص تعمل من خلاله.
يمكن اختيار نوع قاعدة البيانات التي سيعمل عليها المشروع من خلال ملف الإعدادات settings.py، وذلك ضمن القاموس DATABASES.

يستخدم Django قواعد بيانات SQLite بشكل افتراضي، وستجد قاموس DATABASES بالشكل التالي:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

يتطلب ربط Django بقواعد البيانات الأخرى كـ MySQL و PostgreSQL تثبيت حزم الربط الخاصة بكل نوع منها، وإضافة العناصر ‘USER’، ‘PASSWORD’، و’HOST’ إلى القاموس، ويمكن التعرف إلى الحزم المطلوبة لكل نوع من أنواع قواعد البيانات في توثيقات Django.

إنشاء النموذج الأول

سيعتمد تطبيق الاقتراعات على نموذجين فقط هما نموذج الأسئلة ونموذج الإجابات، وسيتضمن النموذج الأول السؤال وتاريخ نشره، أما النموذج الثاني فسيتضمن الإجابات ومجموع الأصوات على كل إجابة، وسترتبط كل إجابة بسؤال معين.
افتح ملف models.py في محرر النصوص المفضل لديك، ثم أضف الأسطر التالية:

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')


class Choice(models.Model):
    question = models.ForeignKey(Question)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

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

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

تمتلك بعض أنواع الصنف Field عددًا من المعاملات الإلزامية، كما هو الحال مع CharField والذي يتطلب تحديد العدد الأقصى للحروف من خلال max_length. وبطبيعة الحالة تمتلك هذه الأصناف بعض المعاملات الاختيارية، كما هو الحال مع IntegerField الذي أضفنا إليه قيمة افتراضية مساوية للصفر من خلال default=0.

يتم تمثيل كل حقل من حقول قاعدة البيانات بـ instance للصنف Field، مثل CharField لحقول الحروف، و DateTimeField لحقول التاريخ والوقت.

يقدّم Django عددًا كبيرًا ومتنوعًا من أنواع الحقول، نورد فيما يلي بعضًا منها:

الحقل الوظيفة
BooleanField يتضمن قيمتي True و False، وعادة ما يكون مرتبطًا بمربع الاختيار Checkbox.
CharField يستخدم هذا الحقل لإضافة السلاسل النصية Strings القصيرة والمتوسطة الحجم.
DateField يستخدم للتعبير عن التاريخ.
DateTimeField يستخدم للتعبير عن التاريخ الوقت.
DecimalField يتيح استخدام الأرقام العشرية التي تحتوي على فواصل.
EmailField عبارة عن حقل حرفي `CharField` ولكنه قادر على التحقق من أن القيمة المدخلة تمتلك صيغة بريد إلكتروني سليمة.
FileField يستخدم هذا الحقل لاحتواء الملفات المرفوعة.
IntegerField يتيح استخدام الأرقام الصحيحة، ويمكن استخدام الأرقام من -2147483648 إلى 2147483647 بشكل آمن مع أنظمة قواعد البيانات المختلفة.
GenericIPAddressField عبارة عن عنوان IPv4 أو IPv6 على هيئة سلسلة نصية.
SlugField حقل Slug.
TextField يستخدم في لإضافة السلاسل النصية الطويلة.
TimeField يستخدم للتعبير عن الوقت.
URLField عبارة عن حقل حرفي يتضمن عنوان URL.

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

تستطيع الشيفرة السابقة القيام بأمور كثيرة، حيث يمكن لـ Django أن:

  • ينشئ جدولًا جديدًا خاصًّا بتطبيقنا هذا وذلك بتنفيذ العبارة CREATE TABLE.
  • إنشاء واجهة برمجية API خاصة يمكن من خلالها التعامل مع قاعدة البيانات التي تم إنشاؤها.

ولكن قبل أن يشرع Django بأداء هذه المهام، يجب علينا تثبيت تطبيق polls في مشروعنا وذلك من خلال إدراج اسمه ضمن قائمة INSTALLED_APPS في ملف الإعدادات settings.py، لذا توجه إلى هذا الملف وابحث عن القائمة التي تحمل الاسم INSTALLED_APPS، ثم عدل عناصرها لتصبح بالشكل التالي:

  • INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'polls',
    ]

     

توجّه الآن إلى مجلد المشروع عن طريق سطر الأوامر ونفّذ الأمر التالي:

python manage.py makemigrations polls

يفترض أن ترى النتيجة التالية في سطر الأوامر:

Migrations for 'polls':
  polls/migrations/0001_initial.py:
    - Create model Choice
    - Create model Question
    - Add field question to choice

وظيفة الأمر makemigrations هي إخبار Django بأنّك قد أجريت بعض التعديلات على النماذج وأنّك ترغب في حفظ هذه التعديلات على هيئة ملف تهجير يُحفظ على القرص الصلب كملف بايثون في مجلد migrations، تحت اسم: 0001_initial.py.

والآن نفّذ الأمر التالي في سطر الأوامر وذلك لتنفيذ التهجيرات وإنشاء الجداول ضمن قاعدة البيانات:

python manage.py migrate

يفترض أن تظهر النتيجة التالية في سطر الأوامر:

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, polls, sessions
Running migrations:
  Rendering model states... DONE
  Applying polls.0001_initial... OK

تتلخص وظيفة الأمر migrate في البحث عن التهجيرات غير المنفّذة (يتابع Django التهجيرات غير المنفّذة من خلال جدول خاص في قاعدة البيانات يحمل الاسم django_migrations) وإجرائها على قاعدة البيانات، سواء أكانت هذه التهجيرات تتضمن إنشاء جداول جديدة أو تعديل وتحديث جداول موجودة بالفعل.

إذًا يمكن تلخيص عملية إجراء التعديلات على النماذج بالخطوات الثلاثة التالية:

  1. إجراء التعديلات على النموذج في ملف models.py.
  2. تنفيذ الأمر python manage.py makemigrations لإنشاء التهجيرات المرتبطة بالتعديلات التي تم إجراؤها على النموذج.
  3. تنفيذ الأمر python manage.py migrate لتطبيق هذه التعديلات على قاعدة البيانات.

الواجهة البرمجية الخاصة بالتعامل مع قاعدة البيانات

ذكرنا سابقًا بأن لدى Django واجهة برمجية للتعامل مع قاعدة البيانات، وأفضل طريقة للتعرف على هذه الواجهة هي التعامل المباشر معها من خلال سطر الأوامر وعن طريق صدفة Python.

للولوج إلى الصدفة نفّذ الأمر التالي في سطر الأوامر:

python manage.py shell

أولًا يجب استيراد صنفي النموذج Question و Choice، كما سنحتاج إلى حزمة timezone من مكتبة django.utils للتعامل مع الوقت والتاريخ:

from polls.models import Question, Choice
from django.utils import timezone

يمكن الاستعلام عن جميع الأسئلة الموجودة في جدول Question من خلال الشيفرة:

Question.objects.all()

وباعتبار أن قاعدة البيانات الخاصة بتطبيقنا هذا خالية تمامًا من الأسئلة، فسنحصل على النتيجة:

<QuerySet []>

لإنشاء السؤال الأول سنقوم بإنشاء كائن من الصنف Question مع تعريف قيم المعاملات المستخدمة في هذا الصنف وإسناد هذا الكائن إلى متغير، وكما يلي:

q = Question(question_text="What's new?", pub_date=timezone.now())

يمكن الآن حفظ السؤال الجديد في قاعدة البيانات من خلال الشيفرة التالية:

q.save()

والآن، يمكن الوصول إلى جميع المعلومات الخاصة بهذا السؤال وبالشكل التالي:

#الحصول على نص السؤال
q.question_text
#الحصول على تاريخ نشر السؤال
q.pub_date
# تغيير نص السؤال
q.question_text = "What’s up?"
q.save()

بعد أن أضفنا سؤالًا إلى قاعدة البيانات، سنجري استعلامًا عن جميع الأسئلة في النموذج Question:

Question.objects.all()
#ستحصل على النتيجة التالية
<QuerySet [<Question: Question object>]>

نلاحظ أن النتيجة التي حصلنا عليها غريبة بعض الشيء، فالسؤال الذي أضفناه قبل قليل لم يظهر ضمن قائمة الأسئلة، ولحل هذه المشكلة سنحتاج إلى إضافة تابع __str()__ إلى كلا الصنفين، وبالشكل التالي:

from django.db import models
from django.utils.encoding import python_2_unicode_compatible

@python_2_unicode_compatible  # إن كنت بحاجة إلى استخدام الإصدار الثاني من بايثون
class Question(models.Model):
    # ...
    def __str__(self):
        return self.question_text

@python_2_unicode_compatible  # إن كنت بحاجة إلى استخدام الإصدار الثاني من بايثون
class Choice(models.Model):
    # ...
    def __str__(self):
        return self.choice_text

من الضروري استخدام التابع __str__() عند التعامل مع النماذج، لأن نتيجة هذا التابع ستظهر في لوحة التحكم التي يتم إنشائها آليًا من خلال هذه النماذج.

وكما قمنا باستخدام أحد التوابع المعرفة مسبقًا في Django فبمقدورنا أن نستخدم توابع مخصّصة تؤدي وظائف ومهام مختلفة، ولتوضيح ذلك، قم باستيراد مكتبة datetime الخاصة بـ Python، ثم أضف الشيفرة التالية في نهاية الصنف Question:

import datetime
# ...
class Question(models.Model):
    # ...
    def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)

سيقوم هذا التابع بالتحقق من أن السؤال قد تم نشره مؤخّرًا (منذ يوم) أو منذ فترة بعيدة (أكثر من يوم).

والآن يمكن استدعاء تابعنا المخصص الجديد بنفس الطريقة التي استدعينا من خلالها تابع الحفظ:

q.was_published_recently()

افتح الآن صدفة بايثون جديدة، واستورد الأصناف Question و Choice بنفس الطريقة السابقة، ثم نفذ شيفرة الاستعلام عن جميع الأسئلة، ولاحظ الفرق.

طرق الاستعلام عن البيانات في Django

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

يقدّم Django ثلاثة توابع خاصة بالصنف QuerySet هي: filter(), exclude(), get()، وجملة الاستعلام عبارة عن معاملات مفتاحية Keyword arguments لهذه التوابع، وتأخذ الصيغة التالية: الحقل__نوع الاستعلام = القيمة.

يقوم كل تابع من هذه التوابع الثلاثة بإنشاء كائن جديد للصنف QuerySet يتضمن النتائج التي تم
الحصول عليها بعد إجراء الاستعلام.

يقوم التابع filter() بإرجاع جميع العناصر المتوافقة مع الاستعلام، أما التابع exclude() فيرجع جميع العناصر غير المتوافقة مع الاستعلام، أما التابع get() فيرجع العنصر الذي يطابق جملة الاستعلام، وفي حالة العثور على أكثر من عنصر واحد يقوم هذا التابع بإطلاق الاستثناء MultipleObjectsReturned وإن لم يعثر على أي عنصر فسيطلق الاستثناء DoesNotExist.
إليك بعض الأمثلة التي توضح طريقة عمل هذه التوابع:

يمكن البحث عن السؤال الذي يبدأ بعبارة معينة بالشكل التالي:

Question.objects.filter(question_text__startswith='What')

ستترجم هذه الشيفرة إلى عبارة SQL التالية:

SELECT  WHERE question_text LIKE What%’;

ويمكن البحث عن سؤال معين من خلال المعرّف الخاص به، فللبحث عن السؤال ذي المعرف رقم 1، يمكن كتابة الشيفرة:

Question.objects.filter(id=1)

والتي تعادل عبارة SQL التالية:

SELECT  WHERE id = 1;

مثال على التابع exclude():

Question.objects.exclude(pub_date__gt=datetime.date(2016, 1, 3), question_text = "What’s up?")

تترجم هذه الشيفرة إلى عبارة SQL التالية:

SELECT  WHERE NOT (pub_date > 2016-1-3 AND question_text = Whats up?”)

يوضح الجدول التالي بعض أنواع الاستعلامات التي يقدّمها Django:

نوع الاستعلام الوظيفة
exact تطابق تام.
iexact تطابق تام مع تجاهل حالة الأحرف.
contains البحث عن كلمة مع أخذ حالة الأحرف بنظر الاعتبار.
icontains البحث عن كلمة مع تجاهل حالة الأحرف.
in البحث ضمن قائمة معينة.
gt أكبر من
gte أكبر من أو يساوي
lt أصغر من
lte أصغر من أو يساوي
startswith يبدأ بـ
istartswith يبدأ بـ مع أخذ حالة الأحرف بنظر الاعتبار.
endswith ينتهي بـ
iendswith ينتهي بـ مع أخذ حالة الأحرف بنظر الاعتبار.
range البحث ضمن مدى معين
date البحث عن تاريخ معين
year البحث عن سنة معينة
month البحث عن شهر معين
day البحث عن يوم معين
week_day البحث عن أسماء أيام الأسبوع
hour البحث عن ساعة
minute البحث عن دقيقة
second البحث عن ثانية

لنستعلم الآن عن السؤال الذي يحمل المفتاح الرئيسي primary key رقم 1 في قاعدة البيانات الخاصة بنا، وإسناد النتيجة إلى متغير:

q = Question.objects.get(pk=1)

يمكننا الآن الوصول إلى مجموعة الأجوبة choice_set والتي ينشئها Django تلقائيًا عند ربط الأسئلة بالأجوبة من خلال ForeignKey.

q.choice_set.all()

بطبيعة الحال، فإن مجموعة الأجوبة الخاصة بسؤالنا هذا فارغة، لذا سنقوم بإنشاء بعض الأجوبة وإضافتها إلى هذه المجموعة:

q.choice_set.create(choice_text = 'Not much', votes = 0)
q.choice_set.create(choice_text = 'The sky', votes = 0)
c = q.choice_set.create(choice_text = 'Just hacking again', votes = 0)

والآن يمكن التعرف على السؤال المرتبط بالجواب:

c.question

ويمكن معرفة عدد الأجوبة المتاحة لسؤال معين عن طريق التابع count():

q.choice_set.count()

ويمكن حذف إجابة معينة عن طريق التابع delete():

c.delete()

خاتمة

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

أما الدرس القادم فسيكون عن المسارات URLS وآلية عملها، وكيفية استخدام التعابير النظامية Regular Expressions في إنشائها.

المصدر:
توثيقات Django.


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

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



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

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

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

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


×
×
  • أضف...