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

تعديل العناصر في علاقات one-to-many باستخدام فلاسك Flask ومحرك قاعدة البيانات SQLite


محمد الخضور

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

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

first_image_b.png

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

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

  • توفُّر بيئة برمجة بايثون 3 محلية، وسنستدعي في مقالنا مجلد المشروع flask_todo المُنشأ مُسبقًا من الخطوات في المقال السابق.
  • توفّر تطبيق إدارة المهام المُنجز في المقال السابق، وفي الخطوة الأولى من هذا المقال سيكون لديك الخيار إما باستنساخ التطبيق مُباشرةً دون إعداده من قبلك، أو باتباع الخطوات التفصيلية في المقال السابق كيفية استخدام علاقات قاعدة البيانات من نوع واحد-إلى-متعدد one-to-many مع إطار العمل فلاسك Flask ومحرك قواعد البيانات SQLite لبناء التطبيق، ومن ثمّ الإكمال في هذا المقال، كما يمكنك الحصول على الشيفرة الكاملة للتطبيق.
  • يجب فهم مختلف مفاهيم فلاسك الأساسية، مثل إنشاء الوجهات وإخراج قوالب HTML والاتصال بقاعدة بيانات SQLite.

الخطوة الأولى - إعداد تطبيق الويب الخاص بإدارة المهام

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

سنبدأ باستخدم الأمر Git لاستنساخ شيفرة تطبيق إدارة المهام المُنشأ سابقًا:

$ git clone https://github.com/do-community/flask-todo

ثم ننتقل إلى المجلّد flask-todo:

$ cd flask-todo

سننشئ بيئة عمل افتراضية جديدة:

$ python -m venv env

ومن ثمّ سنفعلها على النحو التالي:

$ source env/bin/activate

الآن سنحمّل إطار فلاسك ونثبته باستخدام الأمر:

$ pip install Flask

ثمّ سنهيء قاعدة البيانات باستخدام البرنامج الموجود في الملف init_db.py:

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

ثمّ سنسند القيم اللازمة إلى متغيرات بيئة فلاسك:

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

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

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

(env)user@localhost:$ flask run

يمكننا الآن الذهاب إلى الرابط "/http://127.0.0.1:5000" باستخدام المتصفح للوصول إلى التطبيق. ولإيقاف خادم التطوير، نستخدم الاختصار "CTRL + C".

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

الخطوة الثانية - إضافة إمكانية تمييز عناصر المهام المنجزة

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

وللتذكير، الأعمدة الموجودة حاليًا في الجدول items من قاعدة البيانات قبل إضافة العمود الجديد، هي:

  • "id": يحتوي معرّف العنصر.
  • "list_id": يحتوي معرّف القائمة التي ينتمي إليها العنصر.
  • "created": يحوي تاريخ إنشاء العنصر.
  • "content": محتوى العنصر.

بدايةً، سنفتح ملف تخطيط قاعدة البيانات schema.sql لتعديل جدول العناصر items:

(env)user@localhost:$ nano schema.sql

وسنضيف عمودًا جديدًا باسم done إلى جدول العناصر items على النحو التالي:

DROP TABLE IF EXISTS lists;
DROP TABLE IF EXISTS items;

CREATE TABLE lists (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    title TEXT NOT NULL
);

CREATE TABLE items (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    list_id INTEGER NOT NULL,
    created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    content TEXT NOT NULL,
    done INTEGER NOT NULL DEFAULT 0,
    FOREIGN KEY (list_id) REFERENCES lists (id)
);

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

سيتضمّن العمود المُضاف حديثًا done إلى قاعدة بيانات التطبيق إحدى القيمتين الصحيحتين "0" أو "1"، إذ تمثّل القيمة "0" القيمة المنطقية "false للدلالة على عنصر مهام غير مُنجز، في حين تمثّل القيمة "1" القيمة المنطقية "true" للدلالة على عنصر مهام مُنجز، علمًا أن القيمة الافتراضية هي "0"، وبالتالي عند إضافة أي عنصر مهام جديد إلى قاعدة البيانات فإنّ العمود done سيشير تلقائيًا إلى القيمة "0" دلالةً على أنّ المهمّة غير مُنجزة بعد، ولن تتغير حتى يضع المستخدم علامةً على العنصر أنه مُنجز، وعندها تتغير القيمة المقابلة لهذا العنصر في العمود done من القيمة "0" إلى القيمة "1".

