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

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

أمّا SQLAlchemy، فهي أداةٌ في محرك قواعد البيانات SQL تؤمن وصولًا فعالًا وعالي الأداء إلى قواعد البيانات العلاقية، كما توفّر طرقًا للتخاطب مع العديد من محركات قواعد البيانات مثل SQLite، و MySQL، و PostgreSQ، مانحةً إمكانية الوصول إلى آليات SQL الخاصة بقواعد البيانات، كما توفّر مُخطِّط الكائنات العلاقية Object Relational Mapper -أو اختصارًا ORM- ليتيح إمكانية إنشاء الاستعلامات والتعامل مع البيانات باستخدام توابع وكائنات بسيطة في بايثون.

تُعدّ Flask-SQLAlchemy إضافة لفلاسك تسهّل من استخدام SQLAlchemy ضمنه وتؤمن الأدوات والوسائل المناسبة للتعامل مع قاعدة البيانات في تطبيقات فلاسك من خلال SQLAlchemy.

تُعرَّف علاقة قاعدة البيانات من النوع واحد-إلى-متعدد one-to-many بكونها علاقةً بين جدولين من قاعدة البيانات، بحيث يمكن لسجلٍ موجودٍ في أحد الجدولين الإشارة إلى عدّة سجلات من الجدول الثاني، ويشكّل تطبيق المدونة خير مثال على العلاقات في قواعد البيانات من النوع واحد-إلى-متعدد، نظرًا لارتباط الجدول الخاص بتخزين التدوينات posts مع الجدول الخاص بتخزين التعليقات comments بعلاقة من نوع واحد-إلى-متعدد؛ إذ يمكن أن يكون لكل تدوينة عدّة تعليقاتٍ مرتبطةٍ بها، وبالمقابل يرتبط كل تعليق بتدوينة واحدةٍ فقط، وبذلك فإنّ للتدوينة الواحدة علاقةٌ بالعديد من التعليقات. يُعد الجدول الخاص بالتدوينات في هذه الحالة الجدول الأب parent، في حين يُعد الجدول الخاص بالتعليقات الجدول الابن child؛ إذ يمكن للسجل الموجود في الجدول الأب الإشارة إلى عدّة سجلات موجودة في الجدول الابن، وهو أمرٌ أساسي لإتاحة إمكانية الوصول إلى البيانات المطلوبة في كل جدول.

سنعمل في هذا المقال على بناء مدونة ويب صغيرة لبيان كيفيّة إنشاء علاقة من النوع واحد-إلى-مُتعدّد باستخدام الإضافة Flask-SQLAlchemy، إذ سننشئ علاقةً بين التدوينات والتعليقات، فمن الممكن أن يكون لكل تدوينة عددًا من التعليقات.

مستلزمات العمل

قبل المتابعة في هذا المقال لا بدّ من:

الخطوة 1: تثبيت فلاسك والأداة Flask-SQLAlchemy

سنعمل في هذه الخطوة على تثبيت كل من فلاسك وكافّة الحزم اللازمة لعمل التطبيق.

لذا، وبعد التأكّد من تفعيل البيئة الافتراضية، سنستخدم أمر تثبيت الحزم pip لتثبيت كل من فلاسك وأداة Flask-SQLAlchemy على النحو التالي:

(env)user@localhost:$ pip install Flask Flask-SQLAlchemy

وبمجرّد انتهاء التثبيت بنجاح، سيظهر في السطر الأخير من الخرج ما يشبه التالي:

Successfully installed Flask-2.1.1 Flask-SQLAlchemy-2.5.1 Jinja2-3.1.1 MarkupSafe-2.1.1 SQLAlchemy-1.4.35 Werkzeug-2.1.1 click-8.1.2 greenlet-1.1.2 itsdangerous-2.1.2

ومع نهاية هذه الخطوة نكون قد ثبتنا ما يلزم من حزم بايثون، وأصبح من الممكن الانتقال إلى الخطوة التالية المُتمثّلة بإعداد قاعدة البيانات.

الخطوة 2: إعداد النماذج Models وقاعدة البيانات

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

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

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

(env)user@localhost:$ nano app.py

سيتصل هذا الملف بقاعدة بيانات من نوع SQLite باسم database.db، وسيحتوي على صنفين، الأوّل يدعى Post ويمثّل جدول التدوينات في قاعدة البيانات, والثاني يُدعى Comment ويمثّل جدول التعليقات، كما سيتضمّن هذا الملف وجهات فلاسك. الآن، سنضيف الاستدعاءات التالية باستخدام التعليمة import إلى بداية الملف app.py:

import os
from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy

استوردنا في الشيفرة السابقة الوحدة os التي تمكنّنا من الوصول إلى واجهات نظام التشغيل المختلفة، والتي سنستخدمها في بناء مسار ملف قاعدة البيانات database.db.

كما استوردنا من حزمة فلاسك كافّة المُساعدات اللازمة للتطبيق، مثل الصنف Flask المُستخدم في إنشاء النسخة الفعلية من التطبيق، والدالة ()render_template لتصيير قوالب HTML، والكائن request المسؤول عن التعامل مع الطلبيات، والدالة ()url_for لبناء روابط الوجهات، والدالة ()redirect لإعادة توجيه المُستخدمين من صفحة لأُخرى.

