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

استخدام قاعدة بيانات PostgreSQL في تطبيق فلاسك


محمد الخضور

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

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

أمّا PostgreSQL أو Postgres فهو نظام لإدارة قواعد البيانات العلاقية، ويوفّر تطبيقًا للغة الاستعلامات SQL، ومتوافق مع معاييرها، ناهيك عن امتلاكه العديد من الميزات المتطورة من عمليات موثوقة وتحكّم متزامن مُتعدّد النسخ دون قيود على قراءة البيانات من القاعدة.

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

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

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

الخطوة 1 - إنشاء كل من قاعدة بيانات ومستخدم PostgreSQL

سنعمل في هذه الخطوة على إنشاء قاعدة بيانات باسم "flask_db" ومستخدم لها باسم user وذلك لاستخدامهما لاحقًا في تطبيق فلاسك المراد إنشاؤه.

يُنشأ خلال تثبيت postgres مستخدم بصلاحيات على مستوى نظام التشغيل باسم postgres ليتوافق مع حساب المستخدم المسؤول كامل الصلاحيات postgres الخاص بنظام PostgreSQL، إذ سنستخدم هذا الحساب في المهام التي تتطلّب صلاحيات مسؤول، فحينها سنستخدم الأمر sudo ممرّرين بعده اسم المستخدم مع استخدام الخيار iu-.

لذا، سجّل دخولك الآن إلى جلسة عمل تفاعلية في Postgres مُستخدمًا الأمر التالي:

user@localhost:$ sudo -iu postgres psql

فستظهر لك طرفية PostgreSQL والتي من الممكن إعداد المتطلبات من خلالها.

بدايةً، سننشئ قاعدة بيانات لمشروعنا:

postgres=# CREATE DATABASE flask_db;

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

ومن ثمّ سننشئ حساب مستخدم لقاعدة البيانات، وهنا لا بدّ من اختيار كلمة مرور آمنة:

postgres=# CREATE USER user WITH PASSWORD 'password';

والآن سنعطي هذا المستخدم صلاحيات المسؤول على قاعدة البيانات:

postgres=# GRANT ALL PRIVILEGES ON DATABASE flask_db TO sammy;

وللتأكّد من إتمام إنشاء قاعدة البيانات، نكتب الأمر التالي المسؤول عن إظهار قائمة بقواعد البيانات المُنشأة:

postgres=# \l

فستظهر قاعدة البيانات المسماة "flask_db" ضمن القائمة السابقة.

وعند الانتهاء نغلق نافذة طرفية PstgreSQL باستخدام الأمر:

postgres=# \q

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

الخطوة 2 - تثبيت فلاسك ومكتبة psycopg2

سنعمل في هذه الخطوة على تثبيت كل من فلاسك ومكتبة psycopg2 مما يمكننا من التخاطب مع قاعدة البيانات باستخدام لغة بايثون.

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

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

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

Successfully installed Flask-2.0.2 Jinja2-3.0.3 MarkupSafe-2.0.1 Werkzeug-2.0.2 click-8.0.3 itsdangerous-2.0.1 psycopg2-binary-2.9.2

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

الخطوة 3 - إعداد قاعدة بيانات

سننشئ في هذه الخطوة -ضمن مجلد المشروع المسمّى "flask_app"- ملف بايثون وظيفته الاتصال مع قاعدة البيانات "flask_db"، كما سننشئ ضمن قاعدة البيانات هذه جدولًا لتخزين الكتب، وسندخل ضمنه يدويًا بعض الكتب مع نبذة عن كل منها.

الآن وبعد التأكّد من كون البيئة الافتراضية مُفعّلة، نفتح ملفًا جديدًا باسم "init_db.py" ضمن المجلد "flask_app" على النحو التالي:

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

سيفتح هذا الملف الاتصال مع قاعدة البيانات "flask_db"، مُنشئًا فيها جدولًا للكتب باسم "books"، مالئًا إياه ببيانات تجريبية، ولإنجاز ذلك نكتب الشيفرة التالية ضمن الملف "init_db.py":

