نحتاج عادةً إلى قاعدة بيانات في تطبيقات الويب، وهي مجموعةٌ مُنطمّةٌ من البيانات نستخدمها لتخزين وتنظيم البيانات الدائمة، موفّرةً لنا إمكانية استرجاعها ومعالجتها بفعالية، إذ سنحتاج مثلًا في تطبيق ما للتواصل الاجتماعي إلى قاعدة بيانات لتخزين بيانات المستخدم (من معلومات شخصية ومنشورات وتعليقات ومتابعين) لنتمكّن من معالجتها بفعالية، إذ توفّر قاعدة البيانات إمكانية إضافة بيانات جديدة إليها، أو استرجاع بيانات مُخزّنة أصلًا، أو التعديل عليها، أو حذفها بكل مرونة وذلك اعتمادًا على المتطلبات والشروط والظروف المُختلفة، ففي تطبيق ويب ما قد تكون هذه المتطلبات مثلًا هي حالة إضافة المُستخدم لمنشور جديد، أو حذف منشور سابق، أو حذف حسابه مع كافّة منشوراته أو مع الإبقاء عليها، بمعنى أنّ طريقة معالجة البيانات تعتمد أولًا وأخيرًا على الميزات التي يتيحها التطبيق، فقد لا ترغب مثلًا بالسماح لمُستخدمي تطبيقك بإضافة منشورات غير معنونة.
يُعد فلاسك إطار عمل للويب مبني بلغة بايثون، ويتميز بكونه صغير الحجم وسهل المعالجة، ويوفّر أيضًا عدة أدوات وميزات من شأنها إنشاء تطبيقات ويب في لغة بايثون.
أمّا 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.
مستلزمات العمل
قبل المتابعة في هذا المقال لا بُدّ من:
-
توفُّر بيئة برمجة بايثون 3 محلية، مثبّتة على حاسوبك، وسنفترض في مقالنا أن اسم مجلد المشروع هو
flask_app
. - الفهم الجيد لأساسيات فلاسك، مثل مفهوم الوجهات ودوال العرض، ويمكنك في هذا الصدد الاطلاع على المقالين كيفية بناء موقعك الإلكتروني الأول باستخدام إطار عمل فلاسك Flask من لغة بايثون وكيفية استخدام القوالب في تطبيقات فلاسك Flask لفهم مبادئ فلاسك.
- فهم أساسيات لغة HTML.
الخطوة 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.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.