وبعد الانتهاء من الاستدعاءات، سنعيّن مسارًا لملف قاعدة البيانات، ونستنسخ تطبيق فلاسك، كما سنضبط ونؤسس اتصالًا للتطبيق مع الإضافة SQLAlchemy، ثمّ سنضيف الشيفرة التالية إلى الملف app.py:

basedir = os.path.abspath(os.path.dirname(__file__))

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] =\
           'sqlite:///' + os.path.join(basedir, 'database.db')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False


db = SQLAlchemy(app)

أنشأنا في الشيفرة السابقة مسارًا لملف قاعدة البيانات SQLite، إذ حددنا المجلد الحالي ليكون المجلد الرئيسي، كما استخدمنا الدالة ()os.path.abspath للحصول على المسار الكامل لمجلد الملف الحالي، إذ يتضمّن المتغير الخاص __file__ اسم مسار الملف الحالي app.py، لنخزّن المسار الكلي للمجلد الأساسي ضمن المتغير basedir، ثمّ أنشأنا نسخةً فعلية من تطبيق فلاسك باسم app، والتي سنستخدمها لضبط مفتاحي إعدادات خاصّين بالإضافة Flask-SQLAlchemy وهما:

  • SQLALCHEMY_DATABASE_URI: وهو معرّف الموارد الموحّد الخاص بقاعدة البيانات database URI المسؤول عن تحديد قاعدة البيانات المراد إنشاء اتصال معها، وفي هذه الحالة تكون صيغة هذا المعرّف بالشكل الآتي:
sqlite:///path/to/database.db

إذ نستخدم الدالة ()op.path.join لتحقيق الربط المدروس ما بين المجلد الأساسي الذي أنشاناه وخزنّا مساره في المتغير basedir، وبين اسم الملف database.db، الأمر الذي يسمح بالاتصال مع ملف قاعدة البيانات database.db الموجود في المجلد flask_app، إذ سيُنشأ هذا الملف فور تهيئة قاعدة البيانات.

  • SQLALCHEMY_TRACK_MODIFICATIONS: وهو إعداد لتمكين أو إلغاء تمكين ميزة تتبع تغيرات الكائنات، وقد عيّناه إلى القيمة false لإلغاء التتبع وبالتالي تحقيق استخدام أقل لموارد الذاكرة.

ملاحظة: من الممكن استخدام أي محرّك قواعد بيانات آخر مثل، PostgreSQL، أو MySQL، ويجب في هذه الحالة استخدام معرّف الموارد الموحد URI المناسب. ففي حال استخدام PostgreSQL سيتبع الصيغة:

postgresql://username:password@host:port/database_name

أمّا في حال استخدام MySQL:

mysql://username:password@host:port/database_name

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

التصريح عن الجداول

الآن وبعد تأسيس الاتصال مع قاعدة البيانات وإنشاء كائن قاعدة البيانات، سنستخدم هذا الكائن لإنشاء جدول للتدوينات وآخر للتعليقات، حيث تُمثّل الجداول بنماذج models، والنموذج هو صنف بايثون يرث من صنف رئيسي توفرّه الأداة Flask-SQLAlchemy من خلال نسخة قاعدة البيانات db التي أنشأناها مُسبقًا. لذا، وبغية تعريف جدولي التدوينات والتعليقات مثل نماذج، سنضيف الصنفين التاليين إلى الملف app.py على النحو التالي:

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100))
    content = db.Column(db.Text)
    comments = db.relationship('Comment', backref='post')

    def __repr__(self):
        return f'<Post "{self.title}">'