ولتطبيق التغييرات المُنفّذة على ملف تخطيط قاعدة البيانات schema.sql، سنهيئ قاعدة البيانات مجدّدًا باستخدام برنامج init_db.py:

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

الآن، سنفتح الملف app.py لتحريره:

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

ثم سنجلب كلًا من معرّف العنصر وقيمة عمود done الموافقة له إلى الدالة ()index، التي تجلب أصلًا القوائم والعناصر من قاعدة البيانات، مرسلةً إياها إلى ملف HTML المُسمّى index.html ليعمل على عرضها، لذا عدّلنا على تعليمات SQL السابقة لتصبح مثل الشيفرة التالية:

@app.route('/')
def index():
    conn = get_db_connection()
    todos = conn.execute('SELECT i.id, i.done, i.content, l.title \
                          FROM items i JOIN lists l \
                          ON i.list_id = l.id ORDER BY l.title;').fetchall()
    lists = {}
    for k, g in groupby(todos, key=lambda t: t['title']):
        lists[k] = list(g)
    conn.close()
    return render_template('index.html', lists=lists)

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

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

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

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

سنكتب ضمن هذا الملف التجريبي نفس الشيفرة السابقة مع التعديلات التي أجريناها على تعليمات SQL، ولكن سنعدّل دالة الطباعة ()print الأخيرة لتعرض كلًا من معرّف العنصر ID والقيمة الدالة على حالة إنجازه done:

from itertools import groupby
from app import get_db_connection
conn = get_db_connection()
todos = conn.execute('SELECT i.id, i.done, i.content, l.title \
                      FROM items i JOIN lists l \
                      ON i.list_id = l.id ORDER BY l.title;').fetchall()
lists = {}
for k, g in groupby(todos, key=lambda t: t['title']):
    lists[k] = list(g)
for list_, items in lists.items():
    print(list_)
    for item in items:
        print('    ', item['content'], '| id:',
              item['id'], '| done:', item['done'])

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

الآن سنشغِّل هذا البرنامج التجريبي:

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

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

Home
     Buy fruit | id: 2 | done: 0
     Cook dinner | id: 3 | done: 0
Study
     Learn Flask | id: 4 | done: 0
     Learn SQLite | id: 5 | done: 0
Work
     Morning meeting | id: 1 | done: 0

نلاحظ من الخرج السابق أنّ قيمة العمود done هي 0 لكافّة عناصر المهام، لعدم تحديد أيٍّ منها على أنه مُنجز بعد، والآن وحتى يتمكن المستخدمون من تعديل هذه القيمة للمهام المُنجزة، سننشئ وجهة جديدة في الملف app.py.

لذلك، سنفتح الملف app.py:

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

ومن ثمّ سنضيف الشيفرة التالية إلى نهاية الملف لإضافة وجهة جديدة /do/:

. . .
@app.route('/<int:id>/do/', methods=('POST',))
def do(id):
    conn = get_db_connection()
    conn.execute('UPDATE items SET done = 1 WHERE id = ?', (id,))
    conn.commit()
    conn.close()
    return redirect(url_for('index'))

تتعامل هذه الوجهة الجديدة مع الطلبات الواردة وفق الطريقة POST فقط، إذ تستقبل دالة فلاسك ()do معرّف العنصر المراد تمييزه على أنه منجز id وسيطًا، ليفتح اتصالًا مع قاعدة البيانات، ثمّ يغيّر قيمة العمود done من 0 إلى 1 للعنصر المطلوب تمييزه على أنه مُنجزٌ باستخدام التعليمة UPDATE في SQL.

وقد استخدمنا الموضع المؤقت ? في الدالة ()execute، ثم مررنا مجموعة البيانات الحاوية على معرّف العنصر ID ما يضمن إدراجًا صحيحًا وآمنًا للبيانات في قاعدة البيانات. نهايةً، أغلقنا الاتصال مع قاعدة البيانات بعد تأكيد التغييرات ليُعاد توجيه المستخدم إلى الصفحة الرئيسية index.

الآن وبعد الانتهاء من إضافة الوجهة المسؤولة عن تمييز عناصر المهام على أنها مُنجزة، لا بدّ من إضافة وجهة جديدة للتراجع، بمعنى إمكانية إلغاء تمييز عنصر المهام على أنه مُنجز، لذا سنضيف الوجهة التالية إلى نهاية الملف app.py:

. . .
@app.route('/<int:id>/undo/', methods=('POST',))
def undo(id):
    conn = get_db_connection()
    conn.execute('UPDATE items SET done = 0 WHERE id = ?', (id,))
    conn.commit()
    conn.close()
    return redirect(url_for('index'))

