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

استخدام إضافة Flask-SQLAlchemy للتخاطب مع قواعد بيانات تطبيقات فلاسك


محمد الخضور

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

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

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

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

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

سنستخدم الإضافة SQLAlchemy مع محرّك قواعد البيانات SQLite رغم أنه من الممكن استخدامها مع محركات قواعد بيانات أخرى، مثل PostgreSQL و MySQL، إلّا أنّ SQLite تعمل بكفاءة مع بايثون، لأن المكتبة القياسية لبايثون توفر الوحدة sqlite3 المُستخدمة من قبل SQLAlchemy في الخلفية للتعامل مع قاعدة بيانات SQLite دون الحاجة إلى تثبيت أي شيء إضافي، إذ تُثبّت SQlite افتراضيًا على أنظمة لينكس Linux، كما تُثبّت على أنها جزءٌ من حزمة بايثون في أنظمة تشغيل ويندوز Windows.

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

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

الخطوة 1 – تثبيت فلاسك والإضافة Flask-SQLAlchemy

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

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

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

Successfully installed Flask-2.0.3 Flask-SQLAlchemy-2.5.1 Jinja2-3.0.3 MarkupSafe-2.1.0 SQLAlchemy-1.4.31 Werkzeug-2.0.3 click-8.0.4 greenlet-1.1.2 itsdangerous-2.1.0

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

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

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

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

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

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

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

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

from sqlalchemy.sql import func

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

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

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

وبعد الانتهاء من الاستدعاءات، سنعيّن مسار ملف قاعدة البيانات، ونستنسخ تطبيق فلاسك، كما سنضبط ونؤسس اتصالًا للتطبيق مع الإضافة 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)

أنشأنا في الشيفرة السابقة مسارًا لملف قاعدة البيانات 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، والذي سنستخدمه في التخاطب مع قاعدة البيانات.

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

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

# ...