class Comment(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    content = db.Column(db.Text)
    post_id = db.Column(db.Integer, db.ForeignKey('post.id'))

    def __repr__(self):
        return f'<Comment "{self.content[:20]}...">'

أنشأنا في الشيفرة السابقة كل من النموذج Post والنموذج Comment مُستخدمين الصنف db.Column لتعريف أعمدة الجداول، إذ يمثّل الوسيط الأوّل نوع العمود، أمّا باقي الوسطاء فتمثّل إعدادات العمود.

وقد عرفنا الأعمدة التالية للنموذج Post:

  • id: وهو معرّف التدوينة، ويحتوي على بياناتٍ من نوع رقم صحيح وذلك باستخدام db.Integer و primary_key=True التي تعرّفه مفتاحًا أساسيًا، وتُخصّص قيمةٌ فريدة في قاعدة البيانات من أجل كل سجل (والسجل هو التدوينة في حالتنا).
  • title: عنوان التدوينة، وهو سلسلة نصية بعدد محارف أعظمي يساوي 100.
  • content: محتوى التدوينة، وتشير db.Text إلى أنّ هذا العمود يضم نصوصًا طويلة.

وتُعرِّف سمة الصنف المسماة comments علاقةً من النوع واحد-إلى-متعدد ما بين النموذج Post والنموذج Comment، إذ استخدمنا التابع ()db.relationship ممررين إليه اسم نموذج التعليقات (المُسمّى Comment في حالتنا)، كما استخدمنا المعامل backref لإضافة مرجعٍ للعودة back reference، والذي يتمثّل بأحد أعمدة النموذج Comment، بحيث يمكننا الوصول إلى المنشور الذي يتبع له تعليق معيّن باستخدام السمة post. على سبيل المثال، إذا كان لدينا كائن تعليق مُخزّن ضمن متغير باسم comment، فمن الممكن الوصول إلى المنشور الذي وُضع هذا التعليق عليه باستخدام التعليمة comment.post، وسنبين فيما بعد مثال يوضح هذه الفكرة.

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

أمّا النموذج Comment فيمثّل جدول التعليقات، وقد عرفنا الأعمدة التالية له:

  • id: وهو معرّف التعليق، ويحتوي على بياناتٍ من نوع رقم صحيح وذلك باستخدام db.Integer و primary_key=True التي تعرّفه مفتاحًا أساسيًا، وتُخصّص قيمةٌ فريدة في قاعدة البيانات من أجل كل سجل (والسجل هو التعليق في هذه الحالة).
  • content: محتوى التعليق، وتشير db.Text إلى أنّ هذا العمود يضم نصوصًا طويلة.
  • post_id: وهو عدد صحيح يمثّل مفتاحًا خارجيًا foreign مُنشأ باستخدام الصنف ()db.ForeignKey؛ وهو مفتاح يربط جدولًا بآخر من خلال مفتاحه الأساسي، وفي حالتنا سيربط التعليق بالتدوينة التي يتبع لها باستخدام المفتاح الأساسي للتدوينة المتمثّل بمعرّفها، وبذلك يكون جدول التدوينات في هذه الحالة هو الجدول الأب، ما يعني أنّ كل تدوينة سترتبط بعدّة تعليقات، إذ يمثّل جدول التعليقات الجدول الابن، ويرتبط كل تعليق بتدوينة من الجدول الأب باستخدام معرّفها، وبالتالي سيكون لكل تعليق قيمة في العمود post_id والتي سنستخدمها في الوصول إلى التدوينة التي أُضيف التعليق عليها.

تُظهر الدالة الخاصة __repr__ في نموذج التعليقات أوّل 20 محرفًا من محتوى التعليق ما يمنح كائن التعليق تمثيلًا محرفيًا قصيرًا.

وبذلك سيبدو الملف app.py حاليًا بالشّكل التالي:

import os
from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy

basedir = os.path.abspath(os.path.dirname(__file__))

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] =\
           'sqlite:///' + os.path.join(basedir, 'database.db')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False


db = SQLAlchemy(app)


class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100))
    content = db.Column(db.Text)
    comments = db.relationship('Comment', backref='post')

    def __repr__(self):
        return f'<Post "{self.title}">'