import os
import psycopg2

conn = psycopg2.connect(
        host="localhost",
        database="flask_db",
        user=os.environ['DB_USERNAME'],
        password=os.environ['DB_PASSWORD'])

# Open a cursor to perform database operations
cur = conn.cursor()

# Execute a command: this creates a new table
cur.execute('DROP TABLE IF EXISTS books;')
cur.execute('CREATE TABLE books (id serial PRIMARY KEY,'
                                 'title varchar (150) NOT NULL,'
                                 'author varchar (50) NOT NULL,'
                                 'pages_num integer NOT NULL,'
                                 'review text,'
                                 'date_added date DEFAULT CURRENT_TIMESTAMP);'
                                 )

# Insert data into the table

cur.execute('INSERT INTO books (title, author, pages_num, review)'
            'VALUES (%s, %s, %s, %s)',
            ('A Tale of Two Cities',
             'Charles Dickens',
             489,
             'A great classic!')
            )


cur.execute('INSERT INTO books (title, author, pages_num, review)'
            'VALUES (%s, %s, %s, %s)',
            ('Anna Karenina',
             'Leo Tolstoy',
             864,
             'Another great classic!')
            )

conn.commit()

cur.close()
conn.close()

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

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

كما استوردنا المكتبة psycopg2، ومن ثمّ فتحنا اتصالًا مع قاعدة البيانات flask_db باستخدام الدالة ()psycopg2.connect، وفيها حددنا المضيف وهو الحاسوب المحلي في حالتنا، كما مرّرنا اسم قاعدة البيانات إلى المُعامل database.

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

كما أنشأنا مؤشر cur باستخدام التابع ()connection.cursor، الذي يمكّن شيفرات بايثون من تنفيذ أوامر PostgreSQL خلال جلسات عمل قاعدة البيانات.

استخدمنا تابع المؤشر ()execute لحذف أي جداول موجودةٍ مسبقًا باسم "books" في حال وجودها، وذلك لتجنُّب أي تضاربٍ، أو نتائج عشوائية ناتجةٍ عن تشابه أسماء الجداول (مثل حالة وجود جدولين بنفس الاسم وبأعمدة مُختلفة في قاعدة البيانات)، ولكن وفي حالتنا الآن وكوننا لم ننشئ أي جداول بعد، فلن يُنفَّذ هذا السطر في الوقت الراهن، ومن الجدير بالملاحظة أنّ هذا الأمر سيحذِف كل المحتويات في قاعدة البيانات في كل مرة تشغِّل فيها الملف "init_db.py"، ولكن في حالة مثالنا سننفذ هذا الملف لمرّة واحدة فقط لتهيئة قاعدة البيانات، إلّا أنّك قد ترغب مُستقبلًا بتنفيذه مُجدّدًا بغية تفريغ قاعدة البيانات من كافّة محتوياتها والبدء من جديد بقاعدة بيانات فارغة.

