البحث في الموقع
المحتوى عن 'flask 101'.
-
تعرّفنا في الدّرس السّابق على كيفيّة تهيئة بيئة التّطوير وكيفيّة إنشاء تطبيق يقبل قيما من المُستخدم ويُعالجها ثمّ يُرجع النّتيجة على شكل صفحة HTML ليقرأها المُتصفّح، لكنّنا لم نستخدم لغة HTML كما يجب لأنّنا وضعنا شيفراتها داخل ملفّ app.py المكتوب بلغة بايثون. ما يعني بأنّنا جمعنا لغتين في ملفّ واحد، وهذا أمر غير مُناسب ولا يُمثّل مُمارسة جيّدة، الطريقة الأنظف هي بجعل شيفرات بايثون مُستقلّة عن شيفرات لغة HTML. وهذا بالضّبط ما سنتعلّمه في هذا الدّرس. استعمال قوالب HTML يُمكن فصل ملفّات HTML عن ملفّ لغة بايثون بوضعها داخل مُجلّد باسم templates (اسم المُجلّد مهم)، ويكون هذا المُجلّد في نفس مسار الملف app.py. بعد ذلك يُمكن تقديم الملفّ من قبل التّطبيق المكتوب بلغة بايثون عبر الدّالة render_template (يجب استيرادها في البداية) مع تمرير اسم الملفّ. أولا ادخل إلى مُجلّد flask_app ثمّ أنشئ مجلّدا جديدا باسم templates بعدها أنشئ ملفّا باسم index.html داخل هذا المُجلّد، وضع به شيفرة HTML التّاليّة: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>الصّفحة الرّئيسيّة</title> </head> <body style="direction: rtl;"> <h1>السّلام عليكم ورحمة الله</h1> </body> </html> يُمكننا الآن أن نجعل التّطبيق app.py يُقدم هذه الشّيفرة، وذلك أولا باستيراد الدالة render_template واستدعائها مُباشرة بعد جُملة return. # -*- coding:utf8 -*- from flask import Flask, render_template app = Flask(__name__) # Home Page @app.route("/") def home(): return render_template('index.html') if __name__ == "__main__": app.run(debug=True) بعد تشغيل التّطبيق زر العنوان http://127.0.0.1:5000. ستُلاحظ بأنّ مُحتوى الصّفحة هو نفسه مُحتوى الملفّ index.html، ويُمكنك مُشاهدة مصدر الصّفحة بالضّغط على زر الفأرة الأيمن واختيار الخيار View Page Source. تمرير متغير وعرض قيمته يُمكن تمرير المُتغيّرات إلى ملفّ HTML لعرض قيمها بإضافتها كمُعاملات للدّالة render_template. # Home Page @app.route("/") def home(): return render_template('index.html', page = u"الصّفحة الرّئيسيّة") لاحظ بأنّنا قُمنا بإضافة المُتغيّر page وأسندنا له القيمة "الصّفحة الرّئيسيّة"، يُمكننا الآن عرضه في ملفّ HTML بإحاطته بعلامات {{ }}، في المثال التّالي، نعرض قيمة المُتغيّر page داخل وسم h3 مُباشرة بعد الجملة الرّئيسيّة. <body style="direction: rtl;"> <h1>السّلام عليكم ورحمة الله</h1> <h3>{{ page }}</h3> </body> بعد تعديل الملفّ وحفظه ستُلاحظ بأنّ السّطر {{ page }} قد تغيّر إلى عبارة "الصّفحة الرّئيسيّة". عند مُشاهدة مصدر الصّفحة ستُلاحظ بأنّ السّطر: <h3>{{ page }}</h3> قد تحوّل إلى السّطر: <h3>الصّفحة الرّئيسيّة</h3> وهذا هو العمل الرّئيسي لمُحرّك القوالب Jinja2. يُمكن كذلك تقديم نفس ملفّ HTML عبر أكثر من توجيه، كما يُمكن أن تكون قيم المُتغيّرات المُمرّرة مُختلفة، فمثلا المُوّجه الرّئيسي / سيؤدي إلى تقديم الصّفحة index.html مع مُتغيّر يحمل القيمة "الصّفحة الرّئيسيّة". ويُمكن إضافة توجيه آخر /hello، مع تقديم نفس الملفّ ومُتغيّر page بالقيمة "صفحة التّرحيب". وبالتالي سيُصبح التطبيق الكامل كالآتي: # -*- coding:utf8 -*- from flask import Flask, render_template app = Flask(__name__) # Home Page @app.route("/") def home(): return render_template('index.html', page = u"الصّفحة الرّئيسيّة") # Hello Page @app.route("/hello") def hello(): return render_template('index.html', page = u"صفحة التّرحيب") if __name__ == "__main__": app.run(debug=True) يُمكنك كذلك عرض قيمة المُتغيّر في أكثر من موضع، مثلا يُمكنك وضعه كعنوان للصّفحة داخل الوسم title. <head> <meta charset="UTF-8"> <title>{{ page }}</title> </head> بعد تشغيل التّطبيق، سنتمكّن من الوصول إلى صفحتين تُقدّمان نفس ملفّ HTML مع اختلاف في قيمة المُتغيّر page (أي اختلاف في مُحتوى الوسم h3 وعنوان مُناسب لكلّ صفحة). الصّفحة الرّئيسيّة: http://127.0.0.1:5000 صفحة التّرحيب: http://127.0.0.1:5000/hello التعليقات يُمكن وضع تعليقات في مُحرّك القوالب Jinja2 بإحاطتها بعلامات {# تعليق #}، والتّعليق لن يظهر حتى بعد النّظر إلى مصدر الصّفحة، ويُمكن استعماله كالتّالي: <body style="direction: rtl;"> <h1>السّلام عليكم ورحمة الله</h1> {# هذا تعليق لن يظهر للزائر #} <h3>{{ page }}</h3> </body> الجمل الشرطية في محرك القوالب Jinja2 تعرّفنا إلى الآن على طريقة تقديم ملفّات HTML وكيفيّة تمرير المُتغيّرات من ملفّ بايثون إلى ملفّ HTML وكيفيّة عرض قيّمها. لكن مُحرّك القوالب Jinja2 ليس لهذا الغرض فقط، بل يُمكّننا كذلك من استعمال خصائص لغة بايثون، مثل الجمل الشّرطية وحلقة التّكرار for وغير ذلك. انتبه فقط إلى حقيقة أنّ بنية الجمل Syntax الخاصة بلغة بايثون مُختلفة عن بنية الجملة في مُحرّك القوالب Jinja2. فمثلا جملة شرطيّة في لغة بايثون ستكون كالتّالي: x = 10 if x == 10: print x أمّا في Jinja2 فستكون كالتّالي: {% set x = 10 %} {% if x == 10 %} {{ x }} {% endif %} أول فرق قد تُلاحظه هو أنّ جميع الشيفرات مُحاطة بالعلامات {% %} وعرض المُغيّر يكون داخل علامات {{}}. وبالنّسبة لتعريف مُتغيّر وإسناد قيمة له فيلزمه كلمة set. كما يجب إنهاء الجملة الشّرطية بجملة endif، كما أنّ الإزاحة ليست ضروريّة في مُحرّك Jinja2. مُلاحظة: يُفضّل عدم تعريف المُتغيّرات مُباشرة في ملفّات HTML إلا لحاجة، ومن الأفضل تعريفها داخل ملفّات بايثون. لإضافة جملتي elif و else يُمكن القيام بالتّالي: {% set x = 6 %} {% if x == 10 %} x يُساوي 10 {% elif x == 5 %} x يُساوي 5 {% else %} x يُساوي شيئا آخر {% endif %} جرّب تغيير قيمة المُتغيّر x وانظر إلى النّتيجة. حلقة for تعرّفنا على كيفيّة عرض مُتغيّر يحمل قيمة واحدة فقط، لكن ماذا لو أردنا أن نعرض عناصر قائمة ما، بحيث يعرض كلّ عنصر داخل وسم مُعيّن، يُمكن ذلك عبر حلقة for ويُمكن كتابتها كالآتي: {% for item in list %} <h1> {{ item }} </h1> {% endfor %} بحيث list هي القائمة مُتعدّدة العناصر. مثال لنفترض بأنّه لدينا قائمة مقالات لعرضها للزائر، بحيث تكون القائمة كالتّالي: posts = [ u"مُحتوى المقال الأول", u"مُحتوى المقال الثاني", u"مُحتوى المقال الثالث", u"مُحتوى المقال الرابع" ] سنقوم أولا بإنشاء توجيه جديد باسم posts إلى ملفّ app.py وسنُمرّر ملفّ index.html مع تمرير القائمة إلى الملف. # Posts Page @app.route("/posts") def posts(): posts = [ u"مُحتوى المقال الأول", u"مُحتوى المقال الثاني", u"مُحتوى المقال الثالث", u"مُحتوى المقال الرابع" ] return render_template('index.html', posts = posts, page = u"صفحة عرض المقالات") لاحظ بأنّ المُعامل الثاني للدّالة render_template هو posts = posts، الأمر يعني بأنّ القائمة المُمرّرة للقالب اسمها posts وستحمل قيّم القائمة posts المتواجدة في الأعلى. يُمكن عرض كل مقال داخل وسم p بالطّريقة التّاليّة: {% for post in posts %} <p> {{ post }} </p> {% endfor %} يُمكنك الآن زيارة مُدوّنتك المُتواضعة عبر الرّابط http://127.0.0.1:5000/posts. إذا قُمت بعرض مصدر الصّفحة فستُلاحظ ما يلي: <h3>صفحة عرض المقالات</h3> <p> مُحتوى المقال الأول </p> <p> مُحتوى المقال الثاني </p> <p> مُحتوى المقال الثالث </p> <p> مُحتوى المقال الرابع </p> لاحظ بأنّ الوسم p قد تكرّر مع عرض كلّ عنصر. تنسيق الصفحات، إضافة الملفات الساكنة الملفّات السّاكنة هي الصّور وملفّات CSS أو Javascript وتوضع في مُجلّد باسم static بجانب المُجلّد templates. في هذا القسم سنُضيف ملفّ CSS لتنسيق عرض المقالات أعلاه. أنشئ مُجلّدا باسم static داخل مُجلّد المشروع (بجانب المُجلّد templates). بعدها أنشئ 3 مُجلّدات داخل هذا المُجلّد أسماؤها كالتّالي: static ---| css # هنا توضع ملفّات التّنسيق ---| js # في هذا المُجلّد يُمكنك وضع ملفّات جافاسكريبت ---| img # ضع الصّور في هذا المُجلّد بعد إنشاء المُجلّدات أنشئ ملفّ تنسيق باسم style.css داخل مُجلّد css، وضع به ما يلي: body { text-align: center; } h3 { color:#1383EA; } p { font-size: 14px; } للرّبط بين ملفّ css وملفّ HTML، يُمكننا الاعتماد على دالة url_for التّي يُقدّمها مُحرّك القوالب Jinja2 لإعطاء مرونة أكثر في التّعامل مع ملفّات مُتعدّدة، واستخدامها يكون كالتّالي: {{ url_for('static', filename='path/to/file') }} مع تغيير path/to/file إلى مسار الملفّ. وبالتّالي لتضمين ملفّ style.css داخل ملفّ index.html فسيتوجّب علينا إضافة السّطر التّالي إلى وسم head. <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css')}}"> بعد حفظ الملفّ ستُلاحظ بأنّ المُحتوى أصبح يتوسّط الصّفحة، وستُلاحظ كذلك بأنّ لون عنوان الصّفحة قد تغيّر إلى الأزرق. مُلاحظة: عند التّعديل على ملفّ التنسيق وحفظه قد تحتاج إلى إعادة تحميل الصّفحة كاملة بتركيبة المفاتيح CTRL+SHIFT+R وذلك لأنّ المُتصفّح يخبّئ الملف عند أول مرّة تزور فيها الصّفحة وبالتالي فسيعطيك نفس الملف غير المُعدّل في كلّ مرّة تعيد فيها تحميل الصّفحة، والحلّ هو بإعادة تحميل كاملة لجميع الملفّات. ربط ملفات Javascript مثلما هو عليه الحال مع ملفّات css يُمكنك ربط ملفّات جافاسكريبت بوضعها داخل وسم script واستخدام الدّالة url_for. إليك مثالا: <script src="{{ url_for('static', filename='js/main.js')}}"></script> مع مُلاحظة بأنّ ملفّ main.js يجب أن يكون داخل المُجلّد js وليس في مكان آخر. عرض الصور يُمكن كذلك استعمال الدّالة لعرض الصّور، كلّ ما عليك فعله هو وضع الصّورة داخل المُجلّد img وعرضها بالدّالة url_for داخل وسم img، مثلا ضع صورة حسب رغبتك داخل المجلّد img وسمّها logo.png. لعرضها أعلى الصّفحة يُمكن إضافة السّطر التّالي مُباشرة بعد وسم body. <img src="{{ url_for('static', filename='img/logo.png')}}" /> بهذا سيُصبح التّطبيق كالتّالي: يُمكنك تصفّح ملفّات التّطبيق من هذا الرّابط. خاتمة بعد أن تعرّفنا على طريقة تمرير قيم المُتغيّرات من بايثون إلى ملفّات HTML أصبح بإمكاننا أن نستعمل قاعدة بيانات تحتوي على جدول للمقالات عوضا عن استعمال قائمة أو قاموس، في الدّرس القادم سنتعرّف على قاعدة البيانات SQLite يُمكن استخدامها لحفظ المقالات.
-
بعد أن تعرّفنا على أساسيات لغة بايثون حان الوقت للانتقال إلى مرحلة جديدة. في هذه السّلسلة من الدّروس سنتعرّف على أساسيات تطوير تطبيقات الويب بلغة بايثون، وذلك بالاستعانة بإطار العمل Flask، يعتبر Flask إطارا مُصغّرا Micro-Framework أي أنّه يُقدّم للمُبرمج أدوات مُساعدة بسيطة، وبعكس إطار Django فهو مُناسب للمُبتدئين الذين تعرّفوا على لغة بايثون حديثا. متطلبات هذه السلسلة لمتابعة هذه الدّروس وفهمها، ستحتاج إلى معرفة بسيطة بلغة بايثون. ستحتاج كذلك إلى معرفة بسيطة بلغة HTML الهيكلية، وكذلك القليل من لغة CSS لتنسيق الصّفحات إذ لن أشرح ما يتعلق بلغة HTML وCSS لأنّ ذلك ليس من اختصاص السّلسلة. يمكنك مراجعة الدروس التالية على أكاديمية حسوب لتعلم أساسيات هذه اللغات: سلسلة دروس تعلم لغة بايثون تعلّم لغة HTML ولغة CSS ما هو تطبيق الويب؟ تطبيق الويب، هو كل تطبيق يُمكن الوصول إليه عن طريق مُتصفّح للويب (Firefox ،Chrome ،Safari) ويقوم بتقديم صفحات مرئية حسب طلب الزّائر. يُمكن اعتبار موقع الأكاديمية هذا تطبيق ويب، إذ يتفاعل مع الزائر بتقديم المقالات بشكل متناسق، ويوفّر إمكانية المُشاركة للمُستخدمين عبر صندوق التّعليقات وغير ذلك من الخصائص. الصفحة التي تقرأ منها هذا المقال حاليا أصلها شيفرات لغة HTML وهي لغة أساسية في الويب. وتُستعمل لغات البرمجة مثل لغة Python لتقديم شيفرة HTML من الخادوم إلى المُتصفّح الذي يعرضها بدوره للمُستخدم. ما يعني أنّ الهدف النهائي من برمجة التّطبيق هو تقديم ملفات HTML من الخادوم إلى العميل (المُستخدم). خلاصة القول أنّك عندما تدخل إلى موقع الأكاديمية عن طريق رابط academy.hsoub.com، يرسل المُتصفّح طلبا للخادوم الخاص بالأكاديمية، عندما يستقبل الخادوم الطلب يقوم مُباشرة بتنفيذ الشيفرة المكتوبة بلغة برمجية، الشيفرة البرمجيّة تُجيب بملفات HTML ويعرضها لك المُتصفّح فور استقبالها. ما سنتعلّمه في هذه السّلسلة هو كيفيّة التعامل مع طلبات المُستخدم وكيفيّة تقديم ملفات HTML للمُتصفّح باستخدام لغة بايثون. ما هو إطار العمل؟ إطار العمل هو مجموعة من المكتبات والوحدات التي تحتوي على دوال مُساعدة تُمكّن المُبرمج من كتابة تطبيقات دون الاضطرار إلى التعامل مع التفاصيل الدقيقة التي تتطلب وقتا وجهدا كبيرين. يُمكن أن يكون إطار العمل خاصا بتطوير تطبيقات الويب مثل Flask أو Django، ويُمكن كذلك أن يكون مُخصّصا لمجالات أخرى كبناء تطبيقات سطح المكتب مثلا. تتوفّر لغة بايثون على العديد من أطر العمل الخاصّة بتطوير الويب، والتالي قائمة ببعض الأطر مع وصف مختصر لكلّ إطار. Django: إطار عمل ضخم، يتوفّر على عدد هائل من الدوال المُساعدة، كما يعتبر أنسب خيار لمن يرغب بتطوير تطبيقات كبيرة ومُعقّدة متعدّدة الوظائف، يتميّز بشهرته الواسعة وهو سهل التّعلم، يعتبر مناسبا كذلك لمن يرغب بإنشاء تطبيق بسرعة وهو شائع بين الشّركات النّاشئة. Flask: إطار عمل مُصغّر/صغير، يتوفّر على عدد لا بأس به من الدوال المُساعدة، شهرته تقريبا بنفس شهرة Django، مُناسب لتطوير تطبيقات صغيرة ومُتوسّطة (مُدونة، منتدى، موقع شخصي… ). Tornado: إطار عمل مُخصّص للتطبيقات التي تتطلب سرعة في مُعالجة الطّلبات وسرعة في التجاوب كتطبيقات الدّردشة مثلا. Bottle: إطار عمل صغير جدا، يوفّر أدنى المُتطلبات لتطوير تطبيق بسرعة، ويعتبر أصغر من إطار Flask. سبق وأن نشرنا درسا عنه. TurboGears: خصائصه تقترب من خصائص إطار Django، الاختلاف الرئيسي يكمن في الأدوات والمكتبات التي يعتمد عليها كالاتصال بقواعد البيانات وما إلى ذلك ويُعتبر خيارا آخر لمن يرغب بتطوير تطبيقات كبيرة. صحيح أن هناك أطر عمل أخرى لكنّ ما تقدّم ذكره يعتبر أبرزها. لماذا Flask؟ وقع الاختيار على إطار العمل Flask لسهولة تعلّمه بالنّسبة للمبتدئ، إذ سيبدو مألوفا لمن تعرّف حديثا على لغة بايثون، وبما أنّه إطار عمل مُصغّر فسيسهل عليك فهم خطوات إنشاء تطبيق كامل، خاصّة أنّك تستطيع أن تبني تطبيقا في ملفّ بايثون واحد. يتميّز إطار Flask كذلك بإتاحة إمكانيّة ربط تطبيقك بمُختلف مكتبات لغة بايثون، والتي يُمكنك تنصيبها بسهولة بأداة pip، وهي أداة لإدارة الحزم (مثل Gem بالنّسبة للغة روبي و Composer بالنّسبة للغة PHP). يُمكن كذلك الاعتماد على إضافات لجعل الإطار أقرب إلى الأطر الكبيرة مثل Django إذ يمتلك إطار العمل Flask العديد من الإضافات التي يُمكنك تنصيبها واستعمالها في مشروعك، ما يُمكن أن يُساعدك على إنشاء مشاريع كبيرة. Flask أم Django؟ يعتبر الاختيار بين إطار Flask وإطار Django من القرارات الصّعبة على المُبتدئ، لكنّ عليك فهم الفرق بين الإطارين لتختار ما يُناسبك، فكما قلنا سابقا فإطار Django يُوفّر عددا هائلا من الدوال والأدوات المُساعدة، أما إطار Flask فيُوفّر أدوات بسيطة وعددا أقلّ من الدوال المُساعدة. يُمكنك اختيار تعلّم إطار Django إذا كانت لديك خبرة مُسبقة بأحد أطر العمل في اللغات الأخرى مثل Laravel أو Ruby On Rails، كما يُنصح به إذا كان المشروع الذي ستعمل عليه كبيرا كتطبيق تواصل اجتماعي أو تطبيق خدمي. أما إذا لم تكن تملك أية خبرة مُسبقة فأنصح بتعلّم إطار Flask أولا، وبعد التمكن من التعامل معه وإتقان ذلك يُمكنك الانتقال إلى استعمال Django متى ما دعت الحاجة إلى ذلك، وستجد حينها بأنّ الوقت الذي استثمرته في تعلّم Flask قد أتى أكله، وسيسهل عليك تعلّم إطار Django وفهم كيفيّة عمله. كيف تستفيد من هذه السلسلة من الدروس؟ سلسلة الدروس هذه ستكون موزعة حسب المُخطّط التالي: إعداد بيئة التّطوير وإنشاء تطبيقك الأول تقديم ملفات HTML وملفات CSS والصور استخدام قاعدة بيانات مع تطبيق Flask كل درس سيكون شبه مُستقل عن الدّرس الذي يسبقه، وذلك لتكون الدروس مرجعا لك في حالة نسيان أي جزئية. في نهاية السّلسلة ستكون قادرا على استعمال لغة بايثون لتطوير تطبيق يعمل على المُتصفّح ويتصل بقاعدة بيانات. ختاما في الدّرس المُقبل سنقوم بإعداد بيئة التّطوير بتنصيب الأدوات المطلوبة، كما سننشئ تطبيقا بسيطا لعرض صفحة ويب على المُتصفّح.
-
بعد أن تعرّفنا على المفاهيم الأساسيّة لتطوير الويب كماهية تطبيق الويب، وإطار العمل، سنُكمل هذه السّلسلة من الدروس وسنتعرّف في هذا الدّرس على كيفيّة تهيئة بيئة التّطوير وتنصيب الأدوات اللازمة، وكذا بعض أساسيّات التّعامل مع إطار العمل Flask. تنصيب لغة بايثون لغة بايثون مُتواجدة بشكل افتراضي على على أنظمة لينكس و OS X، أما بالنّسبة لمستخدمي نظام Windows فيُمكنك تنزيل Python 2 من الموقع الرّسمي، فقط تأكّد من تنزيل آخر نسخة ذات الرّقم x.2.7. تنصيب إطار العمل Flask إنشاء بيئة وهميّة بأداة virtualenv البيئة الوهمية تُوفّر جميع المكتبات والاعتماديات والأدوات التي نقوم بتنصيبها والتي سنحتاج إليها في المشروع في مُجلّد واحد بمعزل عن اعتماديات نظام التّشغيل العامّة، وذلك لتجنّب تصادم بين الاعتماديات، يُمكنك القيام بالتّطوير بعد تشغيل البيئة الوهميّة ولن يكون لذلك تأثير على نظام التّشغيل، وسيبقى كل شيء بداخل مُجلّد واحد، ويُمكنك كذلك إيقاف تشغيل البيئة الوهميّة متى ما تشاء. يُمكن أن تكون أداة Virtualenv مُنصّبة مُسبقا في نظام التّشغيل لديك، يُمكنك التأكد بالأمر التّالي: $ virtualenv --version إذا حصلت على رقم نُسخة فهذا يعني بأنّ الأداة مُنصّبة من قبل. أما إذا لم يكن الأمر كذلك، فيُمكنك تنصيبها بالأمر التّالي في حالة كنت تستعمل توزيعة Ubuntu. $ sudo apt-get install python-virtualenv إذا لم تكن تستعمل نظام Ubuntu فيُمكنك أن تقوم بتنصيبها عبر أداة pip، فقط نفّذ الأمرين التاليين واحدا تلو الآخر: $ pip install -U pip $ pip install virtualenv الأمر الأول معني بتحديث أداة pip والثاني يقوم بتنصيب أداة virtualenv، قد تحتاج إلى إضافة sudo إلى بداية الأمرين إن لم تكن تملك صلاحيات مُدير النّظام (خاص بأنظمة Gnu/Linux و OSX). $ sudo pip install -U pip $ sudo pip install virtualenv تنصيب Flask سنستعمل أداة virtualenv لإنشاء بيئة وهميّة، أولا قم بإنشاء مُجلّد باسم flask_app أو باسم من اختيارك، بعد إنشاء المُجلّد يُمكنك الانتقال إلى مساره بسطر الأوامر وذلك بتنفيذ الأوامر التالية على الطّرفيّة Terminal، بالنّسبة لمُستخدمي Windows فيُمكن تنفيذ هذه الأوامر باستخدام طرفيّة PowerShell: $ mkdir ~/flask_app $ cd ~/flask_app بعدها يُمكنك يُمكنك إنشاء بيئة وهميّة باسم venv (اختصارا فقط) بالأمر التّالي: $ virtualenv venv انتظر لبضع لحظات إلى أن تُلاحظ ما يلي: Installing setuptools, pip, wheel...done. ستلاحظ بأنّ مُجلّدا جديدا باسم venv يحتوي على العديد من الملفّات قد ظهر، وهناك ستبقى الاعتماديات والمكتبات التي سنقوم بتنصيبها بأداة pip. بعد إنشاء البيئة الوهمية تبقى مهمّة تشغيلها، ويُمكن القيام بذلك بالأمر التّالي: $ . venv/bin/activate بعد تنفيذ الأمر أعلاه ستُلاحظ بأنّ سطر الأوامر قد تغيّر، وأضيفت كلمة (venv) إلى بداية السّطر، هذا يعني بأنّ كلّ شيء يعمل مثلما هو مُخطّط له. إذا أردت أن تقوم بإيقاف تشغيل البيئة الوهميّة فيُمكنك تنفيذ الأمر التّالي (لا تقم بذلك الآن): $ deactivate سنقوم الآن بتنصيب إطار العمل Flask، فقط نفّذ الأمر التّالي: $ pip install flask تنبيه نجاح العمليّة سيكون كالآتي: Successfully installed Jinja2-2.8 MarkupSafe-0.23 Werkzeug-0.11.5 flask-0.10.1 itsdangerous-0.24 تطبيقك الأول، مرحبا بالعالم بعد تشغيل البيئة الوهمية، أنشئ ملفا باسم app.py وافتحه بمُحرّرك المُفضّل، وضع به الأسطر التّالية: from flask import Flask app = Flask(__name__) @app.route("/") def hello(): return "Hello World!" if __name__ == "__main__": app.run() بعد حفظ الملفّ يكفي تشغيله بتنفيذ الأمر python app.py وستُلاحظ بأنّ الخادوم قد بدأ بالاستماع للطّلبات في المنفذ رقم 3000، ما يعني أنّك تستطيع الوصول إليه من المُتصفّح عبر العنوان http://127.0.0.1:5000 وهذا العنوان خاصّ بجهازك فقط ولا يُمكن لأحد غيرك أن يصل إليه ويُسمى عنوان المُضيف المحلي أو localhost ويُمكنك الوصول إليه من المُتصفّح من العنوان localhost:5000 كذلك. بعد الدّخول إلى العنوان عبر المُتصفّح ستُلاحظ جملة "!Hello World" على الصّفحة، لإيقاف الخادوم يُمكنك الضّغط على تركيبة المفاتيح Ctrl+c. الأسطر أعلاه هي كلّ ما تحتاج إليه لعرض نصّ على المُتصفّح، وإليك شرحا لكلّ جزء من البرنامج: هذا السّطر مسؤول عن استيراد Flask من حزمة flask (لاحظ الفرق بين حالة الحرف f). from flask import Flask نقوم بإنشاء كائن باسم app (يُمكنك تغيير الاسم على شرط أن تُغيّره في بقيّة الشيفرة)، الكائن هو الذي سيُمكننا من الوصول إلى الدوال التي يُوفرها Flask. app = Flask(__name__) السّطر التّالي هو نواة التّطبيق، وفيه تُصاغ الإجابة التي تُقدّم عند طلب الصّفحة من طرف المُتصفّح. @app.route("/") def hello(): return "Hello World!" السّطر الأول عبارة عن مُزخرف يُمكّن من ضبط المُوجّه (أي مسار الجواب) وهو ما يأتي في آخر عنوان التّطبيق http://127.0.0.1:5000 ويُمثّل / المُوجّه الرّئيسي. لتغيير المُوجّه يُمكن ببساطة تغيير قيمة المُعامل، فمثلا تعديله إلى السّطر التّالي سيُمكّننا من الوصول إلى صفحة !Hello World عبر العنوان http://127.0.0.1:5000/hello بدلا من العنوان http://127.0.0.1:5000: @app.route("/hello") بالنّسبة للدالة hello فهي مسؤولة عن تنفيذ الشيفرة التي بداخلها فور طلب الصّفحة وإرجاع قيمة نصيّة. ولإنشاء أكثر من صفحة يكفي تغيير المُوجّه Router، وتغيير اسم الدّالة. @app.route("/") def home(): page = 'Home Page' return page @app.route("/hello") def hello(): return "Hello World!" يُلاحظ أنّ اسم الدالة لا يجب تكراره بين المُوجّهات وإلا فلن يعمل التّطبيق. أما الشيفرة المُتواجدة في السّطرين الأخيرين فتقوم بتشغيل الخادوم ما يُمكّنك من الوصول إلى التّطبيق عن طريق المُتصفّح عبر العنوان http://127.0.0.1:5000. if __name__ == "__main__": app.run() الأمر ()app.run يقوم بتشغيل الخادوم ويُتيح الوصول إليه عبر جهازك فقط، أي أنّك لن تستطيع الوصول إلى التّطبيق إلا من الجهاز الذي قُمت بتشغيله منه، أما إذا كنت ترغب بأن يصل إليه من يتّصل بشبكتك المحليّة (شبكة الـ WiFi مثلا) فعليك إضافة مُعامل host بالقيمة 0.0.0.0 كالتالي: if __name__ == "__main__": app.run(host='0.0.0.0') ستتمكن الآن من الوصول إلى التّطبيق من أي جهاز مُتصل بالشّبكة المحليّة عبر عنوان IP جهازك متبوعا برقم المنفذ (مثلا http://192.168.1.5:5000). ويُمكنك الحصول على عنوان IP جهازك عبر تنفيذ الأمر ifconfig على أنظمة جنو/لينكس وأنظمة OS X والأمر ipconfig خاص بمُستخدمي نظام Windows (ستجد العنوان في السّطر الذي يحتوي على IPv4). للحصول على العنوان وحده في أنظمة جنو/لينكس يُمكن تنفيذ الأمر التّالي من الطّرفيّة: $ ifconfig | grep 'inet addr:' | cut -d: -f2 | awk '{ print $1}' لتغيير رقم المنفذ، يُمكن إضافة العامل port مع تعيين رقم منفذ أكبر من 1024 لأنّ كلّ المنافذ ذات الأرقام الصغيرة تتطلّب صلاحيات المُدير، في المثال التالي سنقوم باستعمال الرّقم 1200 كمنفذ للتّطبيق. if __name__ == "__main__": app.run(host='0.0.0.0', port=1200) اللغة العربية عرض اللغة العربية سيحتاج إلى إضافة السطر: # -*- coding:utf8 -*- إلى بداية الملفّ، كما يجب على السّلاسل أن تُسبق بحرف u. @app.route("/hello") def hello(): return u""" <h1 style="direction:rtl"> مرحبا بالعالم! </h1> """ لاحظ بأنّنا أحطنا شيفرات HTML بثلاثة علامات تنصيص لأنّها مُتعدّدة الأسطر. سيُصبح التّطبيق كما يلي: # -*- coding:utf8 -*- from flask import Flask app = Flask(__name__) @app.route("/") def home(): page = 'Home Page' return page @app.route("/hello") def hello(): return u""" <h1 style="direction:rtl"> السّلام عليكم ورحمة الله وبركاته </h1> """ if __name__ == "__main__": app.run() إذا قُمت الآن بزيارة العنوان http://127.0.0.1:5000/hello فستجد صفحة تحتوي على جملة "السّلام عليكم ورحمة الله وبركاته" بخط كبير (بسبب الوسم h1). أما إذا قمت بالدّخول إلى العُنوان http://127.0.0.1:5000 فستجد عبارة Home Page. مع ملاحظة بأن استعمال هذه الطّريقة لتقديم صفحات HTML غير مُجد وغير مرن وقد يجعل التّطوير صعبا في حالة كان التّطبيق مُتعدّد الصفحات، ومن الأفضل فصل ملفّات HTML مع ملفّات لغة Python وذلك لمزيد من التّنسيق وسهولة صيانة التّطبيق، ويُمكن فصلهما بمُحرّك القوالب Jinja2 الذي سنتعرّف عليه في الدّرس القادم. تمرير المتغيرات في عنوان Url الحصول على قيمة من العنوان يُمكن الحصول على قيم مُباشرة من العنوان، ويُمكننا توظيفها في الشيفرة، فمثلا يُمكننا الذهاب إلى العنوان http://127.0.0.1:5000/say_hello/Abdelhadi وسنستطيع الوصول إلى القيمة Abdelhadi كمُعامل بحيث يُمكننا إرجاعها مع جملة ترحيب أو تنفيذ أي عمليّة أخرى. ولنقوم بالأمر سنُضيف أوّلا مُوجّها جديدا باسم say_hello ولكن مع وضع المُعامل داخل علامتي <> وسنُمرّر اسم المُعامل إلى الدّالة كذلك، انظر ما يلي: @app.route("/say_hello/<name>") def say_hello(name): return u"Hello {}".format(name) إذا قُمت الآن بالذهاب إلى العنوان http://127.0.0.1:5000/say_hello/Abdelhadi فستجد عبارة Hello Abdelhadi (جرّب تغيير Abdelhadi إلى اسمك، سواء باللغة العربيّة أو باللغة الانجليزية). بعد هذا الجزء سيُصبح التّطبيق الكامل كالآتي: # -*- coding:utf8 -*- from flask import Flask app = Flask(__name__) @app.route("/") def home(): page = 'Home Page' return page @app.route("/hello") def hello(): return u""" <h1 style="direction:rtl"> السّلام عليكم ورحمة الله وبركاته </h1> """ @app.route("/say_hello/<name>") def say_hello(name): return u"Hello {}".format(name) if __name__ == "__main__": app.run() الحصول على أكثر من قيمة من العنوان الطّريقة السابقة جيّدة في حالة أدرت الحصول على قيمة مُعامل واحد، لكن ماذا لو أردت الحصول على أكثر من مُعامل؟ يُمكننا تحقيق مُرادنا عبر طلبات الـ HTTP من نوع GET، بحيث نُرسل المُعامل وقيمته في العنوان كالتّالي: http://127.0.0.1:5000/first_last?first_name=Abdelhadi&last_name=Dyouri بحيث تُمرّر المفاتيح والقيم التّاليّة: first_name=Abdelhadi last_name=Dyouri لاحظ بأنّنا نفصل بين المُعامل والآخر برمز &. وبالطّبع يُمكنك تمرير مُعامل واحد فقط. http://127.0.0.1:5000/first_last?first_name=Abdelhadi للوصول إلى قيم هذه المُعاملات، سنستخدم الوحدة request التي يُوفّرها إطار Flask وسنستوردها جنبا إلى جنب مع Flask في السّطر الثاني من البرنامج كالتّالي: from flask import Flask, request بعد ذلك سنتمكّن من الوصول إلى قيمة مُعامل كالتّالي: request.args.get('parameter') تطبيق سنُطبّق هذا بإنشاء تطبيق لعرض الاسم الأول للشّخص بأحرف صغيرة مع تكبير الحرف الأول، والاسم الثاني سيكون بأحرف كبيرة، وسنستعمل الدوال upper و capitalize. أولا سننشئ مُوجّها جديدا باسم first_last بعدها سنقوم بالحصول على قيمتي المُعاملين first_name و last_name، ثمّ سنحولّ الاسم الأول باستخدام التّابع capitalize وسنُحوّل الاسم العائلي إلى أحرف كبيرة بالتّابع upper، سنعرضه النتيجة بعد ذلك في وسمي h3 كلّ في سطر. @app.route("/first_last") def first_last(): first_name = request.args.get('first_name').capitalize() last_name = request.args.get('last_name').upper() return "<h3>First Name: {} <br>Last Name: {}</h3>".format(first_name, last_name) يُمكنك تصفّح شيفرة هذا الدّرس وتنزيلها من موقع Github عبر هذا الرّابط تشغيل مصحح الأخطاء Debugger يأتي Flask بمُصحّح أخطاء يعرض مصدر الخطأ مُباشرة على المُتصفّح، ويُنصح باستعماله ليسهل عليك تحديد مصدر الخطأ لإصلاحه. يُمكن تشغيل مُصحّح الأخطاء عبر إضافة مُعامل debug بقيمة True إلى التّابع run. if __name__ == "__main__": app.run(debug=True) وهذه صورة لمُصحّح الخطأ بعد وقوع خطأ في تطبيق Flask. وقع الخطأ لأنّ القيمة الافتراضيّة لمُعامل مُعيّن عند عدم تحديد قيمة له هي None ما يعني بأنّك لا تستطيع تنفيذ التّابع upper الخاص بالسّلاسل النّصية. يُمكنك مُشاهدة هذا الخطأ بالذهاب إلى العنوان http://127.0.0.1:5000/first_last?first_name=abdelhadi لاحظ الجملة الأولى 'AttributeError: 'NoneType' object has no attribute 'upper هذا الخطأ وقع بعد تنفيذ التّابع upper على القيمة None وهذا لأنّنا لم نُوفّر قيمة للمُعامل last_name. خاتمة تعرّفنا إلى الآن على أساسيات التّعامل مع المُوجّهات، وكيفيّة تقديم صفحات HTML للمُتصفّح أو الزّائر، وسنتعرّف في الدّرس القادم بإذن الله على كيفيّة استعمال مُحرّك القوالب Jinja2 لتقديم ملفّات HTML مُستقلّة وكيفيّة استعمال بعض الأساليب البرمجيّة فيه.
- 4 تعليقات
-
- 3
-
- pip
- virtualenv
-
(و 3 أكثر)
موسوم في:
-
بعد أن تعرّفنا على طريقة تمرير قيم المُتغيّرات من بايثون إلى ملفّات HTML أصبح بإمكاننا أن نستعمل قاعدة بيانات تحتوي على جدول للمقالات عوضا عن استعمال قائمة أو قاموس. ما هي قاعدة البيانات قاعدة البيانات ببساطة مخزَن للبيانات المُختلفة كأسماء المستخدمين، كلمات المرور، وباقي القيم التي يُمكن أن تحصل عليها ممن يستخدم تطبيقك، ويُمكن كذلك جلب، تعديل وحذف البيانات منها بسهولة. يُمكن أن تكون قاعدة البيانات عبارة عن ملفّ نصي بسيط، بحيث يمثل كل سطر منه قيمة مُستقلّة، ويُمكن أن تكون عبارة عن جدول، بحيث يكون لهذا الجدول أعمدة وخانات، في كلّ عمود نوع محدد من القيم، وفي كلّ خانة القيمة الخاصّة بهذا النوع. سنستخدم في هذا الدّرس نظام SQL لقواعد البيانات، وهو نظام يعتمد على الجداول، وسنستخدم في هذا الدّرس جدولا لتخزين المقالات كالتّالي: رقم المُعرّف عنوان المقال مُحتوى المقال 1 عنوان المقال الأول مُحتوى المقال الأول 2 عنوان المقال الثّاني مُحتوى المقال الثّاني بنية تطبيق "مدونتي" سنعمل في هذا الدّرس على بناء تطبيق مُتكامل يُمكن أن يعمل كنظام إدارة مُحتوى بسيط، ستكون بنية التّطبيق كالآتي: الصّفحة الرّئيسيّة: هنا تُعرض عناوين ومحتويات المقالات المُتواجدة في قاعدة البيانات، بالإضافة إلى زر لحذف كل مقال. صفحة المقال: هنا ستتمكن من قراءة المقال مع رابط تحت المقال لحذفه. إضافة مقال جديد: ستتمكّن من إضافة مقال جديد إلى قاعدة البيانات في الصّفحة الرّئيسيّة مباشرة بعد عرض المقالات الموجودة. وهذه صور للتّطبيق النّهائي: الصفحة الرئيسية صفحة المقال إنشاء قاعدة البيانات وإنشاء جدول المقالات سنستعمل في الدّرس قواعد البيانات Sqlite لإدارة قاعدة البيانات، وذلك لسهولة التّعامل معها وسهولة نقل ملفّات قاعدة البيانات إلى أجهزة أخرى، كما أنّها لا تعمل على خادوم كما هو الحال مع MySQL أو Postgresql. تنويه: من المُفضّل عدم استخدام Sqlite في التّطبيقات التي ستنشرها على الأنترنت أو المشاريع الرّسميّة، ومن المُفضّل استخدام Postgresql أو MySQL في هذه الحالة. سننشئ قاعدة بيانات باسم database. في قاعدة البيانات هذه سنضيف جدولا للمقالات باسم posts. سيتكون جدول المقالات من ثلاثة أعمدة: رقم المقال/المعرّف (ID) عنوان المقال (Title) مُحتوى المقال (Content) لإنشاء قاعدة البيانات وجدول المقالات يُمكنك تنفيذ الشيفرة التّالية، ضعها داخل ملفّ باسم create_db.py وقم بتنفيذه: # -*- coding: utf-8 -*- import sqlite3 # الاتّصال بقاعدة البيانات db = sqlite3.connect('database.db') # إنشاء مُؤشّر في قاعدة البيانات لنتمكّن من تنفيذ استعلامات SQL cursor = db.cursor() # إنشاء الجدول cursor.execute(""" CREATE TABLE posts( id INTEGER PRIMARY KEY, title CHAR(200), content TEXT )""") # إدخال القيم إلى الجدول cursor.execute('''INSERT INTO posts(title, content) VALUES(?,?)''', (u'عنوان المقال الأول', u'محتوى المقال الأول')) cursor.execute('''INSERT INTO posts(title, content) VALUES (?,?)''', (u'عنوان المقال الثّاني', u'مُحتوى المقال الثّاني')) # تطبيق التغييرات db.commit() لاحظ بأنّنا نستدعي الوحدة sqite3 في البداية، وذلك لتنفيذ شيفرة لغة SQL، والشيفرة الممرّرة كمُعاملات للدّالة execute هي شيفرة SQL خاصّة بقاعدة البيانات Sqlite. بعد تنفيذ الشيفرة سنحصل على ملف database.db وهو الذي سيكون قاعدة بيانات التّطبيق، يوجد داخل قاعدة البيانات جدول مقالات باسم posts يحتوي بدوره على 3 أعمدة (رقم مُعرّف المقال، عنوان المقال ومحتواه)، مُعرّف المقال سيزيد بواحد تلقائيّا في كلّ مرّة نُضيف فيها عنوانا ومحتوى جديدين وهذا لأنّه من النّوع PRIMARY KEY، ما يعني بأنّنا نستطيع توفير قيمتين فقط دون الاهتمام بخانة رقم المعرّف. نضيف بعد ذلك مقالين: المقال الأول: عنوانه "عنوان المقال الأول"، مُحتواه "محتوى المقال الأول" المقال الثاني: عنوانه "عنوان المقال الثاني"، مُحتواه "محتوى المقال الثّاني" بعد الانتهاء من إضافة القيم، نقوم باستدعاء الدّالة commit لحفظ التّغييرات إلى قاعدة البيانات. الحصول على المقالات للحصول على رقم مُعرّف وعنوان ومحتوى المقالات يُمكننا تنفيذ الاستعلام التّالي: SELECT * FROM posts; النّجمة عبارة تعني all أو الكل. يُمكننا كذلك الحصول على قيم عمود واحد فقط: SELECT title FROM posts; ويُمكن الحصول على أكثر قيم أكثر من عمود: SELECT title, content FROM posts; لوضع القيم في مُتغيّر وإرجاعه في دالة في بايثون يُمكنك كتابة دالة كالتّالي: import sqlite3 BASE_DIR = path.dirname(path.realpath(__file__)) DB_PATH = path.join(BASE_DIR, 'database.db') def get_posts(): db = sqlite3.connect(DB_PATH) cursor = db.cursor() query = cursor.execute('''SELECT * FROM posts''') posts = query.fetchall() return posts السّطر الأول يستورد مكتبة sqlite3. السّطر الثّاني مسؤول عن الحصول على مسار المُجلّد الحالي، بعدها نقوم بإيصال مسار المُجلّد الحالي مع ملفّ قاعدة البيانات لنحصل على المسار الكامل للملفّ كقيمة للمُتغيّر DB_PATH، وهذا لتفادي بعض الأخطاء التّي قد تحدث عند نقل ملفّات التّطبيق إلى مكان آخر كاستضافة ما أو نظام تشغيل مُختلف. أما الدالة فتقوم أولا بالاتصال بقاعدة البيانات بالدّالة connect ومعامل DB_PATH الذي يُمثّل مسار ملف قاعدة البيانات database.db، بعدها نُنشئ مؤشّرا بالدّالة cursor، ثمّ ننفّذ الاستعلام كمُعامل مُمرّر للدّالة execute، بعدها نُطبّق الدالّة fetchall على نتيجة الاستعلام للحصول على القيم في قائمة من المجموعات، بحيث تحتوي القائمة على مجموعة بثلاثة عناصر العنصر الأول هو رقم المعرّف والعنصر الثّاني يمثّل عنوان المقال والعنصر الثّالث يمثّل محتوى المقال. وبالتّالي فإنّنا سنتمكن من الوصول إلى محتويات المقال كعناصر في مجموعة داخل قائمة، والقائمة تحتوي على العديد من المجموعات. قائمة المقالات ستكون كالتّالي: posts = [(1, u'عنوان المقال الأول', u'محتوى المقال الأول'), (2, u'عنوان المقال الثّاني', u'محتوى المقال الثّاني') ] ما يعني بأنّنا نستطيع الوصول إلى مُعرّف كل مقال، عنوانه ومحتواه بحلقة For بسيطة: posts = get_posts() for post in posts: post[0] # رقم المعرّف post[1] # عنوان المقال post[2] # محتوى المقال احفظ الدّالة get_posts في ملفّ باسم manage_db.py لنستعملها لاحقا كوحدة مع تطبيقنا (انظر درس الوحدات والحزم في لغة بايثون). الحصول على مقال حسب معرفه/رقمه للحصول على مقال حسب رقم مُعرّفه يكفي أن نُضيف جملة WHERE إلى استعلام SQL: SELECT title, content FROM posts WHERE id=1 ستُمكّننا الجملة أعلاه من الحصول على عنوان ومحتوى المقال الذي يمتلك رقم المُعرّف 1. لاستغلال الأمر في لغة بايثون بمُساعدة وحدة sqlite يُمكننا أن نكتب دالة باسم get_post_by_id لنحصل على مقال حسب رقم مُعرّفه، وبالطّبع سيكون للدّالة مُعامل واحد باسم post_id ليحمل قيمة رقم المُعرّف. def get_post_by_id(post_id): db = sqlite3.connect(DB_PATH) cursor = db.cursor() post_id = int(post_id) query = cursor.execute('''SELECT title, content FROM posts WHERE id=?''',(post_id,)) post = query.fetchone() return post بعد الاتّصال بقاعدة البيانات وإنشاء مؤشّر، نقوم أولا بتحويل قيمة رقم المُعرّف إلى عدد صحيح لأن الدّالة رقم المعرّف في قاعدة البيانات عبارة عن عدد صحيح. بعدها نُنفّذ الاستعلام الذي سبق وأن ذكرناه، لكن هذه المرّة قُمنا بتمرير مجموعة من عنصر واحد، وهذا العنصر هو مُعامل الدّالة، بعدها عرّفنا مُتغيّرا باسم post ليحمل بيانات المقال التي حصلنا عليها بتنفيذ الدّالة fetchone على الاستعلام، بعدها نُرجع المُتغيّر post. إذا استدعيت الدّالة مع تمرير قيمة بالعدد 1 فسيكون المُخرج كالتّالي: (u'عنوان المقال الأول', u'محتوى المقال الأول') أضف الدالة get_post_by_id إلى ملفّ manage_db.py واحفظه. حذف مقال حسب رقم المقال طريقة حذف المقال مُشابهة لطريقة الحصول عليه، فقط استبدل SELECT بالأمر DELETE. DELETE FROM posts WHERE id=? ما يعني بأنّنا نستطيع كتابة دالة في لغة بايثون لحذف مقال حسب رقم مُعرّفه: def delete(post_id): db = sqlite3.connect(DB_PATH) cursor = db.cursor() cursor.execute('''DELETE FROM posts WHERE id=?''', (post_id,)) db.commit() الاختلاف هنا هو أنّنا سنحتاج إلى تنفيذ الدّالة commit لتأكيد العمليّة. وكما العادة، أضف دالة الحذف إلى ملفّ manage_db.py. إضافة مقال تعرّفنا في بداية هذا الدّرس على طريقة إضافة مقال إلى قاعدة البيانات. INSERT INTO posts(title, content) VALUES('Title 1','Content 1') يُمكننا في بايثون إدخال قيم المُتغيّرات إلى قاعدة البيانات بالطّريقة التّالية: import sqlite3 db = sqlite3.connect('database.db') cursor = db.cursor() title_variable = 'Title 3' content_variable = 'Content 3' cursor.execute('''INSERT INTO posts(title, content) VALUES(?,?)''', (title_variable, content_variable)) db.commit() لا تنس أن تقوم باستدعاء الدّالة commit لتأكيد العمليّة. إذا قُمت بتنفيذ الشّيفرة أعلاه، وقُمت بعدها بتنفيذ الدّالة get_posts التي أنشأناها سابقا، ستتمكّن من رؤية القيمتين Title 3 و Content 3 كعنصرين من قائمة المقالات. لنضع هذه الشّيفرة في دالة باسم create لإضافتها إلى الوحدة manage_db، ستقبل الدّالة مُعاملين، مُعامل للعنوان، ومُعامل آخر لمُحتوى المقال. def create(title, content): db = sqlite3.connect('DB_PATH') cursor = db.cursor() cursor.execute('''INSERT INTO posts(title, content) VALUES(?,?)''', (title, content)) db.commit() الحصول على القيم وتمريرها إلى القالب بعد أن أنشأنا وحدة تحتوي على أربعة دوال تؤدّي أربعة أوامر أساسيّة: get_posts: الحصول على المقالات على شكل قائمة من المجموعات يُمكن الدّوران حولها get_post_by_id: الحصول على عنوان ومُحتوى مقال حسب رقم مُعرّفه delete: حذف مقال create: إنشاء مقال جديد إذا اطّلعت على الدّرسين السّابقين، ستعرف كيفيّة الحصول على قائمة المقالات وكيفيّة تقديمها في ملفّ HTML دون قراءة الجزء الموالي، لذا فمن الأفضل أن تُحاول ذلك الآن، وعد إلى هنا إذا واجهتك أية مشاكل. مبدأ التطبيق سيحتوي التّطبيق على 4 موجّهات: موجّه الصّفحة الرّئيسية / موجّه إضافة المقال create/ موجّه صفحة المقال الواحد <post/<post_id/ موجّه حذف المقال <delete/<post_id/ موجها إضافة المقال وحذفه لن يقدّما صفحة HTML بل سيُنفّذان دالّة وبعدها سيعيدان التّوجيه إلى الصّفحة الرّئيسيّة مباشرة. الصفحة الرئيسية ستحتوي الصّفحة الرّئيسية على عناوين ومحتويات المقالات لذا سنستخدم الدّالة get_posts من الوحدة manage_db في المُوجّه الرّئيسي ما يعني بأنّنا يجب علينا استدعاء الوحدة، كما سنُقدّم المقالات في ملفّ HTML باسم index.html. في ملّف app.py ضع ما يلي: # -*- coding:utf8 -*- from flask import Flask, render_template import manage_db app = Flask(__name__) # Home Page @app.route("/") def home(): posts = manage_db.get_posts() return render_template('index.html', posts = posts) if __name__ == "__main__": app.run(debug=True) لاحظ بأنّنا استدعينا الدّالة get_posts وأسندنا قيمتها إلى المُتغيّر posts وبعدها نُقدّم الملفّ index.html مع تمرير المُتغيّر posts. بما أنّ المُتغيّر الذي مرّرناه عبارة عن قائمة سنقوم بالدوران حول هذه القائمة والوصول إلى كل عنصر في المجموعة على حدة. الجزء المسؤول عن عرض المقالات في ملفّ index.html: {% for post in posts %} <a href="post/{{ post[0] }}"> <h2> {{ post[1] }} </h2> </a> <a href="delete/{{ post[0] }}"><span class="delete">حذف</span></a> <p> {{ post[2] }} </p> {% endfor %} الشّيفرة أعلاه هي الجزء المسؤول عن عرض المقالات فقط، وقد تجاهلت العناصر الأخرى التي لا تهمّنا مثل الشّعار والتّنسيق وغير ذلك. يُمكنك الحصول على ملفّ index.html كاملا من على Github بعد حلقة For سنحصل على مجموعة بثلاثة عناصر، العنصر الأول عبارة عن رقم مُعرّف المقال، وسنستخدمه لوضع رابطين للمقال، رابط عرض المقال ورابط حذفه، ما يعني بأنّنا نستطيع الوصول مثلا إلى المقال الأول كالتّالي: post/{{ post[0] }} => http://127.0.0.1:5000/post/1 ويُمكن حذفه بالرّابط التّالي: delete/{{ post[0] }} => http://127.0.0.1:5000/delete/1 الرّوابط لن تعمل حاليّا لأنّنا لم ننشئ المُوجّهات بعد. سيُعرض عنوان المقال داخل وسم h2 بالسّطر التّالي: <h2> {{ post[1] }} </h2> سيُعرض مُحتوى المقال داخل وسم p بالسّطر التّالي: <p> {{ post[2] }} </p> صفحة عرض المقال لعرض المقال الواحد، سنستخدم ملفّ HTML آخر وسنسمّيه post.html، أمّا الموجّه المسؤول عن تقديم هذا المقال فسيكون كالتّالي: موجّه post في ملفّ app.py: # Single Post Page @app.route("/post/<post_id>") def post(post_id): post = manage_db.get_post_by_id(post_id) return render_template('post.html', post = post) الشّيفرة أعلاه عبارة عن مُوجّه باسم post يقبل مُعاملا post_id لنتمكّن من تمريره كمُعرّف للمقال للدّالة get_post_by_id من الوحدة manage_db. بعدها نقوم باستدعاء الدّالة للحصول على بيانات المقال على شكل مجموعة يُمكننا أن نصل إلى عناصرها كالتّالي: post[0] # عنوان المقال post[1] # المحتوى صفحة post.html: <div class="main"> <h2> {{ post[0] }} </h2> <p> {{ post[1] }} </p> </div> <a href="{{ url_for('home') }}" class="back_to_home">عُد إلى الصّفحة الرّئيسيّة</a> في الشّيفرة أعلاه، نقوم بعرض عنوان المقال داخل وسم h2 ونقوم بعرض المُحتوى داخل وسم p. السّطر الأخير عبارة عن رابط لتمكين الزّائر من العودة إلى الصّفحة الرّئيسيّة و home اسم الدّالة المسؤولة عن تقديم الصّفحة الرّئيسية (الموجودة مُباشرة بعد الموجّه /). # Home Page @app.route("/") def home(): ... ملحوظة: نستطيع استخدام الدّالة url_for لتوليد روابط الموجّهات، وذلك بوضع اسم الدّالة كمعامل. مثال: لنفرض بأنّ لدينا مُوجّها باسم hello ودالة باسم hello_page، سنتمكّن من إنشاء رابط إلى الموجّه hello كالتّالي: <a href="{{ url_for('hello_page') }}">Link to Hello Page</a> يُمكن كذلك وضع عنوان المقال كعنوان للصّفحة داخل وسم title: <title>{{ post[0] }}</title> حذف مقال طريقة حذف المقال شبيهة بطريقة عرضه، الاختلاف هنا هو أنّنا سنستخدم الدّالة redirect لإعادة توجيه المُستخدم إلى الصّفحة الرّئيسية مُباشرة بعد حذف المقال. لاستخدام الدّالة redirect سيتوجّب علينا استيرادها من وحدة Flask في بداية الملفّ app.py. سنحتاج كذلك إلى الدّالة url_for للتوجيه إلى الرّابط الصّحيح. from flask import Flask, render_template, redirect, url_for موجّه delete سيقبل معاملا باسم post_id لتمريره إلى الدّالة delete من الوحدة manage_db لحذف المقال الذي يحمل رقم المعرّف المُمرّر. # Delete Post @app.route("/delete/<post_id>") def delete(post_id): manage_db.delete(post_id) return redirect(url_for('home')) لاحظ استخدام الدّالة redirect مُباشرة بعد حذف المقال، تقبل الدّالة مُعاملا بقيمة رّابط الصّفحة الرّئيسية والذي حصلنا عليه بالدّالة url_for، ما يعني بأنّنا نقوم بحذف المقال ثمّ توجيه المُستخدم مُباشرة إلى الصّفحة الرّئيسية. إنشاء مقال جديد تعرّفنا مُسبقا على طريقة الحصول على القيم من المستخدم بطريقة طلبات GET من عنوان URL كالآتي: /create?title=post1&content=content1 يُمكننا استخدام request للحصول على القيم كالتّالي: title = request.args.get('title') content = request.args.get('content') يُمكن استخدام هذه الطّريقة لإضافة مقال إلى قاعدة البيانات لكنّها ليست مُجديّة في هذه الحالة، لأنّنا نرغب بأن نُتيح للمُستخدم إرسال بيانات دون تعديل عنوان URL كما يجب علينا أن نُسهّل المأموريّة على المُستخدم العادي. لكي نحصل على العنوان والمُحتوى بطريقة أفضل، سنستخدم نماذج HTML أو HTML Forms، وسنستخدم طريقة POST عوضا عن GET. سنضع النّماذج في ملفّ index.html مُباشرة تحت الجزء المسؤول عن عرض المقالات. <h4>أضف مقالا</h4> <form action="{{ url_for('create') }}" method="POST"> <input class="input" type="text" name="title" placeholder="عنوان المقال"/> <br> <textarea name="content" class="input" rows="10" placeholder="مُحتوى المقال"></textarea> <br> <input type="submit" value="أضف" /> </form> في الوسم form نضع رابط الموجّه create داخل الصّفة action لنُخبر المُتصفّح بأنّ البيانات التّي سيُرسلها المُستخدم يجب أن تذهب إلى موجّه إضافة مقال جديد create. بعدها نُخصّص طريقة إرسال البيانات بوضع كلمة POST داخل الصّفة method. بعد ذلك ننشئ حقلا لعنوان المقال باسم title وحقل نصّ باسم content وبعدها نضيف زرّا لتأكيد الإرسال. بعد أن تملأ النموذج وتضغط على زر "أضف" سيُرسل المُتصفّح البيانات إلى الخادوم وسنتمكّن من الحصول عليها في المُوجّه create عبر الوحدة request، ما يعني بأنّنا سنحتاج إلى استدعاءها في بداية الملف. from flask import Flask, render_template, redirect, url_for, request سننشئ المُوجّه create مع تمرير مُعامل آخر باسم methods يحتوي على قائمة بعنصرين يُمثّلان الطريقتين GET وPOST لأنّ الإعداد الافتراضي هو GET فقط، نضع هذا العامل لكي نتمكّن من استقبال البيانات. @app.route("/create", methods=['GET', 'POST']) بعدها سنتمكّن من الحصول على البيانات وإدخالها إلى قاعدة البيانات كالتّالي: if request.method == 'POST': title = request.form['title'] # الحصول على عنوان المقال content = request.form['content'] # الحصول على مُحتوى المقال manage_db.create(title, content) # إدخال القيم إلى قاعدة البيانات لاحظ بأنّنا نضع شرطا للتأكّد من أن الطلب الذي يرسله المُتصفح من نوع POST. بعدها نحصل على القيم التي أدخلها المُستخدم في النّموذج الموجود بملفّ index.html عبر القاموس form المُتواجد داخل الوحدة request. وكما فعلنا مع الموجّه delete سنقوم بإعادة توجيه المُستخدم إلى الصّفحة الرّئيسية. return redirect(url_for('home')) الموجّه create كاملا: # Create Post Page @app.route("/create", methods=['GET', 'POST']) def create(): if request.method == 'POST': title = request.form['title'] content = request.form['content'] manage_db.create(title, content) return redirect(url_for('home')) أصبح التّطبيق كاملا الآن ويُمكنك مُشاركته مع العالم. يُمكنك إضافة تنسيق css خاصّ بك أو تحميل الملفّات الكاملة للتّطبيق من Github وإضافة ملفّ style.css إلى التّطبيق الذي أنشأته (يُمكنك كذلك تعديله). إذا كان لديك سؤال حول هذا الدّرس، يُمكنك وضعه في قسم الأسئلة والأجوبة. ختاما تعرّفنا على طريقة بناء تطبيق يتفاعل مع المُستخدم ويترك له حريّة الوصول إلى قاعدة البيانات، لكنك تُلاحظ بأنّ الحماية معدومة في التّطبيق، إذ يُمكن لأي شخص أن يحذف جميع المقالات دون أي حاجز (ككلمة مرور مثلا). سنتعلّم في الدّرس القادم على كيفيّة حماية التّطبيق وإتاحة الوصول إلى قاعدة البيانات لمُستخدم واحد فقط، بحيث يُسجّل دخوله إذا أراد حذف أو إضافة مقال، أمّا بقيّة المُستخدمين فلهم إمكانيّة القراءة فقط.
-
تعرّفنا إلى الآن على كيفيّة إنشاء تطبيق بسيط بلغة بايثون وإطار العمل Flask، يُمكن للتّطبيق الاتّصال بقاعدة بيانات، إضافة عناصر، عرضها وحذفها، لكنّ هذا التّطبيق لا يعتبر محميّا، إذ يُمكن لأي كان أن يُضيف مقالات أو يحذفها، لذا من المفضّل أن نقوم بإضافة نظام لتسجيل دخول بحيث تتمكّن وحدك (كمُدير للتّطبيق) من إجراء العمليّات الحسّاسة، بنهاية هذا الدّرس سنمتلك تطبيقا محميا باسم للمُستخدم وكلمة مرور، ولن يتمكّن أحد من حذف المقالات أو إضافتها إلّا إذا حصل على معلومات تسجيل الدّخول. مفهوم الجلسة في إطار العمل Flask الجلسة عبارة عن كائن يتصرّف تماما كقاموس يحتوي على مفاتيح وقيم، الجلسة تكون مرتبطة بنافذة المُتصفّح افتراضيّا، ما يعني بأنّ المفتاح والقيمة ستُسجّلان طيلة مدّة فتح المُتصفّح، وبمجرّد إغلاق نافذة المُتصفّح فإنّ الجلسة تُحذف (أو تدمّر). فكرة تسجيل الدّخول والخروج في درسنا ستكون كالتّالي: سنضع نموذج HTML في أعلى الصّفحة الرّئيسيّة، سيحتوي النّموذج على حقلين، حقل لكتابة اسم المُستخدم، وآخر لكلمة المرور مع زرّ لإرسال طلب الدّخول (أو طلب الاستيثاق Authentication)، عندما يُدخل المُستخدم كلمة "admin" في حقل اسم المُستخدم، وكلمة "password" في حقل كلمة المرور (يُمكن تغيير هذه المعلومات ببساطة)، سيتأكّد التّطبيق من أنّ البيانات صحيحة، وإذا كانت صحيحة فسنستخدم القاموس session المتواجد في حزمة flask لإنشاء مفتاح باسم logged_in وسنضع القيمة المنطقيّة True للمفتاح، بعدها يُمكننا أن نوجّه المُستخدم إلى الصّفحة الرّئيسيّة مع إخفاء نموذج تسجيل الدخول وعرض رابط لتسجيل الخروج عوضا عن النّموذج، أمّا إذا أدخل المُستخدم بيانات استيثاق خاطئة فسنوجّهه إلى الصّفحة الرّئيسيّة مع عرض نموذج تسجيل الدّخول مُجدّدا. تسجيل الدخول سنقوم أولا بإضافة نموذج HTML لتمكين الزّائر من إدخال بيانات الاستيثاق، سنضيف الشيفرة التّالية في ملفّ index.html مُباشرة بعد وسم body: <form action="{{ url_for('login') }}" method='POST'> <input type="text" placeholder="اسم المُستخدم" name="username"> <input type="password" placeholder="كلمة المرور" name="password"> <input type="submit" value="اُدخُل"> </form> الشيفرة أعلاه عبارة عن نموذج لحقلي اسم المُستخدم وكلمة المرور، سنتمكّن من الحصول على القيمتين في ملفّ app.py كالآتي: username = request.form['username'] password = request.form['password'] لاحظ بأنّنا حدّدنا مُوجّها باسم login في النّموذج، لذا فسيتوجّب علينا إنشاؤه في ملفّ app.py والأمر شبيه بما فعلناه في الدّرس السّابق، بحيث يقبل المُوجه طريقة POST لاستقبال البيانات التي يُرسلها المُتصفّح. سيكون موجّه login كالتّالي: # Login Route @app.route("/login", methods=['GET', 'POST']) def login(): if request.method == 'POST': username = request.form['username'] password = request.form['password'] if username == "admin" and password == "password": session['logged_in'] = True else: return redirect(url_for('home')) return redirect(url_for('home')) يستقبل الموجّه اسم المُستخدم وكلمة المرور ويُنفّذ جملة شرطيّة للتحقق من أنّ البيانات صحيحة، لنفرض بأنّ اسم المُستخدم هو admin وكلمة مروره هي password. إذا تحقّق الشّرط وتأكّدنا من أن البيانات التّي أدخلها المُستخدم صحيحة نقوم بإنشاء جلسة باسم logged_in ونعطيها القيمة المنطقيّة True ونقوم بعد ذلك بتوجيه المُستخدم إلى الصّفحة الرّئيسيّة، أمّا إن لم تكن البيانات صحيحة فنقوم بتوجيه المُستخدم إلى الصّفحة الرّئيسيّة دون إنشاء جلسة. لاستخدام الجلسات سيتوجّب علينا استيرادها من Flask في بداية الملفّ، ليُصبح السّطر كالتّالي: from flask import Flask, render_template, redirect, url_for, request, session سيتطلّب استخدام الجلسات تخصيص مفتاح سريّ كذلك، ويُمكن أن نقوم بذلك بإضافة السّطر التّالي مُباشرة بعد تعريف المُتغيّر app. app = Flask(__name__) app.config['SECRET_KEY'] = "Secret" تنبيه: من المهم أن تُغيّر Secret إلى مفتاح لا يُمكن التّنبؤ به ويجب أن يكون سريّا للغاية، يُمكنك استخدام دالة urandom من الوحدة os لتوليد سلسلة عشوائيّا كالتّالي: >>> import os >>> os.urandom(24) '\xee\x9dA\x81\x19\x17\xdd\x04\xae9\xc1\x1a-\xf2\xf8\xda\x9a\x99u\x90\x96]\xbaT' يُمكنك بعد ذلك استعمال السّلسلة المولّدة كقيمة للمفتاح السرّي. وهكذا سنكون قد انتهينا من نظام تسجيل دخول المُدير، الخطوة التّالية هي تسجيل خروجه، وذلك بتدمير الجلسة عند الوصول إلى المُوجّه logout. تسجيل الخروج لتسجيل الخروج يكفي إضافة مُوجّه باسم logout إلى الملفّ app.py، وسيكون دور الموجّه حذف المفتاح logged_in من الجلسة: # Logout Route @app.route("/logout") def logout(): session.pop('logged_in', None) return redirect(url_for('home')) لاحظ بأنّ الموجّه بسيط للغاية، كلّ ما يقوم به هو حذف المفتاح logged_in باستخدام التّابع pop وبعدها يعيد توجيه المُستخدم إلى الصّفحة الرّئيسيّة. الآن إذا قمت بتسجيل دخولك فسيُنشئ التّطبيق جلسة جديدة، أما إذا قمت بزيارة الموجّه logout عبر العنوان http://127.0.0.1:5000/logout فستُدمّر الجلسة. إضافة زر لتسجيل الخروج عوضا عن نموذج HTML رغم أنّنا قُمنا بإضافة نظام لتسجيل الدّخول والخروج إلا أنّ ذلك لا يظهر في الصّفحة، ولا يُمكن لنا أن نعرف تسجيل الدّخول من عدمه. سنقوم في هذا الجزء بتحويل الصّفحة الرّئيسيّة إلى صفحة مُتجاوبة، أي أنّنا سنتأكّد ممّا إذا كان المُستخدم قد سجّل دخوله، فإن كان ذلك صحيحا فسنعرض له زرّا لتسجيل الخروج، أمّا إن لم يكن قد سجّل دخوله فسنعرض نموذج HTML لتسجيل الدّخول. يُمكن القيام بالأمر بإضافة شرط للتأكّد من أنّ المفتاح logged_in موجود في الكائن session، والتّالي تطبيق للأمر في ملفّ index.html: {% if 'logged_in' not in session %} <form action="{{ url_for('login') }}" method='POST'> <input type="text" placeholder="اسم المُستخدم" name="username"> <input type="password" placeholder="كلمة المرور" name="password"> <input type="submit" value="اُدخُل"> </form> {% else %} <a class="logout" href="{{ url_for('logout') }}">خروج</a> {% endif %} يُمكن الآن التمييز بين تسجيل الدخول وتسجيل الخروج؛ الخطوة التّالية هي حماية الموجّهين create و delete لنمنع من لم يسجّل دخوله من حذف وإنشاء المقالات والسّماح بذلك للمُدير فقط. حماية الموجهين create و delete لحماية موجّه ما يجب أن نحمي الدوال التي التّي تقوم بالعمليّة، ما يعني بأنّنا يجب أن نحمي الدّالتين create و delete. وللقيام بالأمر يُمكن الاستعانة بميّزة المُزخرفات في لغة بايثون، يُمكنك الحصول على المزيد من المعلومات بالرّجوع إلى درس المُزخرفات. . سننشئ مُزخرفا باسم login_required، سنحتاج إلى المُزخرف wraps من الوحدة functools وهو مُزخرف مُساعد ويعتبر استعماله من أفضل المُمارسات. سيكون المُزخرف login_required كالتّالي: from functools import wraps def login_required(function): @wraps(function) def wrapper(*args, **kwargs): if 'logged_in' in session: return function(*args, **kwargs) else: return redirect(url_for('home')) return wrapper نقوم أولا باستدعاء wraps من الوحدة functools ونزخرف الدّالة بشرط أن يكون المفتاح logged_in في الجلسة، ما يعني بأنّ الدّالة ستُنفّذ إذا كان المُستخدم قد سجّل دخوله فقط، أمّا إن لم يكن قد سجّل دخوله فسنقوم بتوجيهه إلى الصّفحة الرّئيسيّة دون تنفيذ الدّالة. يُمكن الآن تطبيق المُزخرف على الدّالتين create و delete لمنع الزوار من إنشاء المقالات أو حذفها. لتطبيق المُزخرف يكفي كتابة اسمه مسبوقا بالرّمز @ مُباشرة فوق الدّالة. ما يعني بأنّ الموجّه create سيصبح كالتالي: # Create Post Page @app.route("/create", methods=['GET', 'POST']) @login_required def create(): if request.method == 'POST': title = request.form['title'] content = request.form['content'] manage_db.create(title, content) return redirect(url_for('home')) أما موجّه delete فسيصبح كالتّالي: # Delete Post @app.route("/delete/<post_id>") @login_required def delete(post_id): manage_db.delete(post_id) return redirect(url_for('home')) يمكنك تصفّح ملفّات الأمثلة على Github. ختاما إلى هنا نكون قد تعرّفنا على طرق بسيطة لحماية تطبيقنا ويُمكنك الآن نشر التّطبيق على الأنترنت، لكن تذكر بأنّ هذه الطّرق ليست آمنة بما فيه الكفاية، لذا إن كان تطبيقك حسّاسا فمن المُفضّل استعمال إضافة لإدارة أنظمة الاستيثاق مثل إضافة Flask-login أو Flask-Security.