class Comment(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    content = db.Column(db.Text)
    post_id = db.Column(db.Integer, db.ForeignKey('post.id'))

    def __repr__(self):
        return f'<Comment "{self.content[:20]}...">'

نحفظ الملف ونغلقه.

إنشاء قاعدة بيانات

الآن، وبعد الانتهاء من إعداد كل من الاتصال مع قاعدة البيانات ونماذج التدوينة والتعليق، سنستخدم صدفة فلاسك لإنشاء قاعدة البيانات وجداول التدوينات والتعليقات اعتمادًا على النماذج المُعرّفة مُسبقًا.

بعد التأكّد من كون البيئة الافتراضية مُفعّلة، نستخدم متغير البيئة FLASK_APP لتحديد الملف app.py على أنه تطبيق فلاسك:

(env)user@localhost:$ export FLASK_APP=app

ومن ثمّ نفتح صَدفة فلاسك باستخدام الأمر التالي وذلك أثناء وجودنا ضمن المجلد flask_app:

(env)user@localhost:$ flask shell

فتُفتح صَدفة بايثون تفاعلية خاصّة، تعمل على تشغيل الأوامر ضمن تطبيق فلاسك، وبذلك نضمن اتصال دوال Flask-SQLAlchemy التي سنستدعيها مع تطبيق فلاسك.

سنستورد الآن كائن قاعدة البيانات ونماذج التدوينات والتعليقات، ثمّ سنشغّل الدالة ()db.create_all بغية إنشاء الجداول الموافقة لتلك النماذج، كما يلي:

>>> from app import db, Post, Comment
>>> db.create_all()

نبقي الصدفة في حالة تشغيل، ونفتح نافذة طرفية جديدة وننتقل إلى المجلد flask_app، فنجد ملفًا جديدًا ضمنه باسم database.db.

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

>>> db.drop_all()
>>> db.create_all()

ستعمل هذه الشيفرة على تطبيق التعديلات على النموذج، كما ستحذف كافّة البيانات الموجودة في قاعدة البيانات، فلو أردت تحديث قاعدة البيانات مع الحفاظ على البيانات الموجودة فيها، لا بدّ من استخدام أداة ترحيل ملف تخطيط قاعدة البيانات schema migration، لنتمكن من تعديل الجداول مع الحفاظ على البيانات، ولاستخدام هذه الأداة من الممكن الاستعانة بالإضافة Flask-Migrate لتنفيذ ترحيل ملف تخطيط قاعدة البيانات باستخدام الأداة SQLAIchemy من خلال واجهة أسطر الأوامر في فلاسك.

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

ملء الجدول

الآن وبعد إنشاء قاعدة البيانات وجداول التدوينات والتعليقات، سننشئ ملفًا ضمن المجلد flask_app لإضافة بعض التدوينات والتعليقات إلى قاعدة البيانات.

لذا، سننشئ ملفًا جديدًا باسم init_db.py:

(env)user@localhost:$ nano init_db.py

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

from app import db, Post, Comment

post1 = Post(title='Post The First', content='Content for the first post')
post2 = Post(title='Post The Second', content='Content for the Second post')
post3 = Post(title='Post The Third', content='Content for the third post')

comment1 = Comment(content='Comment for the first post', post=post1)
comment2 = Comment(content='Comment for the second post', post=post2)
comment3 = Comment(content='Another comment for the second post', post_id=2)
comment4 = Comment(content='Another comment for the first post', post_id=1)


db.session.add_all([post1, post2, post3])
db.session.add_all([comment1, comment2, comment3, comment4])

db.session.commit()

نحفظ الملف ونغلقه.

استوردنا في الشيفرة السابقة كلًا من كائن قاعدة البيانات والنموذج Post والنموذج Comment من الملف app.py، كما أنشأنا بضعة كائنات تدوينات باستخدام النموذج Post، ممررين لذلك عنوان التدوينة إلى المعامل title ومحتواها إلى المعامل content، ثمّ أنشأنا بعد ذلك بضع كائنات تعليقات، ممررين محتوى التعليق؛ وهنا من الممكن استخدام أحدى طريقتين لربط التعليق بالتدوينة التي يتبع لها، الأولى بتمرير كائن التدوينة إلى المعامل post كما هو مبيّن في كلا الكائنين comment1 و comment2، أو من الممكن تمرير معرّف التدوينة إلى المعامل post_id كما هو مبيّن في الكائنين comment3 و comment4، وبالتالي من الممكن تمرير العدد الصحيح المُمثّل لمعرّف التدوينة وحده في حال عدم وجود كائن التدوينة في الشيفرة.

بعد الانتهاء من تعريف كل من كائني التدوينة والتعليق، استخدمنا التابع ()db.session.add_all لإضافة كافة كائنات التدوينات والتعليقات إلى جلسة قاعدة البيانات التي تدير العمليات، ثمّ استخدمنا التابع ()db.session.commit لتأكيد وتطبيق التغييرات على قاعدة البيانات. للمزيد حول جلسات قواعد البيانات في SQLAIchemy، ننصحك بقراءة الخطوة الثانية من المقال كيفية استخدام الإضافة Flask-SQLAlchemy للتخاطب مع قواعد البيانات في تطبيقات فلاسك.

الآن، سنشغل الملف init_db.py لتنفيذ الشيفرة وإضافة البيانات إلى قاعدة البيانات:

(env)user@localhost:$ python init_db.py

وللاطلاع على البيانات المُضافة إلى قاعدة البيانات، نفتح صَدفة فلاسك بغية الاستعلام عن كافّة التدوينات وعرض عناوينها ومحتويات التعليقات على كل منها، على النحو التالي:

(env)user@localhost:$ flask shell

ثمّ نشغّل الشيفرة التالية المسؤولة عن الاستعلام عن كافّة التدوينات وعرض عنوان كل تدوينة والتعليقات عليها أسفلها:

from app import Post

posts = Post.query.all()

for post in posts:
    print(f'## {post.title}')
    for comment in post.comments:
            print(f'> {comment.content}')
    print('----')

استوردنا في الشيفرة السابقة النموذج Post من الملف app.py، ثمّ استعلمنا عن كافّة التدوينات الموجودة في قاعدة البيانات باستخدام السمة query للتابع ()all، وخزّنا نتائج الاستعلام ضمن متغير باسم posts، ثمّ استخدمنا حلقة for تكرارية بغية المرور على كل عنصر من المتغير posts، لتُعرض العناوين، ثمّ استخدمنا بعد ذلك حلقة for تكرارية ثانية للمرور على كافّة تعليقات كل تدوينة، إذ استخدمنا التعليمة post.comments للوصول إلى التعليقات، ليُعرض بعدها محتوى التعليقات ويُطبَع فاصل بالشكل '----' بغية الفصل بين التدوينات في الخرج.

فنحصل على الخرج التالي:

## Post The First
> Comment for the first post
> Another comment for the first post
----
## Post The Second
> Comment for the second post
> Another comment for the second post
----
## Post The Third
----

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

والآن نخرج من الصدفة:

>>> exit()

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

الخطوة 3: عرض جميع السجلات

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

نفتح الملف app.py لنضيف إليه وجهةً للصفحة الرئيسية:

(env)user@localhost:$ nano app.py

ونضيف الوجهة التالية إلى نهاية الملف:

# ...

@app.route('/')
def index():
    posts = Post.query.all()
    return render_template('index.html', posts=posts)

نحفظ الملف ونغلقه.

استخدمنا في الشيفرة السابقة المُزخرف ()app.route لإنشاء دالة عرض باسم ()index، التي نستعلم فيها ضمن قاعدة البيانات لجلب كافّة التدوينات كما فعلنا في الخطوة السابقة، لنخزّن نتائج هذا الاستعلام ضمن متغير باسم posts ممررين إياه إلى ملف قالب باسم index.html، الذي ستصيّره باستخدام الدالة المساعدة ()render_template.

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

لذلك، سننشئ مجلدًا للقوالب باسم templates، وننشئ ضمنه ملف قالب باسم base.html، والذي سيمثّل القالب الأساسي لبقية القوالب، كما يلي:

(env)user@localhost:$ mkdir templates
(env)user@localhost:$ nano templates/base.html

وسنكتب فيه الشيفرة التالية:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %} {% endblock %} - FlaskApp</title>
    <style>
        .title {
            margin: 5px;
        }

        .content {
            margin: 5px;
            width: 100%;
            display: flex;
            flex-direction: row;
            flex-wrap: wrap;
        }

        .comment {
            padding: 10px;
            margin: 10px;
            background-color: #fff;
        }

        .post {
            flex: 20%;
            padding: 10px;
            margin: 5px;
            background-color: #f3f3f3;
            inline-size: 100%;
        }

        .title a {
            color: #00a36f;
            text-decoration: none;
        }

        nav a {
            color: #d64161;
            font-size: 3em;
            margin-left: 50px;
            text-decoration: none;
        }

    </style>
