يُعد فلاسك إطار عمل لبناء تطبيقات الويب مبني باستخدام لغة البرمجة بايثون Python، أمّا SQLite فيُعرَّف على أنّه محرّك قاعدة بيانات يُستخدم ضمن بايثون لتخزين بيانات التطبيقات. سنتعلّم في هذا المقال كيفية التعديل على عناصر تطبيق مبني باستخدام فلاسك و SQLite.
يأتي مقالنا هذا استكمالًا للمقال السابق الذي أنشأنا فيه تطبيق ويب لإدارة المهام باستخدام فلاسك و SQLite، إذ أتاح لمستخدميه إمكانية إدارة عناصر المهام وتنظيمها ضمن قوائم، وإضافة عناصر جديدة إلى قاعدة البيانات؛ وفي مقالنا هذا سنضيف للتطبيق السابق الآليات اللازمة لإتاحة إمكانية تخصيص عناصر مهام مُنجزة، وتعديل عناصر موجودة أصلًا وحذف عناصر وإضافة قوائم جديدة إلى قاعدة البيانات، وباتبّاع الخطوات التالية سيُضاف للتطبيق السابق أزرار تحرير وحذف، إضافةً لإمكانية وضع خط متوسط لتمييّز المهام المُنجزة.
مستلزمات العمل
قبل المتابعة في هذا المقال لا بُدّ من:
- توفُّر بيئة برمجة بايثون 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 كما هي موضحة في الصورة التالية:
وبذلك نكون قد أنشأنا تطبيق إدارة مهام بعدّة ميزات تمكّن مستخدميه من تمييز عناصر المهام المُنجزة، إضافةً لإمكانية تعديل أو حذف العناصر الموجودة أصلًا، وإمكانية إنشاء قوائم جديدة لمختلف أنواع المهام.
يمكنك الاطلاع على شيفرة البرنامج كاملةً.
الخاتمة
من خلال اتبّاع ما شرحناه في مقالنا هذا ستحصل على تطبيق متكامل لإدارة قوائم وعناصر المهام، والذي يتمتّع بالعديد من الميزات المتمثلة بتمكين مستخدميه من تمييّز عناصر مهامهم، مثل عناصر "مُنجزة complete" أو إعادة تعيين العناصر المميزة على أنها مُنجزة مجدّدًا إلى الحالة "غير مُنجزة non-completed"، فضلًا عن توفّر إمكانية تعديل وحذف العناصر الموجودة أصلًا، وإمكانية إنشاء قوائم جديدة لمختلف أنواع المهام.
يمثّل هذا المقال وسيلةً عمليةً لتعلّم كيفية استخدام فلاسك وSQLite لإدارة جداول قواعد البيانات، وتوظيف المعارف النظرية هذه في إطار عملي عن طريق تطوير تطبيق ويب قائم على فلاسك والتعديل عليه وإضافة ميزات جديدة ضمنه وتعديل عناصر قاعدة البيانات من النوع one-to-many.
ترجمة -وبتصرف- للمقال How To Modify Items in a One-to-Many Database Relationships with Flask and SQLite لصاحبه Abdelhadi Dyouri.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.