نلاحظ أنّ شيفرة هذه الوجهة مشابهةٌ إلى حدٍ كبير لسابقتها /do/، كما أنّ دالة فلاسك ()undo تماثل الدالة ()do تمامًا بطريقة عملها ماعدا كونها تخصّص القيمة 0 للعمود done بدلًا من القيمة 1.

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

الآن وبعد إضافة كل من الوجهتين /do/ و /undo/، سنضيف زرًا مهمّته تمييز عنصر المهام المعروض على أنه مُنجز (من خلال إضافة خط متوسّط له) أم لا اعتمادًا على حالته، أي حسب قيمة العمود done في قاعدة البيانات، لذا سنفتح ملف قالب HTML المُسمّى index.html:

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

وسنعدّل محتويات حلقة for التكرارية داخل عنصر القائمة غير المرتبة <ul> لتصبح على النحو التالي:

{% block content %}
    <h1>{% block title %} Welcome to FlaskTodo {% endblock %}</h1>
    {% for list, items in lists.items() %}
        <div class="card" style="width: 18rem; margin-bottom: 50px;">
            <div class="card-header">
                <h3>{{ list }}</h3>
            </div>
            <ul class="list-group list-group-flush">
                {% for item in items %}
                    <li class="list-group-item"
                    {% if item['done'] %}
                    style="text-decoration: line-through;"
                    {% endif %}
                    >{{ item['content'] }}
                    {% if not item ['done'] %}
                        {% set URL = 'do' %}
                        {% set BUTTON = 'Do' %}
                    {% else %}
                        {% set URL = 'undo' %}
                        {% set BUTTON = 'Undo' %}
                    {% endif %}
                  <div class="row">
                        <div class="col-12 col-md-3">
                            <form action="{{ url_for(URL, id=item['id']) }}"
                                method="POST">
                                <input type="submit" value="{{ BUTTON }}"
                                    class="btn btn-success btn-sm">
                            </form>
                        </div>
                    </div>
                    </li>
                {% endfor %}
            </ul>
        </div>
    {% endfor %}
{% endblock %}

أسندنا في الشيفرة السابقة وضمن الحلقة for القيمة line-through لسمة تنسيق النص text-decoration في CSS، والتي تضيف خطًّا متوسطًا للنص في حال كان عنصر القوائم مُنجزًا تبعًا لقيمة السجل الموافق للعنصر من العمود done في قاعدة البيانات، ثمّ استخدمنا تعليمة set من تعليمات محرّك القوالب جينجا Jinja لنصرّح عن متغيرين، هما URL و BUTTON؛ فإذا كان العنصر غير مُحدّدٍ على أنه مُنجز، سيأخذ المتغير BUTTON القيمة Do وسيشير المتغيّر URL إلى الوجهة /do/؛ أمّا إذا كان العنصر محّددًا على أنه مُنجَز، فسيأخذ المتغير BUTTON القيمة Undo، وسيشير المتغيّر URL إلى الوجهة /undo/. استخدمنا بعد ذلك هذين المتغيرين في نموذج إدخال يُرسل الطلب المناسب اعتمادًا على حالة عنصر المهام.

نشغّل الخادم:

(env)user@localhost:$ flask run

الآن وبالذهاب إلى الرابط "/http://127.0.0.1:5000" في المتصفح، أصبح بالإمكان تمييز عناصر المهام المُنجزة من الصفحة الرئيسية للتطبيق، في الخطوة التالية سنعمل على إضافة إمكانية تعديل عناصر المهام.

الخطوة الثالثة - تعديل عناصر المهام

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

الآن وضمن الملف app.py سنضيف وجهة جديدة /edit/، مهمته إخراج صفحة HTML جديدة باسم edit.html، والتي تتيح للمستخدمين إمكانية التعديل على عناصر المهام الحالية، وهنا يتوجب علينا تحديث الملف index.html لإضافة زر Edit لكل عنصر مهام.

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

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

ثم سنضيف الشيفرات الخاصّة بالوجهة الجديدة في نهاية الملف على النحو التالي:

. . .
@app.route('/<int:id>/edit/', methods=('GET', 'POST'))
def edit(id):
    conn = get_db_connection()

    todo = conn.execute('SELECT i.id, i.list_id, i.done, i.content, l.title \
                         FROM items i JOIN lists l \
                         ON i.list_id = l.id WHERE i.id = ?', (id,)).fetchone()

    lists = conn.execute('SELECT title FROM lists;').fetchall()

    if request.method == 'POST':
        content = request.form['content']
        list_title = request.form['list']

        if not content:
            flash('Content is required!')
            return redirect(url_for('edit', id=id))

        list_id = conn.execute('SELECT id FROM lists WHERE title = (?);',
                                 (list_title,)).fetchone()['id']

        conn.execute('UPDATE items SET content = ?, list_id = ?\
                      WHERE id = ?',
                     (content, list_id, id))
        conn.commit()
        conn.close()
        return redirect(url_for('index'))

    return render_template('edit.html', todo=todo, lists=lists)

تُستخدم القيمة id وسيطًا في دالة فلاسك الجديدة المُبيّنة في الشيفرة السابقة لجلب معرّف عنصر المهام المُراد تحريره، إضافةً إلى معرّف وعنوان القائمة التي ينتمي إليها، وقيمة السجل الموافق من العمود done، ومحتوى العنصر، وذلك باستخدام التعليمة JOIN في SQL. تُحفظ هذه البيانات في متغيرٍ باسم todo، ثمّ تُجلب كافّة قوائم المهام من قاعدة البيانات وتُخزّن في المتغير lists.

في حال كان الطلب عاديًا وفق الطريقة GET، فإن الشرط التالي الدال على كون الطلب من نوع POST لن يتحقّق:

if request.method == 'POST'

وبالتالي سينتقل التطبيق لتنفيذ دالة إخراج القوالب ()render_template الذي سيمرِّر قيم المتغيرين todo و lists إلى الملف edit.html المُخرج.

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

request.method == 'POST'

ونستخلص بالتالي المحتوى والعنوان المُعدّلين المُدخلين في النموذج المُرسل؛ وفي حال عدم وجود أي محتوى، ستُعرض رسالةٌ تُخبر المستخدم بأن حقل المحتوى مطلوب "!Content is required"، ويُعاد توجيهه إلى الصفحة الرئيسية index مجدّدًا؛ وإلّا في حال وجود عنوان ومحتوى في النموذج المُرسل، يُجلب معرّف ID القائمة المُرسلة في النموذج بما يسمح للمُستخدم بنقل عنصر المهام من قائمةٍ لأُخرى.

نُحدّث محتوى عنصر المهام -باستخدام التعليمة UPDATE في SQL- إلى المحتوى الجديد المُدخل من قبل المستخدم، ويحدث الأمر نفسه بالنسبة لمعرّف القائمة، إذ يُربط عنصر المهام مع القائمة الجديدة في حال تعديل المستخدم القائمة التي ينتمي إليها العنصر. نهايةً، نحفظ التغييرات ونغلق الاتصال مع قاعدة البيانات ونعيد توجيه المستخدم إلى الصفحة الرئيسية index.

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

لاستخدام هذه الوجهة سنحتاج إلى قالب HTML جديد باسم edit.html:

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

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

{% extends 'base.html' %}

{% block content %}

<h1>{% block title %} Edit an Item {% endblock %}</h1>

<form method="post">
    <div class="form-group">
        <label for="content">Content</label>
        <input type="text" name="content"
               placeholder="Todo content" class="form-control"
               value="{{ todo['content'] or request.form['content'] }}"></input>
    </div>

    <div class="form-group">
        <label for="list">List</label>
        <select class="form-control" name="list">
            {% for list in lists %}
                {% if list['title'] == request.form['list'] %}
                    <option value="{{ request.form['list'] }}" selected>
                        {{ request.form['list'] }}
                    </option>

                {% elif list['title'] == todo['title'] %}
                    <option value="{{ todo['title'] }}" selected>
                        {{ todo['title'] }}
                    </option>

                {% else %}
                    <option value="{{ list['title'] }}">
                        {{ list['title'] }}
                    </option>
                {% endif %}
            {% endfor %}
        </select>
    </div>
    <div class="form-group">
        <button type="submit" class="btn btn-primary">Submit</button>
    </div>
</form>
{% endblock %}

تُستخدم التعليمة التالية:

{{ todo['content'] or request.form['content']‎ }}

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