</head>
<body>
    <nav>
        <a href="{{ url_for('index') }}">FlaskApp</a>
        <a href="#">Comments</a>
        <a href="#">About</a>
    </nav>
    <hr>
    <div class="content">
        {% block content %} {% endblock %}
    </div>
</body>
</html>

نحفظ الملف ونغلقه.

يتضمّن القالب الأساسي كافّة الشيفرات المتداولة التي سنحتاجها في القوالب الأُخرى.

ستُستبدل لاحقًا كتلة العنوان title بعنوان كل صفحة وكتلة المحتوى content بمحتواها، أمّا عن شريط التصفح فسيتضمّن ثلاثة روابط؛ إذ ينقل الأوّل المُستخدم إلى الصفحة الرئيسية للتطبيق باستخدام الدالة المساعدة ()url_for؛ والثاني إلى صفحة التعليقات Comments، أمّا الأخير لصفحة المعلومات حول التطبيق About في حال قررت تضمينها في تطبيقك، مع ملاحظة أنّنا سنعدّل هذا الملف لاحقًا بعد إضافة صفحة لعرض التعليقات الأحدث بغية جعل الرابط إلى هذه الصفحة فعّالًا.

الآن، سننشئ ملف قالب باسم index.html وهو الاسم الذي حددناه في الملف app.py:

(env)user@localhost:$ nano templates/index.html

ونضيف ضمنه الشيفرة التالية:

{% extends 'base.html' %}

{% block content %}
    <span class="title"><h1>{% block title %} Posts {% endblock %}</h1></span>
    <div class="content">
        {% for post in posts %}
            <div class="post">
                <p><b>#{{ post.id }}</b></p>
                <b>
                    <p class="title">
                        <a href="#">
                            {{ post.title }}
                        </a>
                    </p>
                </b>
                <div class="content">
                    <p>{{ post.content }}</p>
                </div>
                <hr>
            </div>
        {% endfor %}
    </div>
{% endblock %}

نحفظ الملف ونغلقه.

اعتمدنا في الشيفرة السابقة على الوراثة من ملف القالب base.html باستخدام تعليمة extends، واستبدلنا محتوى كتلة المحتوى content مُستخدمين تنسيق العنوان من المستوى الأوّل <h1> الذي يفي أيضًا بالغرض لعنوان الصفحة.

استخدمنا في السطر البرمجي {% for post in posts %} حلقة for من تعليمات محرّك القوالب جينجا jinja، والهدف من استخدام هذه الحلقة هو المرور على كل تدوينة ضمن المتغير posts المُمرّر من الدالة ()index إلى هذا القالب، حيث سنعرض معرّف التدوينة وعنوانها ومحتواها، إذ سيعمل لاحقًا عنوان التدوينة مثل رابط إلى صفحة مُستقلّة تعرض هذه التدوينة مع تعليقاتها.

الآن ومع وجودنا ضمن المجلد flask_app ومع كون البيئة الافتراضية مُفعّلة، سنُعلم فلاسك بالتطبيق المراد تشغيله (وهو في حالتنا الملف app.py) باستخدام متغير البيئة FLASK_APP، في حين يحدّد متغير البيئة FLASK_ENV وضع التشغيل وهنا قد اخترنا الوضع development ما يعني أنّ التطبيق سيعمل في وضع التطوير مع تشغيل مُنقّح الأخطاء. لمزيدٍ من المعلومات حول مُنقّح الأخطاء في فلاسك ننصحك بقراءة المقال كيفية التعامل مع الأخطاء في تطبيقات فلاسك، ولتنفيذ ما سبق سنشغّل الأوامر التالية:

(env)user@localhost:$ export FLASK_APP=app
(env)user@localhost:$ export FLASK_ENV=development

والآن سنشغّل التطبيق باستخدام الأمر flask run:

(env)user@localhost:$ flask run

وبعد التأكد من تشغيل خادم التطوير، نذهب إلى الرابط التالي باستخدام المتصفح:

http://127.0.0.1:5000/

فتظهر لنا التدوينات المُضافة إلى قاعدة البيانات في صفحة مشابهة للصورة التالية:

ظهور التدوينات المُضافة إلى قاعدة البيانات بفلاسك

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

الخطوة 4: عرض تدوينة مفردة مع التعليقات عليها

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

في نهاية هذه الخطوة سيعرض الرابط http://127.0.0.1:5000/1 صفحةً تعرض التدوينة الأولى مع تعليقاتها (لأن الرابط يحوي على المعرّف رقم 1)، بينما يعرض الرابط http://127.0.0.1:5000/ID المنشور ذو المعرّف ID الموافق له إن وُجد.

نبقي خادم التطوير قيد التشغيل، ونفتح نافذة طرفية جديدة.