class Student(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    firstname = db.Column(db.String(100), nullable=False)
    lastname = db.Column(db.String(100), nullable=False)
    email = db.Column(db.String(80), unique=True, nullable=False)
    age = db.Column(db.Integer)
    created_at = db.Column(db.DateTime(timezone=True),
                           server_default=func.now())
    bio = db.Column(db.Text)

    def __repr__(self):
        return f'<Student {self.firstname}>'

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

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

  • id: وهو معرّف الطالب، ويحتوي على بياناتٍ من نوع رقم صحيح وذلك باستخدام db.Integer و primary_key=True التي تعرّفه مفتاحًا أساسيًا، الذي يخصّص قيمةً فريدةً في قاعدة البيانات من أجل كل سجل (والسجل هو الطالب في حالتنا).
  • firstname: الاسم الأول للطالب، وهو سلسلة نصية بعدد محارف أعظمي يساوي 100، وتشير التعليمة nullable=False إلى أن هذا العمود لا يجب أن يحتوي على قيمٍ فارغة.
  • lastname: الاسم الأخير للطالب، وهو سلسلة نصية بعدد محارف أعظمي يساوي 100، وتشير التعليمة nullable=False إلى أن هذا العمود لا يجب أن يحتوي على قيمٍ فارغة.
  • email: عنوان البريد الإلكتروني للطالب، وهو سلسلة نصية بعدد محارف أعظمي يساوي 80، وتشير التعليمة unique=True إلى أنّ البريد الإلكتروني يجب أن يكون فريدًا لكل طالب، كما تشير التعليمة nullable=False إلى أن هذا العمود لا يجب أن يحتوي على قيمٍ فارغة.
  • age: عمر الطالب.
  • created_at: يحتوي على تاريخ ووقت إنشاء سجل الطالب في قاعدة البيانات، إذ استخدمنا الوحدة db.DateTime لتعريفه مثل كائن وقت وتاريخ datetime في بايثون. تمكّن التعليمة timezone=True خاصية دعم المنطقة الزمنية، وتعيّن الوحدة server_default القيمة الزمنية الافتراضية في قاعدة البيانات لحظة إنشاء الجدول وبذلك تتعامل قاعدة البيانات مع القيم الافتراضية عوضًا عن النموذج، الذي نمرّر إليه الدالة ()func.now التي تستدعي دالة ()now المسؤولة عن تحديد الوقت والتاريخ بلغة SQL، لتُصيّر في SQLite بالشّكل CURRENT_TIMESTAMP عند إنشاء جدول الطلاب.
  • bio: السيرة الذاتية للطالب، إذ تشير ()db.Text إلى أنّ هذا العمود يضم نصوصًا طويلة.

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

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

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

from sqlalchemy.sql import func


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 Student(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    firstname = db.Column(db.String(100), nullable=False)
    lastname = db.Column(db.String(100), nullable=False)
    email = db.Column(db.String(80), unique=True, nullable=False)
    age = db.Column(db.Integer)
    created_at = db.Column(db.DateTime(timezone=True),
                           server_default=func.now())
    bio = db.Column(db.Text)

    def __repr__(self):
        return f'<Student {self.firstname}>'

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

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

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

نستخدم -بعد التأكّد من تفعيل البيئة الافتراضية- متغير البيئة FLASK_APP لتحديد الملف app.py على أنه يمثّل تطبيق فلاسك، ومن ثمّ نفتح صَدفة فلاسك باستخدام الأمر التالي وذلك أثناء وجودنا ضمن المجلد flask_app:

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

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

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

>>> from app import db, Student
>>> 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 من خلال واجهة أسطر الأوامر في فلاسك.

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

ملء الجدول

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

سنستخدم نفس صدفة فلاسك المُشغَّلة أصلًا، كما من الممكن فتح صَدفة جديدة بعد التأكد من تشغيل البيئة الافتراضية ضمن المجلد flask_app:

(env)user@localhost:$ flask shell

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

>>> from app import db, Student
>>> student_john = Student(firstname='john', lastname='doe',
>>>                        email='jd@example.com', age=23,
>>>                        bio='Biology student')

إذ يمثّل الكائن student_john الطالب المُراد إضافته إلى قاعدة البيانات، إلّا أنّ هذا الكائن لم يُنسخ إلى قاعدة البيانات بعد، لذا سنتحقّق من هذا الكائن في صدفة فلاسك للحصول على السلسلة النصية المُمثّلة له والتي أنشأناها باستخدام التابع ()__repr__:

>>> student_john

فيظهر لنا خرج بالشّكل:

<Student john>

ومن الممكن الحصول على قيم الأعمدة باستخدام سمات الصنف المُعرّفة أصلًا في النموذج Student:

>>> student_john.firstname
>>> student_john.bio

ويكون الخرج:

'john'
'Biology student'

بما أن هذا الطالب لم يُضاف إلى قاعدة البيانات بعد، ستكون قيمة مُعرّفه None:

>>> print(student_john.id)

ويكون الخرج:

None

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

نضيف الكائن student_john إلى الجلسة باستخدام التابع ()db.session.add الذي يجهّزه للنسخ على قاعدة البيانات:

>>> db.session.add(student_john)

سيتطلّب إدخال الطالب في قاعدة البيانات استخدام التعليمة INSERT، ولكن لن يُخصَّص معرّف قبل تأكيد إجراءات قاعدة البيانات، ولتأكيد الإجراءات وحفظ تغيرات قاعدة البيانات، سنستخدم التابع ()db.session.commit كما يلي:

>>> db.session.commit()

والآن وبعد إضافة الطالب John إلى قاعدة البيانات، أصبح من الممكن الحصول على معرّفه، بالشّكل:

>>> print(student_john.id)

ويكون الخرج:

1

كما من الممكن استخدام التابع ()db.session.add لتحرير عنصر ما في قاعدة البيانات، فعلى سبيل المثال، من الممكن تعديل البريد الإلكتروني الخاص بالطالب على النحو التالي:

>>> student_john.email = 'john_doe@example.com'
>>> db.session.add(student_john)
>>> db.session.commit()

سنضيف الآن باستخدام صدفة فلاسك بضعة طلاب إلى قاعدة البيانات:

>>> sammy = Student(firstname='Sammy',
>>>                lastname='Shark',
>>>                email='sammyshark@example.com',
>>>                age=20,
>>>                bio='Marine biology student')

>>> carl = Student(firstname='Carl',
>>>                lastname='White',
>>>                email='carlwhite@example.com',
>>>                age=22,
>>>                bio='Marine geology student')

>>> db.session.add(sammy)
>>> db.session.add(carl)
>>> db.session.commit()

من الممكن الآن الاستعلام عن كافّة السجلات في جدول الطلاب باستخدام السمة query مع التابع ()all، كما يلي:

>>> Student.query.all()

فيظهر الخرج التالي:

[<Student john>, <Student Sammy>, <Student Carl>]

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

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

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

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

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

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

# ...

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

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

استخدمنا في الشيفرة السابقة المُزخرف ()app.route لإنشاء دالة عرض باسم ()index، والتي نستعلم فيها ضمن قاعدة البيانات لجلب كافّة الطلاب باستخدام السمة query من النموذج Student، التي تمكننا من جلب عنصر أو أكثر من قاعدة البيانات باستخدام توابع مُختلفة، واستخدمنا التابع ()all لجلب كافّة مُدخلات الطلاب من قاعدة البيانات، لنخزّن نتائج الاستعلام ضمن متغير باسم students ممررين إياه إلى ملف قالب باسم 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;
        }

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

        .bio {
            padding: 10px;
            margin: 5px;
            background-color: #ffffff;
            color: #004835;
        }

        .name 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="#">Create</a>
        <a href="#">About</a>
    </nav>
    <hr>
    <div class="content">
        {% block content %} {% endblock %}
    </div>
</body>
</html>

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

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

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

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

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

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

{% extends 'base.html' %}

{% block content %}
    <h1 class="title">{% block title %} Students {% endblock %}</h1>
    <div class="content">
        {% for student in students %}
            <div class="student">
                <p><b>#{{ student.id }}</b></p>
                <b>
                    <p class="name">{{ student.firstname }} {{ student.lastname }}</p>
                </b>
                <p>{{ student.email }}</p>
                <p>{{ student.age }} years old.</p>
                <p>Joined: {{ student.created_at }}</p>
                <div class="bio">
                    <h4>Bio</h4>
                    <p>{{ student.bio }}</p>
                </div>
            </div>
        {% endfor %}
    </div>
{% endblock %}

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

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

استخدمنا في السطر البرمجي {% for post in posts %} حلقة for من تعليمات محرّك القوالب جينجا jinja، والهدف من استخدام هذه الحلقة هو المرور على كل طالب ضمن المتغير students المُمرّر من الدالة ()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 الموافق له إن وُجد.

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

الآن سنفتح صدفة فلاسك لنبيّن كيفيّة الاستعلام عن الطلاب، كما يلي:

(env)user@localhost:$ flask shell

توفّر الإضافة Flask-SQLAlchemy السمة query ضمن صنف النموذج للاستعلام عن السجلات وجلب البيانات الموافقة من قاعدة البيانات، ويمكن استخدام توابع هذه السمة لجلب السجلات وفقًا لمُرشّح مُعيّن. على سبيل المثال، من الممكن استخدام تابع الترشيح ()filter_by مع معامل مثل firstname الذي يوافق عمودًا معينًا في الجدول مع استخدام وسيط لجلب طالب معيّن:

>>> from app import db, Student
>>> Student.query.filter_by(firstname='Sammy').all()

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

[<Student Sammy>]

جلبنا في الشيفرة السابقة جميع الطلاب الذين يحملون القيمة Sammy في الاسم الأوّل، كما استخدمنا التابع ()all للحصول على قائمة بجميع النتائج الموافقة، أمّا للحصول على النتيجة الأولى فقط (وهي النتيجة الوحيدة في حالتنا)، نستخدم التابع ()first كما يلي:

>>> Student.query.filter_by(firstname='Sammy').first()

فيكون الخرج بالشّكل:

<Student Sammy>

يمكننا استخدام التعليمة (filter_by(id=ID للحصول على الطالب من خلال معرّفه على النحو التالي:

>>> Student.query.filter_by(id=3).first()

أو يمكننا استخدام التابع الأقصر ()get والذي يمكنّنا من جلب عنصر معين باستخدام مفتاحه الأساسي، كما يلي:

>>> Student.query.get(3)

ستعطي كلا الطريقتين الخرج نفسه:

<Student Carl>

أصبح من الممكن الآن الخروج من الصدفة:

>>> exit()

لجلب بيانات طالب من خلال معرّفه، أنشأنا وجهةً جديدةً تُصيّر صفحةً لكل طالب، إذ استخدمنا التابع ()get_or_404 الذي توفرّه الإضافة Flask-SQLAlchemy بديلًا للتابع ()get التقليدي؛ إذ يكمن الفرق بينهما في أنّ التابع ()get يعيد القيمة None في حال عدم وجود نتيجة موافقة للمعرّف المطلوب، في حين يعيد التابع ()get_or_404 خطأ HTTP من النوع ‎404 Not Found، لذا سنفتح الملف app.py لتعديله، بالشّكل:

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

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

# ...

@app.route('/<int:student_id>/')
def student(student_id):
    student = Student.query.get_or_404(student_id)
    return render_template('student.html', student=student)

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

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

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

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

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

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

{% extends 'base.html' %}

{% block content %}
    <span class="title">
        <h1>{% block title %} {{ student.firstname }} {{ student.lastname }}{% endblock %}</h1>
    </span>
    <div class="content">
            <div class="student">
                <p><b>#{{ student.id }}</b></p>
                <b>
                    <p class="name">{{ student.firstname }} {{ student.lastname }}</p>
                </b>
                <p>{{ student.email }}</p>
                <p>{{ student.age }} years old.</p>
                <p>Joined: {{ student.created_at }}</p>
                <div class="bio">
                    <h4>Bio</h4>
                    <p>{{ student.bio }}</p>
                </div>
            </div>
    </div>
{% endblock %}

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

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

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

http://127.0.0.1:5000/2

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

ظهور الصفحة السجل المفرد حسب فلاسك

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

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

ونعدّل حلقة for التكرارية على النحو التالي:

{% for student in students %}
    <div class="student">
        <p><b>#{{ student.id }}</b></p>
        <b>
            <p class="name">
                <a href="{{ url_for('student', student_id=student.id)}}">
                    {{ student.firstname }} {{ student.lastname }}
                </a>
            </p>
        </b>
        <p>{{ student.email }}</p>
        <p>{{ student.age }} years old.</p>
        <p>Joined: {{ student.created_at }}</p>
        <div class="bio">
            <h4>Bio</h4>
            <p>{{ student.bio }}</p>
        </div>
    </div>
{% endfor %}

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

أضفنا في الشيفرة السابقة وسم الرابط <a> إلى الاسم الكامل للطالب، بغية ربطه مع صفحة الطالب الموافقة باستخدام الدالة ()url_for، ممررين معرّف الطالب ID المُخزّن في الكائن student.id إلى دالة العرض ()student.

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

http://127.0.0.1:5000/

وبذلك نلاحظ أنّ اسم كل طالب أصبح مرتبطًا بصفحة الطالب الموافقة.

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

الخطوة 5 – إنشاء سجل جديد

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

سنفتح نافذة طرفية جديدة مع بقاء خادم التطوير قيد التشغيل، وسنفتح منها بدايةً الملف app.py:

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

وسنضيف في نهايته الوجهة التالية:

# ...


@app.route('/create/', methods=('GET', 'POST'))
def create():
    return render_template('create.html')

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

نمرّر في هذه الوجهة متغير صف tuple يحتوي على القيم ('GET', 'POST') إلى المعامل methods بغية السماح بكلا نوعي طلبيات HTTP وهما GET و POST، إذ تتخصّص الطلبيات من النوع GET بجلب البيانات من الخادم، أمّا الطلبيات من النوع POST فهي مُتخصّصة بإرسال البيانات إلى وجهة مُحدّدة، مع ملاحظة أنّ الطلبيات من النوع GET هي الوحيدة المسموحة افتراضيًا، وحالما يطلب المستخدم الوجهة create/ باستخدام طلبية من النوع GET، سيُصيّر ملف قالب باسم "create.html". سنعدّل هذه الوجهة لاحقًا لتتعامل أيضًا مع الطلبيات POST اللازمة لدى ملء المُستخدمين للنماذج وإرسالها بغية إضافة طلاب جدد.

الآن، سننشئ ملفًا باسم "create.html" داخل مجلد القوالب "templates" على النحو التالي:

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

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

{% extends 'base.html' %}

{% block content %}
    <h1 style="width: 100%">{% block title %} Add a New Student {% endblock %}</h1>
    <form method="post">
        <p>
            <label for="firstname">First Name</label>
            <input type="text" name="firstname"
                   placeholder="First name">
            </input>
        </p>

        <p>
            <label for="lastname">Last Name</label>
            <input type="text" name="lastname"
                   placeholder="Last name">
            </input>
        </p>

        <p>
            <label for="email">Email</label>
            <input type="email" name="email"
                   placeholder="Student email">
            </input>
        </p>

        <p>
            <label for="age">Age</label>
            <input type="number" name="age"
                   placeholder="Age">
            </input>
        </p>

        <p>
        <label for="bio">Bio</label>
        <br>
        <textarea name="bio"
                  placeholder="Bio"
                  rows="15"
                  cols="60"
                  ></textarea>
        </p>
        <p>
            <button type="submit">Submit</button>
        </p>
    </form>
{% endblock %}

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

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

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

الآن، وأثناء عمل خادم التطوير، نستخدم المتصفح للانتقال إلى الوجهة ‎/create:

http://127.0.0.1:5000/create

فستظهر صفحة إضافة طالب جديد Add a New Student تتضمّن نموذج ويب، وزرًا لتأكيد إرسال هذا النموذج Submit، كما في الشّكل:

ظهور صفحة إضافة طالب جديد تتضمّن نموذج ويب

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

الآن، سنفتح الملف app.py بهدف تعديله ليتعامل مع الطلبات من النوع POST المُرسلة من قبل المستخدم:

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

ونعدّل الوجهة create/ لتصبح كما يلي:

@app.route('/create/', methods=('GET', 'POST'))
def create():
    if request.method == 'POST':
        firstname = request.form['firstname']
        lastname = request.form['lastname']
        email = request.form['email']
        age = int(request.form['age'])
        bio = request.form['bio']
        student = Student(firstname=firstname,
                          lastname=lastname,
                          email=email,
                          age=age,
                          bio=bio)
        db.session.add(student)
        db.session.commit()

        return redirect(url_for('index'))

    return render_template('create.html')

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

تُعامل الطلبيات من النوع POST ضمن العبارة الشرطية if request.method == 'POST'، إذ يُستخرج كل من اسم الطالب الأوّل والأخير وبريده الإلكتروني وعمره وسيرته الذاتية التي أرسلها المُستخدم، من الكائن request.form، ليُحوّل العمر المُمرّر مثل سلسة نصيّة إلى نوع عدد صحيح باستخدام دالة بايثون()int، ليُبنى بعدها كائن للطالب باسم student باستخدام النموذج Student، ومن ثم نضيف هذا الكائن إلى جلسة قاعدة البيانات، ونحفظ التغييرات.

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

الآن وأثناء عمل خادم التطوير، نستخدم المتصفح للانتقال إلى الوجهة ‎/create:

http://127.0.0.1:5000/create

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

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

سنحرّر جزء الوسم <body> بتعديل قيمة سمة الرابط href لتصبح مساوية لرابط صفحة الإنشاء Create.

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

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

وبتحديث الصفحة الرئيسية للتطبيق نلاحظ أنّ الرابط Create في شريط التصفّح بات فعّالًا.

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

سنعمل في الخطوة التالية على إضافة صفحة لتعديل بيانات الطلاب الموجودين أصلًا في قاعدة البيانات.

الخطوة 6 – تعديل سجل

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

بدايةً، نفتح الملف app.py:

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

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

# ...


@app.route('/<int:student_id>/edit/', methods=('GET', 'POST'))
def edit(student_id):
    student = Student.query.get_or_404(student_id)

    if request.method == 'POST':
        firstname = request.form['firstname']
        lastname = request.form['lastname']
        email = request.form['email']
        age = int(request.form['age'])
        bio = request.form['bio']

        student.firstname = firstname
        student.lastname = lastname
        student.email = email
        student.age = age
        student.bio = bio

        db.session.add(student)
        db.session.commit()

        return redirect(url_for('index'))

    return render_template('edit.html', student=student)

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

تتعامل الوجهة /int:student_id>/edit>/ مع طلبيات HTTP من النوع POST و GET، إذ تستخدم student_id مثل متغير رابط مسؤول عن تمرير المعرّف ID إلى دالة العرض ()edit.

استخدمنا تابع الاستعلام ()get_or_404 على النموذج Student للحصول على الطالب الموافق للمعرّف المطلوب، والذي سيستجيب بخطأ من النوع "‎404 Not Found" في حال عدم تطابق أي طالب في قاعدة البيانات مع المعرّف المطلوب؛ أمّا في حال وجود طالب موافق للمعرّف المطلوب، فسيُستكمل تنفيذ الشيفرة وصولًا إلى العبارة الشرطية 'if request.method == 'POST، فإذا كانت الطلبية من النوع GET فهذا يعني أنّ المُستخدم لم يملأ نموذج التعديل ويرسله، وبالتالي لن يتحقق الشرط السابق وسنتخطى التعليمات الواردة ضمنه وصولًا إلى السطر البرمجي:

 return render_template('edit.html', student=student)

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

في حال عدّل المستخدم بيانات الطالب في نموذج التعديل وأرسله، فعندها سيتحقق الشرط if request.method == 'POST' وبالتالي ستُنفّذ التعليمات الواردة ضمنه، إذ ستُستخرج بيانات الطالب المُرسلة من الكائن request.form إلى المتغير الموافق لكل منها، لتُضبط كل سمة من سمات كائن الطالب student لتوافق القيم الجديدة المُرسلة من النموذج عبر تعديل قيم الأعمدة بنفس الآلية المُتبعة في الخطوة 2 من هذا المقال؛ وفي حال عدم تعديل أي من حقول نموذج الويب، ستبقى قيم الأعمدة في قاعدة البيانات على حالها.

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

والآن سنعمل على إنشاء صفحة تسمح للمستخدمين بإجراء التعديلات، لذا سننشئ ملف القالب edit.html:

user@localhost:$ nano templates/edit.html

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

{% extends 'base.html' %}

{% block content %}
    <h1 style="width: 100%">
        {% block title %} Edit {{ student.firstname }}
                               {{ student.lastname }}'s Details
        {% endblock %}
    </h1>
    <form method="post">
        <p>
            <label for="firstname">First Name</label>
            <input type="text" name="firstname"
                   value={{ student.firstname }}
                   placeholder="First name">
            </input>
        </p>

        <p>
            <label for="lastname">Last Name</label>
            <input type="text" name="lastname"
                   value={{ student.lastname }}
                   placeholder="Last name">
            </input>
        </p>

        <p>
            <label for="email">Email</label>
            <input type="email" name="email"
                   value={{ student.email }}
                   placeholder="Student email">
            </input>
        </p>

        <p>
            <label for="age">Age</label>
            <input type="number" name="age"
                   value={{ student.age }}
                   placeholder="Age">
            </input>
        </p>

        <p>
        <label for="bio">Bio</label>
        <br>
        <textarea name="bio"
                  placeholder="Bio"
                  rows="15"
                  cols="60"
                  >{{ student.bio }}</textarea>
        </p>
        <p>
            <button type="submit">Submit</button>
        </p>
    </form>
{% endblock %}

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

يضم العنوان كلًا من الاسم الأوّل والأخير للطالب، وتُضبط خاصية القيمة value لكل حقل إدخال وللصندوق النصي المُتعدّد الأسطر المُخصّص للسيرة الذاتية لتساوي القيمة الموافقة من كائن الطالب student المُمرر من الدالة ()edit إلى ملف القالب "edit.html".

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

http://127.0.0.1:5000/1/edit

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

صفحة تعديل تفاصيل أول طالب

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

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

نعدل حلقة for التكرارية في هذا الملف لتبدو تمامًا كما يلي:

{% for student in students %}
    <div class="student">
        <p><b>#{{ student.id }}</b></p>
        <b>
            <p class="name">
                <a href="{{ url_for('student', student_id=student.id)}}">
                    {{ student.firstname }} {{ student.lastname }}
                </a>
            </p>
        </b>
        <p>{{ student.email }}</p>
        <p>{{ student.age }} years old.</p>
        <p>Joined: {{ student.created_at }}</p>
        <div class="bio">
            <h4>Bio</h4>
            <p>{{ student.bio }}</p>
        </div>
        <a href="{{ url_for('edit', student_id=student.id) }}">Edit</a>
    </div>
{% endfor %}

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

أضفنا في الشيفرة السابقة وسم الرابط <a> للربط مع دالة العرض ()edit ممررين إليها القيمة student.id للربط مع صفحة التعديل لكل طالب باستخدام رابط تعديل صفحة Edit.

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

الخطوة 7– حذف سجل

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

بدايةً، سنضيف وجهةً جديدةً للحذف، وهي ‎/id/delete تتعامل مع الطلبات من النوع POST، إذ ستستقبل دالة الحذف الجديدة delete()‎ رقم معرّف الطالب ID المراد حذفه ممرّرة إياه إلى تابع الاستعلام ()get_or_404 في النموذج Student لمعرفة ما إذا كان موجودًا، للاستجابة بصفحة خطأ من النوع "‎404 Not Found" في حال عدم وجود طالب موافق للمعرّف المُمرّر في قاعدة البيانات.

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

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

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

# ...

@app.post('/<int:student_id>/delete/')
def delete(student_id):
    student = Student.query.get_or_404(student_id)
    db.session.delete(student)
    db.session.commit()
    return redirect(url_for('index'))

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

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

@app.post("/login")

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

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

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

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

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

سنعدّل القالب index.html بإضافة زر لحذف طالب Delete Student:

user@localhost:$ nano templates/index.html

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

{% for student in students %}
    <div class="student">
        <p><b>#{{ student.id }}</b></p>
        <b>
            <p class="name">
                <a href="{{ url_for('student', student_id=student.id)}}">
                    {{ student.firstname }} {{ student.lastname }}
                </a>
            </p>
        </b>
        <p>{{ student.email }}</p>
        <p>{{ student.age }} years old.</p>
        <p>Joined: {{ student.created_at }}</p>
        <div class="bio">
            <h4>Bio</h4>
            <p>{{ student.bio }}</p>
        </div>
        <a href="{{ url_for('edit', student_id=student.id) }}">Edit</a>

        <hr>
        <form method="POST"
                action="{{ url_for('delete', student_id=student.id) }}">
            <input type="submit" value="Delete Student"
                onclick="return confirm('Are you sure you want to delete this entry?')">
        </form>

    </div>
{% endfor %}

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

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

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

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

الخاتمة

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

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

ترجمة -وبتصرف- للمقال How to Use Flask-SQLAlchemy to Interact with Databases in a Flask Application لصاحبه 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.


×
×
  • أضف...