وبالنسبة لنموذج اختيار القائمة التي ينتمي إليها العنصر، سنتبِّع الآلية السابقة ذاتها، بمعنى أننا سنمر على المتغير lists الحاوي على عنوان القائمة؛ فإذا كان هذا العنوان مطابقًا لذلك المُخزّن في الكائن request.form (من النموذج المُرسل) عندها يقع الاختيار على عنوان القائمة هذه ليصبح العنصر تابعًا لها؛ أما في حال كان عنوان القائمة في المتغير lists مطابقًا للعنوان في المتغير todo فهذا يعني أنّه لم يطرأ أي تعديل على عنوان القائمة التي ينتمي إليها العنصر ويبقى كما كان أصلًا دون تعديلات؛ وبالنسبة لباقي الخيارات فستُعرض دون وجود السمة selected بمعنى أنها غير قابلةٍ للتعديل.

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

الآن، سنفتح الملف index.html لإضافة زر تحرير عنصر القوائم Edit:

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

سنغيّر محتويات الوسم div بالصنف "row" لإضافة عمودٍ جديد، كما هو مُوضَّح في الشيفرة التالية:

. . .
<div class="row">
    <div class="col-12 col-md-3">
        <form action="{{ url_for(URL, id=item['id']) }}"
            method="POST">
            <input type="submit" value="{{ BUTTON }}"
                class="btn btn-success btn-sm">
        </form>
    </div>
    <div class="col-12 col-md-3">
        <a class="btn btn-warning btn-sm"
        href="{{ url_for('edit', id=item['id']) }}">Edit</a>
    </div>
</div>

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

استخدمنا في الشيفرة السابقة وسم الروابط <a> الذي سيوجّه المستخدم إلى وجهة التحرير /edit/ لكل عنصرٍ من عناصر المهام.

شغِّل خادم التطوير في حال لم يكن مُشغّلًا:

(env)user@localhost:$ flask run

وبالذهاب إلى الرابط "/http://127.0.0.1:5000" في المتصفح، نجد أنّه أصبح بالإمكان تعديل أي عنصر مهام. سنعمل في الخطوة التالية على إضافة زر حذف عناصر مهام.

الخطوة الرابعة - حذف عناصر مهام

سنضيف في هذه الخطوة إمكانية حذف عناصر مهام محدّدة، لذا سننشئ وجهة جديدة في الملف app.py باسم delete:

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

ثمّ سنضيف الشيفرة التالية إلى نهاية الملف لإضافة وجهة جديدة باسم /delete/:

. . .
@app.route('/<int:id>/delete/', methods=('POST',))
def delete(id):
    conn = get_db_connection()
    conn.execute('DELETE FROM items WHERE id = ?', (id,))
    conn.commit()
    conn.close()
    return redirect(url_for('index'))

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

تستخدم دالة فلاسك ()delete معرّف id العنصر وسيطًا، فعند إرسال طلب HTTP من نوع POST، نستخدم تعليمة DELETE في SQL لحذف العنصر الموافق لهذا المعرّف، ثمّ نحفظ التغييرات ونغلق الاتصال مع قاعدة البيانات ونعيد توجيه المستخدم إلى الصفحة الرئيسية index.

الآن، سنفتح الملف index.html الموجود في مجلد القوالب templates لإضافة زر الحذف:

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

نضيف بعد ذلك وسم <div> لإضافة زر الحذف Delete بعد الجزء الخاص بزر التحرير Edit:

<div class="row">
    <div class="col-12 col-md-3">
        <form action="{{ url_for(URL, id=item['id']) }}"
            method="POST">
            <input type="submit" value="{{ BUTTON }}"
                class="btn btn-success btn-sm">
        </form>
    </div>

    <div class="col-12 col-md-3">
        <a class="btn btn-warning btn-sm"
        href="{{ url_for('edit', id=item['id']) }}">Edit</a>
    </div>

    <div class="col-12 col-md-3">
        <form action="{{ url_for('delete', id=item['id']) }}"
            method="POST">
            <input type="submit" value="Delete"
                class="btn btn-danger btn-sm">
        </form>
    </div>
</div>

يرسل زر الحذف الجديد في الشيفرة السابقة طلبًا من نوع POST إلى وجهة route الحذف /delete/ لكل عنصرٍ مراد حذفه.

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

الآن، نشغّل خادم التطوير:

(env)user@localhost:$ flask run

وبالذهاب إلى الرابط "/http://127.0.0.1:5000" في المتصفح، أصبح بالإمكان حذف أي عنصر مهام باستخدام زر الحذف Delete. سنضيف في الخطوة التالية والأخيرة إمكانية إنشاء قوائم جديدة.

الخطوة الخامسة - إضافة قوائم جديدة

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

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

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

ثمّ سنعدّل دالة فلاسك ()create من خلال إضافة الشيفرات المبينة أدناه إلى الجملة الشرطية التالية:

if request.method == 'POST'

التي تختبر ما إذا كان الطلب مُرسلًا وفق الطريقة POST، على النحو التالي:

. . .
@app.route('/create/', methods=('GET', 'POST'))
def create():
    conn = get_db_connection()

    if request.method == 'POST':
        content = request.form['content']
        list_title = request.form['list']

        new_list = request.form['new_list']

        # If a new list title is submitted, add it to the database
        if list_title == 'New List' and new_list:
            conn.execute('INSERT INTO lists (title) VALUES (?)',
                         (new_list,))
            conn.commit()
            # Update list_title to refer to the newly added list
            list_title = new_list

        if not content:
            flash('Content is required!')
            return redirect(url_for('index'))

        list_id = conn.execute('SELECT id FROM lists WHERE title = (?);',
                                 (list_title,)).fetchone()['id']
        conn.execute('INSERT INTO items (content, list_id) VALUES (?, ?)',
                     (content, list_id))
        conn.commit()
        conn.close()
        return redirect(url_for('index'))

    lists = conn.execute('SELECT title FROM lists;').fetchall()

    conn.close()
    return render_template('create.html', lists=lists)

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

حفظنا في الشيفرة السابقة قيمة حقل نموذج الإدخال الجديد المُسمّى new_list ضمن متغير ليُضاف هذا الحقل لاحقًا إلى الملف create.html، ثمّ اختبرنا كون قيمة المتغير list_title تساوي New List من خلال الشرط:

list_title == 'New List' and new_list

والذي يعني بتحقيقه أنّ المستخدم يرغب بإنشاء قائمة جديدة، وهنا لا بدّ من التحقّق من كون قيمة المتحول new_list ليست فارغة None، فعندها سنستخدم التعليمة INSERT INTO في SQL لإضافة عنوان السلسلة الجديد المُدخل من قبل المستخدم إلى جدول القوائم lists من قاعدة البيانات. نهايةً، سنحفظ التغييرات ونحدّث قيمة متغير عنوان القائمة list_title لتصبح موافقةً لعنوان القائمة الجديدة لاستخدامه لاحقًا.

بعد ذلك، سنفتح الملف create.html لإضافة وسم اختيار <option> جديد، وهذا يتيح للمستخدم إمكانية إضافة قائمة مهام جديدة:

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

وسنعدِّله ليصبح كما يلي:

<div class="form-group">
        <label for="list">List</label>
        <select class="form-control" name="list">
            <option value="New List" selected>New List</option>
            {% for list in lists %}
                {% if list['title'] == request.form['list'] %}
                    <option value="{{ request.form['list'] }}" selected>
                        {{ request.form['list'] }}
                    </option>
                {% else %}
                    <option value="{{ list['title'] }}">
                        {{ list['title'] }}
                    </option>
                {% endif %}
            {% endfor %}
        </select>
    </div>

    <div class="form-group">
        <label for="new_list">New List</label>
        <input type="text" name="new_list"
                placeholder="New list name" class="form-control"
                value="{{ request.form['new_list'] }}"></input>
    </div>

    <div class="form-group">
        <button type="submit" class="btn btn-primary">Submit</button>
    </div>

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

أضفنا في الشيفرة السابقة وسم اختيار <option> جديد لإضافة خيار إنشاء قائمة جديدة New List، وهذا سيسمح للمستخدم بإضافة قائمة جديدة إن رغب، ثمّ أضفنا وسم <div> آخر ذو حقل إدخال باسم new_list، إذ سيُدخل فيه المستخدم عنوان القائمة الجديدة المُنشأة.

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

(env)user@localhost:$ flask run

وبالذهاب إلى الرابط "/http://127.0.0.1:5000" في المتصفح، ستبدو الصفحة الرئيسية index كما هي موضحة في الصورة التالية:

Second_image_b.png

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

يمكنك الاطلاع على شيفرة البرنامج كاملةً.

الخاتمة

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

يمثّل هذا المقال وسيلةً عمليةً لتعلّم كيفية استخدام فلاسك وSQLite لإدارة جداول قواعد البيانات، وتوظيف المعارف النظرية هذه في إطار عملي عن طريق تطوير تطبيق ويب قائم على فلاسك والتعديل عليه وإضافة ميزات جديدة ضمنه وتعديل عناصر قاعدة البيانات من النوع one-to-many.

ترجمة -وبتصرف- للمقال How To Modify Items in a One-to-Many Database Relationships with Flask and SQLite لصاحبه 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.


×
×
  • أضف...