نفتح الملف app.py لتعديله كما يلي:

(env)user@localhost:$ nano app.py

ونضيف الوجهة التالية إلى نهاية الملف:

# ...

@app.route('/<int:post_id>/')
def post(post_id):
    post = Post.query.get_or_404(post_id)
    return render_template('post.html', post=post)

نحفظ الملف ونغلقه.

استخدمنا في الشيفرة السابقة الوجهة /<int:post_id>/ إذ يمثّل المعامل :int محوّلًا لتحويل السلسلة النصية الافتراضية في الرابط إلى نوع عدد صحيح، ويمثل post_id متغير الرابط الذي سيُحدّد التدوينة المطلوب عرضها في الصفحة.

مررنا المعرّف من الرابط إلى دالة العرض ()post من خلال المعامل post_id، والتي نستعلم فيها عن جدول التدوينات لنجلب منه التدوينة من خلال معرّفها باستخدام التابع ()get_or_404، الأمر الذي يؤدي إلى حفظ بيانات التدوينة (في حال وجودها) ضمن المتغير post، وإلّا ستُعرض استجابة خطأ HTTP من النوع ‎404 Not Found في حال عدم وجود تدوينة موافقة للمعرّف المطلوب في قاعدة البيانات.

ومن ثمّ صيّرنا قالبًا باسم post.html ممرّرين إليه التدوينة المُستخرجة.

لذا، سنفتح ملف قالب جديد باسم post.html:

(env)user@localhost:$ nano templates/post.html

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

{% extends 'base.html' %}

{% block content %}
    <span class="title"><h1>{% block title %} {{ post.title }}  {% endblock %}</h1></span>
    <div class="content">
            <div class="post">
                <p><b>#{{ post.id }}</b></p>
                <b>
                    <p class="title">{{ post.title }}</p>
                </b>
                <div class="content">
                    <p>{{ post.content }}</p>
                </div>
                <hr>
                <h3>Comments</h3>
                {% for comment in post.comments %}
                    <div class="comment">
                        <p>#{{ comment.id }}</p>
                        <p>{{ comment.content }}</p>
                    </div>
                {% endfor %}
            </div>
    </div>
{% endblock %}

نحفظ الملف ونغلقه.

اعتمدنا في هذه الشيفرة على الوراثة من القالب base.html باستخدام تعليمة extends، وجعلنا عنوان التدوينة عنوانًا للصفحة، كما عرضنا كلًا من معرّف التدوينة ومحتواها، ومن ثمّ مررنا على كافّة التعليقات المتوفرّة على هذه التدوينة باستخدام سمة الكائن post.comments، لنعرض معرّف التعليق ومحتواه.

ننتقل باستخدام متصفح الويب إلى رابط التدوينة الثانية كما يلي:

http://127.0.0.1:5000/2/

فتظهر صفحة مشابهة لما يلي:

الانتقال إلى رابط التدوينة الثانية بمتصفح الويب

ثمّ سنحرّر ملف القالب index.html لنربط عنوان كل تدوينة مع الصفحة الخاصة بها:

(env)user@localhost:$ nano templates/index.html

نعدّل قيمة سمة الرابط href لعنوان التدوينة ضمن حلقة for التكرارية:

...

{% for post in posts %}
    <div class="post">
        <p><b>#{{ post.id }}</b></p>
        <b>
            <p class="title">
                <a href="{{ url_for('post', post_id=post.id)}}">
                {{ post.title }}
                </a>
            </p>
        </b>
        <div class="content">
            <p>{{ post.content }}</p>
        </div>
        <hr>
    </div>
{% endfor %}

نحفظ الملف ونغلقه.

ننتقل إلى الصفحة الرئيسية للتطبيق ونحدثّها:

http://127.0.0.1:5000/

وبالنقر على عنوان كل تدوينة في الصفحة الرئيسية للتطبيق نجد أنّه مرتبط بالصفحة الموافقة.

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

الخطوة 5: إضافة تعليقات جديدة

سنعمل في هذه الخطوة على تحرير الوجهة /<int:post_id>/ ودالة العرض ()post الخاصة بها والمسؤولة عن عرض التدوينات المنفردة، إذ سنضيف نموذج ويب أسفل كل تدوينة من شأنه السماح للمستخدمين بإضافة تعليقات جديدة على التدوينة، ومن ثمّ سنجهّز الآليات اللازمة للتعامل مع إرسال التعليق وإضافته إلى قاعدة البيانات.

لذا، سنفتح بدايةً ملف القالب post.html لإضافة نموذج ويب مكوّن من حقل نصي لمحتوى التعليق بالإضافة إلى زر أوامر Add Comment لتأكيد وإرسال التعليق:

(env)user@localhost:$ nano templates/post.html

نعدّل الملف بإضافة نموذج بعد العنوان من المستوى الثالث Comments وقبل حلقة for التكرارية، كما يلي:

<hr>
<h3>Comments</h3>
<form method="post">
    <p>
        <textarea name="content"
                    placeholder="Comment"
                    cols="60"
                    rows="5"></textarea>
    </p>

    <p>
        <button type="submit">Add comment</button>
    </p>
</form>
{% for comment in post.comments %}

نحفظ الملف ونغلقه.

استخدمنا في الشيفرة السابقة الوسم <form> وضبطنا فيه السمة method التي تُحدّد نوع طلبية HTTP لتكون من النوع POST، ما يعني أنّ نموذج الويب هذا سيرسل طلبًا من النوع POST.

