عبدالهادي الديوري
الأعضاء-
المساهمات
368 -
تاريخ الانضمام
-
تاريخ آخر زيارة
-
عدد الأيام التي تصدر بها
22
نوع المحتوى
ريادة الأعمال
البرمجة
التصميم
DevOps
التسويق والمبيعات
العمل الحر
البرامج والتطبيقات
آخر التحديثات
قصص نجاح
أسئلة وأجوبة
كتب
دورات
كل منشورات العضو عبدالهادي الديوري
-
مقدّمة بعد أن تعرّفنا على كيفيّة التّعامل مع قاعدة بياناتنا لإجراء العمليّات الشّائعة مثل الإضافة، القراءة والحذف في سلسلة مدخل إلى إطار العمل Flask فقد حان الوقت للانتقال إلى مبدأ آخر من مبادئ تطوير الويب، ألا وهو كيفيّة التّحقق من مُدخلات المُستخدم، وسنتعرّف معا في هذا الدّرس على كيفيّة استغلال كل ما تعلّمناه لنُمكّن المُستخدم الذي يستعمل تطبيقنا من إرسال بياناته إلى التّطبيق بطريقة آمنة، ومن ثمّ ستتمكّن أنت من استغلال ما ستتعلّمه لإدخال هذه البيانات إلى قواعد بيانات تطبيقاتك، وخلال هذا المشوار سنعرّف على كيفيّة مُعالجة نماذج HTML ليُرسلها المستخدم إلى الخادوم عن طريق صفحة HTML، وذلك باستخدام كل من مكتبة WTForms و إضافة Flask-WTF التّي تُسهّل لنا التّعامل مع المكتبة، والغرض من كل هذا هو التّعرف على كيفيّة إنشاء وإدارة النّماذج لاستعمالها في مختلف الصّفحات في تطبيقاتك (صفحة تسجيل الدّخول، صفحة تسجيل مستخدمين جدد، صفحة إضافة مقال …). المُتطلّبات يُعتبر هذا الدّرس مُقدّمة لسلسلة جديدة حول كيفيّة إدارة نماذج HTML عند استخدام لغة بايثون وإطار العمل Flask لبناء تطبيقات الويب، لذا فسيتوجّب عليك فهم أساسيّات إطار العمل وكذا فهم آليّة عمل نماذج HTML أو HTML Forms. إن لم تكن لك خبرة في التّعامل مع إطار العمل Flask فأنصحك بهذه السّلسلة من الدّروس. لماذا WTForms قد تتساءل عن السّبب الذي سيدفعنا لاستخدام مكتبة WTForms لإدارة نماذج HTML في تطبيقنا، ففي السّابق، كنّا نقوم بإنشاء نموذج HTML ونستخدم المُساعد request المُتواجد بإطار العمل Flask للحصول على البيانات التّي يُرسلها المُستخدم. فكنّا مثلا نقوم بالحصول على اسم المُستخدم وكلمة مروره ثمّ نتحقّق من أنّ ما يُرسله يُطابق المعطيات الصّحيحة. هذا مثال على موجّه تسجيل الدّخول بالطّريقة السّابقة: @app.route('/login', methods = ['POST']) def login(): username = request.form['username'] password = request.form['password'] if username == 'admin' and password == 'admin': # login the user ... return redirect(url_for('index')) هذه الطّريقة تعمل بشكل جيّد، لكنّها ليست آمنة تماما، إذ أنّنا لا نتحقّق من البيانات التّي يُرسلها إلينا المُستخدم. في حالة تسجيل دخول مستخدم ما، التّأكد من أنّ البيانات آمنة أو أنّها تُحقّق شروطا معيّنة أمر غير ضروري في الحقيقة لأنّنا لا نعتمد على بيانات المُستخدم سوى عند استعمال شرط منطقي، لكن ماذا لو كانت البيانات التّي يُرسلها المُستخدم بيانات تُضاف إلى قاعدة البيانات، هنا التّأكّد من أنّها تستوفي شروطا معيّنة أمر ضروري جدّا. إليك أحد أخطار عدم التّأكد من أنّ البيانات التّي يُرسلها المُستخدم آمنة قبل إدخالها إلى قاعدة البيانات، لنقل بأنّك تُتيح التّسجيل لزوار الموقع، عبر منحهم إمكانية إدخال اسم مُستخدم، بريد إلكتروني وكلمة مرور، رغم أنّ المُستخدمين عادة ما يوفّرون مدخلات معقولة إلّا أنّ عدم التّحقق منها قد يؤدي إلى قاعدة البيانات ممتلئة أو بيانات تخريبيّة بإرسال كم هائل من النّصوص كقيم للحقول الثّلاثة مثلا (اسم المُستخدم، كلمة المرور، البريد الإلكتروني)، ولو عرضت اسم المُستخدم في أحد صفحات التّطبيق فسيُخرّب الاسم الطّويل الصّفحة وستبدو غير متناسقة؛ هذا جانب من العواقب التّي قد تحدث إن لم تتحقّق من مُدخلات المُستخدم. يُمكنك بالطّبع أن تستعمل لغة بايثون لتأكيد ومعالجة قيمة كل حقل على حدة قبل إضافتها إلى قاعدة البيانات، وربّما ستستطيع تأمين مُدخلات المُستخدم بشكل أفضل من ذي قبل، لكنّ الأمر ممل ويتطلّب الكثير من الوقت، بالإضافة إلى أنّ تأكيد ومعالجة قيمة كل حقل بنفسك قد لا يكون خيارا جيّدا لأنّه قد يُؤدي إلى بعض الثغرات الأمنية وقد ترتكب أخطاء خطيرة يصعب مُلاحظتها في أول وهلة، لذا من الأفضل أن نعتمد على مكتبة معروفة وموثوقة للقيام بهذا الأمر، وهنا يأتي دور مكتبة WTForms . تُوفّر مكتبة WTForms طريقة بسيطة لإنشاء نماذج HTML وتأكيدها قبل أن تصل إلى الخادوم لنتمكّن من إرسالها إلى قاعدة البيانات، على سبيل المثال، توفّر لنا المكتبة إمكانيّة التأكّد من أنّ قيمة حقل تحتوي على عدد معيّن من الأحرف، أو تأكيد حقل بريد إلكتروني أو أنّ قيمة حقل تُساوي قيمة حقل آخر (عند تأكيد كلمة المرور مثلا). هناك بالطّبع مكتبات أخرى تؤدّي نفس الغرض (كمكتبة Deform)، لكنّنا اخترنا هذه المكتبة بالذّات لأنّ لها إضافة تُسهل علينا ربطها بالتطبيقات المكتوبة بإطار العمل فلاسك، بالإضافة إلى أنّها خيار شائع بين المُطوّرين الذين يستخدمون إطار العمل هذا، ما يعني بأنّ تعلّم كيفيّة التّعامل مع المكتبة سيُساعدك على فهم معظم المشاريع مفتوحة المصدر. الحماية من هجمات CSRF بالإضافة إلى مُساعدتنا على تأكيد البيانات وتقييدها بشروط خاصّة، فمكتبة WTForms تحمينا من هجمات CSRF (اختصار لـ Cross-site request forgery)، وترجمته ‘تزوير الطلب عبر المواقع’، وهي نوع معيّن من الهجمات تُمكّن المخترق من القيام بأمور خطيرة متنكّرا على هيئة المُستخدم الضّحيّة (الذي قد سجّل دخوله بتطبيقك)، وقد استخدمت هذه الهجمة من قبل لتحويل أموال بشكل غير شرعي من طرف مُخترق لأحد الأبناك في السّابق، وظهرت هذه الثّغرة من قبل في موقع Youtube ما خوّل للمُخترق بالقيام بجميع العمليّات التّي يُمكن أن يقوم بها المُستخدم الذي سجّل دخوله، ويُمكنك زيارة صفحة ويكيبيديا عن الهجمة لمزيد من المعلومات. الحل الأكثر شيوعا للحماية من هذا النّوع من الهجمات هو إرسال مفتاح مخفي مع البيانات التّي يُرسلها المُستخدم، بحيث أنّه إن أرسل المُخترق طلبا غير شرعيا فلن يُرسل معه المفتاح المخفي، لذا فلا يُمكن أن تمرّ البيانات إلى التّطبيق لأنّها لا تعتبر صحيحة، وبالتّالي فلا يُمكن أن تُستقبل لتُرسل إلى قاعدة البيانات، وهكذا سيكون الإجراء آمنا. الهجمة خطيرة وعليك الحذر منها عند تطويرك لتطبيقات الويب، لكن لا تقلق، فمكتبة WTForms تُوفّر لنا طريقة بسيطة لحماية تطبيقنا من هذا النّوع من الهجمات، إذ تعتمد على الحل الذي ذكرته مسبقا بحيث تُحدّد رمزا مخفيا في بداية النّموذج ليُرسل مع طلب المُستخدم في كل مرّة. التّحقق من المُدخلات بالواجهة الأماميّة أم الواجهة الخلفيّة؟ بما أنّ التّحقق من مُدخلات المُستخدم هو الاستخدام الشّائع لمكتبة WTForms (للتحقق مثلا من أن المدخل على شكل بريد إلكتروني وليس أية سلسلة نصيّة أخرى)، فمن المُفيد أن تُدرك بأنّك تستطيع التّحقق من المُدخلات على جهتين أثناء تطوير تطبيقاتك، ومن المهم أن تتبّع الأسلوب الصّحيح، لذا فهذا القسم يُغطي جزءا ممّا يجب عليك معرفته. الواجهة الأماميّة أو جهة العميل Client side مُصطلح يهم كل ما يحدث في مُتصفّح المُستخدم، وعادة ما تتم مُعالجة البيانات فيه باستخدام لغة جافاسكربت أو ميّزات HTML5 بحيث نستطيع مثلا أن نتأكّد من أنّ المُدخل المطلوب لا يُرسَل فارغا أو أنّه بطول مُعيّن وأمور أخرى. أمّا في جهة الخادوم، فالتّحقق من المُدخلات يتم باستخدام لغة بايثون، إمّا عبر كتابة الشّيفرة التّي تقوم بالتّحقق بنفسك أو بالاعتماد على مكتبات مثل مكتبة WTForms. وقد قرّرت التّطرق إلى هذا الأمر لأنّ البعض يعتمد كليا على التّحقق من البيانات بجهة العميل فقط، وبما أنّ الخادوم يستقبل المُدخلات دون التّحقق منها فهذا يعني بأنّ هناك إمكانيّة الاحتيال على المُتصفّح لإرسالها دون التّحقق منها. عندما تتحقّق من المُدخلات باستعمال جافاسكربت فقط فالأمر مُجد في كثير من الأوقات، لكن ماذا لو عطّل المُستخدم جافاسكربت على مُتصفّحه؟، ويُمكنك كذلك استخدام الميّزات الجديدة في HTML5 للتّحقق من أنّ المُدخلات صحيحة، وهذه الطّريقة يسهل الاحتيال عليها كذلك، لذا فأفضل حل هو أن تقوم بالتّحقق من مُدخلات المُستخدم في جهة الخادوم لأنّها الأكثر أمانا، وهذا يرجع إلى كون البيانات التّي نستقبلها من المُستخدم تُدخل إلى قاعدة البيانات على جهة الخادوم لذا يجب أن توقف هناك، وبالطّبع تستطيع استعمال الميّزات الجديدة في لغة HTML5 (وهذا ما أنصح به) أو أحد أطر العمل المكتوبة بلغة جافاسكربت للتّحقق من مُدخلات المُستخدم، لكن تذكّر فقط بأنّ الجهة الخلفيّة (استخدام مكتبة WTForms في حالتنا هذه) هي الأهم والأكثر أمانا. إضافة Flask-WTF لاستخدام مكتبة WTForms، سنلجأ إلى إضافة خاصّة بها لإطار العمل فلاسك، وتُسمى هذه الإضافة Flask-WTF اختصارا لـ Flask-WTForms ، لذا تأكّد من أنّ البيئة الوهميّة مُفعّلة ونفّذ الأمر التّالي لتنصيب الإضافة (والمكتبة كذلك): pip install Flask-WTF لن نستخدم الإضافة كثيرا ولن تجد استخداما لها في المشاريع التي تعتمد على إطار العمل فلاسك في معظم الحالات والاعتماد سيكون على مكتبة WTForms أكثر من الإضافة، إذ سنعتمد على الإضافة لنسترد منها صنفا واحدا فقط باسم FlaskForm لترث منها الأصناف الأخرى التّي ستُمثّل نماذج HTML، وهذا لأنّ WTForms يعتمد على الأصناف في لغة بايثون لإنشاء نماذج HTML بحيث يعبّر كل صنف عن نموذج معيّن وخصائص الصّنف هي التّي تُمثّل الحقول المُختلفة (حقل كلمة المرور، حقل البريد الإلكتروني، حقل الاختيارات المُتعدّدة …) وسنتطرّق إلى هذا الأمر بتفصيل أكثر فيما بعد. استخدام مكتبة WTForms مع أطر العمل الأخرى إنّ استخدام WTForms أمر شائع بين مُطوري الويب الذين يستخدمون أطر العمل الصّغيرة مثل Flask و Bottle و Pyramid، وحقيقة أنّ هذه السّلسلة من الدّروس موجّهة إلى مُطوري الويب الذين يستخدمون إطار العمل Flask لا تعني بالضّرورة بأنّك لن تستفيد من هذا الدّرس إذا كنت تستعمل أحد أطر العمل الأخرى، وقد قرّرت أن أجعل الأمثلة بسيطة جدا ليتمكّن الكل من فهم كيفيّة العمل المبدئيّة، كما أنّ الهدف من هذه السّلسلة هو تقريب مفهوم التحقق من بيانات المُستخدم إلى أكبر شريحة ممكنة من المُطورين لتوفير حماية أفضل لتطبيقاتهم، وبالتّالي فهي سلسلة مُستقلّة تماما عن أي سلسلة دروس أخرى، رغم أنّني أنصحك بقراءة سلسلة مدخل إلى إطار العمل فلاسك قبل الشّروع في هذه السّلسلة. تحديد مفتاح سري إن قرأت الدرس الذي شرحت فيه كيفيّة إنشاء نظام بسيط لتسجيل دخول وخروج المُستخدمين ، فقد كان علينا تحديد مفتاح سري لجعل الجلسة أكثر أمانا بحيث يصعب على المُخترق الوصول إليها، وبما أنّ توفير مفتاح سري أمر ضروري في مُعظم الأوقات (لأنّ مُعظم التّطبيقات تعتمد على الجلسة على كل حال)، فإنّ الإضافات التّي تحتاج إلى سلسلة عشوائيّة من الأحرف لاستخدامها لتوليد سلاسل أخرى مُشفّرة تلجأ إلى الإعداد SECRET_KEY. عادة ما يُسمى المفتاح السرّي مِلحًا Salt في مجال التّشفير، ويعمل على جعل السّلسلة المُولّدة ثابتة إن كان المفتاح السّري هو نفسه، فإن تغيّر المفتاح السّري (أو الملح) فإنّ فك التّشفير غير ممكن، لذا يجب أن يكون المفتاح السّري سريّا جدّا بحيث لا يُمكن توقّعه، كما يجب ألّا تضعه في مكان مكشوف في شيفرتك البرمجيّة، وتأكّد من ألّا تضع نفس المفتاح السّري الذي تستخدمه في تطبيقك على مواقع مثل Github و Pastebin وغيرها، فإن وصل أي شخص إلى مفتاحك السّري الذي تستخدمه لإدارة جلسات مُستخدميك فقد تُسرق بياناتهم أو تُخرّب. هذه المرّة، لن نحتاج إلى المفتاح السّري من أجل الجلسة، بل ستستعمله إضافة Flask-WTF لتوليد المفتاح الخفي الذي تحدّثنا عنه مُسبقا، وتستخدمه بعض الإضافات الأخرى لأغراض التّشفير كذلك، لذا فمن المُهمّ أن تُبقيّه غير قابل للتّنبّؤ. ولتوليد مفتاح سري جيّد، يُمكنك استخدام الدّالّة urandom من الوحدةos على مُفسّر بايثون كما يلي: >>> import os >>> os.urandom(16) ' T\x1f\x85\x9b\xfe^\x0f\x14\x9f\xa3x\xa4\xb5\x92\xbe' كما تُلاحظ، قمنا بتوليد سلسلة عشوائيّة من الصّعب توقّعها، وبالطّبع فعندما تُنفّذ أنت السّطر أعلاه، فستحصل على سلسلة أخرى غير السّلسلة التّي حصلت عليها. الآن كلّ ما عليك فعله هو نسخ السّلسلة النّصيّة التّي حصلت عليها ووضعها في الإعداد SECRET_KEY الخاص بتطبيق فلاسك الذي تعمل عليه، والتّالي مثال على كيفيّة إضافة هذا الإعداد. المهم أن تتأكّد من أنّ السّطر التّالي متواجد مُباشرة بعد تعريف الكائن app: app.config['SECRET_KEY'] = 'T\x1f\x85\x9b\xfe^\x0f\x14\x9f\xa3x\xa4\xb5\x92\xbe' والتّالي هو ما أعنيه بتعريف الكائن app: app = Flask(__name__) وهو نفس الكائن الذي تستعمله لتحديد موجّهات التّطبيق الأساسيّة. مُلاحظة: إن حاولت استخدام WTForms دون توفير مفتاح سري فستحصل على خطأ نصّه كما يلي: “Exception: Must provide secret_key to use csrf.”. خاتمة تعرّفنا في هذا المقال على أهم المفاهيم الأساسيّة التّي يتوجّب عليك الإلمام بها لاستكمال مشوارك في تطوير الويب، فقد تعرّفنا على الأسباب التّي ستجعلك تتحقّق من مُدخلات المُستخدم، ولمَ يُفضّل استخدام مكتبة مثل WTForms عوضا عن القيام بذلك بنفسك، وتعرّفنا كذلك على هجمة CSRF التّي تعتبر واحدة من الهجمات التّي يُمكن أن يتعرّض لها تطبيقك في أي وقت، وقد كان هذا الدّرس تمهيدا بسيطا لسلسلة من الدّروس حول التّحقق من مُدخلات المُستخدم باستخدام مكتبة WTForms.
-
مُقدّمة: بعد أن أعددنا السّند الخلفي لتطبيقنا، حان الوقت لإعداد النّظام الأمامي لتحسين تجربة الاستخدام، سنقوم في هذا الدّرس بإنشاء ملفّ HTML ليحتوي على حقل يُمكن المُستخدم من لصق الرّابط إليه، وسنستخدم لغة جافاسكربت لإرسال الرّابط إلى تطبيق Flask واستقبال الرّابط المُختصر، بعدها سنقوم بطرح الرّابط المُختصر مع إمكانيّة نسخه عن طريق الضّغط على زر واحد. مُتطلّبات هذا الجزء لتتمكّن من فهم هذا الدّرس بشكل كامل، ستحتاج إلى معرفة جيدة بلغة جافاسكربت وخاصّة تقنيّة Ajax وكيفيّة إرسال واستقبال طلبات HTTP باستعمال خاصيّة XMLHttpRequest، ومن المُفضّل إلقاء نظرة على الموضوع قبل إكمال قراءة هذا الدّرس، ويُمكنك أن تتعلّم اللغة هنا على موقع الأكاديميّة. وتجدر الإشارة إلى أنّك ستحتاج إلى معرفة بسيطة بأداة git لتتمكّن من التّعامل مع منصّة Heroku لخدمات الاستضافة السّحابيّة. دروس جافاسكريبت دروس git الهدف من هذا الجزء التّطبيق حاليا يعمل بشكل جيّد، يُمكنك زيارة رابط والحصول على المُعرّف الخاص وإضافته كلاحق لاسم النّطاق، وهكذا ستحصل على رابط مُختصر، صحيح بأنّ التّطبيق يختصر الرّوابط لكنّ طريقة العمل ليست ملائمة للمُستخدم العادي، ما سنقوم به في هذا الجزء هو تنسيق وإعداد ما يراه المُستخدم لجعل اختصار الرّوابط أكثر سهولة، ويُمكنك الاطّلاع على التّطبيق كاملا عبر هذا الرّابط إعداد ملفّ HTML سنقوم باستخدام ملف HTML واحد باسم index.html بداخل مُجلّد templates وسنربطه بملفّ جافاسكريبت باسم index.js وملفّ css باسم style.css اللذان سيكونان بداخل مجلّد static. ما يعني بأنّ الملفّات والمجلّدات ستكون كالتالي: ├── app.db ├── app.py ├── create_db.py ├── static │ ├── css │ │ └── style.css │ └── js │ └── index.js └── templates └── index.html لتقديم ملفّ HTML سنقوم بتعديل الموجّه الرّئيسيّ ليُصبح كالآتي: @app.route('/') def index(): return render_template("index.html") بعدها سنُضيف الشيفرة التّالية إلى ملفّ index.html: <!DOCTYPE html> <html > <head> <meta charset="UTF-8"> <title>قصّرتك</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}"> </head> <body> <header> <h1>قصّرتُك</h1> <h3> خدمة تقصير الروابط الأكثر بساطة </h3> </header> <div id='bg-box'> <input name="link" type='text' id='input' placeholder='ضع رابطك هنا لتقصيره' onchange='change()'> <input type='text' id='result' value='الرّابط المُختصر هنا'> <a id='cp' href='#' onclick='copy()'>انسخ</a> <span id='msg' href='#'></span> </div> <script src="{{ url_for("static", filename="js/index.js") }}" > </script> </body> </html> لاحظ بأنّنا جلبنا ملف style.css في أعلى الملف وindex.js في أسفله. ستحتوي الصّفحة على حقل ليضع فيه المُستخدم رابطه (سمّيناه link)، وحقل لعرض النّتيجة (أي الرّابط المُختصر)، ثم أضفنا رابطا ليعمل كزر لنسخ الرّابط المُختصر وكذلك وضعنا وسم span لنعرض عليه رسالة النّجاح (أو الإخفاق) في نسخ الرّابط. لاحظ كذلك بأنّنا نستدعي دالتين من ملفّ جافاسكربت هما change() و copy() مُباشرة بعد حدثي onchange و onclick على التّوالي. الأولى مسؤولة عن إرسال طلب لاختصار الرّابِط والأخرى لتمكين المُستخدم من نسخ الرّابط المُختصر. إرسال واستقبال الطّلبات من وإلى تطبيق Flask بالاستعانة بلغة جافاسكريبت سنستعمل تقنيّة AJAX لإرسال طلب اختصار الرّابط إلى تطبيق Flask فور استقباله من المُستخدم عبر حقل input الأول المتواجد في الصّفحة الرّئيسيّة وعند استقبال الإجابة (والتي ستكون عبارة عن مُعرّف الرّابط في قاعدة البيانات مُشفّرا)، سنتعمد على تقنيّة DOM لتعديل مُحتويات الصّفحة وعرض النّتيجة إلى المُستخدم. مُحتوى ملفّ جافاسكربت سيكون كالآتي: function request(url,b,method,data,c){ request = new XMLHttpRequest; request.open(method||'get',url); request.onload=b; request.send(data||null) result = document.getElementById("result"); result.value = 'انتظر رجاء...' } function callback(e){ short_link = document.getElementById("result"); short_link.value = window.location.host + this.response } function change() { url = document.getElementById("input").value; request('shorten?link=' + url, callback, 'get') } // الدّالة المسؤولة عن نسخ الرّابِط المُختصر إلى لوحة مفاتيح المُستخدم function copy() { result = document.getElementById("result"); msg = document.getElementById("msg"); result.select(); try { var copy = document.execCommand('copy'); if(copy) msg.innerHTML = 'نُسخَ الرّابِط بنجاح!'; else msg.innerHTML = 'هناك خطأ ما! أعد المُحاولة رجاء'; } catch(err) { msg.innerHTML = 'المُتصفّح لا يدعم هذه العمليّة'; } } الدّالة request مسؤولة عن تنفيذ طلب XMLHttpRequest، حيث تستقبل الرّابِط الأصلي ثمّ تقوم الدّالة callback باستقبال الجواب (أي المُعرّف الخاصّ بالرّابط)، ثمّ تقوم بإسناده إلى العنصر الذي يحمل المُعرّف result في صفحة HTML (أي حقل النّتيجة). لاحظ بأنّنا استخدمنا الخاصيّة window.location.host للحصول على اسم النّطاق، وذلك لمرونة أكثر بحيث لا نضطر إلى تغييره يدويا كلّما غيّرنا نطاق التّطبيق أو المُستضيف، والأمر مُفيد كذلك إذا أردت استخدام أكثر من اسم نطاق واحد. الدّالة copy تقوم بالحصول على قيمة النّتيجة (أي الرّابط المُختصر) ثمّ تعتمد على التّابع select لتضليل النّتيجة، وبعدها نقوم بمُحاولة تنفيذ الأمر copy بالسّطر: var copy = document.execCommand('copy'); فإن لم يكن المُتصفّح يدعم هذه العمليّة (إمّا لقِدَمِ إصداره أو لسبب آخر) فستُعرَض للمُستخدم رسالة تُفيد بأنّ هذه العمليّة غير ممكنة لكي ينتبه وينسخه يدويّا. نشر التّطبيق على منصّة Heroku. الآن وقد أصبح تطبيقنا كاملا ويعمل بشكل جيّد في بيئة التّطوير (المضيف المحلي) حان الوقت لرفعه إلى منصّة Heroku ونشره للعالم ليتمكّن الجميع من استخدامه. لكن قبل ذلك يجب أن نقوم ببعض التّعديلات. أولا غيّر خيار تصحيح الأخطاء، بتعديل السّطر التّالي: app.run(debug=True, host='0.0.0.0') إلى ما يلي: app.run(host='0.0.0.0') وبما أنّ Heroku يعتمد على نظام Postgresql لإدارة قاعدة البيانات فيجب علينا تغيير رابط قاعدة البيانات. الجميل في منصّة Heroku هي إمكانيّة الوصول إلى رابط قاعدة البيانات عن طريق مُتغيّر بيئة، وهذا المُتغيّر يُسمى DATABASE_URL وللوصول إى قيمته يكفي الاعتماد على الوحدة os مع الكائن environ وطريقة الوصول إلى قيمة هذا المُتغيّر هي كالآتي: os.environ['DATABASE_URL'] لكن لا تنس أن تسترد الوحدة os في أعلى الملف: import os وبالتّالي فالتّغيير الذي يجب علينا القيام به هو تعويض السّطر الأول بالسّطر الثّاني في الشّيفرة التّاليّة: app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db' app.config['SQLALCHEMY_DATABASE_URI'] = os.environ['DATABASE_URL'] سنقوم بتنصيب حزمة psycopg2 ليتمكّن SQLAlchemy من التّعامل مع قواعد بيانات Postgresql وسنستخدم خادوم gunicorn لنشر التّطبيق، لذلك عليك تنصيبه كذلك (تأكّد من أن البيئة الوهميّة لا تزال مُفعّلة): pip install psycopg2 gunicorn سيتوجّب علينا كذلك إنشاء ملفّ باسم requirements.txt لتعريف Heroku بمُتطلّبات التّطبيق من حزم ووحدات، وذلك ليقوم بتنصيبها عند النّشر. لن نقوم بإنشاء هذا الملفّ يدويا، وسنستعمل أداة pip لإنشاء الملفّ، تأكّد فقط بأنّك لا تزال تعمل تحت البيئة الوهميّة ثمّ نفّذ الأمر التّالي لإنشاء الملفّ: pip freeze > requirements.txt بعد انتهاء التّنفيذ سيظهر ملفّ جديد باسم requirements.txt وستكون مُحتوياته شيئا مُشابها لما يلي (ربّما تتغيّر أرقام الإصدارات أو قد تظهر حزم جديدة حسب تاريخ تطبيقك لهذا المشروع): click==6.6 Flask==0.11.1 Flask-SQLAlchemy==2.1 gunicorn==19.6.0 hashids==1.1.0 itsdangerous==0.24 Jinja2==2.8 MarkupSafe==0.23 psycopg2==2.6.2 SQLAlchemy==1.0.14 Werkzeug==0.11.10 هذه هي الاعتماديات (أو الحزم التي يعتمد عليها التّطبيق) التي يعتمد عليها تطبيقنا، ولو استخدمت إضافة من إضافات Flask (Flask-login على سبيل المثال) لَظهَرتْ في هذا الملف. يجب كذلك إنشاء ملفّ باسمProcfile لتعريف منصّة Heroku بملفّ التّطبيق الرّئيسيّ، وسيحتوي هذا الملفّ ما يلي: web: gunicorn app:app لاحظ بأنّ الاسم app الأول هو المهم، وهو اسم ملفّ التّطبيق (أي app.py) ولو كان اسم الملف الرّئيسي مثلا run.py لكان مُحتوى ملفّ Procfile كالآتي: web: gunicorn run:app عليك كذلك تنصيب أداة git لتتمكّن من نشر التّطبيق ورفعه إلى المنصّة. التّسجيل في منصّة Heroku لتسجيل حساب جديد في منصّة Heroku ادخل إلى هذا الرّابط ، ثمّ املأ الحقول بما يُناسب، ويُمكنك ترك خانة Company name فارغة إن أردت. عند ملء النّموذج سيكون كالتّالي: اضغط على Create Free Account ثمّ فعّل بريدك الإلكتروني، بعد النّقر على رابط تفعيل البريد الإلكتروني ستنتقل إلى صفحة يُطلب منك فيها تعيين كلمة مرور وإعادة كتابتها كإجراء احتياطي. بعدها اضغط على Set password and log in (الخانة التي تسألك عمّا إذا كنت تريد الحصول على مُحتوى تقني عبر بريدك بشكل دوري ليست مُهمّة ولك كامل الحريّة في الاختيار بين القبول أو ضدّه). بعد تعيين كلمة المرور ستنتقل إلى صفحة ترحيب مع رابط للمُتابعة إلى لوحة التحكم لتستطيع إنشاء التّطبيقات. إنشاء التطبيق بعد أن وصلت إلى لوحة التّحكم، يُمكنك الآن إنشاء تطبيق عبر الضّغط على زر “Create New App”، يُمكنك تعيين اسم للتّطبيق في خانة “App Name” سيكون هذا الاسم الذي ستختاره مُتواجدا في عنوان URL التّطبيق، ويُمكنك تركه فارغا وسيختار Heroku اسما عشوائيّا مُتاحا. ويُمكنك كذلك تعديل المنطقة الجغرافيّة للخادوم إن أردت. بعد الانتهاء، اضغط على “Create App” لتُنشئ التّطبيق، تبقى لنا خطوة أخرى وهي تنصيب أداة Heroku Toolbelt التّي تُمكّننا من إدارة التّطبيقات عن طريق سطر الأوامر. لتنصيب Heroku Toolbelt انتقل إلى هذا الرّابط واختر نظام التّشغيل الخاص بك وقم بالتّنصيب بالطّريقة الاعتياديّة. بالنّسبة لمُستخدمي توزيعة Debian أو ما بُنيَ عليه من توزيعات يُمكنك تنفيذ الأمر التّالي على الطّرفيّة: $ wget -O- https://toolbelt.heroku.com/install-ubuntu.sh | sh الآن بعد تنصيب الأداة، يُمكنك تسجيل دخولك إلى منصّة Heroku عبر سطر الأوامر، وذلك بتنفيذ الأمر: $ heroku login بعد إدخال بريدك الإلكتروني وكلمة المرور ستُلاحظ رسالة كالآتي: Logged in as email@example.com بعدها أنشئ مُستودع git في مُجلّد التّطبيق واربطه مع المستودع الخاص بالمنصة عن طريق تنفيذ الأوامر التّاليّة: $ git init $ heroku git:remote -a app-name مع استبدال app-name باسم التّطبيق الذي اخترته (إن لم تختره فستجده في هذه الصّفحة ) يُفضّل إضافة مجلّد venv الخاصّ بالبيئة الوهميّة إلى ملفّ .gitignore لتجاهله. بعدها يُمكنك نشر التّطبيق عن طريق الأوامر التّالية: $ git add . $ git commit -am "initial commit" $ git push heroku master بعد أن تُرفَع الملفّات بنجاح، ستهتم منصّة Heroku بتنصيب الحزم اللازمة عن طريق ملف requirements.txt الذي أعددناه سابقا. بعد انتهاء الأمر، بقيت خطوة أخيرة، وهي خطوة إنشاء جدول قاعدة البيانات، وللقيام بذلك اذهب إلى هذا الرّابط: https://dashboard.heroku.com/apps/app-name/resources مع تغيير app-name إلى اسم تطبيقك. وابحث في خانة “Quickly add add-ons from Elements” عن إضافة Heroku Postgres ثمّ اضغط على زر Provision، وبعدها يكفي تنفيذ الأمر التّالي على سطر الأوامر لإنشاء قاعدة البيانات والجدول الخاص بالرّوابط: heroku run python create_db.py إذا لاحظت رسالة مُشابهة لما يلي فلا تقلق فهي مُجرّد تنبيه ولا تُشكل أيّة مُشكلة: warnings.warn('SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future. Set it to True to suppress this warning.') بعد الانتهاء من الخطوة السّابقة سيتكون قد أكملت جميع الخطوات اللازمة لتطوير تطبيق ونشره للعالم ويُمكنك زيارة تطبيقك عبر عنوان URL الخاص به أو تنفيذ الأمر التّالي من سطر الأوامر: heroku open ختاما بعد أن أنهينا التّطبيق يجب أن تكون قادرا على فهم كيفيّة عمل تطبيقات اختصار الرّوابط، ويجب عليك أن تكون قادرا على نشر تطبيقاتك على منصّة Heroku دون مشاكل، وإن واجهت أية مُشكلة أو أردت الاستفسار عن هذا التّطبيق، يُمكنك طرح سؤالك في قسم الأسئلة والأجوبة. وأنبّه إلى أنّ هذا المشروع لم يكن للقارئ المبتدئ وإذا واجهت مشاكل مع هذا المشروع أو وجدته مُعقّدا بعض الشيء، فلا تقلق وتابع جديد الأكاديميّة لأنّنا سننشر سلسلة أخرى أكثر تفصيلا عن إطار العمل Flask فكن في الموعد.
-
مُقدّمة: بعد أن تعلّمنا في الدّروس السّابقة كيفيّة استخدام إطار العمل Flask الخاص بلغة بايثون لإنشاء نظام تدوين بسيط، سنكمل مغامرتنا مع هذا الإطار بالمضي قُدما وتعلّم بعض التّقنيات الأكثر تقدّما وسننشئ تطبيقا بسيطا لاختصار الرّوابط في طريقنا، يُمكنك مُعاينة التّطبيق الذي سنبنيه من هذا الرّابط. التّطبيق سيكون عبارة عن خدمة لاختصار الرّوابط، فمثلا عوض كتابة الرّابط على الشّكل: https://mostaql.com/u/abdelhadidyouri يُمكن اختصاره لما يلي: bit.ly/OdrLKju ومن المرجح أنّك صادفت هذا النوع من الرّوابط على مواقع التّواصل الاجتماعي. سنقوم في هذا الدّرس والدّرس الموالي بتبسيط فكرة اختصار الرّوابط وبرمجة تطبيق خدمي يُمكنك نشره على الأنترنت وربطه بنطاق من اختيارك وطرحه للجميع للاستفادة منه. فكرة التّطبيق مهمّة التّطبيق هي تحويل رابط طويل إلى رابط قصير ما يعني بأنّنا سنحتاج إلى حفظ الرّابط في قاعدة بيانات ومنحه مُعرّفا خاصّا به، في هذه الحالة سيكون رقم مُعرّف أو id الرّابط، فمثلا لنقل بأنّ المُستخدم أدخل الرّابط academy.hsoub.com إلى قاعدة البيانات عبر نموذج HTML في الصّفحة الرّئيسيّة، عندما يدخل الرّابط إلى قاعدة البيانات سيحمل رقم المُعرّف 1 والرّابط التّالي سيحمل رقم المعرّف 2 وهكذا دواليك.ما يعني بأنّ الرّابط عندما يدخل إلى قاعدة البيانات يصبح بإمكاننا استدعاؤه باستخدام رقم المُعرّف الخاصّ به، وبالتّالي يُصبح الرّابط الأول كالتّالي: 127.0.0.1:5000/1 عند ربطه باسم نطاق مخصّص سيصبح كالتّالي: example.com/1 لاحظ بأنّ رقم المُعرّف مُتواجد بالرّابط المُختصر، ما يعني بأنّنا نستطيع الاستعانة بخاصّيّة الموجّهات في إطار العمل Flask للحصول على رقم المُعرف وتعيين قيمته لمُتغيّر ومن ثم نستدعي الرّابط الأصلي الذي يحمل رقم المُعرّف من قاعدة البيانات ونقوم باستخدام الدّالة redirect لإعادة توجيه المُستخدم إلى الرّابط الأصلي. وبالتّالي أصبح لدينا طريقة لاختصار الرّوابط. إعداد البيئة الوهميّة وتنصيب الحزم سنقوم مُجدّدا باستخدام أداة virtualenv في هذا المشروع، أنشئ مجلّدا باسم url_shortener وقم بتنفيذ الأمر التّالي داخله: $ virtualenv venv إذا واجهت أية مشاكل يُمكنك العودة إلى هذا الدّرس بعد إنشاء البيئة الوهميّة، فعّلها ونصّب الحزم المطلوبة بتنفيذ السّطرين التّاليين: $ . venv/bin/activate $ pip install flask Flask-SQLAlchemy hashids تشفير الأعداد الصّحيحة باستخدام مكتبة Hashids رغم أنّ الطّريقة أعلاه تعمل بشكل جيّد وتؤدّي الغرض الذي نريده (اختصار الرّابط) لا يزال هناك مُشكلة سهولة التّنبؤ بالرّوابط التّي اختصرها المُستخدمون السّابقون، مثلا لو كان المُعرّف الذي حصلت عليه يحمل الرّقم 421 فمن السّهل عليك أن تحصل على جميع الرّوابط التّي كانت قد اختُصِرَت من قبل، وبكتابة اسم النّطاق مع رقم 420 ستحصل على الرّابط الذي اختصره المُستخدم الذي سبقك. لحل هذه المُشكلة يُمكننا تشفير الأعداد وتحويلها إلى أحرف عشوائيّة مثلا العدد 1 سيكون JshO والعدد 2 سيكون QhIk ما يعني بأنّه من المُستحيل التنبؤ بالرّوابط التي اختُصِرَت مُسبقا. سنستخدم في هذا الدّرس مكتبة Hashids لتحويل الأعداد إلى سلسلة نصيّة عشوائيّة. ويُمكن استخدامها كالتّالي: from hashids import Hashids hashids = Hashids(salt='SALT KEY',min_length=4) hashid = hashids.encode(123) # 'Y975' number = hashids.decode('Y975') # (123,) نقوم أولا باستدعاء Hashids من الحزمة hashids وبعدها ننشئ كائنا باسم hashids ليُمكّننا من استدعاء توّابع الصّنف Hashids والمعامل الأول عبارة عن سلسلة نصيّة تقوم مقام المفتاح الذي يغيّر طريقة التّشفير، كلّما غيّرت السّلسلة النّصيّة كلما تغيّر تشفير عدد معيّن، وهذه السّلسلة مهمّة جدّا ويجب أن تكون عشوائيّة قدر الإمكان، كما يجب ألا تشاركها مع أحد. بالنّسبة للمُعامل الثّاني فهو الحد الأدنى لطول السّلسلة النّصيّة النّابعة من تشفير عدد ما، في حالتنا سيكون أصغر طول للسلسلة المتولدة هو أربعة أحرف. كما ترى نستخدم التّابع encode لتشفير عدد ما، ونقوم بفكّ التّشفير عن طريق التّابع decode. لاحظ بأنّ فك التّشفير يرجع مجموعة بداخلها العدد كأول عنصر، ما يعني بأنّك يجب أن تقوم بالوصول إليه لاستخدامه في التّطبيق كما يلي: number = hashids.decode('Y975') # (123,) ints = hashids.decode('Y975')[0] # 123 تجهيز قاعدة البيانات باستعمال SQLAlchemy إذا فهمت آليّة عمل التّطبيق فمن المؤكّد بأنّك تستطيع تنفيذ المشروع دون الاستمرار في قراءة هذا الدّرس، إذ يُمكن استخدام SQLite و جمل SQL لحفظ الرّوابط إلى قاعدة البيانات، وكذلك جلب الرّوابط عبر أرقام تعريفها. ويُمكنك الاستعانة بالدّروس السّابقة للاستزادة. بدلا من استخدام جمل SQL للقيام بالأمر، سنعتمد على أداة SQLAlchemy للقيام بالعمليّات المُتعلّقة بقاعدة البيانات، الميّزة هنا هي أنّك تستطيع التّعامل مع قاعدة البيانات مُباشرة بلغة بايثون، والتّالي مثال على جملة SQL ومقابلتها المكتوبة بلغة بايثون بالاستعانة بـSQLAlchemy: SELECT * FROM users users = User.query.all() يُوفّر لنا SQLAlchemy كذلك طريقة بسيطة للتّعامل مع مُختلف أنواع قواعد البيانات فبمجرّد تغيير سطر واحد يُمكنك الانتقال من SQLite إلى Postgresql أو Mysql والعكس، وهذا الأمر مُفيد في حالة كنت تمتلك أكثر من بيئة فبيئة التّطوير (على حاسوبك) يُمكن أن تستخدم SQLite أمّا في بيئة الإنتاج (على منصّة PythonAnywhere أو Heroku) فيُمكن استخدام MySQL أو Postgresql. كل هذا دون عناء التّعامل مع مكتبة خاصّة بكل واحدة منها، فأداة SQLAlchemy تقوم بالأشياء المعقّدة وراء الكواليس. سنقوم الآن بإعداد قاعدة بيانات التّطبيق والتّي تتكون من جدول واحد للرّوابط باسم urls، يحتوي الجدول على عمودين عمود خاص برقم مُعرّف الرّابط والعمود الآخر يخصّ الرّابط بحد ذاته، ما يعني بأنّ الرّوابط ستُحفظ في قاعدة البيانات كالتّالي: id | url ------------------------------------------------------- 1 | http://academy.hsoub.com/ ------------------------------------------------------- 2 | https://mostaql.com/u/abdelhadidyouri ------------------------------------------------------- 3 | https://academy.hsoub.com/programming/python/flask كل ما عليك فعله هو فتح ملفّ باسم app.py وقم باستيراد المكتبات والأدوات التي سنحتاج إليها وقم بإعداد الاتّصال بقاعدة بيانات SQLite باسم app.db عبر نسخ الشّيفرة التّالية ولصقها في بداية الملفّ. # Imports from flask import Flask, redirect, request, render_template from flask_sqlalchemy import SQLAlchemy from hashids import Hashids # Config app = Flask(__name__) db = SQLAlchemy(app) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db' SALT = 'Hashids SALT' الأسطر الثلاثة الأولى مسؤولة عن استيراد الأدوات التي سنعتمد عليها، أما سطر تعريف المُتغيّر db فهو ضروري لربط التّطبيق مع أداة SQLAlchemy أما السّطر الذي يليه فهو عبارة عن مسار قاعدة البيانات والمُتغيّر SALT يحمل قيمة مفتاح Hashids الذي تحدّثنا عنه سابقا. الشّيفرة التّي سنحتاج إليها لإنشاء جدول الرّوابط هي كالتّالي (انسخها وألصقها بعد الشيفرة السّابقة): class Url(db.Model): # Create table `urls` |id|url| __tablename__ = "urls" id = db.Column(db.Integer, primary_key=True) url = db.Column(db.Text) def __init__(self, url): self.url = url نبدأ بصنف يحمل نفس اسم الجدول (من المُفضّل أن يكون اسما مُفردا) هذا الصّنف يرثُ من الكائن db.Model. بعدها نسمّي الجدول ونقوم بتحديد الأعمدة، لاحظ بأنّ أنشأنا رقم المُعرّف عبارة عن عدد صحيح (Integer) وقمنا بتمريرالقيمة True للمُعامل primary_key وذلك لزيادة العدد بشكل آلي كلّما أضفنا رابطا جديدا، أما السطر الموالي فهو لإنشاء عمود الرّابط من نوع نصيّ. والدّالة init متواجدة للتمكين من إضافة الرّابط إلى قاعدة البيانات. ولا تنس وضع الشيفرة المسؤولة عن تشغيل الخادوم ومُصحّح الأخطاء في آخر الملف: if __name__ == '__main__': app.run(debug=True) إنشاء قاعدة البيانات بعد أن أعددنا قاعدة البيانات بقي علينا إنشاؤها ويُمكن ذلك عبر كتابة ملفّ بايثون صغير يقوم بالعمليّة، افتح ملفّا باسم create_db.py وضع به ما يلي: from app import db db.create_all() نفّذ الملفّ من سطر الأوامر، بعدها ستُلاحظ ملفّا جديدا باسم app.db (هذا الاسم قد سبق وأن وضعناه كقيمة لـSQLALCHEMY_DATABASE_URI). إذا أنشِئ الملف الجديد بنجاح، فهذا يعني بأنّنا انتهينا من مهمّة إنشاء قاعدة البيانات. إعداد موجّه اختصار الرّوابط سيكون موجّه اختصار الرّوابط باسم shorten وهي كلمة تعني “اختصر”، وسيقوم هذا الموجّه بإضافة الرّابط الذي سنحصل عليه من طلب HTTP من نوع GET إلى قاعدة البيانات ومن ثم تشفير رقم مُعرّفه باستخدام مكتبة Hashids وبعدها إرجاع مُعرّف الرّابط بعد تشفيره. وسيكون المُوجّه كالتّالي: # Controllers @app.route('/shorten') def shorten(): # الحصول على الرّابط link = request.args.get('link') # التّحقق من أنّ الرّابط صالح if link and link != "" and not link.count(' ') >= 1 and not link.count('.') == 0: # إضافة بادئة http إذا لم تتواجد if link[:4].lower() != 'http': link = 'http://{}'.format(link) # إضافة الرّابط إلى قاعدة البيانات db.session.add(Url(url=link)) db.session.commit() # استخراج رقم مُعرّف آخر رابط مُضاف (أي الرّابط المُضاف في هذا الموجّه) url_id = db.session.query(Url).order_by(Url.id.desc()).first().id # تشفير رقم المُعرّف id_code = Hashids(salt=SALT, min_length=4).encode(url_id) # إضافة علامة / إلى بداية المعرّف الخاص بالرّابط ('/HFdK') short_link = '/' + id_code # إرجاع المعرّف return short_link # في حالة عدم صلاحيّة الرّابط، عد إلى الصّفحة الرّئيسيّة else: return render_template("index.html") لاحظ بأنّنا نقدّم صفحة باسم index.html إذا لم يكن الرّابط صالحا، سنقوم بإعداد هذه الصّفحة في الدّرس القادم، لكن يُمكنك إنشاء صفحة بنفس الاسم بداخل مجلّد templates إذا لم ترغب بتركها فارغة حاليا. مثال لتشفير بعض الرّوابط: http://127.0.0.1:5000/shorten?link=http://google.com => /QxvP http://127.0.0.1:5000/shorten?link=http://mostaql.com => /wrJw http://127.0.0.1:5000/shorten?link=http://academy.hsoub.com => /QB5w موجّه إعادة التّوجيه إلى الرّابط الأصلي بعد أن تمكّنا من إضافة الرّابط والحصول على مُعرّفه، يجب أن يتمكّن المُستخدم من إعادة التّوجه إلى الرّابط الأصلي عبر المُعرّف. مثلا إذا كتب الرّابط كالآتي: 127.0.0.1:5000/QB5w فيجب على التطبيق أن يعيد توجيهه إلى الرّابط الأصلي المرتبط بالمُعرّف QB5w. سيكون المُوجّه كالتّالي: @app.route('/<id>') def url(id): # تحويل المُعرّف إلى عدد (فك تشفيره) original_id = Hashids(salt=SALT, min_length=4).decode(id) # إذا كان المعرف مرتبطا بمجموعة فاحصل على العنصر الأول if original_id: original_id = original_id[0] # الحصول على الرّابط من خلال رقم مُعرّفه original_url = Url.query.filter_by(id=original_id).first().url # إعادة توجيه المُستخدم إلى الرّابط الأصلي return redirect(original_url , code=302) else: return render_template('index.html') هنا قُمنا بفك تشفير المُعرّف ومن ثمّ الحصول عليه من قاعدة البيانات عبر السّطر التّالي: original_url = Url.query.filter_by(id=original_id).first().url وهذا شبيه بما فعلناه سابقا في درس ربط تطبيق Flask بقاعدة بيانات SQLite ولو أردنا كتابة الاستعلام بلغة SQL كنّا سنكتب شيئا شبيها لما يلي: select url from urls where id=original_id لاحظ كذلك بأنّنا نُقدّم ملفّا باسم index.html إذا لم يكن المعرّف مُرتبطا بعدد معيّن، لا يهم محتوى هذا الملف، يُمكنك كذلك أن تُرجع ملفّا يُخبر المُستخدم بأنّ المُعرّف غير صحيح أو ببساطة إرجاع خطأ 404 من ملفّ HTML بسيط. خاتمة: تعرّفنا في هذا الدّرس على كل ما سنحتاج إليه لاختصار الرّوابط، لكنّنا لم نُقدّم أي صفحة مرئية للمُستخدم العادي، كل ما قمنا به هو إعداد النّظام الخلفي أو ما يُسمى بالـBack-end وهو الجزء المسؤول عن إدخال الرّوابط إلى قاعدة البيانات وتوليد مُعرّف نصي لكل رابط انطلاقا من رقم مُعرّفه، وكذلك تحويل الرّابط المُختصر إلى الرّابط الأصلي. ما يلزمنا الآن هو النّظام الأمامي أو Front-end وهو كل ما يخص ما يراه المُستخدم العادي وما يحدث في جهته (تُسمى كذلك جهة العميل Client Side). في الدّرس القادم سنقوم بإعداد ملفّ HTML مع حقل يُمكن للمُستخدم أن يلصق به رابطه ويضغط على زر ENTER من لوحة المفاتيح لاختصار الرّابط، وكذا زر يُمكّنه من نسخ الرّابط المُختصر (باستخدام لغة جافاسكربت). سنقوم كذلك بنشر تطبيقنا على منصّة Heroku للتّطبيقات السّحابيّة.
-
بعد أن أصبحنا نمتلك تطبيقا مُتكاملا يُمكّننا من نشر مقالاتنا وتدويناتنا وحذفها بطريقة آمنة نسبيّا، أصبح بإمكاننا الآن أن ننشره للعالم ليصل إليه كل من يمتلك اتّصالا بالأنترنت عبر عنوان الموقع، ولنشره سنستعمل خدمة PythonAnywhere في باقتها المجّانيّة والتّي تتحمّل عددا قليلا من الزوار كل يوم، وأمّا إن كنت تتوقع بأنّ زوار الموقع سيتجاوزون الآلاف يوميّا فمن المُفضّل الاعتماد على باقة مدفوعة. التّسجيل في منصّة PythonAnywhere ادخل إلى هذا الرّبط ثمّ قم بإدخال المعلومات المطلوبة، مع مُلاحظة أنّ اسم المُستخدم (Username) سيكون في عنوان URL الخاص بموقعك، مثلا لو اخترت dyouri كاسم مُستخدم فسيكون عنوان الموقع كما يلي: http://dyouri.pythonanywhere.com/ بعد ملء المعلومات المطلوبة، تحقّق من بريدك الإلكتروني وقم بتفعيله عن طريق الضّغط على رابط التّفعيل الذي ستجده في رسالة من PythonAnywhere بعنوان “confirm your email address”. بعد تفعيل الحساب، عد إلى الصّفحة الرّئيسية واضغط على تبويب Web ثمّ على زر “Create a new web app” ثمّ اضغط على Next ثمّ اختر Flask و Python 2.7 في المسار غيّر flask_app.py إلى app.py ثمّ اضغط Next. بعد الانتهاء من إنشاء التّطبيق، ادخل إلى تبويب Files ثمّ على زر mysite/ للانتقال إلى ملفّات التّطبيق، ثمّ احذف ملف app.py (وكذلك ملفّ app.pyc إذا كان مُتواجدا) وذلك عبر الضّغط على أيقونة سلّة المهملات التي بجانب كل ملفّ. الآن، عد إلى مجلّد المشروع على حاسوبك وقم بضغطه كملفّ zip إما ببرنامج كـWinrar أو Ark أو غيرها من البرامج. بعد الحصول على ملفّ zip يحتوي على ملفّات التّطبيق المُتواجدة على حاسوبك، عد إلى لوحة تحكّم PythonAnywhere واضغط على زر “Upload a file” وارفع الملفّ المضغوط. بعد رفع الملفّ ادخل إلى رابط “Open Bash console here” للوصول إلى سطر الأوامر. ستُلاحظ شاشة سوداء بها سطر مُشابه لما يلي: 16:33 ~/mysite $ لفكّ الضغط على الملف الذي يحتوي على ملفّات التّطبيق أدخل الأمر التّالي واضغط على Enter: unzip flask_app.zip استبدل flask_app.zip باسم الملفّ المضغوط الذي سبق وأن رفعته إلى المنصّة. بعد انتهاء تنفيذ الأمر، عد إلى الصّفحة الرّئيسية واضغط على تبويب Web ثمّ على زر “Reload user.pythonanywere.com” (سيكون اسم المُستخدم الخاص بك مُتواجدا مكان “user”). بعد الانتهاء من هذه العمليّة قم بزيارة عنوان التّطبيق الخاص بك وستجد كلّ شيء جاهزا، وستتمكّن الآن من مُشاركة مدونتك التّي برمجتها بنفسك مع أصدقائك. مُلاحظة:* إذا لم تضغط ملفّ database.db مع ملفّات التّطبيق، سيتوجّب عليك إنشاؤه مُباشرة بعد فكّ الضّغط عن الملفّ المضغوط، وذلك عبر تنفيذ الأمر: $ python create_db.py ختاما انتهينا من هذه السّلسلة البسيطة التّي استعرضنا فيها جانبا ممّا تستطيع القيام به بلغة بايثون وإطار العمل Flask. لتستفيد أكثر من هذه السّلسلة، استغل ما تعلّمته لتُنشأ تطبيقات بأفكار أخرى (منصّة أسئلة وأجوبة، تطبيق للسّماح للآخرين بنشر خواطرهم مُشابه لموقع Twitter). وسننشر كذلك سلاسل أخرى تخصّ تطوير تطبيقات الويب بلغة بايثون وإطار Flask، السّلسلة القادمة ستكون عن كيفيّة إنشاء تطبيق بسيط لاختصار الرّوابط، أمّا السّلسلة التّي ستكون بعدها فستختصّ بتطوير التّطبيقات الكبيرة والأكثر تعقيدا، وفيها سنُغطّي مُختلف إضافات إطار فلاسك وطرق حماية التّطبيق من الهجمات وسدّ الثّغرات الأمنيّة الشّائعة، سنكون السّلسلة طويلة بعض الشّيء لكنّ إنهاءها يستحق الوقت الذي ستستثمره فكن في الموعد
-
يُوفّر لنا إطار العمل Flask طريقة بسيطة لتنبيه المُستخدم إلى التّغييرات الطّارئة في التّطبيق كتسجيل الدّخول، إضافة مقال، حذف مقال وتسجيل الخروج، ويُمكن ذلك عبر الدّالة flash التّي تُمكّننا من عرض رسالة للمُستخدم تُفيد بأنّ التّغيير قد حدث بنجاح، فمثلا إذا قام بإضافة مقال جديد فسيستقبل رسالة كالتّالي: “أُضيف المقال بنجاح!” وهذه الرّسالة خاطفة (flash) ما يعني بأنّها ستختفي فور إعادة تحميل الصّفحة. على سبيل المثال سنقوم بتنبيه المُستخدم إلى أنّ المقال قد أضيف بنجاح بعد إضافته لمقال جديد وبعد التّأكد من أنّ المقال الجديد قد أضيف إلى قاعدة البيانات، وسيكون التّنبيه كالتّالي: ملاحظة: التّنسيق متواجد في ملفّ style.css الذي يُمكنك الحصول عليه من هذا الرّابط يُمكننا أن نقوم بالأمر بالاستعانة بالدّالة flash بعد استيرادها من إطار العمل Flask وبالتّالي يُصبح سطر الاستيراد كالتّالي: from flask import Flask, render_template, redirect, url_for, request, session, flash بعدها يُمكننا استعمال الدّالة ببساطة باستدعاء الدّالة وتمرير الرّسالة كسلسلة نصيّة كما يلي : flash('الرّسالة التّي ترغب بعرضها') تأكّد فقط من أنّ السّطر متواجد في آخر الدّالة المسؤولة عن المُوجّه، مُباشرة قبل تقديم قالب HTML أو إعادة التّوجيه. سنُطبّق هذه الطّريقة لنُضيف رسالة لكل العمليّات التّالية: إضافة مقال جديد بنجاح حذف مقال بنجاح التّنبيه إلى أنّ العمليّة تتطلّب تسجيل الدّخول تسجيل الدّخول بنجاح تسجيل الخروج بنجاح وبعدها سنقوم بعرض الرّسالة في أعلى صفحة index.html. إضافة مقال جديد بنجاح من أجل إضافة الرّسالة التي تُنبّه المُستخدم بأنّ المقال قد أضيف بنجاح سنُضيف سطرا واحدا إلى دالّة الموجّه create: flash(u'أضيف المقال بنجاح!') سنُضيفه مُباشرة قبل السّطر المسؤول عن إعادة التّوجيه إلى الصّفحة الرّئيسيّة وبالتّالي تُصبح الدّالة create كالآتي: def create(): if request.method == 'POST': title = request.form['title'] content = request.form['content'] manage_db.create(title, content) flash(u'أضيف المقال بنجاح!') return redirect(url_for('home')) مُلاحظة: تأكّد من استيراد الدّالة flash قبل أن تحفظ الملفّ. حذف مقال بنجاح بنفس الطّريقة نُضيف رسالة للتّنبيه إلى أنّ عمليّة حذف مقال قد تمّت بنجاح، ومنه تُصبح الدّالة delete كالتّالي: def delete(post_id): manage_db.delete(post_id) flash(u'حُذف المقال بنجاح!') return redirect(url_for('home')) التّنبيه إلى أنّ العمليّة تتطلّب تسجيل الدّخول في السّابق كنّا نتأكّد من أنّ المُستخدم قد سجّل دخوله، فإن سجّل دخوله فمن المسموح له أن يقوم بالعمليّات الحسّاسة كحذف مقال أو إضافة آخر، أمّا إن لم يُسجّل دخوله فأي مُحاولة لإجراء عمليّة حسّاسة تُرجع الزّائر إلى الصّفحة الرّئيسيّة دون أي تنبيه أو رسالة تشرح ما حدث. لتحسين تجربة الاستخدام، سنُضيف رسالة تخبر الزّائر بأنّ إجراء هذه العمليّة يتطلب تسجيل دخول، وسنقوم بذلك على مُستوى المُزخرف login_required لكي يُصبح كالآتي: def login_required(function): @wraps(function) def wrapper(*args, **kwargs): if 'logged_in' in session: return function(*args, **kwargs) else: flash(u'سجّل دخولك أولا لإجراء هذه العمليّة') return redirect(url_for('home')) return wrapper تسجيل الدّخول بنجاح عندما يقوم الزّائر بتسجيل دخوله سنقوم بتوجيهه إلى الصّفحة الرّئيسيّة وعرض رسالة تُفيد بأنّ تسجيل الدّخول قد تمّ بنجاح. وبالتّالي سيتطلّب الأمر تعديل الدّالة login لتُصبح كما يلي: def login(): if request.method == 'POST': username = request.form['username'] password = request.form['password'] if username == "admin" and password == "password": session['logged_in'] = True flash(u'سُجّل دخولك بنجاح') else: return redirect(url_for('home')) return redirect(url_for('home')) تسجيل الخروج بنجاح بنفس الطّريقة نقوم بإضافة سطر الرّسالة مُباشرة بعد حذف الجلسة لتنبيه المُستخدم إلى أنّ تسجيل الخروج قد تمّ بنجاح. def logout(): session.pop('logged_in', None) flash(u'سُجّل خروجك بنجاح!') return redirect(url_for('home')) عرض الرّسائل في صفحة HTML بعد أن أضفنا رسالة لكلّ عمليّة، بقيت خطوة عرضها على المتصفّح، ويُمكن ذلك عبر الدّوران على قيم الدّالة get_flashed_messages وعرض كل عنصر على حدى، وذلك باستخدام حلقة for كما عهدنا: {% for message in get_flashed_messages() %} <div class="flash"> {{ message }} </div> {% endfor %} أضف الشّيفرة السّابقة إلى الملفّ index.html مُباشرة بعد وسم body واحفظ الملف. يُمكنك الآن تجربة التّطبيق وإجراء العمليّات السّابقة وستُلاحظ بأنّ رسالة تظهر حسب العمليّة التي أجريتها. نقل بيانات تسجيل الدّخول إلى إعدادات التّطبيق حاليا نستعمل السّطر التّالي للتحقق من أنّ بيانات تسجيل الدّخول صحيحة: if username == "admin" and password == "password" هذه الجملة الشّرطيّة مُتواجدة في عمق ملفّ app.py ما يجعل تغيير بيانات تسجيل الدّخول أمرا صعبا، وسيكون من الجيّد لو أنّ البيانات تواجدت في أعلى الملف كإعدادات يسهل تغييرها. سنقوم بإضافة السّطرين التّاليين مُباشرة بعد تعريف المُتغيّر app ليُصبح هذا الجزء من الشّيفرة كالآتي: app = Flask(__name__) app.config['SECRET_KEY'] = "Secret" app.config['USERNAME'] = "admin" app.config['PASSWORD'] = "password" وبعدها سنقوم بتغيير الجملة الشّرطيّة لتُصبح كالتّالي: if username == app.config['USERNAME'] and password == app.config['PASSWORD']: بعد هذه العمليّة، أصبح من السّهل تغيير بيانات تسجيل الدّخول دون الاضطرار إلى البحث عن الجملة الشّرطيّة. تخزين الإعدادات في ملفّ مُستقل كلّما أضفنا إعدادا جديدا كلّما كانت إدارة الإعدادات أكثر تعقيدا، لتبسيط الإعدادات يُمكننا أن ننقلها إلى ملفّ مُستقل. افتح ملفّا باسم config.py في مُجلّد المشروع وأضف الإعدادات كالتّالي: SECRET_KEY = 'Secret!!!' USERNAME = 'admin' PASSWORD = 'password' احفظ الملف وأغلقه، سنحتاج الآن إلى ربط ملفّ الإعدادات مع ملفّ app.py، ويُمكن القيام بذلك عبر إستبدال الأسطر التّالية بسطر واحد: app.config['SECRET_KEY'] = "Secret" app.config['USERNAME'] = "admin" app.config['PASSWORD'] = "password" ضع السّطر التّالي عوضا عنها: app.config.from_object('config') أصبح الآن بإمكاننا تعديل الإعدادات عبر تغيير البيانات داخل الملفّ config.py بطريقة بسيطة ودون التّعامل مع شيفرة التّطبيق الأساسيّة. إضافة تاريخ لنهاية صلاحية الجلسة افتراضيّا، يقوم Flask بتسجيل الجلسة طوال مدّة فتح المتصفّح، ما يعني بأنّ تسجيل الدّخول سيكون صالحا لمدّة وجيزة فقط ويجب علينا تغيير هذا الإعداد لكي يبقى المُستخدم مُسجّلا لمدّة أطول، ويُمكنك بعد فهم الطّريقة إضافة خيار “تذكّرني” إلى التّطبيق. يُمكن أن نقوم بإضافة تاريخ لنهاية صلاحية الجلسة عبر تمكين خيار التّحكم بهذه الميزة وذلك بإضافة السّطر التّالي مباشرة بعد تسجيل الجلسة (أي بعد السّطر session['logged_in'] = True): session.permanent = True بعد ذلك سنتمكّن من تحديد مدّة زمنيّة كتاريخ لنهاية الصّلاحيّة، مثلا بعد نصف ساعة أو ساعتين. يجب أولا أن نستورد دالّة timedelta من حزمة datetime لنتمكّن من توقيت مدّة الجلسة، أضف السّطر التّالي إلى بداية الملفّ. from datetime import timedelta سنتمكّن الآن من تعيين مُدّة زمنيّة محدّدة لتدمير الجلسة، مثلا لو أردنا حذف الجلسة بعد نصف ساعة يُمكن أن نضيف السّطر التّالي مُباشرة بعد السّطر session.permanent = True: app.permanent_session_lifetime = timedelta(minutes=30) ما يعني بأنّ دالّة الموجّه login ستصبح كالتّالي: def login(): session['logged_in'] = True session.permanent = True # Make session last for only 30 min app.permanent_session_lifetime = timedelta(minutes=30) if request.method == 'POST': username = request.form['username'] password = request.form['password'] if username == "admin" and password == "password": session['logged_in'] = True flash(u'سُجّل دخولك بنجاح') else: return redirect(url_for('home')) return redirect(url_for('home')) ويكفي تغيير مُعامل الدّالة timedelta لتغيير مُدّة صلاحيّة الجلسة ويمكن كذلك التحكم في وحدة الزّمن. والتّالي بعض الأمثلة على مُختلف وحدات الزّمن: timedelta(days=0) # الأيام timedelta(seconds=0) # الثواني timedelta(minutes=0) # الدّقائق timedelta(hours=0) # السّاعات timedelta(weeks=0) # الأسابيع
- 1 تعليق
-
- 1
-
مرحبا. إليك السّؤالين التّاليّين وإجاباتهما على موقع Stackoverflow فربّما يُساعدانك: arabic text mining using R Reading arabic data text in R and plot()
-
تعلّم كيفيّة التّعامل مع برنامج Android Studio، ستحتاج كذلك إلى فهم بعض الأساسيّات حول لغة XML، لكنّ فهمها سهل وستتعلّمها خلال مسيرتك في تعلّم صنع التطبيقات. إن كانت خبرتك في لغة جافا قليلة، أنصحك بالإكمال في تعلّمها والنّظر إلى بعض المفاهيم المُتقدّمة فيها. أهم شيء يجب أن تُدركه هو أنّ أفضل وسيلة للتّعلم هي بالبدء بمشاريع صغيرة ثمّ مشاريع أكبر مع الوقت. نصيحة أخرى، يُمكنك مُشاهدة الدورات والفيديوهات على موقع Youtube التّي تُسير معك شيئا فشيئا أثناء بناء تطبيق ما، وتذكّر بأنّ تطبيق ما تتعلّمه لا مفر منه إن أردت أن تتعلّم بالفعل
- 2 اجابة
-
- 1
-
تعرّفنا إلى الآن على كيفيّة إنشاء تطبيق بسيط بلغة بايثون وإطار العمل 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.
-
بعد أن تعرّفنا على طريقة تمرير قيم المُتغيّرات من بايثون إلى ملفّات 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 إلى التّطبيق الذي أنشأته (يُمكنك كذلك تعديله). إذا كان لديك سؤال حول هذا الدّرس، يُمكنك وضعه في قسم الأسئلة والأجوبة. ختاما تعرّفنا على طريقة بناء تطبيق يتفاعل مع المُستخدم ويترك له حريّة الوصول إلى قاعدة البيانات، لكنك تُلاحظ بأنّ الحماية معدومة في التّطبيق، إذ يُمكن لأي شخص أن يحذف جميع المقالات دون أي حاجز (ككلمة مرور مثلا). سنتعلّم في الدّرس القادم على كيفيّة حماية التّطبيق وإتاحة الوصول إلى قاعدة البيانات لمُستخدم واحد فقط، بحيث يُسجّل دخوله إذا أراد حذف أو إضافة مقال، أمّا بقيّة المُستخدمين فلهم إمكانيّة القراءة فقط.
-
تعرّفنا في الدّرس السّابق على كيفيّة تهيئة بيئة التّطوير وكيفيّة إنشاء تطبيق يقبل قيما من المُستخدم ويُعالجها ثمّ يُرجع النّتيجة على شكل صفحة 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 يُمكن استخدامها لحفظ المقالات.
-
لا أعلم سببًا كي يكون هناك فرق في سرعة الموقع، لكن يُمكن القول بأنّ حفظها على مجلّدات شهريّة سيكون أفضل خاصّة في حالة كنت ترغب في تنظيم الصّور، كما أنّ البحث سيكون أفضل بحيث يُمكن بسهولة الوصول إلى صورة رُفعت في شهر مُحدّد بالعودة إلى مُجلّد الشّهر. أما في حالة وضعها في مجلّد واحد فالأغلب أنّها لن تكون منظّمة وسيصعب البحث حسب زمن رفعها. إذا اردت تخزين الصور في مُجلّد واحد والحفاظ على الخط الزّمني لكل صورة فمن الممكن تغيير اسم الصّورة بعد رفعها ووضع التّاريخ في نهاية أو بداية اسم الصّورة، وبهذا ستتجنّب المُشكلة السّابقة.
-
Try و Except خاصّيتان تُستعملان للتعامل مع الأخطاء في لغة بايثون، جرّب مثلا قسمة عدد حقيقي على الصّفر. ستحصل على خطأ كالتّالي: >>> 5/0 Traceback (most recent call last): File "<stdin>", line 1, in <module> ZeroDivisionError: integer division or modulo by zero لاحظ نوع الخطأ: ZeroDivisionError لنفرض بأنّنا نريد أن نحصل على عددين من المُستخدم وننفّذ القسمة بينهما. ماذا لو أدخل المُستخدم الصّفر كقيمة للعدد الثّاني؟ نستطيع أن نقوم بتجاوز هذا الخطأ وتنفيذ شيفرة عوضا عن طباعة جُملة الخطأ باستعمال التّجربة والاستثناء (Try and except). يُمكننا مثلا طباعة الجملة "Integer should not be zero" عند مُحاولة تقسيم عدد مُعيّن على الصّفر كالتّالي: num1 = raw_input("Enter the first number: ") num2 = raw_input("Enter the second number: ") num1 = int(num1) num2 = int(num2) try: print num1/num2 except ZeroDivisionError: print "Integer should not be zero" لاحظ بأنّنا نُحاول أن نقوم بطباعة ناتج القسمة ونستثني خطأ القسمة على الصّفر، فإن حدث الخطأ سيقوم البرنامج بتنبيه المُستخدم إلى أنّ القسمة على الصّفر غير مُمكنة. يُمكنك تطبيق هذا المبدأ على العديد من الدوال، فقط حاول تنفيذ الدّالة واستثن أي خطأ يُمكن أن يحدث، ولاستثناء جميع الأخطاء يُمكن كتابة except دون نوع مُعيّن كالتّالي: try: # ... except: # ...
-
رغم أنّك تستطيع أن تقوم بصناعة تطبيقات دون برمجة إلا أنّه لا غنى عن تعلّم البرمجة لفهم كيفيّة عمل التّطبيق والوصول إلى مرحلة تُمكّنك من صنع أي تطبيق مهما كان تعقيده. أنصحك أن تبدأ بالبرمجة، لا تهم اللغة المحدّدة فقط حاول أن تختار لغة برمجة تُمكّنك من الوصول إلى هدفك، وإليك بعضا من اللغات التّي يُمكنك الاختيار بينها للوصول إلى هدفك، وهي مُرتّبة حسب مجال ونوع التّطبيق: - تطبيق الويب: تعلّم لغة php أو بايثون أو لغة روبي - تطبيق أندرويد: تعلّم لغة Java - تطبيق أيفون: تعلّم لغة Swift - تطبيقات سطح المكتب لأنظمة Windows: تعلّم لغة C# المهم أن تبدأ بالتّعلم، ولا يهم أي لغة تختار، فقط امض بضعة أسابيع في لغة واحدة لتتعلّم الأساسيّات وبعدها يُمكنك أن تنتقل إلى اللغة الأنسب والتي ستعرفها بالتأكيد.
-
يُمكنك ببساطة أن تُضيف خاصيّة title لعنصر HTML بالنّص الذي تريد أن يظهر. المثال التّالي يُظهر النّص "نص يظهر بعد تمرير الفأرة فوق هذا الرّابط" عند تمرير الفأرة فوق رابط يُشير إلى موقع الأكاديميّة: <a href="https://academy.hsoub.com/" title="نص يظهر بعد تمرير الفأرة فوق هذا الرّابط">Hsoub Academy</a> وكما ترى، لا تحتاج إلى لغة جافاسكريبت، فقط لغة HTML كافيّة بالغرض.
-
تقنيا لا يُمكن أن تقوم بالأمر، فالغرض من الشيفرة المصدريّة هو أن تعمل على خادوم من يحصل عليها، ولا يُمكنك أن تقوم بإغلاقها أو تشفيرها بحيث يتمكّن الخادوم من فهمها فقط دون الحاصل عليها، حتى ولو تمكّنت من إغلاقها مع إبقاء إمكانيّة الوصول إليها من طرف آخر فالشيفرة المصدريّة لا قيمة لها في هذه الحالة، فالغرض من مُشاركة الشّيفرة هو إتاحة تطويرها وتخصيصها لكل من يحصل عليها. على الرّغم ممّا سبق، فهناك طرق يُمكنك أن تحمي بها خوارزميات خاصّة كاستعمال واجهة برمجيّة واستضافتها على خادومك الخاص وإتاحة الوصول إليها لأشخاص مُحدّدين فقط، مثلا لنقل بأنّك استطعت أن تُنشئ برنامجا يقوم بتحويل أي كلمة مُفردة إلى جمعها في اللغة العربيّة لكنّك لا ترغب بمُشاركة الشيفرة أو أنّك لا ترغب بإتاحة استخدام مجّاني للشيفرة وتريد أن يستخدمها من يشتري حقوق الاستخدام منك فقط. في هذه الحالة يُمكنك انشاء موقع بسيط يُرجع جمع كلمة عند ارسال مُفردها، ولا يمكن للخدمة أن تعمل إلا إذا أرفق المُستخدم كلمة سر حصل عليها من المُطور إمّا بمُقابل أو مجّانا. مثال على الطّلب والإجابة: MyAPI.com/singular_to_plural?singular=كلمة?secret_key=secret => كلمات MyAPI.com/singular_to_plural?singular=موقع?secret_key=secret => مواقع MyAPI.com/singular_to_plural?singular=مكان?secret_key=secret => أمكنة MyAPI.com/singular_to_plural?singular=كلمة => تحتاج إلى كلمة سر لاستخدام هذه الخدمة لكن انتبه إلى أنّ لهذه الطّريقة العديد من السّلبيات، إذ يتطلّب توفير الخدمة مصاريف كثيرة من استضافة وإدارة الخادوم وكذا العمل على توفير الخدمة دون مشاكل، وقد يتطلّب الأمر حماية كبيرة.
-
بعد أن تعرّفنا على المفاهيم الأساسيّة لتطوير الويب كماهية تطبيق الويب، وإطار العمل، سنُكمل هذه السّلسلة من الدروس وسنتعرّف في هذا الدّرس على كيفيّة تهيئة بيئة التّطوير وتنصيب الأدوات اللازمة، وكذا بعض أساسيّات التّعامل مع إطار العمل 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 أكثر)
موسوم في:
-
لترجمة ملفّ مكتوب بلغة Sass إلى ملف CSS يكفي أن تقومي بتنفيذ الأمر التّالي على الطّرفيّة: sass style.scss المُخرج سيكون ملفّ CSS جاهزا، إذا أردت أن يقوم مُترجم Sass بمُشاهدة ملفّ ويقوم بترجمة فوريّة كلّما طرأ تغيير على ملفّ Sass يكفي إضافة --watch كالتّالي: sass --watch style.scss:style.css الأمر سيقوم بترجمة Sass إلى CSS فور استشعار تغيير على الملفّ. بالطّبع يجب أن تكون Sass مُنصّبة على الجهاز، وذلك بتنصيب كلّ من لغة روبي وجوهرة sass. لتنصيب جوهرة sass يُمكن تنفيذ الأمر التّالي: gem install sass قد يحتاج الأمر أعلاه إلى صلاحيّات المدير في أنظمة gnu/linux و Mac OS، لذا يجب إضافة كلمة sudo في بداية الأمر: sudo gem install sass
- 1 جواب
-
- 2
-
وعليكم السّلام ورحمة الله وبركاته. يُمكنك تنفيذ الفكرة بتوظيف مُبرمج مُستقل أو بتعلّم البرمجة وتطوير التّطبيق بنفسك. إذا قرّرت أن تُوظفّ مُستقلا للعمل معك فأنصحك بتوفير جميع معلومات المشروع وطرحها ببساطة، أي أنّه يتوجّب عليك أن تشرح جميع تفاصيل الفكرة لمن سيعمل عليها، ولا يكفي أبدا أن تُعطيه مثالا وتطلب منه أن يُنشئ شيئا مثله (التّقليد الخالص)، يجب عليك أن تُميّز بين الخدمات الموجودة بالفعل وبين الخدمة التّي ترغب بإنشائها، فبهذه الطّريقة ستحقّق التّميز.
- 3 اجابة
-
- 1
-
بالنّسبة للطرق المُتبعة للحصول على عناوين البريد الإلكتروني بغرض التّرويج فأكثر طريقة شائعة هي بتقديم مُحتوى مُفيد حصري لمن اشترك بقائمتك البريديّة، مثلا اكتب كتابا صغيرًا أو وظّف كاتبا مُستقلا ليكتبه لك، وأرسله لزوار موقعك مجّانا عبر البريد الإلكتروني، كل ما عليك فعله هو طلب العنوان من الزّائر، وفور الحصول على العنوان يُمكنك إرسال الكتاب إليه عبر أحد خدمات البريد مثل mailerlite أو MailChimp وغيرها. صمّم صفحة هبوط لكتابك وركّز على أن تكون أنيقة ومراعيّة لشروط الجودة، وهذه سلسلة من المقالات المُفيدة في هذا الموضوع. يُمكن كذلك إنشاء مُدونة مُتعلّقة بالمشروع وعرض إرسال خلاصتها إلى الزوار عبر البريد في كل أسبوع. انظر أيضا الجواب على هذا السؤال: أمّا بالنّسبة لكيفيّة الحصول على أرقام الهواتف فلا أعلم بطريقة مُحدّدة، لكنّني أقترح اعتماد مبدأ الهدايا، بحيث تُرسل هديّة لكل من يضع رقم هاتفه.
- 5 اجابة
-
- 1
-
تتميّز لغة بايثون بكونها سهلة التّعلم، لذلك يجب عليك أن تعلم بأنّ المصدر الذي تعتمده لتعلّم اللغة غير مهم كثيرا، فمعظم المصادر المُتواجدة على الويب ذات جودة عاليّة، ويُمكنك أن تجد كتبا مجّانيّة لتعلّم اللغة وهي في الغالب كتب جيّدة، يُمكنك مثلا البدأ بالتّعلم من كتاب Think Python وهو كتاب مجاني ورائد في هذا المجال لكنّه باللغة الإنجليزيّة. يُمكنك كذلك تعلّم لغة بايثون بالعربيّة من خلال سلسلة لغة بايثون في موقع الأكاديميّة. بعد تعلّم الأساسيّات أنصحك بتعلّم تطوير الويب بإطار العمل Flask والذي ستجد دروسا لتعلّمه في قسم Flask بالأكاديميّة.
-
يُعتبر Compass إطار عمل للغة Sass ويُمكّنك من الحصول على الكثير من الدوال المُساعدة للتعامل مع Sass بشكل أفضل، ويُمكّنك كذلك من تحزيم تنسيقاتك على شكل مكتبة صغيرة مثل Bootstrap. يُمكنك أن تُشبه Compass للغة Sass بإطار Rails للغة روبي. المصدر
-
لا يتطلب الأمر جهدا كثيرا، وهو سهل في الحقيقة، أضف التالي إلى ملف css الخاص بك: @media only screen and (max-width: 500px) { body { font-family: 12px; } } غير body إلى الوسم الذي تستهدفه، وغير 500px إلى حجم الشاشة الذي ترغب بتغيير حجم الخط عند الوصول إليه. تأكّد من أن تُضيف السّطر التّالي داخل وسم <head> في ملف HTML. <meta name="viewport" content="width=device-width, initial-scale=1">
-
تسمح الاستضافة الافتراضيّة بتوفير العديد من أسماء النّطاقات Domain names على خادوم واحد، يُمكن استعمال خوادم الويب لإنشاء مثل هذه الإستضافات، وهي حل جيد لاستضافة العديد من تطبيقات الويب في نفس الخادوم لكنّها قد تكون عائقا في حالة كان عدد الزوار كبيرا. يُمكن مثلا أن تقوم باستضافة موقعين على نفس الخادوم بهذه الطّريقة. بحيث يكون لكل موقع اسم نطاق مُختلف. مثلا الموقع الأول عبارة عن مُدونة بسيطة تعتمد على سكربت ووردبريس ونطاقها كالتّالي blog.example.com، بعدها يُمكنك استضافة موقع آخر باسم نطاق مُختلف وليكن موقعا مُعتمدا على سكربت Opencart للمتاجر الإلكترونيّة وليكن اسم نطاقه كالتّالي store.example.com. الاختلاف الجوهري هو أنّ ملفات سكربت ووردبريس متواجدة في مسار مُختلف عن مسار ملفات المتجر. مثلا ملفّات سكربت الووردبريس ستكون في المسار التّالي: /var/www/site/wordpress أمّا ملفّات سكربت Opencart فستكون في مسار آخر: /var/www/site/opencart تُمكّن هذه التّقنية من تقسيم موارد الخادوم على أكثر من تطبيق، ما يعني أنّك قد تجد بعض المشاكل في حالة استعمل أحد التّطبيقات الموارد بشكل كبير. مُلاحظة: يُمكن لاسم النّطاق أن لا يكون فرعيا، أي أنّ blog.example.com يُمكن أن يكون blogexample.com كذلك.
- 2 اجابة
-
- 1
-
بعد أن تعرّفنا على أساسيات لغة بايثون حان الوقت للانتقال إلى مرحلة جديدة. في هذه السّلسلة من الدّروس سنتعرّف على أساسيات تطوير تطبيقات الويب بلغة بايثون، وذلك بالاستعانة بإطار العمل 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 كل درس سيكون شبه مُستقل عن الدّرس الذي يسبقه، وذلك لتكون الدروس مرجعا لك في حالة نسيان أي جزئية. في نهاية السّلسلة ستكون قادرا على استعمال لغة بايثون لتطوير تطبيق يعمل على المُتصفّح ويتصل بقاعدة بيانات. ختاما في الدّرس المُقبل سنقوم بإعداد بيئة التّطوير بتنصيب الأدوات المطلوبة، كما سننشئ تطبيقا بسيطا لعرض صفحة ويب على المُتصفّح.