ثمّ استخدمنا الأمر CREATE TABLE books لإنشاء جدول للكتب ضمن قاعدة البيانات باسم books يحتوي على الأعمدة التالية:

  • 'id': يحتوي على بياناتٍ من نمط رقم تسلسلي serial، أي أعداد صحيحة متزايدة تلقائيًا، ويمثّل هذا العمود مفتاحًا أساسيًا والذي قد حدّدناه باستخدام الكلمات المفتاحية PRIMARY KEY. وستُخصّص قاعدة البيانات قيمة فريدة لهذا المفتاح من أجل كل سجل مُدخل (والسجل هو الكتاب في حالتنا).
  • "title": يمثّل عنوان الكتاب، ويحتوي على بيانات من النوع varchar، وهو نوع بيانات يحتوي على محارف بطول معين، وفي حالتنا (varchar (150 تعني أنّ عدد المحارف المتاحة للعنوان هي حتى 150 محرف، أمّا التعليمة NOT NULL فتعني أنّه من غير المسموح ترك هذا العمود فارغًا.
  • author: يحتوي على اسم مؤلف الكتاب، على ألّا يتجاوز 50 محرف، أمّا التعليمة NOT NULL فتعني أنّه من غير المسموح ترك هذا العمود فارغًا.
  • pages_num: يحتوي على عدد صحيح يمثّل عدد صفحات الكتاب، وتعني التعليمة NOT NULL أنّه من غير المسموح ترك هذا العمود فارغًا.
  • review: يحتوي على نبذة عن الكتاب، وبياناته من النوع text، ما يعني أنّ محتويات هذا العمود هي سلاسل نصية بأي طول.
  • 'date_added': يحتوي على تاريخ إضافة الكتاب إلى الجدول، والقيمة DEFAULT تعني ضبط القيمة الافتراضية إلى CURRENT_TIMESTAMP، التي تمثِّل وقت إضافة الكتاب إلى قاعدة البيانات، وكما هو الحال في عمود id، لا يتوجب عليك تحديد قيم لهذا العمود، إذ أنها تُملأ تلقائيًا.

بعد الانتهاء من إنشاء الجدول، استخدمنا تابع المؤشّر ()execute لإدخال كتابين في الجدول، الأول بعنوان "A Tale of Two Cities" للكاتب "Charles Dickens"، والثاني بعنوان "Anna Karenina" للمؤلف "Leo Tolstoy"، وقد استخدمنا الموضع المؤقت s% لتمرير القيم إلى تعليمات SQL، إذ تتعامل مكتبة psycopg2 مع عمليات الإدخال في الخلفية بطريقة تضمن الحماية من هجمات حقن تعليمات SQL.

بعد الانتهاء من إدخال بيانات الكتب إلى الجدول، استخدمنا التابع ()connection.commit لتأكيد العملية وتطبيق التغييرات على قاعدة البيانات، ونهايةً أغلقنا كل من المؤشر والاتصال باستخدام التابعين ()cur.close و ()conn.close على التوالي.

الآن، سنشغّل الأوامر التالية لتأسيس الاتصال مع قاعدة البيانات، وإعداد كل من متغيري البيئة DB_USERNAME و DB_PASSWORD، ولكن استخدم اسم المستخدم وكلمة المرور الخاصين بك:

(env)user@localhost:$ export DB_USERNAME="user"
(env)user@localhost:$ export DB_PASSWORD="password"

والآن، سنشغّل الملف "init_db.py" ضمن نافذة الطرفية باستخدام الأمر python كما يلي:

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

وفور انتهاء تنفيذ الملف بنجاح، سيُضاف جدول جديد باسم "books" إلى قاعدة البيانات "flask_db".

والآن سجّل الدخول إلى جلسة Postgres تفاعلية للتحقّق من الجدول الجديد المُسمّى "books"، على النحو التالي:

(env)user@localhost:$ sudo -iu postgres psql

نتصل مع قاعدة البيانات "flask_db" باستخدام الأمر c\:

postgres=# \c flask_db

سنستخدم الآن التعليمة SELECT للحصول على عناوين الكتب وأسماء مؤلفيها من جدول الكتب books على النحو التالي:

postgres=# SELECT title, author FROM books;

فيظهر الخرج كما يلي:

       title         |      author
----------------------+------------------
 A Tale of Two Cities | Charles Dickens
 Anna Karenina        | Leo Tolstoy

نُغلق الجلسة التفاعلية باستخدام الأمر q\.

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

الخطوة 4 - عرض الكتب

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

لذا وأثناء كون البيئة البرمجية مُفعّلة وبعد تثبيت فلاسك، سنفتح ملف "app.py" ضمن المجلد "flask_app" لتحريره:

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

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

import os
import psycopg2
from flask import Flask, render_template

app = Flask(__name__)

def get_db_connection():
    conn = psycopg2.connect(host='localhost',
                            database='flask_db',
                            user=os.environ['DB_USERNAME'],
                            password=os.environ['DB_PASSWORD'])
    return conn


@app.route('/')
def index():
    conn = get_db_connection()
    cur = conn.cursor()
    cur.execute('SELECT * FROM books;')
    books = cur.fetchall()
    cur.close()
    conn.close()
    return render_template('index.html', books=books)

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

استوردنا بدايةً في الشيفرة السابقة كل من الوحدة os والمكتبة psycopg2 والصنف Flask ودالة تصيير القوالب ()render_template من حزمة فلاسك، كما أنشأنا نسخة من التطبيق باسم "app"، ثمّ عرفنا دالةً باسم ()get_db_connection لإنشاء اتصال مع قاعدة البيانات "flask_db" باستخدام اسم المستخدم وكلمة المرور المخزنيْن في متغيرات البيئة DB_USERNAME و DB_PASSWORD على التوالي، ويعيد التابع كائن الاتصال conn، الذي سنستخدمه للوصول إلى قاعدة البيانات.

استخدمنا بعد ذلك المُزخرف ()app.route لإنشاء وجهة رئيسية / ودالة عاملة في فلاسك باسم ()index، وفتحنا اتصالًا مع قاعدة البيانات باستخدام الدالة get_db_connection()‎، ثم أنشأنا مؤشرًا ونفذنا التعليمة ;SELECT * FROM books من تعليمات SQL بغية الحصول على كل الكتب الموجودة في قاعدة البيانات، إذ أنّنا نستدعي التابع fetchall()‎ لحفظ البيانات ضمن متغير باسم books، ومن ثم نغلق كل من المؤشر والاتصال مع قاعدة البيانات. ونهايةً نجعل القيمة المعادة استدعاءً للدالة ()render_template لإخراج ملف قالب باسم "index.html" ممررين له قائمة الكتب التي جلبناها من قاعدة البيانات إلى المتغير books.

الآن، وبغية عرض الكتب المُخزنة ضمن قاعدة البيانات في الصفحة الرئيسية للتطبيق، سننشئ بدايةً ملف قالب ليتضمّن كافة شيفرات 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>
        nav a {
            color: #d64161;
            font-size: 3em;
            margin-left: 50px;
            text-decoration: none;
        }

        .book {
            padding: 20px;
            margin: 10px;
            background-color: #f7f4f4;
        }

        .review {
                margin-left: 50px;
                font-size: 20px;
        }

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

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

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

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

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

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

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

{% extends 'base.html' %}

{% block content %}
    <h1>{% block title %} Books {% endblock %}</h1>
    {% for book in books %}
        <div class='book'>
            <h3>#{{ book[0] }} - {{ book[1] }} BY {{ book[2] }}</h3>
            <i><p>({{ book[3] }} pages)</p></i>
            <p class='review'>{{ book[4] }}</p>
            <i><p>Added {{ book[5] }}</p></i>
        </div>
    {% endfor %}
{% endblock %}

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

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

استخدمنا في السطر البرمجي {% for book in books %} حلقة for من تعليمات محرّك القوالب جينجا jinja، والهدف من استخدام هذه الحلقة هو المرور على كل عنصر في القائمة books، إذ نظهر بدايةً رقم معرّف الكتاب ID والذي يمثّل العنصر الأوّل باستخدام الأمر [book[0، ثمّ نظهر كلًا من عنوان الكتاب واسم مؤلفه وعدد صفحاته والنبذة التعريفية عنه وتاريخ إضافته.

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

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

وهنا لا بُدّ من التأكد من تعيين متغيرات البيئة DB_PASSWORD و DB_USERNAME في حال عدم إعدادها مُسبقًا:

(env)user@localhost:$ export DB_USERNAME="user"
(env)user@localhost:$ export DB_PASSWORD="password"

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

(env)user@localhost:$ flask run

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

http://127.0.0.1:5000/

وعندها ستظهر لك الكتب التي أضفتها بدايةً إلى قاعدة البيانات بالشّكل التالي:

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

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

الخطوة 5 - إضافة كتب جديدة

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

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

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

للتعامل مع نموذج الويب، لا بُد أولًا من استيراد التالي من حزمة فلاسك:

  • الكائن request العام المسؤول عن الوصول إلى بيانات الطلب المُرسَل من قبل المُستخدم.
  • الدالة url_for()‎ لتوليد عناوين الروابط.
  • الدالة redirect()‎ لإعادة توجيه المستخدم إلى صفحة التطبيق الرئيسية بعد إضافة كتاب إلى قاعدة البيانات.

أضِف هذه الاستيرادات إلى السطر الأوّل من الملف "app.py" كما يلي:

from flask import Flask, render_template, request, url_for, redirect

# ...

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

# ...


@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>{% block title %} Add a New Book {% endblock %}</h1>
    <form method="post">
        <p>
            <label for="title">Title</label>
            <input type="text" name="title"
                   placeholder="Book title">
            </input>
        </p>

        <p>
            <label for="author">Author</label>
            <input type="text" name="author"
                   placeholder="Book author">
            </input>
        </p>

        <p>
            <label for="pages_num">Number of pages</label>
            <input type="number" name="pages_num"
                   placeholder="Number of pages">
            </input>
        </p>
        <p>
        <label for="review">Review</label>
        <br>
        <textarea name="review"
                  placeholder="Review"
                  rows="15"
                  cols="60"
                  ></textarea>
        </p>
        <p>
            <button type="submit">Submit</button>
        </p>
    </form>
{% endblock %}

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

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

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

http://127.0.0.1:5000/create

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

إضافة الكتب في قاعدة البيانات

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

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

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

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

# ...

@app.route('/create/', methods=('GET', 'POST'))
def create():
    if request.method == 'POST':
        title = request.form['title']
        author = request.form['author']
        pages_num = int(request.form['pages_num'])
        review = request.form['review']

        conn = get_db_connection()
        cur = conn.cursor()
        cur.execute('INSERT INTO books (title, author, pages_num, review)'
                    'VALUES (%s, %s, %s, %s)',
                    (title, author, pages_num, review))
        conn.commit()
        cur.close()
        conn.close()
        return redirect(url_for('index'))

    return render_template('create.html')

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

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

فتحنا بعد ذلك اتصالًا مع قاعدة البيانات باستخدام الدالة get_db_connection()‎، وأنشأنا مؤشرًا، ونفّذنا الأمر INSERT INTO من أوامر SQL باستخدام التابع ()execute لإدخال كل من عنوان الكتاب واسم مؤلفه وعدد صفحاته ونبذة عنه إلى جدول الكتب books في قاعدة البيانات وفق ما أُدخل أصلًا من قبل المُستخدم في النموذج.

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

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

http://127.0.0.1:5000/create

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

سنفتح الآن ملف القالب الأساسي base.html لإضافة رابط في شريط التصفّح للوصول إلى صفحة إضافة كتاب جديد:

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

ونعدّل الملف ليصبح كما يلي:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %} {% endblock %} - FlaskApp</title>
    <style>
        nav a {
            color: #d64161;
            font-size: 3em;
            margin-left: 50px;
            text-decoration: none;
        }

        .book {
            padding: 20px;
            margin: 10px;
            background-color: #f7f4f4;
        }

        .review {
                margin-left: 50px;
                font-size: 20px;
        }

    </style>
</head>
<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>
</html>

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

أضفنا في الشيفرة السابقة وسم رابط <a> جديد إلى شريط التصفُّح والذي يشير إلى صفحة إضافة كتاب جديد، إذ سيظهر الرابط الجديد بتحديث الصفحة الرئيسية للتطبيق ضمن شريط التصفُّح.

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

الخاتمة

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

ترجمة -وبتصرف- للمقال How To Use a PostgreSQL Database 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.


×
×
  • أضف...