وبذلك أصبح لدينا حقلًا نصيًا لمحتوى التعليق بالإضافة إلى زر تأكيد وإرسال له.

الآن -وأثناء عمل خادم التطوير- نستخدم المتصفح للانتقال إلى تدوينة ما (ولتكن التدوينة الثانية مثلًا):

http://127.0.0.1:5000/2/

فستظهر صفحة مشابهة للصورة التالية:

استخدام المتصفح للانتقال إلى تدوينة ما عبر فلاسك

يرسل نموذج الإدخال هذا طلبًا من النوع POST إلى دالة العرض ()post، ولكن حتى هذه اللحظة لا توجد شيفرة مسؤولة عن معالجة هذا الطلب، وبالتالي لن يحدث شيء في حال ملء النموذج الآن وإرساله بمعنى أنّ النموذج لا يعمل حتى اللحظة.

الآن، سنضيف الشيفرة اللازمة لدالة العرض ()post لمعالجة النماذج المُرسلة وإضافة التعليق الجديد إلى قاعدة البيانات. لذا، سنفتح الملف app.py بهدف تعديله ليتعامل مع الطلبات من النوع post المُرسلة من قبل المستخدم:

(env)user@localhost:$ nano app.py

ونعدّل الوجهة /<int:post_id>/ ودالة العرض ()post الخاصة بها لتصبح كما يلي:

@app.route('/<int:post_id>/', methods=('GET', 'POST'))
def post(post_id):
    post = Post.query.get_or_404(post_id)
    if request.method == 'POST':
        comment = Comment(content=request.form['content'], post=post)
        db.session.add(comment)
        db.session.commit()
        return redirect(url_for('post', post_id=post.id))

    return render_template('post.html', post=post)

نحفظ الملف ونغلقه.

سمحنا في الشيفرة السابقة بكلا نوعي طلبيات HTTP وهما GET و POST من خلال المعامل method؛ إذ تتخصّص الطلبيات من النوع GET بجلب البيانات من الخادم، أمّا الطلبيات من النوع POST فهي مُتخصّصة بإرسال البيانات إلى وجهة مُحدّدة، مع ملاحظة أنّ الطلبيات من النوع GET هي الوحيدة المسموحة افتراضيًا.

تُعالج الطلبية من النوع POST المُرسلة من قبل المُستخدم عبر النموذج ضمن العبارة الشرطية 'if request.method == 'POST، إذ ننشئ كائن تعليقات باستخدام النموذج Comment، ممررين إليه محتوى التعليق المُرسل والمُستخرج من الكائن request.form، ثمّ نحدّد التدوينة التي يتبع لها التعليق باستخدام المعامل post ممررين إليه الكائن post المُستخرج اعتمادًا على مُعرّف التدوينة الموافقة باستخدام التابع ()get_or_404.

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

الآن، نحدّث صفحة التدوينة ونضيف تعليقًا ونرسله، فسيظهر هذا التعليق الجديد أسفل التدوينة.

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

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

الخطوة 6: عرض جميع التعليقات

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

نفتح الملف app.py:

(env)user@localhost:$ nano app.py

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

# ...

@app.route('/comments/')
def comments():
    comments = Comment.query.order_by(Comment.id.desc()).all()
    return render_template('comments.html', comments=comments)

نحفظ الملف ونغلقه.

طبقنا في الشيفرة السابقة التابع ()order_by على السمة query بغية جلب كافّة التعليقات ولكن وفق ترتيب معيّن، وقد استخدمنا في حالتنا التابع ()desc على العمود Comment.id بهدف جلب التعليمات وفق ترتيب تنازلي، بحيث يكون التعليق الأحدث هو الأوّل، ومن ثمّ استخدمنا التابع ()all للحصول على النتيجة وتخزينها ضمن متغير باسم comments.

ثمّ صيّرنا ملف قالب باسم comments.html، ممررين إليه الكائن comments المُتضمّن كافّة التعليقات مُرتبّة بحيث يكون التعليق الأحدث أولًا.

لذا، سنفتح الآن ملف القالب comments.html:

(env)user@localhost:$ nano templates/comments.html

ونكتب ضمنه الشيفرة التالية، والتي ستعمل على عرض التعليقات وربط كل منها بالتدوينة التي يتبع لها:

{% extends 'base.html' %}

{% block content %}
    <span class="title"><h1>{% block title %} Latest Comments {% endblock %}</h1></span>
    <div class="content">
                {% for comment in comments %}
                    <div class="comment">
                        <i>
                            (#{{ comment.id }})
                            <p>{{ comment.content }}</p>
                        </i>
                        <p class="title">
                        On <a href="{{ url_for('post',
                                                post_id=comment.post.id) }}">
                                {{ comment.post.title }}
                              </a>
                        </p>
                    </div>
                {% endfor %}
            </div>
    </div>
{% endblock %}

نحفظ الملف ونغلقه.

اعتمدنا في الشيفرة السابقة على الوراثة من ملف القالب الأساسي base.html باستخدام تعليمة extends، وعيّنا عنوانًا ومررنا على كافّة التعليقات باستخدام حلقة for تكرارية، لنعرض بعدها معرّف التعليق ومحتواه ورابط التدوينة التي يتبع لها، إذ تمكنا من الوصول إلى بيانات التدوينة باستخدام سمة الكائن comment.post.

الآن وباستخدام المتصفّح ننتقل إلى صفحة التعليقات باستخدام الرابط التالي:

http://127.0.0.1:5000/comments/

فتظهر صفحة مشابهة للصورة التالية:

الانتقل إلى صفحة التعليقات من المتصفح

والآن سنعمل على تمكين رابط صفحة التعليقات Comments في شريط التصفّح، لذا نفتح ملف القالب base.html لتحريره:

(env)user@localhost:$ nano templates/base.html

ونعدل الشيفرة الخاصّة بشريط التنقل لتبدو كما يلي:

<nav>
        <a href="{{ url_for('index') }}">FlaskApp</a>
        <a href="{{ url_for('comments') }}">Comments</a>
        <a href="#">About</a>
    </nav>

نحفظ الملف ونغلقه.

الآن وبتحديث صفحة التعليقات نجد أنّ الرابط Comments في شريط التصفّح بات يعمل.

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

الخطوة 7: حذف تعليقات

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

بدايةً، سنضيف وجهةً جديدةً للحذف، وهي ‎/comments/ID/delete تتعامل مع الطلبات من النوع POST، إذ ستستقبل دالة الحذف رقم معرّف التعليق ID المراد حذفه، لتجلبه من قاعدة البيانات وتحذفه، ليُعاد بعدها التوجيه إلى صفحة التدوينة التي حّذف منها التعليق.

الآن، افتح الملف app.py:

(env)user@localhost:$ nano app.py

أضِف الوجهة التالية إلى نهاية الملف:

# ...

@app.post('/comments/<int:comment_id>/delete')
def delete_comment(comment_id):
    comment = Comment.query.get_or_404(comment_id)
    post_id = comment.post.id
    db.session.delete(comment)
    db.session.commit()
    return redirect(url_for('post', post_id=post_id))

نحفظ الملف ونغلقه.

استخدمنا المزخرف app.post المُقدّم في الإصدار 2.0.0 من فلاسك بدلًا من استخدام المزخرف app.route المعتاد، والذي أضاف اختصارات للعديد من توابع HTTP الشائعة، فعلى سبيل المثال الأمر:

@app.post("/login")

هو اختصارٌ للأمر:

@app.route("/login", methods=["POST"])

ما يعني أن الدالة العاملة في فلاسك هذه تتعامل فقط مع طلبيات من النوع POST، وبزيارة الوجهة comments/ID/delete/ في المتصفّح سيظهر الخطأ ‎405 Method Not Allowed لأن المتصفحات تستخدم طريقة GET افتراضيًا للطلبات، لذا بغية حذف تعليق ما، سنضيف زر أوامر عندما يضغط المستخدم عليه، تُرسَل إلى الوجهة طلبية من النوع POST.

تستقبل دالة العرض ()delete_comment في الشيفرة السابقة معرّف التعليق المراد حذفه من خلال متغير الرابط comment_id، إذ استخدمنا التابع ()get_or_404 لجلب التعليق وحفظه في متغير باسم comment، أو الاستجابة بخطأ من النوع ‎404 Not Found في حال عدم وجود تعليق موافق، كما خزّنا معرّف التدوينة التي يتبع لها التعليق المحذوف في المتغير post_id والذي سنستخدمه في إعادة التوجيه إلى صفحة هذه التدوينة بعد حذف التعليق.

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

سنعدّل القالب "post.html" بإضافة زر لحذف التعليق Delete Comment أسفل كل تعليق:

(env)user@localhost:$ nano templates/post.html

سنعدّل حلقة for التكرارية بإضافة وسم نموذج <form> مباشرةً أسفل محتوى التعليق:

{% for comment in post.comments %}
        <div class="comment">
            <p>#{{ comment.id }}</p>
            <p>{{ comment.content }}</p>
            <form method="POST"
                action="{{ url_for('delete_comment',
                                    comment_id=comment.id) }}">
                <input type="submit" value="Delete Comment"
                    onclick="return confirm('Are you sure you want to delete this entry?')">
            </form>
        </div>
    {% endfor %}

نحفظ الملف ونغلقه.

أنشأنا في الملف السابق نموذج ويب يُرسل طلبًا من النوع POST إلى دالة العرض ()delete_comment ممررين القيمة comment.id وسيطًا للمعامل comment_id لتحديد التعليق المراد حذفه، كما استخدمنا التابع confirm()‎ المتوفّر في متصفحات الويب لعرض رسالة تأكيد قبل إرسال الطلب.

الآن وبالانتقال إلى صفحة إحدى التدوينات (التدوينة الثانية مثلًا):

http://127.0.0.1:5000/2/

سيظهر زر أوامر لحذف التعليق Delete Comment أسفل كل تعليق، وبالنقر عليه ستظهر رسالة لتأكيد عملية الحذف، ومن ثمّ نجد أنّ التعليق قد حُذِف فعلًا.

وبذلك أصبحت الآلية اللازمة لحذف التعليقات من قاعدة البيانات متوفّرة في التطبيق.

الخاتمة

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

ترجمة -وبتصرف- للمقال How to Use One-to-Many Database Relationships with Flask-SQLAlchemy لصاحبه Abdelhadi Dyouri.

اقرأ أيضًا


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

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

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



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

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

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

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


×
×
  • أضف...