البحث في الموقع
المحتوى عن 'flask_cms'.
-
مقدّمة بعد أن تعرّفنا على كيفيّة تجهيز جدولي المقالات والمُستخدمين اللّذين سيُشكّلان أساس بيانات تطبيقنا، وبعد أن تعرّفنا في الدّرس السّابق على كيفيّة الرّبط بين الجدولين ليكون لكلّ كاتب مقالاته ولكل مقال كاتب خاص به، حان الوقت لإنشاء الجدولين في قاعدة البيانات والتّعرّف على كيفيّة استغلال كلّ من مكتبة SQLAlchemy وإضافة Flask-SQLAlchemy للتّعامل مع البيانات. لماذا الدّروس القادمة أساسيّة؟ هذا الدّرس والدّروس القادمة مُهمّة لك إن كنت ترغب بالاعتماد على قواعد بيانات SQL ومكتبة SQLAlchemy في تطوير تطبيقاتك، وما ستتعلّمه من هذه الدروس سيُفيدك في حالات كثيرة، وستتمكّن من القيام بأكثر العمليّات شيوعا باستخدام SQLAlchemy عوضا عن لغة SQL ما سيبقي شفرتك نظيفة وسيُمكّنك من العمل مع بياناتك بأريحيّة أكثر. ورغم أنّنا لن نستعمل الكثير من الأشياء التّي سأذكرها في تطبيق “كلمة”، إلّا أنّ الإلمام بها مهم جدّا، وعندما أقول بأنّ هذه المفاهيم مهمّة فأنا لا أعني أن تحفظها عن ظهر قلب، إذ يُمكنك أن تعود إلى درس مُعيّن في كلّ مرّة تُريد أن تتذكّر كيفيّة القيام بعمليّة معيّنة باستخدام مكتبة SQLAlchemy وإضافة Flask-SQLAlchemy، وأشجّعك على توظيف كل ما ستتعلّمه لإضافة ميّزات جديدة إلى التّطبيق الذي سنبنيه سويّا (مثلا إضافة جزء لعرض مقال عشوائي في كلّ مرّة يُعاد فيها تحميل الصّفحة)، وإن أردت أن تعرف كيفيّة القيام بعمليّة أخرى من عمليّات SQL التّقليديّة باستخدام SQLAlchemy فعد أولا إلى التّوثيق الرّسمي للمكتبة أو ضع سؤالا مصاغا بوضوح ولغة سليمة في قسم الأسئلة والأجوبة للحصول على إجابة كافيّة. إنشاء جدولي المقالات والمُستخدمين في قاعدة البيانات بعد أن أنشأنا الصّنفين المسؤولين عن إنشاء الجدولين في ملفّ models.py الذي يقع تحت مُجلّد project أصبح بإمكاننا إنشاء الجدولين عن طريق مكتبة SQLAlchemy اعتمادا على محتويات ملفّ models.py والكائن db الذي سبق لنا وأنشأناها بمُساعدة إضافة Flask-SQLAlchemy. بعد إنشاء الجدولين، ستُطبّق التّغييرات مُباشرة إلى خادوم PostgreSQL الذي نُصّب بجهازك المحلي، ما يعني بأنّك ستتمكّن من التّعامل مع قاعدة البيانات باستخدام لغة SQL والميّزات التي تُوفّرها قاعدة البيانات PostgreSQL. لإنشاء الجدولين في قاعدة بياناتنا، سنقوم أولا بإنشاء ملف باسم create_db.py في المجلّد الرّئيسي kalima وسنضع به ثلاثة أسطر فقط: # create_db.py from project import db from project.models import Post, User db.create_all() تأكّد فقط من أنّ إعداد SQLALCHEMY_DATABASE_URI يُشير إلى قاعدة بيانات PostgreSQL التّي أنشأناها سابقا باسم kalima وأنّ اسم المُستخدم وكلمة المرور صحيحان في ذات الإعداد، وللمزيد أعد إلقاء نظرة على درس تجهيز قاعدة البيانات PostgreSQL ودرس إنشاء جداول البيانات. كما تُلاحظ، الشّفرة بسيطة، أولا نستدعي الكائن db من حزمة المشروع، ثمّ نستدعي كلّا من الصّنف Post وUser لإعلام SQLAlchemy بأنّنا قد حدّدنا هذه الجداول والأعمدة الأساسيّة فيها، وبعد ذلك نقوم بإنشاء الجداول في قاعدة البيانات باستعمال الدّالة db.create_all. وبنفس الطّريقة لو أردت حذف الجداول يُمكنك استدعاء الدّالة db.drop_all عوضا عن db.create_all. وإن أمكنك الوصول إلى سطر أوامر PostgreSQL عن طريق أداة psql فيُمكنك الاتّصال بقاعدة البيانات kalima وعرض الجداول فيه، بالأمرين التّاليين: postgres=# \c kalima You are now connected to database "kalima" as user "postgres". kalima=# \d No relations found. الأمر الأول للاتّصال بقاعدة البيانات الخاصّة بتطبيقنا، وذلك لأنّ الخادوم يحتوي على أكثر من قاعدة بيانات واحدة، أمّا الأمر الثّاني فهو لعرض الجداول المُتواجدة بقاعدة البيانات، وبما أنّنا لم ننفّذ ملفّ create_db.py بعد فالنّتيجة هي بطبيعة الحال أنّنا لا نمتلك أية جداول أو علاقات كما تُلاحظ من الرّسالة No relations found. الآن، نفّذ الملفّ عبر الأمر: python create_db.py إن لم تلاحظ أية رسالة، فهذا يعني بأنّ كل شيء بخير، ولو عدت إلى سطر أوامر psql وأعدت تنفيذ الأمر \d للاحظت ما يلي: List of relations Schema | Name | Type | Owner --------+--------------+----------+---------- public | posts | table | postgres public | posts_id_seq | sequence | postgres public | users | table | postgres public | users_id_seq | sequence | postgres ربّما تختلف قيمة العمود Owner لديك لأنّها تُشير إلى اسم المُستخدم الذي أنشأ الجداول أو مالكها، وهذا الاسم هو نفسه الذي تُحدّده في إعداد قاعدة البيانات الذي سبق وأن أشرنا إليه، لاحظ معي فقط كلّا من الجدول posts و الجدول users: public | posts | table | postgres public | users | table | postgres بما أنّ نوع العلاقة هو table (جدول)، فهذا يعني بأنّنا استطعنا إنشاء الجدولين بنجاح، ونستطيع الآن التّعامل معه بلغة SQL مباشرة من أداة psql. في ما يلي استعلامان يُمكنك تنفيذهما للحصول على جميع السّجلات في كلّ جدول: select * from posts; select * from users; بالطّبع، لأنّنا لم نُضف أية سجلّات بعد، فالمُخرج سيكون كما يلي في كلتا الحالتين: Posts Table: id | title | content | created_date | author_id ----+-------+---------+--------------+----------- (0 rows) Users Table: id | name | email | password | created_date ----+------+-------+----------+-------------- (0 rows) وكما تُلاحظ، جميع الأعمدة التّي سبق وأن حدّدناها في ملفّ models.py باستعمال db.Column متواجدة في هذين الجدولين. يُمكنك بالطّبع إضافة بعض السّجلّات بلغة SQL إن أردت ذلك بالاعتماد على جملة INSERT INTO وكذا التّعامل مع البيانات بمُختلف الطّرق التّي تُتيحها PostgreSQL، ولكنّنا لن نستعمل لغة SQL لأنّنا نمتلك أداة SQLAlchemy وإضافة فلاسك الخاصّة به. ختاما تعرّفنا في هذا الدّرس على كيفيّة إنشاء جدولي المقالات والمُستخدمين في قاعدة البيانات مُباشرة باستعمال الدّالة db.create_all() التّي توفّرها لنا إضافة Flask-SQLAlchemy. في الدّرس القادم، سنتعرّف على كيفيّة استعمال مُفسّر لغة بايثون للتعامل مع قاعدة البيانات بمُساعدة من مكتبة SQLAlchemy، وبذلك سنتمكّن من التواصل مع قاعدة البيانات وإدارة سجلات الجداول فيها.
-
- jinja2
- sqlalchemy
-
(و 1 أكثر)
موسوم في:
-
مقدّمة بعد أن ألقينا نظرة على كيفيّة استخدام إطار العمل Flask لإنشاء تطبيقات ويب بسيطة بالاستعانة بقاعدة بيانات SQLite، سنبدأ هذه السّلسلة بالدّخول إلى عالم إضافات Flask التّي ستسهّل علينا الكثير من المهام كإعداد نظام لتسجيل الدّخول والخروج، والتّعامل مع نظام قاعدة بيانات، وكذلك التّحكم في تطوير التّطبيق عن طريق سطر الأوامر؛ وكذلك استعمال أدوات لتطوير تطبيق أكثر أمانا. سنتطرّق أيضا إلى بعض المفاهيم المتقدّمة كتشفير كلمات المرور وتهجير قواعد البيانات وغير ذلك. صحيح بأنّ هذه السّلسلة مطولة نوعا ما إلا أنّها ستكون شاملة لمُساعدتك على تطوير أي تطبيق يمر على خاطرك أو يطلبه منك أحد عملائك. وإن كنت جديدا إلى عالم تطوير الويب فستتعلّم من هذه السّلسلة ما يكفي لتبدأ مشوار العمل كمطوّر تطبيقات ويب سواء عن طريق إنجاز مشاريع لعملاء على منصّات العمل الحر كمنصّة مُستقل أو بناء مشاريعك الخاصّة. سنقوم في هذا الدّرس بالتّعرف على فكرة وبنية التّطبيق الذي سنبنيه في رحلتنا هذه. المُتطلّبات بالإضافة إلى أساسيّات إطار العمل Flask، ستحتاج كذلك إلى معرفة كيفيّة التّعامل مع نماذج HTML عبر مكتبة WTForms وإضافة Flask-WTF ويُمكنك الاطّلاع على سلسلة دروس خاصّة بهذا الموضوع على الأكاديميّة. سلاسل الدّروس التّي يجب عليك أن تطّلع عليها لفهم أفضل لهذه السّلسلة من الدّروس المُتقدّمة نوعا ما، عليك أن تطّلع وتطبّق سلاسل الدّروس التّاليّة: سلسلة مدخل إلى إطار العمل Flask درس إنشاء تطبيق لاختصار الرّوابط باستخدام إطار العمل Flask بجزئيه الأول و الثاني. سلسلة التّحقق من مُدخلات المُستخدم باستخدام مكتبة WTForms وإضافة Flask-WTF ستكون لغة جافاسكربت مُفيدة كذلك، ولا مفرّ من إتقانها إن كنت ترغب بأن تُصبح مُطور ويب أفضل، كما أنّني لن أشرح كل كبيرة وصغيرة عند التّعامل معها في التّطبيق، وهذا لأنّ الهدف من السّلسلة هو تعليمك كيفيّة إتقان التّعامل مع إطار العمل فلاسك وهذا بالطّبع لا يشمل شيفرة جافاسكربت. ستحتاج كذلك إلى تعلّم أداة Git لإدارة نُسَخ التّطبيق والتّعامل مع التّطبيق بعد نشره على منصّة Heroku. ما الذي ستتعلّمه من هذه السّلسلة؟ ستتعلم في هذه السّلسلة تقنيات متقدّمة لإدارة مشاريع تطبيقات الويب، وكيفيّة التّعامل مع قاعدة بيانات PostgreSQL وأداة SQLAlchemy لإدارة الجداول (إضافة، قراءة، تعديل وحذف البيانات)، وكذلك الطرق البسيطة لتهجير قواعد البيانات Database Migration وستتعرّف كذلك على طرق توزيع ملفّات التّطبيق على شكل وحدات وحزم لتنظيم أكثر. سنستخدم كذلك مكتبة WTForms لإدارة نماذج HTML وجعلها أكثر أمانًا للحماية من هجمات مُحتملة من المُخرّبين والمُخترقين. وسنعد كذلك نظامًا أكثر أمانًا لتسجيل الدّخول واستيثاق المُستخدمين، وسنتطرّق كذلك إلى كيفيّة نشر التّطبيق إلى منصّة Heroku لتربطه بعد ذلك باسم نطاق خاصّ إن أردت ذلك. ستتعلّم كذلك طريقة حماية كلمات المرور عن طريق تشفيرها في قاعدة البيانات باستعمال خوارزميّة Bcrypt للتّشفير، وذلك لتجنّب حفظها كنصّ مجرّد يسهل استخدامه من طرف المُخترق إذا وصل إلى قاعدة البيانات. وقد تعرّفنا سابقًا على كيفيّة استعمال الجلسات Sessions لإنشاء نظام تسجيل دخول وخروج بسيط، صحيح بأنّ ذلك النّظام يعمل بشكل جيّد، إلّا أنّه لا يعد آمنًا، ويُمكن أن يتعرّض تطبيقنا لهجمات كثيرة من قبل المُخترقين، ولحماية العضويات والبيانات في الموقع، سيتوجّب علينا أن نُدير الجلسات بطريقة أكثر تعقيدًا، لكنّ هناك إضافة لإطار العمل فلاسك لتبسيط هذا الأمر ولن نحتاج إلى العمل على ذلك بأنفسنا، وبالتّالي فسيُصبح تطبيقنا أكثر أمانًا دون عناء كبير، وكلّ هذا وأكثر سنتعلّمه بتفصيل أكبر في الدّروس القادمة. بنية التّطبيق التّطبيق الذي سنعمل عليه في هذه السّلسلة من الدّروس عبارة عن نظام إدارة محتوى ومنصّة للتّدوين الجماعي، ستكون هناك بعض الميّزات الفريدة وستكون هناك ميّزات أخرى مفقودة، إذا سبق لك وأن تعاملت مع أحد أنظمة التّدوين الأخرى كمنصّة ووردبريس WordPress، Blogger أو Tumblr أو حتى منصّة medium فستتمكّن بنهاية هذه السّلسلة من التّخلي عن هذه المنصّات وإنشاء منصّة بسيطة خاصّة بك لتكتب عليها، أو تستطيع تعديل المشروع بعد الانتهاء منه لتُضيف ميّزات أخرى أو تُطبّق فكرة فريدة لتطرح مشروعًا على أرض الواقع لتتمكّن من الاستفادة منه ماديّا إن أردت ذلك. سيتمكّن مُستخدمو المنصّة من إنشاء حساب خاصّ بهم لينشروا مقالاتهم التّي يكتبونها، إضافة المقالات ستتمّ عن طريق كتابتها بتنسيق الماركداون Markdown ومن ثمّ نشرها، سيتمكّن كلّ مُستخدم من الحصول على رابط خاصّ بمقالاته لنشرها للآخرين، سيتمكّن الكاتب من إضافة وسوم لمقالاته، وكذلك إدراجها ضمن قسم معيّن، لكل مقالة قسم للتّعليقات ليتمكّن المُستخدمون الآخرون من التّعليق على مقال معيّن. سيكون للتّطبيق نكهة اجتماعيّة، بحيث سيتمكن المُستخدمون من مُتابعة بعضهم البعض، وكذلك إضافة مقالات إلى المُفضّلة والإعجاب بها لنتمكّن من فرزها وترتيبها حسب الأكثر إعجابًا، الأكثر نشاطًا (أكبر عدد من التعليقات) وبعدها ستتمكّن من إضافة خاصيّات أخرى بنفسك باتّباع نفس الطّريقة، مثلًا بعد الانتهاء من التّطبيق ستتمكّن من تطبيق فكرة ترتيب المقالات حسب الأكثر إعجابًا لإضافة خاصيّات أخرى (الأكثر شعبيّة حسب عدد التّعليقات، الأكثر زيارة...). صحيح بأنّنا سنبني تطبيقًا مُعقّدا وكبيرًا، إلّا أنّني لن أشرح كلّ شيء بتفصيل ممل، سأشرح بالتّفصيل فقط الأفكار الجديدة وما تراه لأوّل مرّة حتى لا تكون هذه السّلسلة أطول من اللازم، خاصّة وأنّ بعض الأساليب ستكون مُشابهة جدّا لبعضها البعض، فمثلاً طريقة السّماح للمُستخدم بإضافة مقال خاص به مُشابهة جدّا لطريقة فرز المقالات حسب قسم مُعيّن (لكل مُستخدم مقالاته ولكلّ قسم مقالاته كذلك). سنضع للتّطبيق بعض الميّزات المُفيدة كنظام أرشفة لتأريخ كلّ مقال حسب تاريخ إضافته، كما سيكون لتعليقات المقال نظام مُشابه، وسنستغل نفس الطّريقة لتخصيص تاريخ انضمام للمُستخدم. في النّهاية، تذكّر بأنّ هذه السّلسلة من الدّروس ستكون تطبيقيّة بالدّرجة الأولى، وستتعلّم فيها كيفيّة بدء مشاريعك التّطويريّة الخاصّة بك بلغة بايثون وإطار فلاسك، أمّا كيفيّة الاستفادة القصوى منها فمعروفة، عليك تطبيق ما تعلّمته وتختبر مع إضافة ميّزات ولو لم أذكرها، وإن أردت معرفة معمّقة عن أي شيء تحدّثت عنه دون أن أفصّل، يُمكنك دائما العودة إلى التّوثيق الرّسمي للأدوات والمكتبات والإضافات المعنيّة بالأمر، وإن كان لك سؤال معيّن فلا تتردّد في طرحه على قسم الأسئلة والأجوبة على الأكاديميّة. هناك المزيد الدّروس التّي ستندرج تحت هذه السّلسلة لن تُغطي فقط ما قرَأتَهُ للتّو، بل أكثر من ذلك، فهدفي من كتابة سلسلة الدّروس هذه هو تغطيّة الفراغ الموجود في الويب العربي، إذ أنّ التّوثيقات الرّسميّة للمكتبات وأطر العمل المُستعملة حاليّا عادة ما تكون مكتوبة باللغة الانجليزيّة فقط، لذا بتوفير سلسلة دروس كهذه ستمتلك أكبر قدر مُمكن من المُساعدة لتتمكّن من العمل على تطبيقاتك الخاصّة دون أن تضطر إلى اتّباع سلسلة دروس أخرى أو الاطّلاع على التّوثيقات الرّسميّة التّي عادة ما تكون مُفيدة أكثر إذا ما أردت أن تُنجز شيئًا بسيطًا، وعلى العكس من ذلك، فكتابة تطبيق كامل (كما في هذه السّلسلة) طريقة أفضل للتّعلم. ختاما هذا المقال عبارة عن مُقدّمة بسيطة لما سنخوضه في قادم الدّروس، في الدّرس القادم، ستبدأ المُتعة مع استكشاف كيفيّة التّعامل مع ملفّات ومُجلّدات التّطبيقات الكبيرة والمُعقّدة بطريقة بسيطة.
-
إن كانت لدينا آلاف المقالات فلا يُمكننا أن نعرضها جميعها في الصّفحة الرّئيسية، ولو كانت تحتوي على آلاف الصّور ومقاطع الفيديو فمن المؤكّد بأنّ الصّفحة ستأخذ عدّة دقائق لتنهي التّحميل، والحل الشّائع حاليّا في تطبيقات الويب هو تجميع عدد محدّد من السّجلات في كل صفحة، مثلا خمسة سجلّات في الصّفحة الأولى وخمسة أخرى في الصّفحة الثّانيّة، وهكذا… بهذه الطّريقة يُمكنك عرض شريط أسفل الصّفحة لتوفير طريقة للمُستخدم للتنقل بين الصّفحات، كذلك يُمكنك استعمال لغة جافا سكربت لوضع زر لتحميل المزيد من المقالات كما هو شائع في الكثير من المواقع على الانترنت. كيف تعمل خاصية الصفحات في Flask-SQLAlchemy قبل أن ننتقل إلى كيفيّة تقسيم الصّفحات إلى سجلّات لا بد أن تعرف المبدأ الرّئيسي للعمل، بدءًا من معنى كلمة Pagination (تحويل مجموعة من السّجلات إلى صفحات مُرقّمة، كل صفحة تحمل عددا مُعيّنا من السّجلات، والعدد الذي تُحدّده لكل صفحة يكون ثابتا إلى آخر مجموعة من السّجلات). فمثلا لنفترض بأنّ الأسماء التّاليّة مُسجلة لدينا في قاعدة البيانات: 1 عمر 2 عبد الرّحمن 3 خالد 4 يوسف 5 أحمد 6 إبراهيم 7 كمال 8 كريم 9 مُعاذ 10 جواد لنُقسّمها إلى صفحات مُرقّمة، كل صفحة ستحتوي على 3 أسماء أو أقل: الصّفحة 1: 1 عمر 2 عبد الرّحمن 3 خالد الصّفحة 2: 4 يوسف 5 أحمد 6 إبراهيم الصّفحة 3: 7 كمال 8 كريم 9 مُعاذ الصّفحة 4: 10 جواد بهذه الطّريقة، عندما تطلب من قاعدة البيانات السّجلات المُتواجدة في الصّفحة رقم 1، ستحصل على الأسماء: عمر، عبد الرّحمن، خالد. وإن طلبت السّجلات في الصّفحة رقم 4 فستحصل على الاسم "جواد"، وهكذا ستتمكّن من تقسيم السّجلات المُتواجدة في قاعدة البيانات لتفادي الاستعلام عنها كلّها في مرّة واحدة ومن ثم تقسيمها بنفسك باستعمال لغة Python، وهذا يكون بطيئا جدّا إذا ما كانت لديك الكثير من السّجلات في قاعدة البيانات، وعوضا عن الحصول عليها جميعا في مرّة واحدة، تطلب سجلّات صفحة واحدة فقط. إذا كنت تستخدم كلّا من مكتبة SQLAlchemy وإضافة Flask-SQLAlchemy للتّعامل مع قاعدة البيانات فيُمكنك استعمال خاصيّة تجميع عدد من السّجلات في عدد من الصّفحات عبر استعمال التّابع paginate الذي يأخذ مُعاملين أساسيّين، المُعامل page والذي سيُعبّر عن رقم الصّفحة، والمُعامل per_page الذي سيُعبّر عن عدد السّجلات التّي تتواجد بكل صفحة، وكمثال على هذين المعاملين، فقيمة per_page في مثال الأسماء سابقا هي 3 وكلّما غيّرت قيمة المُعامل page من 1 إلى 4 ستحصل على الأسماء المتواجدة بكل صفحة حسب رقم الصّفحة. السّطر التّالي مثال بسيط على كيفيّة استخدام التّابع paginate: >>> from project.models import Post >>> pagination = Post.query.paginate(page=1,per_page=3) >>> pagination <flask_sqlalchemy.Pagination object at 0x7f0b4a3cc910> ستُلاحظ بعد تنفيذك للأسطر السّابقة، أنّ المُتغيّر pagination عبارة عن كائن من الصّنف Pagination وليس عبارة عن قائمة سجلّات تُمثّل المقالات، ذلك أن SQLAlchemy توفّر لنا العديد من الخصائص والتّوابع التّي يُمكننا استخدامها مع أي كائن من الصّنف Pagination، فيُمكنك مثلا أن تتحقّق من أنّ الصّفحة الحاليّة هي آخر صفحة أو العكس أو أن تحصل على عدد الصّفحات الإجمالي والعديد من التّوابع والخصائص الأخرى التّي سنتطرّق إليها والتّي ستُساعدك حتما عندما ترغب في العمل مع صفحات مُتعدّدة أو استخدام تنسيقات مُحدّدة حسب شروط مُعيّنة. سنُغطّي في الفقرات التّاليّة بعضا من أهم الأمور التّي يُمكنك القيام بها مع خاصيّة ترقيم الصّفحات لتُقدّم لمُستخدمي تطبيقك أفضل تجربة استخدام مُمكنة. الوصول إلى السجلات الموجودة بالصفحة نفّذنا الأسطر التّالية قبل قليل لنحصل على كائن باسم pagination من الصّنف Pagination: >>> from project.models import Post >>> pagination = Post.query.paginate(page=1, per_page=3) ولكن الأهم أن نحصل على المقالات المُتواجدة في الصّفحة كي نتمكّن من عرض عنوان كل مقال ومحتواه أو مُعالجة بيانات كل مقال على حدة. وللوصول إلى السّجلات المُتواجدة بالصّفحة، أضف خاصيّة items للكائن pagination، وبالتّالي ستتمكّن من الدّوران حول المقالات الثّلاثة بحلقة for بسيطة ومنه ستتمكّن من الوصول إلى عنوان كل مقال ومُحواه وكاتبه وما إلى ذلك (تماما كما تتعامل مع المقالات التّي تحصل عليها عن طريق ()query.all أو أي استعلام مُشابه). والمثال التّالي يوضح هذا: >>> posts = pagination.items >>> posts [<title Post 1 | post 1 content >, <title a post from abdelhadi | content of a post >, <title another post from abdelhadi | some other content for the post >] كما ترى فقد تمكّنّا من الوصول إلى قائمة المقالات المُتواجدة في الصّفحة الأولى، ويُمكنك الآن التّعامل مع المُتغيّر posts كما تتعامل مع أي مُتغيّر آخر يُمثّل قائمة سجلّات في قاعدة البيانات. الانتقال إلى الصفحة التاليّة بعد أن تعرّفنا على كيفيّة الوصول إلى الصّفحة الأولى وعناصر الصّفحة، حان الوقت للانتقال إلى الصّفحة التّاليّة والوصول إلى عناصرها. للوصول إلى الصّفحة التّاليّة، كل ما عليك القيام به هو إضافة التّابع next إلى مُتغيّر الصّفحة الحاليّة والذي يجب أن يكون كائنا من الصّنف Pagination، انظر: >>> page_1 = Post.query.paginate(page=1, per_page=3) >>> page_1.items [<title Post 1 | post 1 content >, <title a post from abdelhadi | content of a post >, <title another post from abdelhadi | some other content for the post >] >>> page_2 = page_1.next() >>> page_2.items [<title a post from dyouri | other content >, <title Post from user4 | Content 4 >, <title Post from user5 | Content 5 >] في المثال السابق: نصل أولًا إلى الصّفحة الأولى ثم نسند القيمة إلى المُتغيّر page_1 وبعدها نطلع على عناصر الصّفحة للتأكّد، بعدها تأتي الفكرة الأساسيّة إذ نُنشئ مُتغيّرا جديدا page_2 بحيث نستخدم المُتغيّر page_1 مع التّابع next للحصول على الصّفحة الثّانيّة، وبعدها ننظر إلى عناصر الصّفحة. الصفحة السابقة تتشابه طريقة الوصول إلى الصّفحة السّابقة مع طريقة الوصول إلى الصّفحة التالية، فقط استعمل التّابع prev عوضا عن التّابع next، انظر: >>> page_2 = Post.query.paginate(page=2, per_page=3) >>> page_2.items [<title a post from dyouri | other content >, <title Post from user4 | Content 4 >, <title Post from user5 | Content 5 >] >>> page_1 = page_2.prev() >>> page_1.items [<title Post 1 | post 1 content >, <title a post from abdelhadi | content of a post >, <title another post from abdelhadi | some other content for the post >] نستعمل التّابع paginate أولا مع تمرير القيمة 2 إلى المُعامل page للحصول على الصّفحة الثّانيّة، ومن ثمّ نقوم بإنشاء مُتغيّر باسم page_1 بالقيمة ()page_2.prev (أي الصّفحة التّي تسبق الصّفحة الثّانيّة)، وهكذا نصل إلى الصّفحة الأولى مرة أخرى. التحقق من وجود صفحة تالية للصفحة الحالية قد ترغب أحيانا بمعرفة ما إذا كانت هذه الصّفحة التّي تتعامل معها هي الأخيرة أم لا، وتستطيع ذلك عن طريق الخاصيّة has_next التّي تُرجع إمّا القيمة المنطقيّة True أو القيمة False، بحيث يكون الشرط صحيحًا إذا كانت للصّفحة صفحة مُواليّة والعكس إذا كانت آخر صفحة. مثال على الحالة الأولى: >>> page_1 = Post.query.paginate(page=1, per_page=3) >>> page_1.items [<title Post 1 | post 1 content >, <title a post from abdelhadi | content of a post >, <title another post from abdelhadi | some other content for the post >] >>> page_1.has_next True كما تُلاحظ، فالصّفحة الأولى ليست آخر صفحة لأنّ عدد المقالات يتجاوز 3 مقالات، وعليه فالخاصيّة has_next تُرجع القيمة المنطقيّة True، وهذا يعني بأنّ القيمة كانت لتكون False لو كانت عدد المقالات المُتواجدة بقاعدة البيانات ثلاثة أو أقل من ذلك. مثال على الحالة الثّانيّة: >>> page_5 = Post.query.paginate(page=5, per_page=3) >>> page_5.items [<title New title for post from user8 | Content 8 >] >>> page_5.has_next False في هذه الحالة تكون الصّفحة الخامسة هي آخر صفحة وليس بعدها أية صفحة أخرى، حتى أنّ بها مقالا واحدا فقط، وبالتّالي فالنّتيجة هي القيمة False عند استعمالنا للخاصيّة has_next. التحقق من أن للصفحة الحالية صفحة تسبقها بما أنّ هناك طريقة لمعرفة ما إذا كانت للصّفحة الحاليّة صفحة أخرى بعدها، فلا بد أن تكون هناك طريقة للتّعرف على عكس ذلك، أي معرفة ما إذا كانت هذه الصّفحة أول صفحة بحيث لا صفحة تسبقها أم لا. ولمعرفة ما إذا كانت لهذه الصّفحة الحاليّة صفحة أخرى تسبقها يُمكنك استعمال الخاصيّة has_prev، انظر: >>> from project.models import Post >>> page_1 = Post.query.paginate(page=1, per_page=3) >>> page_1.items [<title Post 1 | post 1 content >, <title a post from abdelhadi | content of a post >, <title another post from abdelhadi | some other content for the post >] >>> page_1.has_prev False >>> page_1.has_next True نحصل هنا على الصّفحة الأولى ونتحقّق من أنّ لها صفحة تسبقها، والنّتيجة هي القيمة False كما هو مُتوقّع، بعدها نقوم بالتّحقق ممّا إذا كانت للصّفحة صفحة تالية بعدها، والنّتيجة مُتوقّعة كذلك. لنجمع الآن كلّا من التّابع next والخاصيّة has_prev ونرى النّتيجة: >>> page_1.next().has_prev True النّتيجة هي القيمة True لأنّ قيمة ()page_1.next هي الصّفحة الثّانيّة، ومن المُؤكّد بأنّ للصّفحة الثّانيّة صفحة تسبقها. عدد الصفحات وكيفية الدوران حول قائمة الصفحات للوصول إلى عدد الصّفحات الكليّ عند استخدام التّابع paginate، أضف الخاصيّة pages إلى الكائن، وسيعمل مهما كانت الصّفحة الحاليّة. مثال: >>> from project.models import Post >>> page_1 = Post.query.paginate(page=1, per_page=3) >>> page_1.pages 5 >>> page_3 = Post.query.paginate(page=3, per_page=3) >>> page_3.pages 5 في المثال أعلاه، نستورد الصّنف Post أولًا كالعادة ونحصل على الصّفحة الأولى ونُسند قيمتها إلى المُتغيّر page_1، وبعدها نستعمل الخاصيّة pages للتّعرف على العدد الإجماليّ للصّفحات الذي هو خمسة، وبعدها نقوم بنفس الشّيء مع الصّفحة الثّالثة، وذلك فقط لتوضيح أنّك تستطيع الحصول على العدد الإجمالي للصّفحات بغض النّظر عن الصّفحة التّي أنت فيها. الدوران حول قائمة أرقام الصفحات قد ترغب أحيانًا في الوصول إلى رقم كل صفحة على حدة، وأغلب الظّن أنّك تعمل على شريط لتمكين المُستخدم من الانتقال بين الصّفحات دون ترتيب مُحدّد (الانتقال من الصّفحة الثّالثة إلى الخامسة على سبيل المثال). يُمكنك استخدام التّابع iter_pages للحصول على كائن مُولّد (generator) والذي يسمح لك بالوصول إلى كل رقم على حدة، ويُمكنك التّعامل معه باستخدام حلقة for كما تتعامل مع قوائم python عاديّة، ويُمكنك كذلك الحصول على قائمة عبر استخدام الدّالة list لكنّ الأمر ليس ضروريا. انظر المثال التالي: >>> from project.models import Post >>> page_1 = Post.query.paginate(page=1, per_page=3) >>> page_1.iter_pages() <generator object iter_pages at 0x7f4d96115230> >>> for page in page_1.iter_pages(): ... print(page) ... 1 2 3 4 5 >>> list(page_1.iter_pages()) [1, 2, 3, 4, 5] عندما طبّقنا التّابع ()iter_pages على المُتغيّر page_1 حصلنا على معلومة بسيطة تُشير إلى أنّ هذا الكائن من النّوع generator، أي أنّه يُولّد كل عنصر عندما تحتاج إليه فقط، وكما تُلاحظ، فعندما استخدمنا حلقة for وطبعنا كل عنصر على حدة، استطعنا الوصول إلى أرقام الصّفحات من واحد إلى خمسة، وفي السّطر الأخير، ينتج استدعاء الدّالة list وتمرير المُولّد إليها قائمةً عاديّة، وهذا السّطر الأخير غير مفيد إلا في التّوضيح فقط، واستخدام المُولّد مُباشرة أمر طبيعي. فهذا المُولّد يُستخدم بشكل شائع لتوفير شريط تصفّح لتمكين الزّائر من الانتقال من صفحة إلى أخرى، ولا بدّ أنّك صادفت مثل هذا الشّريط من قبل. ولتوفير الشّريط لمُستخدمي تطبيقك، استخدم ماكرو خاص مع مُحرّك القوالب Jinja، وهو كالتّالي: {% macro render_pagination(pagination, endpoint) %} <div class=pagination> {%- for page in pagination.iter_pages() %} {% if page %} {% if page != pagination.page %} <a href="{{ url_for(endpoint, page=page) }}">{{ page }}</a> {% else %} <strong>{{ page }}</strong> {% endif %} {% else %} <span class=ellipsis>…</span> {% endif %} {%- endfor %} </div> {% endmacro %} يأخذ الماكرو render_pagination مُعاملين اثنين، الأول هو الكائن الذي يُمثّل صفحة مُعيّنة، ويجب أن يكون من الصّنف Pagination، مثل page_1 أو page_2 في الأمثلة السّابقة، أمّا المُعامل الثّاني فهو رابط المُوجّه الذي سيكون مسؤولا عن عرض الصّفحات في المُتصفّح، ويجب أن يكون الرّابط على شكل اسم الطّبعة الزّرقاء (إن وُجدت) ثمّ نقطة ثمّ اسم الدّالة المُرتبطة بالمُوجّه. فمثلا إن كان الموجّه الخاصّ بك يقع تحت طبعة زرقاء باسم posts وكانت اسم الدّالة المُرتبطة بهذا الموجّه هي (pages(page بحيث page رقم الصّفحة، فقيمة المُعامل endpoint في الماكرو أعلاه يجب أن تكون كما يلي: 'posts.pages'، والسّبب الرّئيسي لتوفير هذا المُعامل هو أنّ الماكرو يستخدم الدّالة url_for لتوليد رابط للصّفحة حسب رقمها، والتّالي بعض الرّوابط التّي يُمكن أن تُولّد: /posts/page/1 /posts/page/2 /posts/page/3 ... وعليه يكون الشريط في نفس المكان في كل مرّة تصل فيها إلى صفحة مُعيّنة. لاحظ بأنّنا نستعمل في الماكرو حلقة for مع التّابع ()iter_pages الذي تحدّثنا عنه سابقا، وذلك لعرض أرقام الصّفحات وإضافة رابط لكل صفحة، وهكذا سيكون الشّريط كما يلي: 1 2 3 4 5 مع رابط تحت كل رقم ليُوجّه إلى الصّفحة المُناسبة في التّطبيق. تتحقق الجملة الشّرطيّة if إن كانت الصّفحة الحاليّة تحمل نفس رقم الصّفحة المُتواجد في pagination.page وذلك لعرض الصّفحة الحاليّة بشكل مُميّز (في هذه الحالة سيكون خطّا عريضا مع إزالة الرابط) ليعرف الزّائر أية صفحة يتواجد فيها. تستطيع كذلك أن تنسقه كما تشاء، فقط أضف خاصيّة css وسيكون التّنسيق هو نفسه في جميع أنحاء التّطبيق ما دمت تستدعي هذا الماكرو في المكان الذي تُريد به الشّريط. إذا كنت تستخدم إطار العمل Bootstrap فإن الماكرو التّالي يُوفّر تنسيقا جميلا لشريط التّصفّح مع زرّين إضافيّين، واحد للانتقال إلى الصّفحة السّابقة والآخر للانتقال إلى الصّفحة التّاليّة. {% macro render_pagination(pagination, endpoint) %} <div class=pagination> <nav aria-label="Page navigation"> <ul class="pagination"> {# انتقل إلى الصّفحة السّابقة إن كانت مُتواجدة #} {% if pagination.has_prev %} <li> <a href="{{ url_for(endpoint, page=pagination.prev_num) }}" aria-label="Previous"> <span aria-hidden="true">«</span> </a> </li> {% else %} <li class='disabled'> <a href="#" aria-label="Previous"> <span aria-hidden="true">«</span> </a> </li> {% endif %} {# عرض أزرار الصّفحات (<< 1, 2, 3 >>)#} {%- for page in pagination.iter_pages() %} {% if page %} {% if page != pagination.page %} <li><a href="{{ url_for(endpoint, page=page) }}">{{ page }}</a></li> {% else %} <li class="active"><a href="{{ url_for(endpoint, page=page) }}">{{ page }}</a></li> {% endif %} {% else %} <span class=ellipsis>…</span> {% endif %} {%- endfor %} {# انتقل إلى الصّفحة المُواليّة إن كانت مُتواجدة #} {% if pagination.has_next %} <li> <a href="{{ url_for(endpoint, page=pagination.next_num) }}" aria-label="Next"> <span aria-hidden="true">»</span> </a> </li> {% else %} <li class='disabled'> <a href="#" aria-label="Next"> <span aria-hidden="true">»</span> </a> </li> {% endif %} </ul> </nav> </div> {% endmacro %} لاحظ جيّدا بأنّ هذا الماكرو يستخدم كذلك كلّا من الخاصيّتين has_next و has_prev، وذلك لتعطيل زرّ الانتقال إلى الصّفحة السّابقة إذا لم تكن تتواجد وتعطيل زر الانتقال إلى الصّفحة التّالية في نفس الحالة كذلك. كل ما عليك فعله لاستخدام الماكرو هو وضعه في أعلى ملفّ HTML واستدعائه كما يلي: {{ render_pagination(pagination, 'posts.pages') }} يفترض المثال أعلاه أنّ المُتغيّر الذي يحمل كائنا من الصّنف Pagination يُسمّى pagination والذي سيتوجّب عليك تمريره إلى ملفّ HTML عن طريق الدّالة render_template ورابط الموجّه المسؤول عن عرض الصّفحات هو posts.pages والذي سيُمرّر إلى الدّالة url_for لتوليد رابط كل صفحة على حدة. الحصول على رقم الصفحة التالية تطرّقنا من قبل إلى أنّك تستطيع الوصول إلى الكائن الذي يُمثّل الصّفحة التّاليّة عن طريق استخدام التّابع next، ولكن ماذا لو أردت أن تصل إلى رقم الصّفحة التّالية فقط؟ يُمكنك الحصول على رقم الصّفحة التّاليّة عن طريق استخدام الخاصيّة next_num، انظر: >>> from project.models import Post >>> page_1 = Post.query.paginate(page=1, per_page=3) >>> page_1.next_num 2 في المثال، نحصل على الصّفحة الأولى ثمّ نستخدم الخاصيّة next_num للحصول على رقم الصّفحة التالية للصّفحة الأولى والذي هو الرّقم 2 وهذا طبيعي لأنّ رقم الصّفحة الأولى هو 1. الحصول على رقم الصفحة الحالية للوصول إلى رقم الصّفحة الحاليّة كل ما عليك القيام به هو استخدام الخاصيّة page وستحصل على رقم الصّفحة المرتبطة بالكائن من الصّنف Pagination. انظر المثال التالي لكيفية الحصول على رقم الصّفحة الحاليّة: >>> from project.models import Post >>> page_1 = Post.query.paginate(page=1, per_page=3) >>> page_1.page 1 الحصول على رقم الصّفحة السّابقة تشبه طريقة الوصول إلى رقم الصّفحة السّابقة ما سبق، فقط استخدم الخاصيّة prev_num عوضا عن next_num و page. يغطي المثال التّالي كيفيّة الحصول على أرقام الصّفحات التّي ذكرناها إلى الآن. >>> from project.models import Post >>> page_2 = Post.query.paginate(page=2, per_page=3) >>> page_2.page 2 >>> page_2.prev_num 1 >>> page_2.next_num 3 خاتمة هكذا نكون قد وصلنا إلى جزء مُهم من مشوارنا لتعلّم كيفيّة التّعامل مع قاعدة بياناتنا باستخدام كل من مكتبة SQLAlchemy وإضافة Flask-SQLAlchemy، وتبقى لنا بضعة أمور مُهمّة لتعلّم كيفيّة إجراء عمليّات CRUD كلّها، إذ أنّنا لم نتعرّف بعد على كلّ من كيفيّة تعديل وحذف السّجلات إلّا أنّها أمور بسيطة لذا لن تأخذ الكثير من المساحة في هذه السّلسلة من الدّروس. لذا سنُنهي جزء التّعامل مع قاعدة البيانات لنتمكّن بعدها من العودة إلى تطبيق "كلمة" لنستمرّ في تطويره بإنشاء نظام لتسجيل مُستخدمين جدد وتسجيل دخولهم وتسجيل خروجهم، إضافة إلى عرض المقالات المتواجدة بقاعدة البيانات وكذا تمكين كل مُستخدم من إضافة مقالات خاصّة به، وسنستغلّ ما تعلّمناه من طرق لتقسيم السّجلات إلى صفحات مُتعدّدة ومُختلف المعلومات التّي اكتسبناها لتُعرض كل المقالات بشكل مُناسب للمُستخدمين مع شريط تصفّح للانتقال بين الصّفحات وما إلى ذلك.
-
بعد أن تعرفنا على كيفيّة استعمال التّابع filter في مكتبة SQLAlchemy لترشيح السجلات عند الحصول عليها من قاعدة البيانات باستعمال شروط منطقيّة متقدمة في الدّرس السّابق، سنستمر في استكشاف ميّزات SQLAlchemy لبناء تطبيقات ويب أفضل، إذ سنتعرف في هذا الدّرس على كيفيّة استعمال التابع filter مع بقيّة التّوابع والدّوال المتوفّرة في المكتبة، إضافة إلى مقدمة بسيطة إلى كيفيّة التّعامل مع التّاريخ والوقت في بايثون للحصول على سجلّات من قاعدة البيانات حسب وقت وتاريخ محددين. مزج التابع filter ببقية التوابع، والدوال التي توفرها مكتبة SQLAlchemy يمكن مزج التّابع filter ببقيّة التّوابع، كما في حالة مزجه مع التّابع limit لتحديد عدد معيّن من النّتائج بعد ترشيحها وفق شرط أو شروط معيّنة، أو حتى مزج filter مع filter_by إن أردت ذلك (لكن مزجهما لا فائدة منه). لنحصل مثلًا على جميع المُستخدمين الذين تبدأ أسماءهم بالمقطع user ونحدّد عدد النّتائج في نتيجتين فقط: >>> User.query.filter(User.name.startswith('user')).limit(2).all() [<username: user4 | email: user4@example.com >, <username: user5 | email: user5@example.com >] كذلك يمكنك استعمال order_by بعد التابع filter لترتيب النّتائج التي تُحقّق الشروط المطروحة: >>> User.query.filter(User.name.in_(['user4', 'khalid', 'abdelhadi'])).order_by(User.name).all() [<username: abdelhadi | email: abdelhadi@example.com >, <username: khalid | email: khalid@example.com >, <username: user4 | email: user4@example.com >] وهذا مثال آخر على كيفيّة مزج كل من filter و order_by ثمّ limit في استعلام واحد: >>> User.query.filter(User.name.in_(['user4', 'khalid', 'abdelhadi'])).order_by(User.name).limit(2).all() [<username: abdelhadi | email: abdelhadi@example.com >, <username: khalid | email: khalid@example.com >] استعمال filter للحصول على نتائج حسب تاريخ إضافتها قد يحتاج التّعامل مع التّاريخ والوقت في بايثون إلى درس مُستقل لما فيه من التّعقيد والحالات المُختلفة، ورغم أنّني سأحاول ذكر معظم الأشياء المهمّة في هذه الفقرة، إلّا أنّني لن أذكر كل شيء، بل أهيب بك أن تجد درسا يشرح هذا الجزء بتفصيل أكبر. الهدف هدفنا من هذه الفقرة هو تقديم فكرة عن كيفيّة التّعامل مع السّجلات حسب تاريخ إضافتها (أي قيمة العمود created_date)، فبما أنّنا نمتلك معلومة مهمّة عن السّجل وهو وقت إضافته فسنتمكّن من الحصول على سجلّات حسب يوم إضافتها والسّجلات التّي أضيفت أثناء مقطع زمني معيّن (مثلا بين 5 أيام و10 أيام الماضيّة)، وعندما تفهم أساسيّات التّعامل مع التّاريخ والوقت فستتمكّن من إضافة ميّزات أخرى إلى تطبيقاتك كتحديد تاريخ آخر تعديل وكذلك أرشفة السّجلات حسب تاريخ إضافتها والعديد من الأفكار الأخرى. وحدة datetime الوحدة datetime هي وحدة مبنيّة مُسبقا بلغة بايثون، ويكفي للوصول إليها أن تقوم باستيرادها مُباشرة. تحتوي هذه الوحدة على العديد من الدّوال المُساعدة والوحدات الأخرى داخلها، وسنهتم حاليا بكل من الوحدة البنت datetime والدّالة timedelta فقط، لذا سنقوم باستيرادهما كما يلي: >>> from datetime import datetime, timedelta هنا نستدعي كلا من datetime وtimedelta من الوحدة datetime وبالتّالي فبغض النّظر عن التّشابه في الاسم فإن الوحدة التّي نستدعي منها هي الأصل والوحدة datetime الأخرى مجرّد فرع من الوحدة الأصليّة.هذا يعني بأنّنا إن أردنا الوصول إلى الوحدة الفرعيّة datetime عند استدعاء الوحدة الأصليّة datetime مُباشرة فعلينا إخبار بايثون بالأمر صراحة. بالمثال يتضح المقال: >>> import datetime >>> datetime <module 'datetime' (built-in)> >>> datetime.datetime <type 'datetime.datetime'> قارن الخرج بما يلي: >>> from datetime import datetime, timedelta >>> datetime <type 'datetime.datetime'> الاستنتاج هنا هو أنّ datetime هي الوحدة الأصلية وdatetime.datetime هي وحدة فرعيّة، وبينهما اختلاف واضح. والآن بعد أن حدّدنا الفرق بين كل من الوحدة datetime والوحدة البنت datetime، وبما أنّنا نستدعي هذه الأخيرة فسأشير إليها بـdatetime مُباشرة. إليك تذكيرا بسطر الاستيراد: >>> from datetime import datetime, timedelta لنحدّد الآن الفرق بين كل من datetime وtimedelta، وأبسط طريقة لشرح كل واحدة هي كالآتي: datetime تُمكّننا من التّعامل مع التاريخ (date) والوقت (time).timedelta تُحدّد الفرق بين مُدّتين زمنيّتين.` هذا يعني أنّك إن أردت الحصول على مدّة زمنّية معيّنة (الوقت الحالي حسب توقيتك المحلي أو توقيت UTC مثلا) فسيتوجّب عليك استخدام datetime مع مُختلف التّوابع التّي تُوفّرها الوحدة. وإن أردت الحصول على مقدار الوقت بين مدّتين زمنيّتين فسيتوجّب عليك استخدام timedelta، أمّا لو أردت الحصول مثلا على الفرق بين الوقت الحالي والوقت قبل 3 أيام فستستعمل كلا من datetime وtimedelta . سأشرح لك بعض الأمثلة على كيفيّة استخدام كل من datetime و timedelta للحصول على سجلات من قاعدة البيانات حسب تاريخ إضافتها وشروط أخرى بفضل التّابع filter الذي تُوفّره لنا مكتبة SQLAlchemy، وسنتعرّف في الفقرات التّاليّة على بضعة أساليب لترشيح السّجلات حسب النقطة الزمنيّة التي أضيفت فيها، وذلك عبر استخدام التّابع filter وخصائص الصّنف datetime. الحصول على سجلات حسب سنة إضافتها سنبدأ أولا بالتّعرف على كيفيّة الحصول على السجلات التي أضيفت في سنة مُعيّنة، كالمستخدمين الذين سجّلوا في الموقع في سنة 2018 أو المقالات التي أضيفت في سنة 2017 وغير ذلك من الإمكانيّات الأخرى التي يُمكنك التفكير فيها عند إنشاء تطبيقات تعتمد على ترتيب المحتوى حسب السنوات أو عرض بيانات خاصّة بسنة مُعيّنة. للحصول مثلا على جميع المُستخدمين الذين سجّلوا في التّطبيق في سنة 2017، سنستعمل دالّة في SQLAlchemy باسم extract لاستخراج السنة من قيمة التاريخ لمُقارنتها مع السنة التي نرغب في الحصول على السجلات المرتبطة بها، أي أنّنا سنستعمل التّابع filter للحصول على السّجلات التي يحتوي تاريخ إضافتها على السّنة 2017، والمثال التّالي توضيح على ما سبق: from sqlalchemy import extract from project.models import User users2017 = User.query.filter(extract('year', User.created_date) == 2017).all() في الشّيفرة أعلاه، نستورد الدّالة extract أولًا من حزمة sqlalchemy (لاحظ أنّ هذه الحزمة مُتعلّقة بمكتبة SQLAlchemy وليس بإضافة Flask-SQLAlchemy)، ثمّ نقوم باستيراد الصّنف User، بعدها نُنشئ المُتغيّر users2017 الذي سيحمل قائمة تحتوي على جميع المُستخدمين الذين تمّت إضافتهم إلى قاعدة البيانات في أي وقت من سنة 2017. الشّرط الذي نُمرّره للتّابع filter أعلاه هو كما يلي: extract('year', User.created_date) == 2017 لاحظ بأنّ السّطر عبارة عن مُقارنة بسيطة إذ نُقارن القيمة النّاتجة عن استدعاء الدّالة extract مع تمرير سلسة نصيّة ’year’ كمعامل أول وUser.created_date كمُعامل ثانِِ مع العدد الصّحيح 2017، ما يعني بأنّك لو أردت الحصول على المُستخدمين الذين أُضيفوا في سنة 2018 فكل ما عليك فعله هو تغيير 2017 بـ 2018 كما يلي: extract('year', User.created_date) == 2018 الحصول على سجلات أضيفت في شهر معين نتّبع نفس المنهج عند الرّغبة في الحصول على سجلّات أُضيفت في شهر مُعيّن من السّنة، علينا فقط استخراج الشّهر (month) من التاريخ عوضا عن السّنة، فللحصول على السّجلات التّي أضيفت في الشّهر الأول من كلّ سنة سنستعمل الشّرط التّالي: extract('month', User.created_date) == 1 ما يعني بأنّ الشيفرة الكليّة ستُصبح كما يلي: from sqlalchemy import extract from project.models import User jan_users = User.query.filter(extract('month', User.created_date) == 1).all() في هذه الحالة، سيحمل المُتغيّر jan_users قائمة بجميع المُستخدمين الذين سجّلوا في الشّهر الأول. لاحظ أنّنا لم نُحدّد السّنة هنا، ما يعني بأنّ النتيجة ستكون عبارة عن المُستخدمين الذين سجّلوا في الشّهر الأول من أي سنة كيفما كانت. الحصول على سجلات أضيفت في يوم معين من الشهر للحصول على سجلّ حسب اليوم من الشّهر الذي تمّت إضافته فيه إلى قاعدة البيانات، فكلّ ما عليك فعله هو استخدام الخيار day عوضا عن year للسّنة أو month للشّهر. مثال على كيفيّة الحصول على جميع المُستخدمين الذين سجّلوا في اليوم الأول من الشّهر: from sqlalchemy import extract from project.models import User users = User.query.filter(extract('day', User.created_date) == 1).all() الشّهر والسّنة لا يُهمّان هنا، المُهمّ أن يُضاف السّجل في اليوم الأول من أي شهر كان بغضّ النظر عن السّنة. الحصول على سجلات أضيفت في تاريخ معين (سنة، شهر، يوم) يُمكن جمع المعلومات السّابقة والدّالَّة db.and_ التي تعرّفنا عليها في درس سابق للحصول على سجلّات من قاعدة البيانات حسب تاريخ مُحدّد، أي في يوم وشهر مُحدّدين وسنة مُعيّنة. للحصول مثلا على جميع المُستخدمين الذين سجّلوا في اليوم الأول من الشّهر الأول من سنة 2018: from sqlalchemy import extract from project.models import User from project import db users2018 = extract('year', User.created_date) == 2018 first_jan_users = db.and_(extract('month', User.created_date) == 1, extract('day', User.created_date) == 1 ) users = User.query.filter(db.and_(users2018, first_jan_users)).all() هنا يحمل المُتغيّر users2018 شرطا يحدّد المُستخدمين الذين تمّت إضافتهم إلى قاعدة البيانات في سنة 2018، والمُتغيّر first_jan_users يحمل شرطا يُحدّد بأن على المستخدم أن يُسجَّل في الشهر الأول وكذا في أول يوم، ما يعني بأنّنا جمعنا شرطين في شرط واحد. بعدها نستعمل الشّرطين users2018 وfirst_jan_users للحصول على المستخدمين الذين يُحقّقون كلا الشّرطين في آن واحد، أي أن يُسجّلوا في سنة 2018 واليوم الأول من الشّهر الأول. يُمكنك تغيير القيم كيفما تشاء، وكذلك استعمال خصائص الوقت (السّاعة والدّقيقة على سبيل المثال) للحصول على سجلات حسب مُددِِ زمنيّة أكثر دقّة، وهذا ما سنتعرّف عليه في الفقرات التّاليّة من هذا الدّرس. الحصول على سجلات أضيفت في ساعة معينة من اليوم سنتّبع نفس المنهج الذي كنّا نستعمله عند التّعامل مع السنوات والأشهر والأيام للحصول على سجلّات حسب السّاعة من اليوم، أي الدّالة extract، وسنستخرج القيمة hour للعمل مع ساعات اليوم كما يلي (مع استبدال HOUR بالساعة في النظام الأربع والعشرينيّ 24h): extract('hour', User.created_date) == HOUR إن أردنا الحصول على جميع المُستخدمين الذين سجّلوا في الخامسة مساءً: from sqlalchemy import extract from project.models import User five_pm_users = extract('hour', User.created_date) == 17 users = User.query.filter(five_pm_users).all() الحصول على سجلات أضيفت في دقيقة معينة للحصول على سجلّات حسب الدقيقة التي أُضيفت فيها، استعمل minute مع الدّالة extract. وللحصول على المُستخدمين الذين سجّلوا في الدّقيقة الأولى من كلّ ساعة: from sqlalchemy import extract from project.models import User first_minute_users = extract('minute', User.created_date) == 1 users = User.query.filter(first_minute_users).all() الحصول على سجلات حسب وقت وتاريخ إضافتها لنجمع جميع المعلومات أعلاه للحصول على سجلّات حسب كلّ من تاريخ إضافتها (سنة، شهر، يوم) ووقت إضافتها (السّاعة والدّقيقة)، نحصل في المثال التّالي على أي مُستخدم سجّل في اليوم الأول من الشّهر الأول من سنة 2018 في الدّقيقة الأولى من السّاعة الخامسة مساءً: from sqlalchemy import extract from project.models import User from project import db users2018 = extract('year', User.created_date) == 2018 first_jan_users = db.and_(extract('month', User.created_date) == 1, extract('day', User.created_date) == 1 ) five_pm_first_minute_users = db.and_(extract('hour', User.created_date) == 17, extract('minute', User.created_date) == 1 ) first_jan_five_pm_first_minute = db.and_(first_jan_users, five_pm_first_minute_users ) users = User.query.filter(db.and_(users2018, first_jan_five_pm_first_minute ) ).all() لاحظ الشّروط التّي استخدمناها: users2018: المستخدمون الذين سجّلوا في سنة 2018 first_jan_users: المستخدمون الذين سجّلوا في اليوم الأول من الشّهر الأول five_pm_first_minute_users: المستخدمون الذين سجّلوا في الدّقيقة الأولى من السّاعة الخامسة first_jan_five_pm_first_minute: المستخدمون الذين سجّلوا في الدّقيقة الأولى من السّاعة الخامسة في 1 يناير. يُمكن كذلك مزج الشّروط الواحد داخل الآخر دون حفظها في مُتغيّرات خاصّة، لكنّ يفضل تسجيل الشّروط في مُتغيّرات لتسهيل قراءة الشّيفرة. التعامل مع الفروقات الزمنية يُمكن استعمال الدّالة timedelta من وحدة datetime للتّعامل مع الفروقات الزّمنية، إذ تُرجع timedelta فرقا زمنيّا بين نقطتين زمنيّتين مُحدّدتين بمقدار زمنيّ مُعيّن. وللحصول على فرق زمني مقدار يوم واحد، سنُمرّر عدد الأيام (1 في هذه الحالة) إلى المُعامل days كما يلي: >>> from datetime import timedelta >>> one_day = timedelta(days=1) >>> one_day datetime.timedelta(1) للحصول على فرق زمني مقداره 30 ثانيّة سنُمرّر القيمة إلى المُعامل seconds: >>> thirty_seconds = timedelta(seconds=30) >>> thirty_seconds datetime.timedelta(0, 30) للحصول على فرق زمني مقداره يوم وثلاثون ثانيّة: >>> timedelta(days=1, seconds=30) datetime.timedelta(1, 30) يُمكن كذلك الحصول على الفرق الزمني بين قيمتي datetime مُختلفتين، فللحصول على الفرق الزّمني بين سنتي 2017 و2018 مثلًا: >>> from datetime import datetime, timedelta >>> date1 = datetime(year=2017, month=1, day=1) >>> date2 = datetime(year=2018, month=1, day=1) >>> print(date1) 2017-01-01 00:00:00 >>> print(date2) 2018-01-01 00:00:00 >>> delta = date2 - date1 >>> delta datetime.timedelta(365) >>> print(delta) 365 days, 0:00:00 لاحظ بأنّ كل ما قمنا به هو عمليّة فرق بسيطة date2 – date1 وأسندنا القيمة إلى المتغيّر delta، والذي يُعطينا القيمة 365 days, 0:00:00 عند طباعته، أي أن الفرق الزمني بين السّنتين هو 365 يوما، صفر ساعة، صفر دقيقة وصفر ثانيّة. نستطيع استعمال قيمة الفرق الزمني في SQLAlchemy للحصول على سجلّات بشروط مثل أن تكون قد سُجّلت قبل شهر واحد، قبل سنة، ما بين الشّهر والشّهرين، إلى غير ذلك. يمكنّنا المثال التّالي من الحصول على جميع المُستخدمين الذين سجّلوا بعد أسبوعين مُنصرمين وقبل أسبوع واحد من الآن: from datetime import datetime, timedelta from project import db from project.models import User, Post # Get Users who signed up between # two weeks and one week ago datetime_now = datetime.utcnow() seven_days = timedelta(days=7) one_week_ago = datetime_now - seven_days fifteen_days = timedelta(days=15) two_weeks_ago = datetime_now - fifteen_days users = User.query.filter( db.and_(User.created_date >= two_weeks_ago, User.created_date <= one_week_ago) ).all() خاتمة تعرّفنا في هذا الدّرس على كيفيّة استعمال التّابع filter لإجراء عمليّات متقدّمة، ومزجه ببقيّة التوابع المتوفرة في SQLAlchemy، كما ألقينا نظرة على خصائص التّاريخ والوقت وكيفيّة الاعتماد عليها للحصول على سجلّات من قاعدة البيانات حسب شروط زمنيّة مُعيّنة.
-
سنُتم في هذا الدرس ما بدأناه في الدرس السابق من تعلم لكيفيّة استعمال الشروط والمعاملات المنطقيّة المتقدمة في لغة بايثون، من أجل الحصول على نتائج دقيقة من قاعدة البيانات عند استعمال مكتبة SQLAlchemy وإضافة Flask-SQLAlchemy. التعامل مع السلاسل النصية قد تحتاج أحيانا إلى التّحقق من أنّ سلسلة نصيّة ما تبدأ أو تنتهي بكلمة أو حرف، أو أنّ كلمة أو حرفا معيّنا يتواجد في قائمة أو سلسلة نصيّة، لذا سنتعرّف في الفقرات التّاليّة على كيفيّة فعل ذلك في لغة بايثون. التابع startswith يُطبّق التّابع startswith على السّلسلة النّصيّة للتأكّد من أنّها تبدأ بمقطع معيّن، فمثلًا، سنتأكّد أنّ السّلسلة النّصيّة abdelhadi تبدأ بالمقطع abd: >>> name = 'abdelhadi' >>> name.startswith('abd') True يُمكنك تطبيق هذه الميّزة من أجل الحصول على جميع المقالات التّي يبدأ عنوانها بمقطع معيّن، فمثلا لنقل بأنّك تمتلك في مدوّنتك مقالات مُختلفة إضافة إلى مقالات تُشير إلى كل تحديث في تطبيقك لتعرض الميّزات الجديدة، ثم لنفترض زيادة حجم التطبيق بعد مدة إلى أن صارت له مُدونته المستقلة عن مُدوّنتك، سيتوجّب عليك الآن نقل المقالات المُخصّصة للتّحديثات إلى هذه المُدوّنة الجديدة، وعلى فرض أنّ تلك المقالات تبدأ بكلمة "تحديث"، فكيف يُمكنك الحصول على هذه المقالات لنقلها دون المقالات الأخرى؟ فكما ترى، تلك طريقة واحدة لاستعمال هذا التّابع، إذ يُمكنك استعمال startswith للحصول على جميع المقالات التّي تبدأ بالمقطع، ومن ثم تنقلها إلى مكان آخر بشكل آلي. العامل in يُستعمل العامل in للتّأكّد من أن سلسلة نصيّة أو قائمة في بايثون تحتوي على مقطع نصي معيّن. فمثلا، سنتأكّد من أن الحرف a يتواجد بكلمة abcd، وسنتأكّد في المثال الآخر من أنّ اسم المُستخدم khalid يتواجد ضمن قائمة usernames، بالإضافة إلى حالتين لعدم استيفاء الشّرط والحصول على القيمة المنطقيّة False: >>> 'a' in 'abcd' True >>> 'e' in 'abcd' False >>> usernames = ['abdelhadi', 'ahmed', 'ibrahim', 'ali', 'khalid', 'mohammed'] >>> 'khalid' in usernames True >>> 'youssuf' in usernames False إليك استخدامًا للعامل in: قلنا في الفقرة السّابقة أنك تستطيع نقل جميع المقالات التّي تبدأ بالمقطع "تحديث" إلى مدونة أخرى، لكن ماذا لو كانت مقالاتنا التّي تبدأ بهذا المقطع تُشير إلى أكثر من تطبيق واحد؟ لنقل بأنّ المدونة حاليا بها تحديثات لتطبيقين، تطبيق "كلمة" وتطبيق آخر، وكلاهما يحصلان على تحديثات ومقال لكل تحديث، هذا يعني بأنّ الحصول على المقالات التّي تبدأ بالمقطع "تحديث" ستكون مزيجا بين تحديثات التّطبيقين، ولكنّنا نرغب بنقل مقالات التطبيق "كلمة" إلى مدونة "كلمة" دون المقالات الخاصّة بالتّطبيق الآخر. نستطيع هنا مزج كل من startswith وin وand للوصول إلى مُرادنا، بحيث نتأكّد من أنّ عناوين المقالات التّي سنحصل عليها تبدأ بكلمة "تحديث" وفي نفس الوقت تحتوي على اسم التّطبيق "كلمة" وإليك مثالا بسيطا: >>> kalima_update = 'تحديث في تطبيق كلمة' >>> app_update = 'تحديث في تطبيق آخر' >>> kalima_update.startswith('تحديث') and 'كلمة' in kalima_update True >>> app_update.startswith('تحديث') and 'كلمة' in app_update False >>> app_update.startswith('تحديث') True >>> 'كلمة' in kalima_update True >>> 'كلمة' in app_update False كما تُلاحظ، استخدمنا كلًا من startswith مع and وin لنستطيع ترشيح عنوان المقال الذي يحمل تحديثا للتطبيق "كلمة" من العنوان الآخر. هذا مجرّد مثال على كيفيّة الاستفادة من مختلف الأدوات التّي توفّرها لنا لغة بايثون وكيفيّة مزجها لحل مشاكل تحدث على الواقع، وسنرى فيما بعد كيفيّة استخدام هذه الجمل التّي تُرجع حاليا القيمتين True وFalse فقط لترشيح بياناتنا في قاعدة البيانات باستعمال SQLAlchemy. التابع endswith يُطبّق التّابع endswith على السّلسلة النّصيّة للتأكّد من أنّ سلسلة نصيّة تنتهي بمقطع معيّن، على سبيل المثال، يُمكن أن نتأكّد من أنّ بريدا إلكترونيّا ينتهي بـexample.com كما يلي: >>> 'abdelhadi@example.com'.endswith('example.com') True >>> 'abdelhadi@gmail.com'.endswith('example.com') False لاحظ أن البريد الإلكتروني الأول ينتهي بـexample.com لذا تحقّق الشّرط وأرجع مُفسّر بايثون القيمة True، أمّا الثّاني فينتهي بـgmail.com وليس example.com لذا أُرجِعّت القيمة المنطقيّة False. إن تساءلت عن الفائدة من هذا الأمر على الواقع، فإليك السيناريو التّالي: لنقل مثلا بأنّ خدمة example.com ستلغي خدمات بريدها الإلكتروني وسيتوجّب عليك تنبيه جميع مُستخدميك لتنصحهم بالانتقال إلى خدمة بريد إلكتروني أخرى، يُمكنك مثلا أن تستعمل هذه الطّريقة للحصول على جميع المُستخدمين الذين سجّلوا ببريد إلكتروني من هذه الخدمة، ومن ثم تستطيع تنبيههم وحدهم دون المُستخدمين الآخرين. لا شك أن هناك أمثلة كثيرة أخرى على الواقع، وستجد تحديّات ومشاكل يُمكنك حلّها بمثل هذه الأدوات التّي توفّرها لغة بايثون. ترشيح البيانات التي نحصل عليها من قاعدة البيانات باستعمال شروط منطقية عبر مكتبة SQLAlchemy في السّابق، كنّا نستعمل التّابع filter_by للحصول على سجل أو عدّة سجلّات عند تَطابُقِ قيمة عمود معيّن مع القيمة المُمرّرة، فمثلا لو أردنا الحصول على مُستخدم يُطابق اسمه السّلسلة النّصيّة khalid يُمكن أن نستعمل التّابع filter_by مع تمرير السّلسلة النّصيّة إلى المعامل name والذي يمثّل اسم العمود في هذه الحالة: >>> from project.models import User >>> User.query.filter_by(name = 'khalid').first() <username: khalid | email: khalid@example.com > في الشّيفرة أعلاه، كل ما نفعله هو طلب سجل من قاعدة البيانات في كل مكان تُساوي فيه قيمة العمود name السّلسلة النّصيّة "khalid”. أي كأنّنا نقول: أعطني السّجل الذي يُرجع طَلبُه القيمة المنطقيّة True عند تنفيذ الجملة التّاليّة: User.name == 'khalid' وبالمثل يُمكن أن تستخدم filter_by مع بقيّة الأعمدة. يُعد التّابع fliter_by جيدًا في حالات كثيرة كالوصول إلى مستخدم عن طريق اسمه أو بريده الإلكتروني، أو حتى مقال عبر عنوانه، لكن ماذا لو أردنا إجراء عمليّات أكثر تقدّما، مثل الحصول على السّجلات التّي تبدأ أو تنتهي بمقطع نصي معيّن أو تلك التّي تحتوي بداخلها على مقطع ما، أو الوصول إلى جميع السّجلات التّي تحقّق مجموعة من الشّروط أو بعضها؟ التابع filter في SQLAlchemy يُمكنك استعمال التّابع filter للحصول على نتائج تستوفي شروطا معيّنة تُمرّر إلى هذا التّابع كما يلي: >>> Table.query.filter(Table.column == value) لدينا ثلاثة متغيّرات يُمكنك استبدالها هنا لتتحصّل على نتيجتك المرغوبة: Table هو اسم الصّنف المرتبط بالجدول الذي ترغب بالحصول على البيانات منه، وcolumn يُمثّل اسم العمود، أمّا value فتمثّل القيمة. وكما تلاحظ، فهذا التّعبير شبيه جدّا بالتّابع filter_by لأنّ الشّرط هو ما إذا كانت قيمة العمود تُساوي كذا. أمّا للوصول إلى نتائج متنوّعة فتستطيع استبدال العامل == لكتابة شروط منطقيّة أخرى، وإليك بعض الأمثلة: Table.query.filter(Table.column == value) Table.query.filter(Table.column < value) Table.query.filter(Table.column > value) Table.query.filter(Table.column.endswith(value)) Table.query.filter(Table.column.startswith(value)) يُمكنك أن تعوض المتغيّر value بقيمة عمود آخر من الجدول: Table.query.filter(Table.column == Table.column2) Table.query.filter(Table.column.startswith( Table.column2)) بعد ترشيح السّجلات، يُمكنك بعد ذلك تطبيق كل من all أو first على النّتيجة. لاحظ في هذا المثال كيف أنّ كلّا من filter و filter_by يرجعان نفس النّتيجة: >>> User.query.filter_by(name = 'khalid').first() <username: khalid | email: khalid@example.com > >>> User.query.filter(User.name == 'khalid').first() <username: khalid | email: khalid@example.com > وكذلك يتّبع get نفس المنهج لكنّه لا يحتاج إلى التّابع first لأنّ المعروف أنّ المفتاح الأولي لا يُمكن أن يحمل من طرف أكثر من سجل واحد: >>> User.query.filter_by(id = 3).first() <username: dyouri | email: dyouri@example.com > >>> User.query.get(3) <username: dyouri | email: dyouri@example.com > >>> User.query.filter(User.id == 3).first() <username: dyouri | email: dyouri@example.com > ترشيح السجلات عن طريق in قد تُفكّر في تطبيق العامل in للحصول على سجلات تتواجد إحدى قيم أعمدتها في قائمة ما، لكنّ SQLAlchemy لن يقبل العامل in الذي توفّره لنا لغة بايثون، وعليه لن تحصل على أيّة نتيجة. يُمكنك تنفيذ التّابع in_ في SQLAlchemy مع تمرير قائمة من القيم كما يلي: >>> User.query.filter(User.name.in_(['abdelhadi', 'user4', 'user5'])).all() [<username: abdelhadi | email: abdelhadi@example.com >, <username: user4 | email: user4@example.com >, <username: user5 | email: user5@example.com >] لاحظ كيف طبّقنا التّابع in_ كقيمة ممرّرة للتّابع filter: Table.column.in_(list) أمّا كل من startswith و endswith فلا تتغيّر ويُمكنك استخدامها كما وضّحت سابقا: >>> User.query.filter(User.name.startswith('abd')).first() <username: abdelhadi | email: abdelhadi@example.com > >>> User.query.filter(User.name.endswith('lid')).first() <username: khalid | email: khalid@example.com > تعلّمنا الآن كيفيّة الحصول على سجلّات تبدأ أو تنتهي أحد قيم أعمدتها بمقطع معيّن، لكن ماذا لو أردنا الحصول على السّجلات التي تحتوي على مقطع معيّن في أي مكان من النّص؟ توفّر لنا مكتبة SQLAlchemy طريقة للحصول على السّجلات التّي تحتوي على مقطع معيّن بغض النّظر عن مكان تواجد هذا المقطع (سواء كان في بداية السّلسلة النّصيّة أو وسطها أو نهايتها)، وذلك باستعمال contains: >>> User.query.filter(User.name.contains('d')).all() [<username: khalid | email: khalid@example.com >, <username: dyouri | email: dyouri@example.com >, <username: abdelhadi | email: abdelhadi@example.com >] كما تلاحظ فقد حصلنا على جميع المُستخدمين الذين تحتوي أسماءهم على الحرف d سواء أكان في بداية الاسم أو في آخره أو وسطه. ترشيح السجلات بأكثر من شرط واحد قد تود عند استعمال filter أن تحصل على نتائج تُحقّق أكثر من شرط واحد، وعلى سجل أو سجلّات تستوفي العديد من الشّروط (مثل الحصول على كل مُستخدم انضمّ قبل شهر واحد وأضاف أكثر من 10 مقالات). وغالبًا ستستعمل and التّي توفّرها لغة بايثون، لكن كما الحال مع in فمكتبة SQLAlchemy توفّر بديلا لها، والبديل هو الدّالة and_ في SQLAlchemy والتّي نصل إليها عن طريق الكائن db. إليك مثالا لطريقة القيام بالأمر: >>> user = User.query.filter(db.and_(condition1, condition2)) مع استبدال condition1 بالشّرط الأول و condition2 بالشّرط الثّاني. وبما أنّ and_ شبيهة بجملة and في لغة بايثون فالمتوقّع أنّ حالة تحقّق الشّرطين معا هي الحالة الوحيدة التي يُمكن أن تُرجع نتيجة من الجدول: >>> True and True True >>> False and False False >>> False and True False >>> True and False False لنفترض أنّنا نُريد الحصول على أول مُستخدم يبدأ اسمه بكلمة abd وفي نفس الوقت يكون رقم مُعرّفه أكبر من الرّقم 1: >>> from project import db >>> user = User.query.filter( ... db.and_(User.name.startswith('abd'), ... User.id > 1)).first() >>> user <username: abdelhadi | email: abdelhadi@example.com > كما تُلاحظ، حصلنا على المُستخدم عبدالهادي لأنّه يستوفي الشّرطين، فرقم المعرفّ الخاص بالمُستخدم هو 2 أي أنّه أكبر من 1، ثمّ إنّ اسم المُستخدم يبدأ بالمقطع abd بكل تأكيد. يُمكنك كذلك استخدام filter أكثر من مرّة لتطبيق العديد من الشّروط: >>> user = User.query.filter( ... User.name.startswith('abd')).filter( ... User.id > 1).first() >>> user <username: abdelhadi | email: abdelhadi@example.com > >>> user.id 2 لاحظ هنا بأنّ النّتيجة هي نفسها، أي أنّ كلّا من db.and_ و استعمال filter أكثر من مرّة له نفس المفعول وشخصيّا أرى أنّ استعمال db.and_ أفضل وأسهل. يُمكنك كذلك استخدام الدّالّة or_ للحصول على شرط يستوفي إمّا هذا الشّرط أو ذاك، وهو شبيه بجملة or في بايثون، وإليك تذكيرا بسيطا: >>> True or True True >>> False or False False >>> False or True True >>> True or False True يعني لو أردنا الحصول على مُستخدم ينتهي اسمه بالحرف d أو رقم مُعرّفه يُساوي 10 لكتبنا استعلامنا كما يلي: >>> user = User.query.filter( db.or_(User.name.endswith('d'), User.id == 10)).first() >>> user <username: khalid | email: khalid@example.com > >>> user.id 1 لاحظ بأنّ الشّرط الثّاني خاطئ، ومع ذلك حصلنا على المُستخدم خالد لأنّ الشّرط الأول صحيح، لو استخدمت and_ هنا لكانت النّتيجة عبارة عن القيمة None أو بمعنى آخر لا نتيجة لهذا الاستعلام وهذا لأنّ الشّرط الثّاني خاطئ. مزج and و or معًا كما رأينا سابقا، يُمكنك وضع شروط معقّدة بمزج and و or أكثر من مرّة داخل بعضها البعض كما يلي: >>> (True and True) or (False and True) True الشّرط الأول في الشيفرة أعلاه ينتِج القيمة True، والثّاني يُنتج القيمة False، يعني كأنّنا نقول: True or False ومن ثم فالخرج هو القيمة المنطقيّة True. يُمكنك أن تأخذ هذا المبدأ إلى أبعد الحدود لتكتب شروطًا أكثر تعقيدًا: >>> ((True and True) or (False and True)) and ((True and True) and (False and True)) False >>> (True and True) or (False and True) True >>> (True and True) and (False and True) False أعتقد بأنّني بالغت في مزج كل هذه الشّروط، لكنّني أشعر بأنّ فهم طريقة العمل هذه مُهمّة، خاصّة إن كنت تخطّط لقراءة شيفرة مشروع مفتوح المصدر. قد لا تحتاج أبدا إلى مزج كل من or و and لإنشاء شروط معقّدة في SQLAlchemy، لكنّي رأيت أن أمنحك مثالا لكيفيّة القيام بالأمر، ولا بأس إن لم تفهم المثال الآن إذ أنّه يختلف بعض الشّيء عمّا تطرّقنا إليه سابقا، وستستطيع أن تفهمه ببعض من المُمارسة. إليك مثالا على كيفيّة مزج and و or في SQLAlchemy: db.and_( db.or_( condition1, condition2 ), db.and_( condition3, condition4 ) ) لاحظ بأنّك تستطيع وضع الشّروط في مُتغيّرات لتعمل الشّيفرة السّابقة، مثلا: condition1 = User.name == 'khalid' condition2 = User.name == 'username' condition3 = User.id < 10 condition4 = User.email.endswith('example.com') هذا كأنّنا نقول: (condition1 or condition2) and (condition3 and condition4) إن لاحظت بيانات المُستخدم khalid فستدرك بأنّ هذه الشّروط تتحقّق فيه. وفي الشّيفرة التّاليّة، أضع الشّروط في مُتغيّر باسم condition لأمرّرها فيما بعد إلى التّابع filter. condition = db.and_( db.or_( User.name == 'khalid', # True User.name == 'username' # False ), db.and_( User.id < 10, # True User.email.endswith('example.com') # True ) ) لنجرّب الآن تمرير هذا الشّرط إلى مكتبة SQLAlchemy وللنَنظر هل هناك من مُستخدم يحقّق الشّروط المذكورة: >>> User.query.filter(condition).all() [<username: khalid | email: khalid@example.com >] جيد! كل شيء سار كما هو متوقّع، فالمُستخدم خالد هو الوحيد الذي يُحقّق جميع الشّروط. خاتمة تعرّفنا في هذا الدّرس على كيفيّة استعمال التابع filter في مكتبة SQLAlchemy لترشيح السّجلات عند الحصول عليها من قاعدة البيانات باستعمال شروط منطقيّة متقدّمة. وسنتعرّف في الدرس القادم على كيفيّة استغلال ما تعلّمناه من حيل للتّعامل مع قواعد البيانات بطريقة أفضل.
-
بعد أن تعلمنا كيفية الحصول على السجلات على حدة باستعمال أرقام مُعرّفاتها إضافة إلى كيفية الحصول على جميع السجلات من جدول معيّن والحصول عليها حسب اسم عمود مُعيّن باستعمال التّابع filter_by، سنتعرّف في هذا الدّرس على كيفيّة ترتيب السّجلات، والحد من عدد السجلّات التي نحصل عليها من كل استعلام، وكذلك كيفيّة الحصول على سجلّات بشكل عشوائيّ، إضافة إلى مقدمة قصيرة في استعمال طرق مختلفة لترشيح النّتائج بشروط منطقيّة. ترتيب السجلات حسب قيم عمود معين تُرتَّب السجلات عادة في تطبيقات الويب حسب قيمة معيّنة، فقد تجد مقالات في مدونة ما مرتّبة حسب تاريخ إضافتها ومن ثم تحصل على آخر المقالات المُضافة أولا بأول، كذلك يمكن ترتيب المُستخدمين حسب أسمائهم أبجديا، وهناك العديد من الخيارات الأخرى لترتيب البيانات كما سترى بعد قليل. ولترتيب البيانات حسب عمود معيّن، يمكنك استعمال التّابع order_by بعد query مع تمرير اسم العمود في الجدول بإحدى طريقتين، إمّا كسلسلة نصيّة أو باستخدام الصّنف ثمّ اسم العمود مع فصلهما بنقطة كما يلي: >>> from project.models import User >>> users1 = User.query.order_by("created_date").all() >>> users2 = User.query.order_by(User.created_date).all() >>> users1 == users2 True كما تُلاحظ، نفّذنا استعلامًا عن المُستخدمين ثمّ رتبناهم حسب تاريخ الإضافة بكلتا الطّريقتين، ثمّ حصلنا على جميع النّتائج بالتّابع all، بعدها نتأكد أنّ الطّريقتين تؤديان إلى نفس النّتيجة، والنّتيجة أنّ هذا الأمر صحيح. لنحصل الآن على أول نتيجة ولننظر هل التّرتيب صحيح أم لا: >>> user = User.query.order_by(User.created_date).first() >>> user <username: khalid | email: khalid@example.com > التّرتيب صحيح كما ترى، فخالد هو أوّل مُستخدم أضفناه إلى قاعدة البيانات. طريقة التّرتيب حسب بقيّة الأعمدة بسيطة كذلك: >>> User.query.order_by(User.id).first() <username: khalid | email: khalid@example.com > >>> User.query.order_by(User.name).first() <username: abdelhadi | email: abdelhadi@example.com > >>> User.query.order_by(User.email).first() <username: abdelhadi | email: abdelhadi@example.com > لنطبّق على جدول المقالات كذلك: >>> from project.models import Post >>> Post.query.order_by(Post.id).first() <title Post 1 | post 1 content > >>> Post.query.order_by(Post.title).first() <title another post from abdelhadi | some other content for the post > >>> Post.query.order_by(Post.created_date).first() <title Post 1 | post 1 content > لاحظ أنّ ترتيب القيم وفق عمودٍ يحتوي على سلسلة نصيّة يكون أبجديا، أمّا الأعمدة التّي تحمل أعدادا كقيمة فتُرتّب من أصغر عدد إلى الأكبر، أمّا التّاريخ فحسب الأقدم إلى الأحدث. ملاحظة: أستخدمُ التّابع first فقط لكي لا يكون الخرج كبيرا، ولو أردتَ جميع السّجلات على شكل قائمة فتستطيع استخدام التّابع all. الترتيب عكسيّا يبدو بأنّك لاحظت بأنّنا لم نصل بعد إلى كيفيّة ترتيب المقالات حسب الأحدث، وذلك لأنّ التّرتيب حسب التّاريخ يُرجع المقالات الأقدم. يُمكننا عكس التّرتيب في SQLAlchemy للحصول على المقالات الأحدث والمُستخدمين المُنضمّين حديثا، وذلك من بإضافة التّابع desc بعد اسم العمود: >>> User.query.order_by(User.created_date.desc()).first() <username: user12 | email: user12@example.com > لاحظ بأنّنا حصلنا على المُستخدم user12 وهو آخر مُستخدم أضيف باستعمال حلقة التّكرار في درس سابق. إن استعملت الطّريقة الأخرى في التّرتيب فتستطيع عكسه كما يلي: User.query.order_by("created_date desc") هذا يعني أنّ السّطرين التّاليين سيُرجعان قائمتين، القائمة الأولى عبارة عن جميع المُستخدمين مُرتّبين حسب آخر مُستخدم أضيف إلى أقدم مُستخدم، والقائمة التّاليّة عبارة عن جميع المقالات المُضافة بنفس التّرتيب. >>> Post.query.order_by(Post.created_date.desc()).all() >>> User.query.order_by(User.created_date.desc()).all() هكذا استطعنا الوصول إلى قائمة بالمقالات المُضافة حديثا وآخر المُستخدمين المُنضمّين، لكنّنا لا نمتلك سوى طريقتين للحصول على النّتائج، إمّا بالحصول على أول نتيجة أو جميع النّتائج، فكيف يُمكن الحصول على عدد معيّن فقط من السّجلات؟ تحديد الناتج بعدد معين من السجلات لنقل بأنّ تطبيقنا وصل إلى آلاف المقالات والمُستخدمين، وبالتّالي فعرض جميع المُستخدمين المُنضمّين حديثا وأحدث المقالات سيجعل صفحتنا طويلة وقد يأخذ وقتا كبيرا، هذا بالإضافة إلى أنّه أمر غير ضروري. قطعًا يُمكنك الحصول على جميع المقالات على شكل قائمة واستعمال لغة بايثون لتحديد جزء منها فقط (أول خمسة عناصر مثلا)، لكنّ الحصول على جميع السّجلات وإسنادها إلى مُتغيّر قد يأخذ مساحة كبيرة، وكذلك ستستغرق مُعالجتها وقتًا طويلا. وبما أنّ قواعد بيانات SQL تُمكّننا من الحصول على عدد معيّن من السجلّات بجملة LIMIT فمن الأفضل أن نستخدمها عوضا عن معالجة آلاف البيانات لعرض جزء صغير منها. ولتحديد عدد معيّن من النّتائج في SQLAlchemy، استعمل التّابع limit مع تمرير عدد صحيح كمُعامل ليُمثّل عدد النّتائج، فمثلا للحصول على خمسة مقالات وخمسة مُستخدمين فقط يُمكنك استخدام التّابع limit() مع تمرير العدد 5: >>> five_posts = Post.query.limit(5).all() >>> five_users = User.query.limit(5).all() لاحظ أنّنا نستخدم التّابع all مرة أخرى للحصول على جميع النّتائج. سيحمل الآن كلا المُتغيّرينfive_posts و five_users خمسة سجلّات في كل متغيّر، ويُمكنك أن تصل إلى كل سجل وبياناته من خلال استخدام حلقة for كما في السّابق. كذلك يُمكنك تغيير المُعامل المُمرّر إلى التّابع limit إلى أي عدد تريده: Post.query.limit(10).all() Post.query.limit(7).all() Post.query.limit(8).all() Post.query.limit(1).first() لعلك لاحظت أننا نحصل في آخر سطر على سجل واحد فقط، يُمكنك استعمال هذه الطّريقة إن لم تكن مُهتما بالحصول على سجل مُعيّن من قاعدة البيانات (باستعمال إمّا get أو filter_by). مزج ما سبق لنتائج أكثر دقة الشّيء المُميّز في قواعد بيانات SQL هو أنّها تُوفّر لنا طريقة لمزج العديد من الشّروط في استعلام واحد، فمثلًا يُمكنك إخبار قاعدة البيانات أنّك تريد الحصول على مقالات مُستخدم رقم مُعرّفه كذا، لكن بدلًا من جلب جميع مقالاته، أعطني مقالين فقط. وتستطيع مزج التوابع في SQLAlchemy للحصول على هذه النّتيجة، فالسّطر التّالي مثلًا يستعلم عن المقالات من المُستخدم abdelhadi عن طريق رقم مُعرّفه، ثمّ يطبّق حدّا على النّتائج للحصول على سجلّين فقط: >>> posts_from_abdelhadi = Post.query.filter_by(author_id=2).limit(2).all() هنا مزجنا كلّا من التّابعين filter_by وlimit، الأول للحصول على المقالات التّي يحمل عمود author_id فيها القيمة 2 (أي رقم مُعرّف المُستخدم عبد الهادي)، والثاني لحدّ النّتائج في مقالين فقط. لاحظ أن التّابع all هو آخر ما يُضاف إلى الاستعلام للحصول على قائمة بجميع النّتائج، لذا لا تضعه والتّابع first في وسط الشّيفرة. وهذا مثال آخر نقوم فيه بترتيب المُستخدمين حسب تاريخ إضافتهم عكسيا ثمّ نُحدّد ثلاثة نتائج. >>> User.query.order_by(User.created_date.desc()).limit(3).all() [<username: user12 | email: user12@example.com >, <username: user11 | email: user11@example.com >, <username: user10 | email: user10@example.com >] تلاحظ في الشّيفرة أعلاه أنّ الخرج قد رُتّب عكسيا حسب تاريخ الإضافة ثمّ حُدّد بثلاثة سجلّات فقط، يُمكنك تطبيق هذه الطّريقة في عدّة مواضع لعرض عدد محدّد من المُستخدمين المُنضمّين حديثا أو ترتيب المقالات حسب تاريخ إضافتها وتحديد عددها، وكذلك يمكنك إضافة تقييم لكل مقال وترتب المقالات حسب الأفضل (أي الأكثر تقييما إيجابيا). يُمكنك مزج ما تشاء من التّوابع للحصول على نتائج أكثر دقّة من قاعدة بياناتك، لكن احترس، فمكتبة SQLAlchemy لا تسمح بتطبيق بعض التّوابع إلّا بترتيب خاص، فمثلا لا يُمكنك وضع حدّ لعدد النّتائج قبل ترتيبها، أي أنّ الشّيفرة التّاليّة خاطئة: User.query.limit(3).order_by(User.created_date).all() إن جرّبت تنفيذ الشّيفرة فستحصل على خطأ يُشبه نصّه ما يلي: sqlalchemy.exc.InvalidRequestError: Query.order_by() being called on a Query which already has LIMIT or OFFSET applied. To modify the row-limited results of a Query, call from_self() first. Otherwise, call order_by() before limit() or offset() are applied. لذا تأكّد دائما من أنّك تُطبّق التّابع limit في مكانه الصّحيح، والأغلب أنّك ستحتاج إليه في آخر الشّيفرة لتحديد عدد النّتائج بعد إجراء العمليّات الأخرى. يُمكن أن تواجهك بعض المشاكل من هذا القبيل، فإن واجهتك فاقرأ نص الخطأ أولا ثمّ ابحث عن حل له في الويب، فإن لم تجد فيمكنك طرح سؤال في قسم الأسئلة والأجوبة في الأكاديميّة، وتأكّد أن تضع نص الخطأ كاملا والجزء الذي سبّب الخطأ من شيفرتك. الحصول على سجلات بشكل عشوائي يُمكن أن تجد نفسك بحاجة إلى بناء تطبيق يتطلّب الحصول على سجلّات من قاعدة البيانات بشكل عشوائي، صحيح أنك تستطيع استخدام بايثون والوحدة random للقيام بالأمر، لكن تذكر قولي أن استخدام بايثون لمعالجة بيانات كبيرة فكرة سيّئة، لذا لا تقم بذلك. الحصول على سجلّات بشكل عشوائي ممكن، لكن للأسف فالطّريقة مُختلفة في قواعد بيانات SQL المُتعدّدة، وبما أنّنا نستعمل هنا قاعدة البيانات PostgreSQL فسأشرح طريقة القيام بالأمر فيها فقط، وإن أردت الحصول على كيفيّة القيام بالأمر في قواعد البيانات الأخرى فيُمكنك الرّجوع إلى توثيق SQLAlchemy. للحصول على سجلّات عشوائيّة في كلّ مرّة ننفّذ فيها الاستعلام في SQLAlchemy و PostgreSQL (نفس الطّريقة تعمل على SQLite كذلك) يُمكنك استعمال التّابع func المتواجد في الكائنdb الذي أنشأناه بمُساعدة إضافة Flask-SQLAlchemy ، لذا إن لم تستورده فلن تعمل الشّيفرة: >>> Post.query.order_by(db.func.random()).first() <title Post from user11 | Content 11 > >>> Post.query.order_by(db.func.random()).first() <title Post from user12 | Content 12 > >>> Post.query.order_by(db.func.random()).first() <title Post from user7 | Content 7 > الشّيفرة الأساسيّة لترتيب السّجلات بشكل عشوائي هي باستعمال التّابع func المتواجد في الكائن db ثمّ التّابع random مع تمرير كل شيء إلى التّابع order_by كما يلي: order_by(db.func.random()) لاحظ بأنّنا في كل مرّة نقوم فيها بتنفيذ الشّيفرة تختلف النّتيجة بشكل غير مُتوقّع. أيضًا، لعلك لاحظت أننا نقوم هنا بتطبيق التّابع first للحصول على أول سجل، ويُمكنك قطعًا الوصول إلى جميع السّجلات عبر التّابع all، وكذلك تحديد عدد من السّجلات بالتّابع limit بعد ترتيبها عشوائيّا: >>> User.query.order_by(db.func.random()).limit(3).all() [<username: user5 | email: user5@example.com >, <username: abdelhadi | email: abdelhadi@example.com >, <username: user11 | email: user11@example.com >] >>> User.query.order_by(db.func.random()).limit(3).all() [<username: user9 | email: user9@example.com >, <username: abdelhadi | email: abdelhadi@example.com >, <username: dyouri | email: dyouri@example.com >] >>> User.query.order_by(db.func.random()).limit(3).all() [<username: user8 | email: user8@example.com >, <username: user5 | email: user5@example.com >, <username: user6 | email: user6@example.com >] النّتائج عشوائيّة كما ترى، وكل نتيجة تختلف عن النّتيجة السّابقة، لذا تستطيع الآن التّفكير في إضافة قسم بالتّطبيق الذي سنبنيه لعرض مقالات ومُستخدمين بشكل عشوائي لتُساعد زوارك على اكتشاف المقالات والمُستخدمين الذين لا يُمكن أن تصل إليهم إلا بهذه الطّريقة (مثل المقالات التي مرت عليها سنوات أو مُستخدمون لم يُشاركوا منذ مدّة طويلة حتى اختفوا من الصّفحات الرّئيسية). ترشيح السجلات حسب شروط منطقية لم تعد تطبيقات الويب بسيطة كالسابق حين كان المستخدم يطلب صفحة المقال فيحصل عليها، أما الآن فقد كثرت الخيارات المنطقية في التطبيقات، فبعد أن كانت خيارات المستخدم محدودة جدًا بين الحصول على المقالات بترتيب معيّن أو مقالات كاتب محدد، صارت تطبيقات الويب تُتيح للمُستخدم حريّة كبيرة في كيفيّة حصوله على النّتائج. فالصّفحة والنّتائج المعروضة عليها يُمكن أن تتغيّر طبقا لعدّة شروط، وهناك خيارات كثيرة للحصول على نتائج دقيقة، فمثلا يُمكن أن يطلب المُستخدم مقالات أضيفت في شهر معيّن، أو الحصول على جميع المقالات المُضافة من السّنة الفلانيّة إلى يومنا الحالي، أو كل مقال يبدأ عنوانه بكلمة "تحديث" وفي نفس الوقت يكون كاتبه المُستخدم فلان بالإضافة إلى أنّه أضيف قبل 5 أيام مع وجوب تجاوزه المئة تعليق وهكذا…، وإن نظرنا إلى آخر مثال وحلّلناه فسنجد أنّه يحتوي على العديد من الشّروط المنطقيّة: فالمقال يبدأ بكلمة "تحديث” وكاتب المقال هو المُستخدم "فلان” وتاريخ إضافته قبل خمسة أيام وعدد تعليقاته مئة أو أكثر. لاحظ الآن كلّا من حرفي "و” و"أو” في التّحليل السّابق، هل يُذكّرك ذلك بشيء معيّن في لغة بايثون؟ الجواب هو المعاملات المنطقيّة and وor، التّي تعمل كما يلي: and: إن تحقّق هذا الشّرط و الشّرط الآخر فالجملة صحيحة، ولو تحقّق شرط دون آخر أو لم يتحقّق أي شرط فالجملة خاطئة. >>> 1 == 1 and 2 == 2 True >>> 1 == 2 and 2 == 2 False >>> 1 == 2 and 2 == 3 False or: إن تحقّق هذا الشّرط أو الشّرط الآخر فالجملة صحيحة إن كانت جميع الشّروط خاطئة فالجملة خاطئة. >>> 1 == 1 or 2 == 3 True >>> (1 == 1 or 2 == 3) True >>> 1 == 2 or 2 == 3 False لاحظ أن استعمال الأقواس أمر اختياري وستصل إلى نفس النّتيجة، لكن الأقواس مهمّة إن كنت تستعمل أكثر من عامل منطقي للتأكد من أنّ جملة تحتوي على العديد من الشّروط صحيحة بالفعل. يُمكنك استعمال أحدها أكثر من مرّة: >>> 1 == 2 or 2 == 3 or 3 == 3 >>> True >>> 1 == 2 or 2 == 3 or 3 == 4 >>> False >>> 2 == 2 and 3 == 3 and 4 == 4 >>> True يُمكن مزج هذه الشّروط المنطقيّة الواحد داخل الآخر لتصل إلى نتائج أكثر دقّة أو نتائج تستوفي شروطا عديدة والقوانين هنا كقوانين الرّياضيّات البسيطة (ما بداخل القوسين منعزل عن بقيّة الجملة): >>> (1 == 2 or 6 == 3) False >>> (1 == 1 and 2 == 2) True >>> (1 == 1 and 2 == 2) or 1 == 2 True >>> (1 == 1 and 2 == 2) and 1 == 2 False >>> (1 == 1 and 2 == 2) and (1 == 2 or 6 == 3) False >>> (1 == 1 and 2 == 2) and (1 == 2 or 6 == 6) True >>> (1 == 1 and 2 == 2) and (1 == 2 or 6 == 3) False >>> (1 == 1 and 2 == 2) or (1 == 2 or 6 == 3) True يُمكنك كذلك اختبار هذه القواعد عبر استعمال كل من القيمتين True و False فقط: >>> True or False True >>> True and True True >>> False or False False >>> False and False False خاتمة تعرّفنا في هذا الدّرس على كيفيّة استعمال مكتبة SQLAlchemy لترتيب السّجلات والحدّ من عددها والحصول على سجلات عشوائيّا من قاعدة البيانات، و كذلك ألقينا نظرة على كيفيّة استعمال المعامليْن and وor في لغة بايثون، واللذيْن يُعدان أساسًا سنبني عليه بقية معارفنا في الدّروس القادمة للحصول على نتائج أكثر دقّة من قاعدة البيانات، ومن ثم إتاحة إمكانية ترشيح النّتائج لتجربة استخدام أكثر أريحيّة.
-
تعرّفنا في الدّروس السّابقة على ماهيّة أداة SQLAlchemy وكيفيّة الاعتماد عليها في تطبيقات Flask باستعمال إضافة Flask-SQLAlchemy للتعامل مع قواعد بيانات SQL، وسنتعرّف في هذا الدّرس على كيفيّة ملء قاعدة البيانات التي أنشأناها ببيانات تجريبيّة وكذلك كيفيّة الحصول على السّجلات من قاعدة البيانات باستعمال لغة بايثون. إضافة عدة سجلات في نفس الوقت قد ترغب أحيانا في إضافة أكثر من سجل في نفس الوقت، إمّا لأنّك تحصل على الكثير من السّجلات من نفس الزّائر في صفحتك أو أنّك ترغب في توفير بعض الوقت والأسطر البرمجيّة، ولإضافة أكثر من كائن إلى الجلسة، استعمل التّابع add_all عوضا عن add، وذلك عبر تمرير قائمة من الكائنات إلى التّابع كما يلي: >>> user2 = User('abdelhadi', 'abdelhadi@example.com', 'secret_pass') >>> user3 = User('dyouri', 'dyouri@example.com', 'p_not_found') >>> users_list = [user2, user3] >>> db.session.add_all(users_list) >>> db.session.commit() كذلك يُمكنك تمرير القائمة مُباشرة دون إسنادها إلى متغيّر سابق: db.session.add_all([user2, user3]) إن عُدت الآن إلى أداة psql ونفّذت الاستعلام select للحصول على جميع المُستخدمين فستجد أنّ المُستخدمَيْن abdelhadi و dyouri قد أُضيفا إلى قاعدة البيانات بنجاح. لِنُضف بضعة مقالات لكل مُستخدم من المُستخدمين اللّذين أضفناهما للتو: >>> post2 = Post('a post from abdelhadi', 'content of a post', author_id = 2) >>> post3 = Post('another post from abdelhadi', 'some other content for the post', author_id = 2) >>> post4 = Post('a post from dyouri', 'other content', author_id = 3) >>> posts_list = [post2, post3, post4] >>> db.session.add_all(posts_list) >>> db.session.commit() لاحظ أنك إن أضفت وحذفت بضعة مُستخدمين من قاعدة بياناتك قبل تنفيذ الشّيفرة، فقد يكون مُعرّف كلا المُستخدمَيْن مغايرا لما حدّدته بالشّيفرة أعلاه، لذا تأكّد من أنّ الأرقام صحيحة. كذلك يمكنك إضافة المقالات والمُستخدمين بشكل مُختلط كما يلي: >>> db.session.add_all([user2, user3, post2, post3]) استغلال حلقة For لإضافة عدة سجلات سنحتاج في هذا الدّرس إلى العديد من السّجلات في قاعدة البيانات من أجل التجربة عليها سواء تعديلا أو قراءة أو حتى حذفًا، بإمكاني أن أطلب منك إضافة بضعة مُستخدمين وبضعة مقالات مُستفيدا مما تعلّمته سابقا (أتمنى أنّك فعلت ذلك بالفعل) لكن الأمر سيأخذ بعض الوقت، ونحن لا يهمنا أسماء المُستخدمين ولا بيانات المقالات، بل كل ما نُريده هو بيانات مزيّفة لأغراض تجريبيّة، لذا لمَ لا نستغل البرمجة في شيء مُفيد ونُضيف المُستخدمين والمقالات بحلقة for بحيث يكون لكل سجل من البيانات رقم يميزه عن السّجلات الأخرى، أي سنضيف مُستخدمين بالأسماء التّاليّة: user1, user2, user3, user4 ... جرب حل الاختبار دون النظر إلى الشيفرة التالية: اكتب حلقة لإضافة مُستخدمين ومقالات بالطّريقة السّابقة. الحلّ الشّيفرة التّي سنُضيف بها المُستخدمين ستكون كما يلي: for u in range(4, 13): name = 'user{}'.format(u) email = 'user{}@example.com'.format(u) password = 'password{}'.format(u) user = User(name, email, password) db.session.add(user) db.session.commit() وهذه لإضافة مقال لكل مُستخدم من المُستخدمين : for p in range(4, 13): title = 'Post from user{}'.format(p) content = 'Content {}'.format(p) author_id = p post = Post(title, content, author_id) db.session.add(post) db.session.commit() تفترض شيفرة المقالات أنّ آخر مُستخدم تمت إضافته أو حذفه من جدول المُستخدمين يمتلك الرقم المُعرّف 3، مما يعني أنّ المُستخدم user4 الذي يُعد أول مُستخدم يُضاف باستخدام حلقة التّكرار سيكون رقمُ مُعرّفه 4. مُلاحظة: سنحذف كثيرًا من المُستخدمين والمقالات التّي أضفناها للتو فيما بعد، لذا توجّب علينا إضافة هذا العدد، كما يجب عليك إدراك أنّ تطبيقات الويب الحاليّة تستطيع جمع كم هائل من البيانات دون أن يؤثّر ذلك على الأداء بشكل ملحوظ، لذا لا تخف من اختبار قاعدة بياناتك بحلقات التّكرار لإضافة وحذف العديد من البيانات. الحصول على البيانات، والطرق المختلفة للتحكم بالنّتائج إن اتّبعت ما سبق وأضفت المُستخدمين والمقالات التّي أضفناها لقاعدة البيانات فسيكون لديك الآن في قاعدة بياناتك العديد من المقالات والمُستخدمين، وسنتمكّن من الحصول على هذه البيانات لقراءتها والتّعامل معها بعمليّات CRUD الأخرى. والبيانات التّي تتواجد في قاعدة بياناتنا الآن هي كالآتي: Users: id | name | ----+-----------+ 1 | khalid | 2 | abdelhadi | 3 | dyouri | 4 | user4 | 5 | user5 | 6 | user6 | 7 | user7 | 8 | user8 | 9 | user9 | 10 | user10 | 11 | user11 | 12 | user12 | ----------------- Posts: id | title | ----+-----------------------------+ 1 | Post 1 | 2 | a post from abdelhadi | 3 | another post from abdelhadi | 4 | a post from dyouri | 5 | Post from user4 | 6 | Post from user5 | 7 | Post from user6 | 8 | Post from user7 | 9 | Post from user8 | 10 | Post from user9 | 11 | Post from user10 | 12 | Post from user11 | 13 | Post from user12 | مُجدّدا، تجاهلت الأعمدة الأخرى لأنّها غير مهمّة، كما أنّ الجدول سيكون كبيرا إن لم أتجاهلها، وتستطيع الحصول على البيانات كاملة إن شئت عبر أداة psql وأمريْ select السّابقين. لا بأس إن لم تمتلك نفس البيانات في قاعدة بياناتك، فالمهم أن تفهم العلاقة الأساسيّة بين كلّ مقال والمُستخدم الذي أضافه. أمّا إن كنت ترغب اتّباع هذا الدّرس بحذافيره، فنفِّذ الشيفرة التالية لحذف جميع بياناتك وإضافة البيانات أعلاه. from project import db from project.models import Post, User db.drop_all() db.session.commit() db.create_all() db.session.commit() user = User('khalid', 'khalid@example.com', 'password') user2 = User('abdelhadi', 'abdelhadi@example.com', 'secret_pass') user3 = User('dyouri', 'dyouri@example.com', 'p_not_found') post = Post('Post 1', 'post 1 content', author_id=1) post2 = Post('a post from abdelhadi', 'content of a post', author_id = 2) post3 = Post('another post from abdelhadi', 'some other content for the post', author_id = 2) post4 = Post('a post from dyouri', 'other content', author_id = 3) db.session.add_all([user, user2, user3, post, post2, post3, post4, ]) db.session.commit() for u in range(4, 13): name = 'user{}'.format(u) email = 'user{}@example.com'.format(u) password = 'password{}'.format(u) user = User(name, email, password) db.session.add(user) db.session.commit() for p in range(4, 13): title = 'Post from user{}'.format(p) content = 'Content {}'.format(p) author_id = p post = Post(title, content, author_id) db.session.add(post) db.session.commit() طريقة العمل الآن وبعد أن أدخلنا بعض المُستخدمين والمقالات إلى قاعدة البيانات، صار بإمكاننا الحصول على كل سجل وبيانات أعمدته، وسنحصل في أغلب الحالات على السّجل أو السّجلّات إن كانت مُتعدّدة ومن ثم إسنادها إلى مُتغيّر، فإن كان سجلّا واحدا (مثلا المُستخدم خالد) يُمكننا الوصول إلى بيانات الأعمدة ببساطة بإلحاق اسم العمود بالمُتغيّر الذي يحمل السّجل وفصلهما بنُقطة (user.name للحصول على اسم المُستخدم على سبيل المثال)، أمّا إن كان المُتغيّر يحمل سجلّات عديدة، فسنستخدم حلقة for بسيطة للدّوران حول السّجلات واستخراج بيانات كلّ سجل على حدة. تلك الطّريقة هي التي سنعتمد عليها في ما بعد، أمّا الاختلاف فسيكون في كيفيّة الحصول على السّجل (أو السّجلات) لإسنادها إلى المُتغيّر. الحصول على سجل سنستعمل خاصيّة الاستعلام (querying) في SQL للحصول على السّجلات، ويُوفّر لنا SQLAlchemy طريقة بسيطة للوصول إلى الاستعلام وبقيّة العمليّات عليه. >>> User.query.method الآن يُمكنك استبدال method بالعديد من التّوابع التّي تُوفّرها لنا مكتبة SQLAlchemy مثل all للحصول على جميع السّجلات من الجدول و filter_by لترشيح السّجلات والوصول إلى سجل يوافق شروطا معيّنة والمزيد من الطّرق المُتعدّدة التّي توفّرها قواعد بيانات SQL، وسنتطرّق إلى بعض أهم تلك الطّرق بالتّفصيل، وكما قلت من قبل فإني لن أشرح كل شيء توفّره المكتبة، لذا إن أردت الحصول على معلومة لم أذكرها فيُمكنك طرح سؤال في الأكاديميّة. كيفيّة الوصول إلى جداول قاعدة البيانات من مفسر لغة بايثون ذكرنا في الدروس السابقة أنه من أجل التعامل مع جدول معين فإن علينا استعمال اسم الصنف الخاص بالجدول والذي سبق وأنشأناه في ملفّ models.py. وسنبدأ أولًا بقراءة البيانات من جدول المُستخدمين، لكن يجب أن تستدعي الصّنف User أولًا من وحدة models كي نستطيع الوصول إليه. تأكد من أن البيئة الوهمية مُفعّلة ثمّ افتح مُفسّر لغة بايثون واكتب ما يلي: >>> from project.models import User بعد استيراد الصّنف User، ستتمكن من استخدامه للتعامل مع جدول المُستخدمين. الحصول على سجل برقم مُعرّفه أول طريقة قد تطرأ على ذهنك للحصول على سجلّ معيّن هي استخدام مفتاحه الأولي، أي رقم المُعرّف في أغلب الحالات، ذلك أن رقم المُعرّف يعبّر عن كلّ سجل بشكل فريد، فمن المُستحيل أن يحمل أكثر من مُستخدم مثلا رقم المُعرّف 1، وبالمثل في حالة المقالات. وللحصول على المُستخدم الذي يحمل رقم المُعرّف 1، استعمل التّابع get() بعد User.query مع تمرير الرّقم 1: >>> User.query.get(1) <username: khalid | email: khalid@example.com > كما تُلاحظ، حصلنا على نبذة عن المُستخدم ذو رقم المُعرّف 1 بمُجرّد أن قمنا بتنفيذ الشّيفرة، لهذا من المُهم أن يتواجد التّابع __repr__ في جداولك ولو لم يكن إضافته ضروريا. ولأنّنا سنقوم بالعديد من العمليّات على المُستخدم "خالد"، لنقم بإسناده إلى مُتغيّر باسم user ثم لننظر إلى بعض من البيانات الخاصّة بخالد: >>> user = User.query.get(1) >>> user.name u'khalid' >>> user.email u'khalid@example.com' >>> user.password u'password' >>> user.password == "password" True >>> user.password == "wrong_password" False لاحظ آخر سطرين حيث نقوم بإجراء عمليّة تحقّق بسيطة لتفهم أنّ البيانات التّي تحصل عليها مجرّد سلاسل نصيّة عاديّة، وأنّك تستطيع القيام بمُختلف العمليّات التّي يُمكنك استعمال بايثون لإنجازها. وبالمُناسبة، إن كنت ترغب بالحصول على أول سجل من الجدول فقط بغض النّظر عن رقم مُعرّفه يُمكنك استخدام التّابع first: >>> User.query.first() <username: khalid | email: khalid@example.com> الحصول على عدد السجلات داخل جدول للحصول على العدد الإجمالي للسجلات في جدول مُعين، استعمل التّابع count() كما يلي: >>> User.query.count() 12 يوضح المثال التالي كيفية الحصول على العدد الإجمالي للمقالات في قاعدة بياناتنا (بعد استيراد الصّنف Post بالطّبع): >>> from project.models import Post >>> Post.query.count() 13 وكما تُلاحظ، فالنتيجة عبارة عن عدد صحيح (Integer)، لذا يُمكنك التعامل معه بلغة بايثون بشكل عادي. الحصول على سجل بقيمة عمود معين لا يكفي رقم المعرِّف وحده لإجراء العمليات الشّائعة في تطبيقات الويب، عمليات مثل الحصول على مُستخدم عن طريق اسمه، أو الحصول على مقالات حسب تاريخ إنشائها، أو ترتيب المُستخدمين وفق قيم أعمدة مختلفة -تاريخ الانضمام، ترتيب المُستخدمين حسب أسمائهم أبجديًا-، والعديد من العمليات الأخرى التّي تستطيع القيام بها بلغة SQL في قواعد البيانات التّي تستخدمها. يُمكنك الحصول على سجلّ أو عدّة سجلّات حسب قيمة عمود معيّن باستخدام التّابع filter_by كما يلي: >>> result = Table.query.filter_by(column=value) حصلنا هنا على نتيجة عبر ترشيح النّتائج وفق العمود وقيمته، أي أنّك لو أردت أن تحصل على مُستخدم حسب اسمه فتستطيع استبدال column بname و value باسم المُستخدم. وإن أردت الحصول على القيم الفعليّة فاستعمل إمّا التّابع first للحصول على أول نتيجة أو التّابع all للحصول على جميع السّجلات داخل قائمة. >>> result.first() >>> result.all() كتطبيق على هذا المفهوم، لنحصل على المُستخدم عبد الهادي ونُسنده إلى مُتغيّر باسم user: user = User.query.filter_by(name='abdelhadi').first() لاحظ بأنّنا نقوم بإضافة التّابع first لنحصل على المُستخدم الأول، وذلك لأنّ التّابع filter_by يُمكن أن يُرجع أكثر من سجل واحد. فمثلا لو كان لمقالين نفس العنوان -Post على سبيل المثال- فيُمكن أن يُرجع الاستعلام التّالي مقالين: >>> Post.query.filter_by(title='Post') لذا فمن أجل الحصول عليها جميعًا، عليك إلحاق ما سبق بالتّابع all، أمّا لو أردت أول مقال فقط، فيُمكنك استعمال التّابع first. بعد أن حصلنا على المُستخدم عبد الهادي وأسندناه إلى المُتغيّر user، نستطيع الحصول على اسمه وكلمة مروره وبقيّة بياناته كما ذكرنا سابقا، ويُمكننا كذلك الحصول على مقالاته، وقد أضفنا مقالين كما تعلم برقم المُعرّف الخاص بهذا المُستخدم، لذا لنحصل على مقالات المُستخدم عبد الهادي باستخدام التّابع posts الذي ذكرته سابقا. إليك الآن أهم طريقتين لمُعالجة المقالات: للوصول إلى المقال الأول وقيّمه: >>> post1 = user.posts.first() >>> post1 <title a post from abdelhadi | content of a post > >>> post1.title u'a post from abdelhadi' >>> post1.content u'content of a post' وللوصول إلى جميع المقالات على شكل قائمة يُمكنك الدّوران حولها باستعمال حلقة for، يُمكنك استخدام التّابع all كما يلي: >>> posts = user.posts.all() >>> posts [<title a post from abdelhadi | content of a post >, <title another post from abdelhadi | some other content for the post >] يُمكنك الآن الوصول إلى كل مقال على حدة بحلقة for بسيطة: for post in posts: print(post.title) print(post.content) print('-----------------------') يكون الخرج كما يلي: a post from abdelhadi content of a post ----------------------- another post from abdelhadi some other content for the post ----------------------- سنتطرّق إلى الدّوران حول العديد من السّجلات بتفصيل أكثر فيما بعد. للوصول إلى مقال معيّن يُمكنك إضافة علامات [] مع رقم العنصر، فللوصول إلى المقال الأول مثلًا: >>> user.posts[0] <title a post from abdelhadi | content of a post > والمقال الثّاني: >>> user.posts[1] <title another post from abdelhadi | some other content for the post > الدّوران حول قيم مجموعة من السجلات في هذا القسم، سنستعلم عن بعض القيم من قاعدة بياناتنا ومن ثم ندور حولها للوصول إلى كل سجلّ على حدة: سنبدأ بالحصول على جميع القيم في جدول معيّن، وسنحتاج في حالتنا إلى استخدام هذه الطّريقة من أجل الحصول على جميع المقالات لعرضها على الصّفحة الرّئيسيّة للمقالات. وللوصول إلى جميع السجلّات في جدول مُعيّن في Flask-SQLAlchemy يُمكنك ببساطة أن تستخدم كلّا من التّابعين query ثمّ all على الصّنف المرتبط بالجدول، فمثلا للوصول إلى جميع المقالات المُتواجدة في الجدول: >>> posts = Post.query.all() الآن، سيحمل المُتغيّر posts قائمةً بجميع المقالات، ونستطيع الوصول إلى كلّ قيمة من القيم في أعمدة الجدول بالإضافة إلى بيانات المُستخدم الذي أضاف المقال بحلقة for: for post in posts: print('-----------------------') print(post.title) print(post.content) print(post.created_date) print(post.author.name) print(post.author.email) print(post.author.password) print(post.author.created_date) تستطيع تُنفيذ حلقة التّكرار أعلاه لترى النّتيجة. وكما هو مُتوقّع، ستلاحظ أنّنا نستطيع الوصول إلى بيانات كل مقال على حدة وكذلك بيانات المُستخدم الذي أضاف المقال. كمثال إضافي، سنحصل على جميع المُستخدمين مع عرض بيانات كل مُستخدم ثمّ المقالات التّي كتبها: users = User.query.all() for user in users: print('-----------------------') print(user.name) print(user.email) print(user.password) print(user.created_date) for post in user.posts: print('----') print(post.title) print(post.content) print(post.created_date) إن أمعنت النّظر في الشّيفرة فستبدو لك بسيطة جدّا، مجرد حلقة للحصول إلى كل مُستخدم ثمّ حلقة أخرى للوصول إلى كل مقال من مقالاته. سنستخدم محرّك القوالب Jinja2 للدّوران حول البيانات وعرضها بطريقة مُشابهة من أجل الوصول إلى القيم وعرضها في ملفّات HTML داخل تطبيق الويب الذي نبنيه بإطار Flask، لذا أقترح أن تُحاول عرض المقالات وكاتب كلّ مقال منذ الآن لتختبر مدى فهمك للموضوع. في الدروس التالية، سنستخدم طرقًا أخرى للحصول على عدة سجلات وإسنادها إلى مُتغيّر، وستكون طريقة الوصول إلى كل قيمة من بيانات السّجل هي نفسها ما قد ذكرناه ها هنا.
-
مُقدّمة بعد إنشاء جدولي المقالات والمُستخدمين في قاعدة البيانات في الدّرس السّابق، حان الوقت للتّعامل مع البيانات وإدارتها باستعمال لغة بايثون عوضا عن لغة SQL؛ في هذا الدّرس سنتعرّف على كيفيّة الوصول إلى مُفسّر بايثون في مجلّد مشروعنا وكذلك كيفيّة إضافة البيانات وجلبها من قاعدة البيانات. الوصول إلى مُفسّر بايثون داخل مُجلّد المشروع سنقوم في هذا الدّرس بالتّعامل مع قاعدة البيانات باستعمال Flask-SQLAlchemy مُباشرة من مُفسّر بايثون، إن كنت على نظام Windows فهو في الغالب متواجد في قائمة ابدأ، أمّا إن كنت على لينكس أو OSX فادخل إلى الطّرفيّة Terminal واكتب python تأكّد فقط من أنّك في مُجلّد kalima قبل أن تقوم بالوصول إلى مُفسّر بايثون، وتأكّد كذلك بأنّك قد فعّلت البيئة الوهميّة. إن لم تستطع الوصول إلى مُفسّر بايثون في مُجلّد kalima فيُمكنك تنفيذ الأمر chdir من وحدة os لتغيير المُجلّد الحالي إلى مسار مُجلّد kalima: >>> import os >>> os.chdir('path/to/kalima') مع تغيير path/to/kalima إلى مسار مُجلّد kalima في حاسوبك. مُلاحظة: الشّيفرة التّي سأقوم بتنفيذها ستُسبق بعلامة >>> أمّا المُخرج/النّتيجة فلن يُسبق بشيء. مثال: >>> site = 'Hsoub Academy' >>> site 'Hsoub Academy' بما أنّنا في مُفسّر بايثون نستطيع الوصول إلى قيمة المُتغيّر دون طباعتها، فقط اكتب اسم المُتغيّر ثم اضغط مفتاح ENTER. سنستعمل كلّا من db والصّنفين اللذ]ن يُشكلان كلّا من جدول المقالات والمُستخدمين للتّعامل مع البيانات فيها، وللوصول إلى كلّ من الكائن db والصّنفين Post و User يجب عليك أن تقوم باستيرادها: >>> from project import db >>> from project.models import Post, User إن حصلت على خطأ كما يلي: Traceback (most recent call last): File "<stdin>", line 1, in <module> ImportError: No module named project فهذا يعني بأنّك لست في مجلّد kalima لذا تأكّد من أنّك اتّبعت الإرشادات السّابقة على نحو صحيح. إدخال البيانات إلى قاعدة البيانات الآن، بعد استيراد كلّ من الكائن db والصّنفين Post وUser نستطيع القيام بأول عمليّة، ألا وهي عمليّة الإضافة، وسنُضيف أولا مُستخدما باسم khalid وبعدها سنُضيف مقالا له بعنوان Post 1. إضافة المُستخدم لإضافة مُستخدم سنُنشئ أولا كائنا من الصّنف User مع المعاملات التّي حدّدناها سابقا في دالّة init الخاصّة بالصّنف، ما يعني بأنّنا سنُنشئ مُستخدما كما يلي: user = User('khalid', 'email@example.com', 'password') وهنا يكون التّرتيب مُهمّا ومُطابقا لترتيب المُعاملات في الدّالة __init__، ويُمكنك كذلك تسميّة كلّ معامل وتغيير التّرتيب إن لم تتذكّر سوى أسماء المعاملات كما يلي: user = User(password='password', email='email@example.com',name='khalid') كما تُلاحظ عندما تذكر اسم كلّ معامل، فالتّرتيب غير مهم خلافا للمثال الأول. بعد إنشاء الكائن من صنف المُستخدم بالبيانات التّي نُريدها، يُمكننا الوصول إلى كل قيمة أو تغييرها ببساطة: >>> user = User('khalid', 'email@example.com', 'password') >>> user.name 'khalid' >>> user.email 'email@example.com' >>> user.password 'password' >>> user.email = 'khalid@example.com' >>> user.email 'khalid@example.com' >>> user <username: khalid | email: khalid@example.com > كما تُلاحظ، يُمكننا الوصول إلى كل قيمة من القيم التّي حدّدناها سابقا وكذا تعديلها بإسناد قيمة جديدة بكل بساطة. لاحظ كذلك مُخرجات آخر أمر، يُمكنك أن ترى بأنّ طريقة التّنسيق هذه هي نفسها ما قد حدّدناه سابقا في الدّالة __repr__ بالصّنف User، وإليك تذكيرا سريعا: def __repr__(self): return '<username: {} | email: {} >'.format(self.name, self.email) وأنبّه إلى أنّنا لم نغيّر أي شيء بعد في قاعدة البيانات، وإنشاء الكائن ليس سوى تمهيد لإضافته إلى جلسة تتواجد في الكائن db ثمّ تأكيد التّغييرات لإضافة المُستخدم إلى قاعدة البيانات حقيقة، ولإضافة الكائن user الذي أنشأناها قبل قليل إلى الجلسة نقوم بما يلي: >>> db.session.add(user) والجلسة هنا عبارة عن مكان لإضافة البيانات لوضعها بعد ذلك في قاعدة البيانات، وعند إضافة كل كائن إلى الجلسة لا يُضاف إلى قاعدة البيانات حقيقة إلى أن تنفّذ الأمر db.session.commit. لذا لإضافة المُستخدم خالد إلى قاعدة البيانات PostgreSQL فسننفّذ الأمر التّالي: >>> db.session.commit() بعد تنفيذ الأمر والعودة إلى أداة psql لعرض جميع البيانات في جدول المُستخدمين فستلاحظ بأنّ المُستخدم الذي أضفناه موجود بالفعل: kalima=# select * from users; id | name | email | password | created_date ----+--------+--------------------+----------+---------------------------- 1 | khalid | khalid@example.com | password | 2017-04-08 18:48:05.316805 (1 row) لاحظ بأنّ العمود id أخذ القيمة 1 رغم أنّنا لم نُحدّدها، ولو أضفنا مُستخدما آخر الآن لكان رقم مُعرّفه 2 ولو حذفت مثلا المُستخدم الذي يحمل رقم المعرّف 1 وأضفت مُستخدما آخر، فرقم مُعرّف هذا المُستخدم لن يأخذ مكان المُستخدم الأول بل سيأخذ رقما جديدا مُخالفا للأرقام السّابقة؛ ولاحظ كذلك بأنّ تاريخ الإضافة قد أضيف دون أن نقوم بتحديده يدويا. إضافة المقال بعد أن قُمنا بإضافة المُستخدم، حان الوقت لإضافة مقال خاص به، بالطّبع في التّطبيق المُستخدِم هو من سيُضيف مقالاته، وبعد تسجيله الدّخول، سنستطيع إضافة المقال ووضع قيمة مفتاح مُعرّفه كقيمة للمُعامل author_id، وسنقوم في هذا الجزء بإعطاء مثال لكيفيّة إضافة مقال وإسناد كاتب له، وفي الجزء التّالي سوف نضيف المزيد من المقالات والمُستخدمين لشرح كيفيّة الوصول إلى بيانات كل مُستخدم بما فيها مقالاته، وكذا كاتب كلّ مقال. لإضافة المقال ذي العنوان Post 1 نقوم بنفس الشّيء، أولا نُنشئ كائنا من الصّنف Post ثمّ نُسند له القيم التّي حدّدناها سابقا في الدّالة __init__ الخّاصّة بالصّنف، لكن هذه المرّة نقوم بإضافة مُعامل آخر، وهو عبارة عن قيمة رقم مُعرّف المُستخدم الذي أضاف المقال، وبما أنّنا نمتلك مُستخدما رقم مُعرّفه 1 فهذا يعني بأنّنا نستطيع أن نُسند له هذا المقال بصفته كاتبا له. >>> post = Post('Post 1', 'post 1 content', author_id=1) >>> post <title Post 1 | post 1 content > >>> db.session.add(post) >>> db.session.commit() >>> post.author <username: khalid | email: khalid@example.com > >>> post.author.name u'khalid' >>> post.author.email u'khalid@example.com' >>> post.author.password u'password' كما تُلاحظ، يُمكنك تحديد أسماء معاملات إن أردت، وترك أسماء أخرى على شرط أن تكون القيم مُرتّبة، فقد ذكرت اسم المُعامل author_id فقط لكي تفهم القيمة الثّالثة، وسيتوجّب عليك ذكر الاسم إن كان لك العديد من المفاتيح الأجنبيّة (أرقام مُعرّف تُشير إلى جداول كثيرة) لأنّها ستكون كلّها عبارة عن أرقام لا معنى لها لمن يقرأ الشّيفرة، ويجب عليك أن تتذكّر ترتيبها باستمرار، لذا من المُفضّل تجنّب هذا بذكر اسم كلّ معامل. لاحظ كذلك عندما نستدعي الكائن، فإنّ الدّالة __repr__ تستبدل كلّا من المنطقتين بالعنوان والمحتوى كما هو مُتوقّع. بعد إضافة المقال إلى الجلسة ثمّ تأكيد التّغييرات وكتابتها إلى قاعدة البيانات، أصبح بإمكاننا الآن الوصول إلى كاتب المقال عن طريق post.author والمُخرج عبارة عن كائن يحمل بيانات المُستخدم الذي أدخل البيانات كما لو أنّك حصلت عليه من قاعدة البيانات، إذ تستطيع الوصول إلى قيمة أي عمود في جدول المُستخدمين والنّتيجة تكون عبارة عن سلسلة نصيّة مسبوقة بحرف u للإشارة إلى أنّها عبارة عن Unicode وبالطّبع يُمكنك التّعامل معها كما تتعامل مع سلسلة نصيّة عاديّة. بعد الوصول إلى المُستخدم كاتب المقال، يُمكنك الحصول على مقالاته الأخرى كذلك: >>> post.author.posts.all() [<title Post 1 | post 1 content >] المُخرج عبارة عن قائمة من المقالات يُمكنك الدّوران حولها باستخدام حلقة for ولأنّنا لا نمتلك سوى مقال واحد حاليّا، سنقوم بالوصول إليه مُباشرة برقم العنصر في القائمة: >>> post.author.posts[0] <title Post 1 | post 1 content > >>> post.author.posts[0].title u'Post 1' >>> post.author.posts[0].content u'post 1 content' >>> post.author.posts[0].author <username: khalid | email: khalid@example.com > لاحظ أنّنا استطعنا الوصول إلى كاتب المقال مُجدّدا في آخر سطر، يُمكنك الاستمرار إلى ما لانهاية، المقال –> كاتبُه –> مقال كَتَبه –> كاتبُه –> مقال كَتَبه –> كاتبُه وهكذا… ما يعني بأنّ السّطر التّالي مُمكن: >>> post.author.posts[0].author.posts[0].author.posts[0].author.posts[0].author.posts[0].author.name u'khalid' بالطّبع، لا فائدة من استعمال SQLAlchemy بهذه الطّريقة، إنّما يجب عليك أن تفهم كيف يعمل، وبأنّ مثل هذه الأمور مُمكنة فربّما تُفيدك في تفادي بعض الأخطاء البرمجيّة. خاتمة تعرّفنا في هذا الدّرس على كيفيّة استعمال مُفسّر لغة بايثون للتّعامل مع قاعدة البيانات الخاصّة بنا، وتمكّنّا من إضافة سجلّ إلى جدول المُستخدمين وسجلّ آخر إلى جدول المقالات. في الدّرس القادم، سنطّلع على كيفيّة إنشاء العديد من السّجلات لنتمكّن من العمل معها في بقيّة الدّروس الخاصّة بالتّعامل مع قاعدة البيانات باستعمال مكتبة SQLAlchemy لتتمكّن من تطوير تطبيقات ويب أفضل وأكثر تعقيدا باستعمال إطار العمل Flask.
-
مُقدّمة: بعد أن عرّفنا كلّا من جدول المقالات وجدول المُستخدمين، بقيت لنا خطوة الرّبط بينهما ليُشير كلّ مقال إلى كاتبه ونستطيع الوصول إلى مقالات كلّ مُستخدم، وسنقوم بهذا الأمر باستغلال خصائص قواعد بيانات SQL العلائقيّة لإنشاء علاقة واحد للعديد، بحيث يُمكن لكلّ مُستخدم أن يمتلك العديد من المقالات ويكون لكلّ مقال كاتب واحد فقط. المُشكلة عند تصفّحك لتطبيقات الوب في وقتنا الحالي، ستُلاحظ بأنّ المُستخدمين يمتلكون بياناتهم الخاصّة، مثلا يُمكن للمُستخدم الواحد أن يمتلك عدّة مقالات وتدرج تحت اسمه، ويُمكن أن يمتلك كذلك عدّة تعليقات باسمه، إذن كيف يُمكن أن نُدرج مقالات من جدول المقالات الخاصّ بنا إلى المُستخدم الذي يقوم بإضافتها؟ بمعنى آخر، كيف يُمكن للمُستخدم الواحد أن يمتلك عدّة مقالات مُندرجة باسمه؟ الحل الخاطئ الطّريقة التّي يُمكن أن تُفكّر فيها هي بإدراج رقم مُعرّف كل مقال يُضيفه المُستخدم إلى عمود خاص به مع الفصل بينها بمسافة أو شيء من هذا القبيل، بعدها تحصل عليها وتقوم باستخراج مقالات كل مُستخدم. الجدول سيكون كما يلي: id | name | posts 1 | Ali | 1, 3, 5 ... أهملت هنا عمودي البريد الإلكتروني وكلمة المرور لأنّها غير مُهمّة في مثالنا. الآن إن نظرت إلى العمود posts فستجد بأنّ المقالات ذات المُعرّفات 1 و 3 و 5 قد أضافها المُستخدم Ali. هذه الطّريقة فكرة سيّئة جدّا، فلو أردنا حذف مقال فحذف علاقته بالمُستخدم أمر مُعقّد، وهناك العديد من المشاكل مع استخدام هذه الطّريقة، وقد أشرت إليها لأنّها شائعة جدّا ويجب أن تحذفها من ذهنك قبل أن تحدث أمور أسوأ، فإن أردت أن تكون مُطوّر وب جيّدا فعليك تعلّم الطّرق الصّحيحة وعليك كذلك الإلمام بالطّرق الخاطئة في التّطوير. الطّريقة الصّحيحة لربط سجلّ واحد بالعديد من السّجلّات في جدولين مُختلفين علاقة الواحد-للعديد أو One-to-Many Relationship هي طريقة لربط جدولين أو أكثر بحيث يمتلك كلّ فرد في جدول العديد من السّجلّات في جدول آخر. في مثالنا، سيمتلك المُستخدم الواحد عدّة مقالات باسمه. للقيام بهذا الأمر، نقوم بوضع عمود آخر في الجدول الذي سيمتلك العديد من السّجلّات الخاصّة بفرد واحد من جدول آخر ليحمل قيمة رقم مُعرّف هذا الفرد، أي أننا سنُضيف عمودا آخر في جدول المقالات ليحمل رقم مُعرّف المُستخدم الذي أضاف المقال ليكون جدول المقالات كما يلي: | id | title | user_id | 1 | Post 1 | 1 | 2 | Post 2 | 2 | 3 | Post 3 | 1 | 4 | Post 4 | 3 لنفترض بأنّ لدينا ثلاثة مُستخدمين في جدول المُستخدمين كما يلي: | id | name | 1 | Ali | 2 | Abdelhadi | 3 | Hassan مُجدّدا، قمت بإهمال الأعمدة الأخرى لأنّها لا تهمّنا في هذا المثال، وكما تُلاحظ لكل مقال قيمة في عمود user_id لتُشير إلى المُستخدم الذي أنشأ هذا المقال، وعند النّظر إلى جدولي المقالات والمُستخدمين والسّجلّات المتواجدة فيها حاليّا، يتبيّن لنا بأنّ المستخدم Ali قد أضاف مقالين، المقال ذو رقم المُعرّف 1 و المقال ذو رقم المُعرّف 3، أمّا Abdelhadi فهو صاحب المقال ذي المُعرّف 2 أمّا المقال الأخير فمن الواضح بأنّ المُستخدم Hassan هو من أضافه. هذه هي علاقة الواحد للعديد ببساطة، عندما يمتلك سجلّ واحد من جدول العديد من السّجلّات في جدول آخر، فإنّنا نُضيف عمودا إلى الجدول الذي يحمل العديد من السّجلات ليحمل كل سجلّ منه قيمة المفتاح الأولي Primary Key للسّجل الواحد في الجدول الآخر والذي يُسمّى فيه مفتاحا أجنبيا Foreign Key، أي أنّ العمود user_id عبارة عن مفتاح أجنبي لأنّه يحمل قيمة المفتاح الأولي الخاصّ بالمُستخدم، وعندما نقوم بتحديد عمود على أنّه مفتاح أجنبي، فالعلاقة بين الجدولين تكون مفهومة بالنّسبة لقاعدة البيانات لدينا، كما يُساعدنا ذلك على تخطّي بعض المشاكل في حالة ما أضيفت قيمة خاطئة إلى العمود عوضا عن قيمة صحيحة. في SQLAlchemy يُمكننا إنشاء علاقة واحد للعديد بسهولة وبساطة بإضافة سطرين فقط إلى الشفرة التّي حدّدناها سابقا، سطر في صنف (جدول) المقالات لإضافة عمود باسم author_id والذي سيكون عبارة عن مفتاح أجنبي يُشير إلى قيمة المفتاح الأولي للمُستخدم، وسطر في صنف المُستخدم لنتمكّن من الوصول إلى مقالات كل مُستخدم ببساطة. أضف هذا السّطر في آخر الصّنف Post: # project/models.py author_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) لتعريف مفتاح أجنبي، نستعمل الدّالة db.ForeignKey مع تمرير اسم الجدول واسم المفتاح الأولي مفصولين بنُقطة (في هذا المثال users.id) ونقوم كذلك بإضافة المُعامل nullable وإسناد القيمة المنطقيّة False له للتأكّد من أنّ الحقل الذي يربط بين المُستخدم والمقال الذي أضافه لا يحمل قيمة فارغة. بعد إضافة المفتاح الأجنبي، حان الوقت لإخبار SQLAlchemy بأنّنا نريد الحصول على مقالات كلّ مُستخدم، والمُستخدم الذي أضاف كلّ مقال انطلاقا من العلاقة بين الجدولين، لذا أضف السّطر التّالي في آخر الصّنف User: # project/models.py posts = db.relationship("Post", backref="author", lazy="dynamic") في هذا السّطر، نقوم بتعريف العلاقة بين المُستخدم والمقال عن طريق الدّالّة db.relationship مع تمرير ثلاثة مُعاملات، الأول عبارة عن اسم الصّنف الذي يشير لجدول المقالات، والمُعامل backref يُمكّننا من الوصول إلى المُستخدم الذي كتب المقال عن طريق الاسم author أمّا المعامل الأخير فهو لتحديد نوعيّة النّتيجة التّي سنحصل عليها عند طلب جميع المقالات التّي كتبها أحد المُستخدمين، وقد وضعنا القيمة dynamic لتكون النّتيجة عبارة عن استعلام ديناميكي لجميع المقالات التّي كتبها المُستخدم، وهكذا سنحصل على مرونة أكثر عند طلب المقالات وسنتمكّن من التّعامل معها على أنّها كائنات يُمكننا استغلالها بجميع ما توفّره مكتبة SQLAlchemy من طرق ودوال مُساعدة، وسنرى تطبيقا لهذا الكلام فيما بعد. سنُضيف كذلك عمود author_id إلى التّابع __init__ الخاصّة بالصّنف Post وبالتّالي سيُصبح التّابع كما يلي: def __init__(self, title, content, author_id, created_date = None): self.title = title self.content = content self.author_id = author_id self.created_date = datetime.utcnow() بعد تحديد العلاقة بين الجدولين، سنستطيع إضافة مقال وإسناد رقم مُعرّف المُستخدم أو المُستخدم ككاتب للمقال الذي أضافه لنتمكّن فيما بعد من الحصول على كاتب كل مقال بجميع معلوماته (بريده الإلكتروني، اسمه …)، وكذا المقالات التّي كتبها كلّ مُستخدم مع مرونة في التّعامل معها عبر ما تُوفّره لنا مكتبة SQLAlchemy من مُساعدة. خاتمة بعد أن تعرّفنا على طريقة ربط المُستخدم ومقالاته والمقالات وكاتبها، أصبحنا نستطيع تطبيق التّغييرات إلى قاعدة بياناتنا والتّعامل مع السّجلّات بعمليّات قواعد البيانات المعروفة اختصارا بـCRUD أو Create, Read, Update, Delete، أي الإضافة/الإنشاء، القراءة/العرض، التّعديل والحذف.
-
مُقدّمة بعد تجهيز قاعدة بيانات PostgreSQL التي سنستعملها في تطبيقنا، وبعد أن تعرّفنا في الدرس السابق على كيفيّة تنصيب وتجهيز كل من مكتبة SQLAlchemy و إضافة Flask-SQLAlchemy، حان الوقت لتجهيز جدولين من أهم جداول تطبيقات إدارة المحتوى، ألا وهي جدول المقالات الذي يمثل المحتوى بعينه وجدول المُستخدمين الذين سيُدِيرُون المحتوى، وبالتالي فسنقوم في هذا الدرس بوضع أساس لتطبيق “إدارة المحتوى” عبر تجهيز الطرف الذي سيقوم بالإدارة والطرف الذي يُمثل المحتوى. تذكير نعلم بأنّ قاعدة البيانات تحتوي على عدة جداول، كل جدول يمثل نوعا من المحتوى، وكل جدول يشمل عدة أعمدة، فمثلا جدول المُستخدمين سيحتوي على عمود لأسماء المستخدمين، وعمود لكلمات المرور، بحيث تكون قيمة كل عمود مربطة بمستخدم واحد فقط. تعرّفنا في الدرس السابق على الشكل العام لجداول قاعدة البيانات عند استعمال إضافة Flask-SQLAlchemy وقلنا بأنّ جدولا باسم table_name سيكون كالتّالي: from project import db class TableName(db.Model): __tablename__ = 'table_name' column_name = db.Column(db.Type, args) الجدول به عمود واحد باسم column_name ونقوم بإخبار SQLAlchemy بأنّ هذا عمود في الجدول عبر استخدام التّابع db.Column الذي يقبل عدّة معاملات لتحديد نوعية العمود وما يمكن له أن يقبل من قيم. نقوم بتحديد نوع البيانات التي يجب أن يقبلها العمود عبر استخدام المعامل db.Type مع استبدال Type بأحد الأنواع التي يدعمها SQLAlchemy. ما يلي أكثر أنواع القيم استخداما والتي يدعمها SQLAlchemy: Integer الأعداد الصّحيحة String(size) سلسلة نصيّة، size مُعامل يشير إلى أقصى قيمة لعدد المحارف في السّلسلة النّصيّة، مثلاString(25) يشير إلى أنّ العمود يُمكن أن يحمل سلسلة نصيّة بحدود خمسة وعشرين محرفا فقط ولا يُمكن تجاوز هذا الحد. Text نوع للإشارة إلى أنّ هذا العمود يُمكن أن يحتوي على النّصوص الطّويلة. DateTime نوع يُشير إلى أنّ العمود سيحمل تواريخ مُعبّر عنها بالكائن datetime في لغة بايثون، وسنستخدم هذا النّوع لوضع تاريخ نشر للمقال وتاريخ لانضمام مُستخدم معيّن. يُساعد تحديد نوع القيم في العمود على تجنّب الكثير من الأخطاء، ومن المُفضّل أن تختار دائما النّوع الذي يُناسبك أكثر لتحفظ المساحة وتتجنّب بعض الهجمات المُمكنة، فمثلا لو كان نوع القيمة Text في حقل اسم المُستخدم لتمكّن أحدهم من وضع نص كاسم مُستخدم له، وإن عرضناه في صفحة لكانت تلك الصّفحة بشعة لوجود نص عوضا عن كلمة أو كلمتين، هذا بالإضافة إلى أنّ المساحة في الخادوم ستُستهلك بشكل غير معقول. أعلم بأنّ الكلام النّظري مُمل بعض الشّيء، لكنّه مهم جدّا لتفهم ما سنقوم به تاليّا، وسنرى تطبيقا لهذا الكلام عند إنشاء جدولي المقالات والمُستخدمين في ما بعد وأتمنّى أن تُساعد هذه المُقدّمة على تقليص الصّداع الذي يحدث عندما تتعلّم شيئا لأول مرّة. تجهيز جدول المقالات بعد أن وضّحنا المفاهيم الأساسيّة، حان الوقت لتطبيق هذه المفاهيم وإنشاء الجداول التّي سنتعامل معها في تطبيقنا، تذكر بأنّ هذه الجداول عبارة عن أصناف بايثون عادية، ويجب عليك وضعها داخل ملفّ models.py المتواجد بمُجلّد project. سنبدأ بجدول المقالات الذي سيكون كالآتي: # project/models.py from datetime import datetime from project import db class Post(db.Model): __tablename__ = "posts" id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(255), nullable=False) content = db.Column(db.Text, nullable=False) created_date = db.Column(db.DateTime) def __init__(self, title, content, created_date = None): self.title = title self.content = content self.created_date = datetime.utcnow() def __repr__(self): return '<title {} | {} >'.format(self.title, self.content) مُلاحظة: تذكّر بأنّ جميع الجداول التّي نعرّفها عبارة عن أصناف في ملفّ project/models.py. أولا، نقوم باستيراد الكائن datetime لنستعمله في تأريخ إضافة المقال، بعدها نستدعي الكائن db الذي عرّفناه سابقا، بعد ذلك نقوم بتطبيق مفهوم الصّنف كجدول لإنشاء جدول المقالات باسم Post الذي يرث من db.Model، بعدها نقوم بتحديد اسم الجدول posts ثمّ نعرّف عمود رقم المُعرّف كمفتاح أولي، ثمّ العنوان كسلسلة نصيّة بحدود 255 حرفا، محتوى المقال كنصّ، تاريخ إضافة المقال من نوع db.DateTime. بعد تعريف جميع الأعمدة، نعرّف الأعمدة التّي يُمكننا إدارتها عبر الدّالة __init__، وتشمل هذه الأعمدة حاليا العنوان والمحتوى وتاريخ الإضافة فقط، وإدارة تاريخ الإضافة اختياري لأنّنا سبق وأن وضعنا له قيمة افتراضيّة عبارة عن تاريخ إضافة السّجل إلى قاعدة البيانات والذي نصل إليه من الدّالة datetime.utcnow، الآن إن أردنا إضافة قيم إلى الجدول فسنحتاج إلى توفير عنوان المقال ومحتواه فقط، أمّا العمودان الآخران فيهتمّ بهما SQLAlchemy. بعد الانتهاء من التّابع الخاص __init__ نعرّف التّابع الخاص __repr__ ليُساعدنا على عرض عنوان المقال ومحتواه بعد استعلامه بـSQLAlchemy ككائن، وقد سبق وأن شرحت وظيفة هذه الدّالة الخاصّة في درس البرمجة كائنيّة التوجّه ضمن سلسلة تعلّم بايثون، وسنرى في ما بعد نتيجة هذه الدّالة وبماذا ستُفيدنا. تجهيز جدول المُستخدمين سنقوم بإنشاء جدول المُستخدمين بنفس الطريقة، صنف باسم User، جدول باسم users، رقم مُعرّف كقيمة أوليّة، اسم مُستخدم، بريد إلكتروني، كلمة المرور، وتاريخ الانضمام. class User(db.Model): __tablename__ = "users" id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(25), nullable=False) email = db.Column(db.String(25), nullable=False) password = db.Column(db.String(25), nullable=False) created_date = db.Column(db.DateTime) def __init__(self, name, email, password, created_date = None): self.name = name self.email = email self.password = password self.created_date = datetime.utcnow() def __repr__(self): return '<username: {} | email: {} >'.format(self.name, self.email) هذه المرّة سيتوجّب علينا تحديد كلّ من اسم المُستخدم، كلمة المرور وبريده الإلكتروني إذا أردنا إضافة سجل مُستخدم إلى قاعدة البيانات، أمّا الدّالّة __repr__ فستعرض كلّا من اسم المُستخدم وبريده الإلكتروني. مُلاحظة: حاليّا نقوم بتخزين كلمات المرور على شكل نصوص واضحة، ويُمكن لأي شخص أن يحصل عليها ببساطة إن استطاع الوصول إلى قاعدة بيانات التّطبيق، وهذا أمر خطير جدّا، إذ أنّ حماية بيانات مُستخدمي التّطبيق أمر مهم جدّا، ولابد من تشفير كلمات المرور قبل إدخالها إلى قاعدة البيانات، وسنقوم بذلك فيما بعد، فالهدف الرّئيسي من هذا الدّرس هو إعطاؤك مقدّمة لكيفيّة تجهيز الجداول في قاعدة البيانات أمّا الحماية فسنتطرّق إليها لاحقا، لذا لا تحاول القيام بتخزين كلمات المرور بهذه الطّريقة، وتابع السّلسلة إلى نهايتها لتتعلّم كيفيّة تطوير تطبيقاتك بإطار العمل Flask بشكل آمن. خاتمة بعد أن تعرّفنا على كيفيّة تجهيز جداول قاعدة البيانات التّي سنتعامل معها في تطبيقنا باستعمال مكتبة SQLAlchemy وإضافة Flask-SQLAlchemy وتعرّفنا على بعض المفاهيم المُهمّة في التّعامل مع قواعد البيانات بهذه المكتبة، بقيت نقطة مهمّة يجب الإشارة إليها قبل إنشاء الجداول في قاعدة البيانات وجلب، إضافة وتعديل وحذف السّجلات منها، ألا وهي علاقة الواحد للعديد One-to-Many Relationship، وهي العلاقة التّي سنستكشفها وسنستعملها لربط كلّ مقال بالمُستخدم الذي أضافه وربط المقالات بكاتبها في الدّرس القادم.
-
مُقدّمة لا شك بأنّ معظم تطبيقات اليوم تعتمد على نوع من أنواع البيانات، لذا لا بدّ من حفظها في مكان مُناسب، وتعتبر قواعد البيانات التّي يُمكن التّعامل معها بلغة SQL أحد أفضل الطّرق لحفظ بيانات تطبيقات الويب، وبما أنّنا أنشأنا قاعدة بيانات خاصّة بنا، فقد حان الوقت للتعرف على كيفية استعمال مكتبة SQLAlchemy لإدارة قاعدة بيانات تطبيقنا. في هذا الدرس، سنتعرف على كيفية تجهيز إضافة Flask-SQLAlchemy لتسهيل العمل مع مكتبة SQLAlchemy. تنصيب إضافة Flask-SQLAlchemy تأكّد أولا من أنّ البيئة الوهميّة مفعّلة عندك وأنّك في مُجلّد kalima، وبعدها نفّذ الأمر التّالي لتنصيب الإضافة مع مكتبة psycopg2: pip install Flask-SQLAlchemy psycopg2 لكي نتمكّن من التّعامل مع قاعدة بيانات PostgreSQL باستعمال SQLAlchemy يجب تنصيب مكتبة psycopg2 التّي تُمكنّنا من ذلك. مُلاحظة: تعتمد الإضافة أساسا على مكتبة SQLAlchemy لذا ستُنصّب كذلك، هذا يعني بأنّك تستطيع الوصول إلى جميع الدّوال والكائنات المُتواجدة في المكتبة إن أردت ذلك. لاختبار نجاح التّنصيب، افتح مفسّر بايثون وقم باستيراد الحزم التّاليّة: import flask_sqlalchemy import sqlalchemy إن لم يحدث أي خطأ فالحزمة مُنصّبة بنجاح، وإن واجهت أي خطأ فحاول التّأكد من أنّك اتّبعت ما ذكرته في الدّروس الأولى من السّلسلة بشكل صحيح. تذكير قواعد البيانات من نوع SQL تعتمد بشكل رئيسيّ على الجداول Tables، كلّ جدول يحمل أعمدة مُختلفة، وكلّ عمود يحمل قيما معيّنة، وهناك عمود خاصّ يسمى برقم المُعرّف وعادة ما يُشار إليه بـid ولأنّ كل مُدخل من المُدخلات يحمل رقم مُعرّف فريدا، فسيُمكّننا من تمييز المُستخدمين والمقالات وبقيّة الأجزاء المُهمّة في تطبيقاتنا، وقد سبق وأن شرحت هذا في درس ربط تطبيق Flask مع قاعدة بيانات SQLite، لذا عد إلى ذلك الدّرس لتفهم أكثر. ربط التّطبيق بإضافة Flask-SQLAlchemy لربط التّطبيق مع إضافة Flask-SQLAlchemy نقوم أولا باستيراد SQLAlchemy من حزمة الإضافة بالسّطر التّالي: # project/__init__.py from flask_sqalchemy import SQLAlchemy وبعدها نقوم بتمرير كائن التّطبيق app إلى ما قمنا باستيراده وإسناد النّاتج إلى مُتغيّر باسم db كما يلي: # project/__init__.py db = SQLAlchemy(app) سنقوم كذلك بإضافة سطرين لإعدادات التّطبيق app.config، السّطر الأول سيكون عبارة عن رابط قاعد البيانات، كما يلي: app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://username:password@127.0.0.1/kalima' استبدل username بالمُستخدم الذي أنشأت به قاعدة البيانات وكلمة المرور بكلمة المرور التّي سبق أن حدّدتها، إن كنت على أحد أنظمة لينكس فالمُستخدم المبدئي هو postgres والعنوان 127.0.0.1 يُشير إلى أنّنا سنعتمد على قاعدة بياناتنا المحليّة، أمّا kalima فهو اسم قاعدة البيانات التّي أنشأناها سابقا. السّطر الثّاني غير مُهم وقد لا تحتاج إليه في قادم النّسخ من إضافة Flask-SQLAlchemy، وقد قمت بإضافته لإخفاء تنبيه يظهر بعد تشغيل الخادوم. app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True مُلاحظة: من المعروف بأنّ التّطبيق يمر عبر عدّة مراحل، مرحلة التّطوير، الاختبار ثمّ مرحلة الإنتاج (النّشر إلى العالم الخارجي)، وعادة ما تُعزل كلّ مرحلة عن الأخرى فيما يُسمّى بالبيئة، ولذا تسمع مُصطلحات كبيئة التّطوير Development Environment، بيئة الاختبار Testing Environment، بيئة الإنتاج Production Environment، وإعدادات كلّ بيئة تكون مختلفة عن البيئات الأخرى، ولهذا يعدّ تخزين الإعدادات في الملفّ الرّئيسي للمشروع أمر سيئا لأنّك ستضطر إلى تغيير الإعدادات في كل مرّة تنتقل فيها من بيئة إلى أخرى، وقد يُشكل إعداد خاطئ واحد خطرا كبيرا على تطبيقك، خاصّة إن كان التّطبيق ذا وظائف مُتعدّدة، والحل الأمثل هو تخزين هذه الإعدادات في ملفّ مُستقل وهذا الأمر مهم جدا وقد سبق وأن شرحت طريقة بسيطة للقيام بذلك، وسنتطرّق إليه بالتّفصيل في ما بعد، المهم الآن أن تُدرك بأنّ كل ما يقع تحت قاموس app.config عبارة عن إعداد من إعدادات التّطبيق. بعد ربط كائن التّطبيق بإضافة Flask-SQLAlchemy سنتمكّن من استغلال الكائن db للاستفادة من كل ما تمنحه لنا الإضافة من تسهيلات للعمل مع مكتبة SQLAlchemy. ومع التّغييرات التّي قُمنا بها سيُصبح الجزء العلوي من ملفّ project/__init__.py كالآتي: # project/__init__.py from flask import Flask, render_template app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://username:password@127.0.0.1/kalima' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy(app) from project.posts.views import posts from project.users.views import users app.register_blueprint(posts) app.register_blueprint(users) تأكّد فقط من أنّ الإعدادين يقعان مُباشرة بعد تعريف الكائن app وأنّ استيراد الطّبعات الزّرقاء Blueprints وتسجيلها يقع بعد تعريف المُتغيّر db لتجنّب بعض المشاكل التّي قد تحدث عندما تعتمد كل حزمة على الأخرى أثناء الاستيراد أو ما يُسمى بـ circular imports أو الاستيرادات الدّائريّة. الجداول في Flask-SQLAlchemy لتجهيز الجداول في التّطبيق، سنفتح أولا ملفّا جديدا باسم models.py في مجلّد project، وسنضع فيه كل ما يتعلّق بالجداول وطريقة تسجيلها والبيانات المبدئية. وطريقة إنشاء الجداول في Flask-SQLAlchemy هي كالآتي: from project import db class TableName(db.Model): __tablename__ = 'table_name' column_name = db.Column(db.Type, args) كما ترى، نقوم أولا باستيراد الكائن db من حزمة المشروع ثمّ نقوم بإنشاء صنف باسم الجدول، ولابد لهذا الصّنف أن يرث من الصّنف db.Model الذي تُوفّره لنا إضافة Flask-SQLAlchemy، بعدها نقوم بتسميّة الجدول عبر وضع اسمه على شكل سلسلة نصيّة وإسنادها للمُتغيّر الخاصّ __tablename__، ثمّ بعد ذلك نقوم بتعريف مُختلف أسماء الأعمدة وإسنادها قيمة عن طريق التّابع db.Column، ثمّ نُخصّص نوع قيم العمود وبعض المُعاملات الإضافيّة، وأحد أشهر المُعاملات هو مُعامل primary_key الذي يجعل من العمود مفتاحا أوليا إن أسندت له القيمة المنطقيّة True وعادة ما يُستخدم مع عمود رقم المُعرّف id، والمُعامل الآخر الذي ستصادفه كثيرا هو مُعامل nullable ويقبل كذلك القيمتين المنطقيّتين True و False فإن كانت قيمة المُعامل False فهذا يعني بأنّ العمود لا يمكن أن يحمل قيمة فارغة، ويُستعمل في الأعمدة المُهمّة كالبريد الإلكتروني وكلمة المرور، ويُمكن أن تسمح بأن تكون قيم العمود فارغة إن لم يكن توفير المعلومة ضروريا مثل نبذة عن المُستخدم وصورته الشّخصيّة أو اسمه الحقيقي وهذه الأمور تختلف حسب نوعيّة التّطبيق بكل تأكيد. المفتاح الأولي Primary Key عبارة عن عمود يحمل قيما فريدة في الجدول بأكمله، ولا يُمكن أن تكون قيمته فارغة ولكنّنا لا نحتاج إلى إضافة المُعامل nullable عند تعريف العمود، ويُستعمل لتعريف كل سجلّ من سجلّات الجدول على حدة، ومن المُفضّل أن يكون نوعه عددا صحيحا لحفظ بعض المساحة، ولإبقاء القيم فريدة ومُختلفة عن بعضها البعض، يضاف واحد إلى القيمة السّابقة عند كل إضافة إلى قاعدة البيانات، وكما قُلت سابقا، الطّريقة الشّائعة لاستخدامه هو بوضع عمود باسم id في الجدول ليحمل القيم الفريدة على شكل أعداد صحيحة، فمثلا لو كان لدينا قاعدة بيانات بأسماء ما كما يلي: id | name 1 | أحمد 2 | مُحمّد 3 | أحمد للوهلة الأولى يبدو بأنّ سجلّ أحمد قد كرّر مرّتين، لكن لاحظ رقم المُعرّف الخاص بكل سجلّ، أحمد الأول رقم مُعرّفه 1 والآخر 3، هذا يعني بأنّهما شخصان مُختلفان بنفس الاسم، لا بد أنّك أدركت الآن مدى أهميّة المفتاح الأولي، فلو كان تطبيقنا يسمح بتسجيل المُستخدمين بأسمائهم لوجدنا مُشكلة مع أحمد هذا، لكنّ رقم المُعرّف يقوم بحل المُشكلة. أمّا عن db.Type فهو لتحديد نوع القيم التي سيحملها العمود، وهي مُشابهة لأنواع القيم المُتاحة في لغة SQL، مثل العدد الصّحيح Integer والسّلسلة النّصيّة String (VARCHAR في لغة SQL) والتّاريخ وغير ذلك من أنواع القيم. خاتمة قمنا في هذا الدرس بتجهيز أداة SQLAlchemy وتعرّفنا على كيفيّة استخدام إضافة Flask-SQLAlchemy للتعامل معها وألقينا نظرة بسيطة إلى الصورة العامة لجدول SQL مكتوب بلغة بايثون. في الدرس القادم، سنقوم بإنشاء جدولي المقالات والمُستخدمين في قاعدة بياناتنا.
-
تجهيز قاعدة البيانات بما أنّنا سنستخدم قاعدة البيانات PostgreSQL لهذا التّطبيق، عليك أولا أن تقوم بتنصيبها، إن كنت تستخدم Ubuntu فيُمكنك مُراجعة درس كيفيّة تنصيب PostgreSQL على Ubuntu، أمّا إن كنت على نظام Windows أو نظام Mac OS فتستطيع تحميلها من الموقع الرّسمي وتنصيبها، يُمكنك كذلك استخدام برنامج pgAdmin لإدارة قواعد بياناتك عن طريق برنامج رسومي. يُمكنك استعمال البرنامج الرّسومي لإنشاء قاعدة البيانات إن أردت ذلك، لكنّنا سنستعمل أداة psql لتنفيذ أوامر PostgreSQL واستعمال لغة SQL مباشرة من سطر الأوامر، لذا تأكّد من أنّك تستطيع الوصول إلى أداة psql. إنشاء قاعدة البيانات لإنشاء قاعدة البيانات، يكفي أن ننفّذ الأمر التّالي على سطر أوامر psql : CREATE DATABASE kalima; بعد تنفيذ الأمر، سيكون المُخرج كما يلي: CREATE DATABASE كلمة المرور للاتّصال بقاعدة البيانات، سنحتاج إلى كلمة مرور، إن سبق لك وأن خصّصت كلمة مرور أثناء التّنصيب، تستطيع تخطي هذه الخطوة، أمّا إن لم تمتلك كلمة مرور أو أنّك نسيتها، فيُمكنك ببساطة تغييرها في سطر أوامر psql بالأمر التّالي: \password بعد تنفيذ الأمر سيُطلب منك كتابة كلمة مرور جديدة وإعادة كتابتها للتّأكد من أنّك لم تُخطئ. كلمة المرور هذه مُهمّة جدا للاتّصال بقاعدة البيانات من تطبيق فلاسك باستخدام أداة SQLAlchemy لذا تأكّد من أنّها مُتاحة. ما معنى ORM؟ ORM اختصار Object-Relational Mapper أي رابط الكائنات بالعلاقات (الجداول)، الهدف منه ببساطة التّعامل مع قواعد بيانات SQL باستعمال الكائنات Objects والأصناف Classes عوضا عن تعليمات SQL، فعوضا عن إنشاء البيانات وجلبها وتعديلها وحذفها عن طريق تعليمات SQL التّي ستتكرّر كثيرا في الشّفرة يُمكن ببساطة استخدام لغة بايثون للوصول إلى نفس الهدف، كما أنّ استعمالها مع لغة برمجة يخلق شيئا من عدم التّناسق. لو تابعت سلسلة دروس Flask للمُبتدئين، للاحظت بأنّنا استعملنا ملفّا باسم manage_db للتّعامل مع قواعد البيانات عن طريق الدّوال عوضا عن كتابة تعليمات SQL عند الرّغبة في إجراء كل عمليّة، ولو استعملنا SQL الخام لكان تكرار الشّفرة كثيرا. بالإضافة إلى ميّزة تعريف الأصناف التّي تُمثّل الجداول والتّعامل معها عن طريق التّوابع (الدوال) لتجنّب تكرار الشّفرة، يقوم الـORM بالكثير من الأشياء المُملّة خلف الكواليس، فعوضا عن الاهتمام بالتّفاصيل الصّغيرة بأنفسنا، يقوم الـORM بالاهتمام بها من أجلنا لنهتمّ بالأمور الأكثر أهميّة، وفي النّهاية، مُعظمنا لا يتقن لغة SQL كثيرا لذا من المُفضّل استعمال مكتبة للتّعامل مع قواعد البيانات باللغة التّي نُجيدها. وبما أنّ استعمال الـORM يُتيح لنا عزل الشّفرة المسؤولة عن التّعامل مع قاعدة البيانات في معزل عن التّطبيق فهذا يُتيح لنا تعديل الخصائص وإضافة الميّزات للتّطبيقات دون التّأثير على تطبيق معيّن بحد ذاته، ومن الشّائع جمع الشّفرة المسؤولة عن التّعامل مع قاعدة البيانات في ملف باسم models.py في مجلّد المشروع. يُساعدنا الـORM كذلك على تحقيق مبدأ MVC أو Model-View-Controller أي “نموذج، عرض، مُتحكّم ” وهو مبدأ يقوم على عزل جزء العرض، المُتحكّمات، والنّماذج في تطوير التّطبيقات لمرونة أكثر في التّطوير، أي أنّ كلّا من العرض (الذي يُمثّل القوالب في تطبيقنا) والمُتحكّم (الذي يُمثّل دوال الموجّهات في ملفّات views.py) والنّموذج (أي الجزء الذي يتعامل مع البيانات) أجزاء منفصلة حسب الوظيفة متّصلة في التّطبيق ويُمكن أن تنتقل البيانات بينها ببساطة، ورغم أنّ إطار Flask يمنحك الحريّة الكاملة في طريقة تسميّة أجزاء تطبيقك، إلّا أن أطر عمل أخرى مثل Django و Rails تفرض عليك تسميّات محدّدة. ما هو SQLAlchemy؟ بعد أن عرّفنا معنى الـORM بقي تعريف مكتبة SQLAlchemy، وهي مكتبة تُسهّل علينا التّعامل مع قواعد البيانات المُختلفة، كما أنّها تحتوي على ORM قوي ومُعتمد عليه من طرف المشاريع الكبيرة. تحتوي SQLAlchemy على الكثير من الدّوال المُساعدة والكائنات التّي تُسهّل عليك كتابة SQL بلغة بايثون، والORM المبني فيه يُستعمل لربط الكائنات بالبيانات في قاعدة البيانات. سنستعمل في غالب هذا المشروع الـORM الخاص بـSQLAlchemy ولن نتطرّق إلى بقيّة ميّزات المكتبة، لذا إن أردت معرفة المزيد عن المكتبة فعليك بالتّوثيق الرّسمي. إضافات Flask Flask Extensions أو إضافات فلاسك عبارة عن مكتبات وأطر عمل Frameworks مبنيّة لتُسهّل على المُطوّر القيام بالأشياء المُفصّلة وتجنّب التّعامل مع كل شيء بأنفسنا، فمثلا، يُمكننا أن نستعمل مكتبة SQLAlchemy مع تطبيق Flask مُباشرة، إلّا أن في هذه الطّريقة الكثير من الأمور غير المُهمّة والكثير من الإعدادات، وبما أنّنا في مسيرة تعلّم إطار فلاسك لا كيفيّة استعمال SQLAlchemy، ولتسهيل استعمال SQLAlchemy مع المشاريع المبنيّة بإطار العمل فلاسك، قام كاتب الإطار Armin Ronacher بتطوير إضافة للإطار باسم Flask-SQLAlchemy التّي أصبحت أحد أشهر الإضافات على موقع Github. بالإضافة إلى Flask-SQLAlchemy هناك الكثير من الإضافات التّي يُمكننا استعمالها لتطوير تطبيقات فلاسك، وإليك بعضا من هذه الإضافات: Flask-Login إضافة لتسهيل التّعامل مع جلسات المُستخدمين Sessions، تُتيح إمكانيّة تسجيل دخول مُستخدم وتسجيل خروجه، لكنّها لا تقوم بالتّحقق من صحّة البيانات (اسم المُستخدم وكلمة المرور مثالا) وتترك المُطوّر للاهتمام بالأمر. Flask-Bcrypt إضافة لتشفير كلمات المرور باستعمال خوارزميّة Bcrypt قبل حفظها بقاعدة البيانات لحماية أكثر، هذا لمنع المُخترق من سرقة كلمات المرور حتى بعد وصوله إلى قاعدة البيانات، وكلّما كانت خوارزميّة التّشفير أعقد كلّما كان من الصّعب فك التّشفير عن كلمة المرور ما يُوفّر حماية أكثر. Flask-WTF بما أنّ مُعظم التّطبيقات تعتمد على نماذج HTML (حقول النّصوص، كلمات المرور و مساحة النّص TextArea) وبما أنّها تُسبّب الكثير من الثّغرات الأمنيّة في التّطبيقات والتّي يُمكن أن يُساء استغلالها، فلا بد من توفير طريقة آمنة وبسيطة للتأكّد من أنّ ما يُدخله المُستخدم في هذه الحقول آمن. يُمكنك بالطّبع التّأكد من مُدخلات المُستخدم بنفسك، لكنّ الأمر يأخذ وقتا، كما أنّه ممل في الحقيقة، إذ عليك تكرار نفس الشّيء كلّ مرّة. عوضا عن الاعتماد على مهاراتك البرمجيّة للتأكّد من سلامة المُدخلات، يُمكنك أن تترك إضافة Flask-WTF للاهتمام بالأمر في تطبيقاتك المبنيّة بإطار فلاسك، الإضافة مبنيّة على مكتبة WTForms التّي يُمكنك استعمالها مع أطر عمل أخرى، وأنصحك بالاطلاع على توثيق المكتبة الرّسمي للمزيد من المعلومات عن كيفيّة مُعالجة النّماذج. Flask-Migrate ببساطة، Flask-Migrate إضافة تُتيح لك تهجير البيانات Migrations عند تطوير تطبيقك. هذه الإضافة مهمّة جدّا إن كنت تطوّر تطبيقا سبق وأن نشرته على نحو مُتواصل، إذ تُتيح لك تهجير البيانات في كل مرّة تُضيف جدولا جديدا أو علاقة بين الجداول إلى قاعدة البيانات. تخيّل أنّك تمتلك تطبيقا يحتوي جدول المُستخدمين فيه على أكثر من ألف مُدخل، إن أردت إتاحة إمكانيّة إضافة تاريخ ازدياد للمُستخدمين فستضطر إلى حذف كلّ شيء في هذا الجدول وإضافة العمود الجديد ثمّ إعادة البيانات من جديد، الأمر مُعقّد بعض الشّيء وفقدان البيانات أمر غير مرغوب فيه بتاتا. مُجدّدا عوضا عن القيام بكل شيء بنفسك، دع إضافة Flask-Migrate لتهتمّ بكل هذا التّعقيد من أجلك، فبسطر أو سطرين تستطيع تطبيق الميّزة الجديدة لتطبيقك وتهجير البيانات القديمة بكل بساطة. Flask-Testing إضافة لتسهيل إجراء اختبارات وحدة Unit tests لتطبيقك، إذ أنّ اختبار التّطبيق مُهمّ جدّا للتأكّد من أنّ كلّ شيء بخير وأنّ المُستخدمين لا يواجهون أية مشاكل، خاصّة إذا كان مشروعك كبيرا، فاختبار كل وظيفة يدويّا قد يأخذ منك وقتا طويلا، وإن لم تختبره آليا فنسبة الخطأ كبيرة جدّا. إذا أضفت خاصيّة جديدة لتطبيقك فأنت بذلك تُعرّض أجزاء أخرى من التّطبيق إلى الخطأ، وستُسهّل عليك الاختبارات معرفة ما إذا كان التّطبيق لا يزال يعمل كما ينبغي له أو لا، وبما أنّ اختبارات الوحدة تختبر كلّ وظيفة على نحو مُستقل، فسيسهل عليك معرفة أي جزء يجب عليك الاهتمام به لإصلاح التّطبيق. Flask-Gravatar هذه الإضافة تُسهّل لنا استعمال خدمة Gravatar لتمكين المُستخدمين من إضافة صورة شخصيّة لهم، إذ تكون الصّورة الشّخصيّة مُرتبطة بالبريد الإلكتروني للمُستخدم وتُستعمل هذه الخدمة في الكثير من التّطبيقات في وقتنا الحالي. هناك مئات الإضافات الخاصّة بإطار Flask لذا لا يُمكنني ذكرها كلّها، إن أردت الاطّلاع على إضافات أخرى، تستطيع البحث عنها عن طريق أداة pip كما يلي: pip search flask سنتعرّف على معظم هذه الإضافات في قادم الدّروس مع التّقدم في المشروع، وهذا الهدف الأساسي من هذه الإضافات، كلّما تقدّمت أكثر وكبر المشروع كلّما احتجت إلى التّعامل مع الكثير من المهام المُملّة كالتّأكد من أنّ ما يرسله المُستخدم إلى قاعدة بياناتك آمن، وكالتّعامل مع الجلسات لتوفير نظام تسجيل دخول آمن دون الاضطرار للقيام بكل التّفاصيل؛ كما أنّ الإضافات تُقلّل من حجم الخطأ في التّطوير، فالاعتماد على إضافة لتسجيل دخول المُستخدمين بشكل آمن أفضل من مُحاولة إنشاء طريقة خاصّة بك قد تكون مليئة بالثّغرات الأمنيّة. وبما أنّ فلاسك إطار عمل مُصغّر، فبإضافاته يُمكنك أن تستخدمه كما تُستخدم أطر العمل الضّخمة كـDjango و Rails و Laravel وغيرها من الأطر المشهورة. خاتمة بعد أن أنشأنا قاعدة البيانات وتعرّفنا على بعض المفاهيم المُهمّة في تطوير تطبيقات الويب مع إطار فلاسك، حان الوقت لإنشاء الجداول الأساسيّة في قاعدة البيانات لنتمكّن من التّعامل معها عن طريق إضافة Flask-SQLAlchemy وهذا بالضّبط ما سنقوم به في الدّرس القادم.
-
مُقدّمة تعرّفنا سابقا على حلقة التّكرار for للدّوران على عناصر قائمة أو مفاتيح وقيم قاموس بايثون، سنتعرّف الآن على تقنيات مُتقدّمة ستُساعدك على التّعامل مع حلقة for على نحو أفضل لتتمكّن من تصميم قوالب HTML مع مُحرّك القوالب Jinja بطريقة أسرع وأكثر إتقانا. استعمال حلقة for مع قائمة بايثون تُستعمل حلقة for مع قوائم بايثون عاديّة كما يلي: {% set names = ['Ahmed', 'Khalid', 'Ali', 'Abdelhadi', 'Yasser'] %} <ul> {% for name in names %} <li>{{ name }}</li> {% endfor %} </ul> النّتيجة: Ahmed Khalid Ali Abdelhadi Yasser بهذه الطّريقة، يُمكنك عرض كل عنصر من القائمة داخل وسم HTML مُعيّن وتطبيق تنسيق باستخدام CSS كما تشاء. استعمال حلقة for مع قواميس بايثون بالإضافة إلى استعمالها مع قائمة عاديّة، يُمكنك كذلك استعمال حلقة for مع قاموس ما للوصول إلى مفاتيحه وقيّم كلّ مفتاح بحيث تدور حول القاموس لتحصل على كل مفتاح وقيمته، يُمكنك القيام بذلك ببساطة كالتّالي: {% set website = { 'url': 'academy.hsoub.com', 'company': 'Hsoub', 'description': 'Learn new skills for free' } %} <ul> {% for key, value in website.iteritems() %} <li> {{ key }} : {{ value }} </li> {% endfor %} </ul> لاحظ بأنّنا نقوم بالوصول إلى محتويات القاموس عبر تطبيق التّابع iteritems() على المُتغيّر website، بحيث نحصل على المفتاح والقيمة عبر المُتغيّرين key و value على التّوالي، داخل حلقة for نقوم بعرض كلّ من المفتاح وقيمته داخل وسم <li>. النّتيجة: url : academy.hsoub.com company : Hsoub description : Learn new skills for free يُمكنك كذلك استخدام تنسيقات إطار العمل Bootstrap لعرض مُحتويات قاموس ما بشكل جميل. بالإضافة إلى ما سبق، يُمكنك كذلك إسناد قائمة بايثون على هيئة قيمة لمفتاح مُعيّن من القاموس، على سبيل المثال، يُمكن لمقال ما أن يحمل عنوانا واحدا وأكثر من تعليق واحد، بحيث تكون التّعليقات عناصر من قائمة بايثون كما يلي: {% set post = {'title': 'Introduction to Flask', 'comments': ['Great!', 'Thank you!'] } مثال تطبيقي لنجمع الآن الأفكار السّابقة ولنُصمّم صفحة HTML بسيطة لعرض معلومات شخص ما: {% set person = { 'first_name': 'Ali', 'last_name': 'Sayed', 'age': '27', 'languages': ['Arabic', 'English'], 'Children': ['Hassan', 'Fatima'], } %} <div class="col-sm-4"> <ul class="list-group"> <li class="list-group-item active"> {{ person['first_name']}} {{ person['last_name']}} </li> {% for key, value in person.iteritems() %} {% if value is string %} <li class="list-group-item">{{ key }} : {{ value }}</li> {% else %} <li class="list-group-item"> {{ key }} : {% for item in value %} {{ item }}, {% endfor %} {% endif %} </li> {% endfor %} </ul> </div> رغم أنّ الشّفرة مُعقّدة بعض الشّيء، إلّا أنّها في الحقيقة بسيطة، وستبدو لك كذلك إن تابعت الفقرتين السّابقتين من هذا الدّرس. إليك ما يحدث في الشّفرة أعلاه، أولا نُعرّف قاموسا باسم person ليحمل المفاتيح التّالية: الاسم الأول للشّخص، الاسم العائلي (النّسب)، العمر، اللغات التي يُتقنها الشّخص، أطفاله. لاحظ بأنّ قيم اللغات والأطفال عبارة عن قوائم بايثون. في شفرة HTML نقوم بالدّوران حول مُحتويات القاموس person، بعدها نستعمل الاختبار string المُعرّف قياسيا في Jinja للتّحقق ممّا إذا كانت القيمة عبارة عن سلسلة نصيّة أو لا، إن كانت كذلك فإنّنا نعرضها مع مفتاحها بشكل طبيعي، إن لم تكن القيمة سلسلة نصيّة فهذا يعني بأنّها قائمة من عناصر مُتعدّدة وأنّ الجزء الذي يلي الكلمة المفتاحيّة else هو الذي سيُعرَض. في الجزء else نقوم بعرض المفتاح ثمّ نستعمل الحلقة for مُجدّدا للدّوران على عناصر القائمة المُتواجدة في قيمة المفتاح المذكور آنفا. هذا هو الجزء الخاص بعرض عناصر قائمة بعد قيمة المفتاح: {% else %} <li class="list-group-item"> {{ key }} : {% for item in value %} {{ item }}, {% endfor %} إن استخدمت إطار العمل Bootstrap فستكون النّتيجة مُشابهة لما في الصّورة التّاليّة: لاحظ بأنّ القاموس غير مُرتّب على نحو صحيح، ذلك لأنّ قواميس بايثون ليست مُرتّبة مبدئيًّا، لكن يُمكنك ترتيب مُحتويات القاموس أبجديا عبر استعمال المُرشّح dictsort كما يلي: {% for key, value in person | dictsort %} مع مُلاحظة أنّك لن تحتاج إلى التّابع iteritems() عند استعمال المُرشّح. المُتغيّر loop عند استخدام الحلقة for حلقة for تعمل بطريقة بسيطة، إذ تأخذ عدّة قيم وتمنحك كل قيمة داخل مُتغيّر مؤقّت، وفي كلّ دورة تتغيّر قيمة المُتغيّر. يوفّر مُحرّك القوالب Jinja عند التّعامل مع حلقة for مُتغيّرا خاصّا داخل الحلقة باسم loop؛ باستعمال هذا المُتغيّر الخاص، يُمكنك الوصول إلى معلومات خاصّة بالدورة الحاليّة وتُمكّننا كذلك من استعمال تقنيّات الاستدعاء الذّاتي (Recursion) لتضمين عناصر قائمة داخل عناصر قائمة أخرى خاصّة بالدّورة الحاليّة (ستتضّح الصّورة أكثر عندما ننتقل إلى الأمثلة التّطبيقية). الوصول إلى رقم الدّورة الحاليّة يُمكنك استخدام الخاصيّة index مع المُتغيّر loop في الحلقة for كما يلي: {% for item in items %} {{ loop.index }} {% endfor %} مثال: {% set list = ['Python', 'Flask', 'Jinja', 'Programming', 'Web developement'] %} {% for item in list %} <p>{{ loop.index }} - {{ item }}</p> {% endfor %} النّتيجة: 1 - Python 2 - Flask 3 - Jinja 4 - Programming 5 - Web development لاحظ بأنّ العدّ يبدأ من واحد، إن أردت أن يبدأ العدّ من الصّفر، سيتوجّب عليك استخدام التّابع index0 عوضا عن التّابع index كما يلي: {{ loop.index0 }} بهذا التّعديل ستكون النّتيجة كما يلي: 0 - Python 1 - Flask 2 - Jinja 3 - Programming 4 - Web developement الوصول إلى رقم الدّورة الحاليّة عكسيّا يُمكنك كذلك عكس تأثير التّابع index عبر استعمال التّابع revindex كالتّالي: {{ loop.revindex }} النّتيجة: 5 - Python 4 - Flask 3 - Jinja 2 - Programming 1 - Web developement وللانتهاء بالرّقم 0 يُمكنك استعمال التّابع revindex0. التحقّق مما إذا كانت الدّورة الحاليّة هي الأولى أو الأخيرة يُمكنك استعمال التّابع first الذي يُرجع القيمة True إذا كانت الدّورة الحاليّة هي أول دورة في حلقة for. وبنفس الطّريقة فإنّ التّابع last يُساعد على التّحقّق ممّا إذا كانت الدّورة الحاليّة هي الأخيرة أو لا. مثال: {% for item in list %} {% if loop.first %} <p>{{ loop.index }} (First) - {{ item }}</p> {% elif loop.last %} <p>{{ loop.index }} (Last) - {{ item }}</p> {% else %} <p>{{ loop.index }} - {{ item }}</p> {% endif %} {% endfor %} النّتيجة: 1 (First) - Python 2 - Flask 3 - Jinja 4 - Programming 5 (Last) - Web developement الوصول إلى عدد عناصر القيم الإجمالي بالإضافة إلى إمكانيّة الحصول على عدد عناصر قائمة ما باستعمال المُرشّح count أو المُرشّح length، يُمكنك كذلك استخدام loop.length للحصول على عدد عناصر القيم التي اِستُخْدِمَتْ مع حلقة for الحاليّة. يُمكنك التّحقق من الأمر عبر المثال التّالي (على افتراض أنّك تستعمل القائمة list السّابقة): <p>{{ list | length }}</p> <p>{{ list | count }}</p> {% for item in list %} <p>{{ loop.length }} - {{ item }}</p> {% endfor %} ستُلاحظ بأنّ النّتيجة هي نفسها سواء استخدمنا المرشّحات أو التّابع loop.length، بهذه الطّريقة يُمكنك الحصول على عدد عناصر قائمة من داخل حلقة for. إعادة استعمال نفس الحلقة مع قائمة داخل قائمة أخرى لنفترض بأنّنا نُريد عرض أسماء أشخاص مع أبنائهم بحيث تكون قائمة HTML الخاصّة بالأبناء تابعة للأب. إليك البيانات التّي سنستعملها في مثالنا هذا: {% set people = [ dict(name='Ali', children=[dict(name='Mohammed'), dict(name='Hassan')]), dict(name='Khaled', children=None), dict(name='Fahd', children=[dict(name='Kamal'), dict(name='Omar')]) ] %} في القائمة أعلاه، لدينا ثلاثة قواميس، القاموس الأول خاصّ بعلي، وأطفاله (الذين يعتبرون قائمة من القواميس كذلك) محمد وحسن، أما القاموس الثّاني فخاص بخالد الذي ليس لديه أبناء، والقاموس الأخير خاص بفهد، وأبناؤه كمال وعمر. لعرض أسماء الآباء الذين يتواجدون في القواميس الثّلاثة، يُمكن أن نستخدم حلقة for بسيطة، لكن سيتوجّب علينا تكرار الشّفرة إذا ما أردنا الدوران حول أبناء كل شخص. عوضا عن استخدام أكثر من حلقة for واحدة، يُمكننا استخدام حلقة for خاصّة تُكرّر نفسها، ويُمكننا فعل هذا الأمر عبر إضافة الكلمة recursive عند استخدام حلقة for كالتّالي: {% for person in people recursive %} هكذا سنتمكّن من استعمال المُتغيّر loop لإعادة الدّوران حول قائمة أطفال كل شخص، لكن سنفعل ذلك فقط إن كان للشّخص أبناء بالفعل، لذا سنتحقّق من أنّ القيمة لا تُساوي القيمة None عبر الاختبار كما يلي: {% if person.children is not none %} <ul class="list-group"> {{ loop(person.children) }} </ul> {% endif %} المثال النّهائيّ سيكون كالتّالي: {% set people = [ dict(name='Ali', children=[dict(name='Mohammed'), dict(name='Hassan')]), dict(name='Khaled', children=None), dict(name='Fahd', children=[dict(name='Kamal'), dict(name='Omar')]) ] %} <div class="col-sm-4"> <ul class="list-group"> {% for person in people recursive %} <li class="list-group-item"> {{ person.name }} {% if person.children is not none %} <ul class="list-group"> {{ loop(person.children) }} </ul> {% endif %} </li> {% endfor %} </ul> </div> النّتيجة ستكون كما في الصّورة التّاليّة: يُمكنك كذلك معرفة عمق الحلقة باستخدام التّابع loop.depth داخل حلقة for من النّوع recursive كالتّالي: <span class="badge">{{ loop.depth }} </span> تأكّد فقط من أن تضع السّطر السّابق مُباشرة بعد السّطر: {% for person in people recursive %} النّتيجة ستكون كالتّالي: لاحظ بأنّ رقم العمق يبدأ من واحد، إن أردت أن يبدأ من الصّفر فاستعمل loop.depth0. يُمكنك كذلك استخدام هذه الميّزة مع تعليقات في مُدوّنة أو منصّة تتطلّب تفاعل الأعضاء بين بعضهم البعض. الصّورة التّاليّة توضيح بسيط للنّتيجة النّهائيّة التي يُمكنك أن تصل إليها إن كنت تمتلك تعليقات متداخلة في قاعدة بياناتك: خاتمة تعرّفنا في هذا الدّرس على كيفيّة استخدام حلقة for في مُحرّك القوالب Jinja للحصول على قوالب HTML أحسن وتطويرها بسرعة. وبهذا نكون قد أكملنا أساسيّات دروس مُحرّك القوالب Jinja وسننتقل في الدّروس القادمة للتّعرف على جزء آخر مُهمّ في تطوير الويب، ألا وهو كيفيّة التّعامل مع قواعد البيانات في التّطبيقات المكتوبة بإطار العمل Flask.
-
مُقدّمة تعرّفنا في الدّروس السّابقة على العديد من ميّزات مُحرّك القوالب Jinja وكيفيّة استخدامها لتصميم صفحات HTML بسرعة، إذ بدأنا بمبدأ المرشّحات ثمّ مررنا على الاختبارات لنُنهي مشوارنا عبر التّعرف على طرق لتفادي تكرار شفرة HTML وإعادة استخدام القوالب أو أجزاء منها في مشروع مُعيّن، بل يُمكنك كذلك كتابة شيفرة واحدة واستعمالها مع أكثر من مشروع لما يُوفّره لنا مُحرّك القوالب Jinja من مرونة في التّطوير وقابليّة إعادة استعمال الشّفرة في مُختلف الظّروف. في هذا الدّرس، سنتعرّف على السّياق (Context) في Jinja عند التّضمين والاستيراد لتتفادى الوقوع في أخطاء يصعب تصحيحها. لتطبيق أمثلة هذا الدّرس، يُمكنك إنشاء تطبيق Flask صغير ليقوم بتقديم قوالب HTML أو يُمكنك استعمال إطار عمل آخر مثل Pyramid أو Django أو Bottle لكن سيتوجّب عليك البحث عن كيفيّة استعمال Jinja مع إطار العمل الذي تختاره. السّياق في مُحرّك القوالب Jinja عند تضمين قالب في قالب آخر أو عند الاستيراد يعني مُختلف المُتغيّرات والدّوال المُعرّفة في جزء مُعيّن من الشّفرة. فمثلا، إن عرّفت مُتغيّرا في الجزء العلوي من قالب HTML كما يلي: {% set website = 'academy.hsoub.com' %} بعد تعريف المُتغيّر website في القالب، فسيُصبح هذا المُتغيّر مُتاحا في هذا القالب فقط، ما يعني بأنّك لن تستطيع الوصول إلى المُتغيّر website في قالب HTML آخر دون استيراده. السّياق عند تضمين قالب داخل قالب آخر فلنضع السّيناريو التّاليَ لتتوضّح الفكرة أكثر، لنقل بأنّ لدينا ملفّات HTML التّالية: base.html index.html url.html مُحتويات ملفّ base.html ستكون كالتّالي: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"> <title>{% block title %}{% endblock %}</title> </head> <body> {% block body %} {% endblock %} </body> </html> وملفّ index.html يرث من الملفّ base.html مع تعريف المُتغيّر website وتضمين الملفّ url.html باستعمال الجملة include: {% extends 'base.html' %} {% block title %} My Application {% endblock %} {% block body %} {% set website = 'academy.hsoub.com' %} {% include 'url.html' %} {% endblock %} لنقل بأنّك تُريد الوصول إلى قيمة المُتغيّر website داخل الملفّ url.html لعرض الرّابط داخل وسم <a> كما يلي: {# 'url.html' #} <a href='http://{{ website }}'>{{ website }}</a> ستكون النّتيجة كما يلي: <a href='http://academy.hsoub.com'>academy.hsoub.com</a> نجح الأمر لأنّ السّياق قد مُرِّر من الملفّ index.html إلى الملفّ url.html عند استخدام التعليمة include، ما يعني بأنّ المُتغيّرات المُعرّفة في الملفّ الأصلي ستكون مُتاحة في الملفّ الفرعي الّذي ضُمِّن بالجملة include. لكنّك لن تستطيع الوصول إلّا إلى المُتغيّرات التي عُرّفت قبل استعمال الجملة include. ما يعني بأنّك لو عرّفت المُتغيّر website بعد تضمين القالب url.html كما يلي: {% include 'url.html' %} {% set website = 'academy.hsoub.com' %} فالقالب url.html لن يتعرّف على المُتغيّر website وسيعتبره سلسلة نصيّة فارغة لأنّه لا يستطيع الوصول إلى قيمته. ستحصل بالتّالي على النّتيجة التّالية: <a href='http://'></a> لاحظ بأنّ مُحرّك القوالب Jinja لم يُصدر أية أخطاء، لذا كن حذرا من هذا النّوع من المشاكل الصّامتة، وتأكّد دائما من الاطّلاع على مصدر الصّفحة لتفهم ما يحدث وتستطيع حلّ المُشكلة. تضمين قالب داخل قالب آخر باستعمال التعليمة include يُمرّر السّياق مبدئيا من قالب لآخر ، لكن إن أردت منع حدوث هذا الأمر، يُمكنك استعمال التّعبير without context لمنع القالب الفرعي من الوصول إلى سيّاق القالب الأصلي، وبالتّالي لن يتمكّن من الوصول إلى المُتغيّرات التّي سبق تعريفها. لتجربة هذا، أضف التّعبير without context إلى صفحة index.html ليُصبح مُحتوى الملفّ كما يلي: {% extends 'base.html' %} {% block title %} My Application {% endblock %} {% block body %} {% set website = 'academy.hsoub.com' %} {% include 'url.html' without context %} {% endblock %} إن ألقيت نظرة على النّتيجة، ستجدها مُشابهة لما كانت عليه عندما عرّفنا المُتغيّر website بعد التعليمة include ما يعني أنّ القالب url.html لم يعد مُؤهّلا للوصول إلى قيمة المُتغيّر، ولهذا نفس التّأثير مع جميع المُتغيّرات والدّوال المُعرّفة في السّياق. السّياق عند الاستيراد يُمرّر السّياق مبدئيا بين القوالب عند استعمال الجملة include، لكنّ العكس يحدث عند استيراد قالب داخل قالب آخر. لتوضيح هذه الفكرة، لنُغيّر مُحتويات الملفّ url.html إلى الماكرو التّالي : {% macro url() %} <a href='http://{{ website }}'>{{ website }}</a> {% endmacro %} لاحظ بأنّ الماكرو لا يقبل أية مُعاملات، لكنّنا نحاول أن نصل إلى المُتغيّر website داخل الماكرو. لنُغيّر الآن التعليمة include إلى جملة import لاستيراد هذا الماكرو من القالب url.hmtl: {% set website = 'academy.hsoub.com' %} {% from 'url.html' import url %} {{ url() }} لاحظ بأنّنا نقوم بالاستيراد بعد تعريف المُتغيّر. بما أنّ السّياق لا يُمرّر مبدئيا عند الاستيراد، فالنّتيجة ستكون كما لو أنّ قيمة المُتغيّر website فارغة. استعمل التّعبير with context عند الاستيراد كما يلي، للتصريح برغبتك في تمرير السّياق : {% from 'url.html' import url with context %} التّعبير with context له تأثير مُعاكس لتأثير التّعبير without context، لذا فالمُتغيّر website سيُمرَّرُ إلى القالب url.html وبالتّالي فالماكرو url() يستطيع الوصول إلى قيمة المُتغيّر ومن ثمّ عرضه بصيغة رابط. ملحوظة: المثال أعلاه مُجرّد توضيح لفكرة السّياق عند الاستيراد، ومن الأفضل استعمال المُعاملات لنقل قيمة مُتغيّر إلى ماكرو مُعيّن عوضا عن تفعيل السّياق. الخلاصة أنّك تستطيع التّحكم في كيفيّة تعامل القوالب مع السّياق عند التضمين أو الاستيراد عبر استخدام كلّ من التّعبيرين with context و without context. الحالة المبدئية للتعليمة include هي كالتّالي: {% include 'url.html' with context %} والحالة االمبدئية للتعليمة import: {% import 'url.html' as url without context %} تجاهل تضمين القوالب إن لم تكن متواجدة أساسا سأعرض لك في هذه الفقرة بشكل وجيز تقنيتين من التّقنيات التّي ستُساعدك على تصميم قوالب HTML أفضل عند استخدام التعليمة include. أولا، يُمكنك منح مُحرّك القوالب Jinja إمكانيّة تضمين قالب فقط إن كان موجودا عبر تمرير قائمة من أسماء القوالب إلى التعليمة include كالتّالي: {% include ['page1.html', 'page2.html'] %} إذا كان القالب page1.html موجودا فسيكون للمثال أعلاه نفس تأثير ما يلي: {% include 'page1.html' %} سواء أكان القالب page2.html موجودا أم لا فهذا غير مُهمّ ما دام القالب page1.html أول قالب في القائمة وكان موجودا بالفعل داخل مُجلّد templates. أمّا في حالة لم يكن القالب page1.html موجودا داخل مُجلّد القوالب وكان القالب page2.html موجودا عوضا عنه، فهذا الأخير هو القالب الذي سيُضمّن. وفي حالة لم يكن أي قالب من القوالب المُوفَّرة موجودا، فستحصل على خطأ كالتّالي: jinja2.exceptions.TemplatesNotFound TemplatesNotFound: none of the templates given were found: page1.html, page2.html ولتجاهل هذا الخطأ وعرض مُحتويات القالب index.html رغم عدم وجود هذه القوالب المُضمَّنة، يُمكنك إضافة التّعبير ignore missing عند استعمال التعليمة include كالآتي: {% include ['page1.html', 'page2.html'] ignore missing %} التّعبير ignore missing ليس محصورا بقائمة قوالب فقط، بل تستطيع كذلك استخدامه عند تضمين قالب واحد فقط كما يلي: {% include "sidebar.html" ignore missing %} عند استعمال التّعبير، سيتحقّق مُحرّك القوالب Jinja من أنّ القالب sidebar.html موجود في مُجلّد القوالب، إن كان كذلك فعمليّة التّضمين ستكون ناجحة دون أخطاء، وإن لم يكن القالب موجودا فلن تحدث أية أخطاء وسيبقى مكان التعليمة include فارغا عند عرض النّتيجة. تنبيه: عند استخدام التّعبير ignore missing مع كلّ من التّعبيرين with context و without context في آن واحد، تأكّد من أنّ التّعبير ignore missing يأتي أولا. ما يعني بأنّ الأمثلة أسفله صحيحة: {% include ['page1.html', 'page2.html'] ignore missing with context %} {% include "sidebar.html" ignore missing with context %} {% include "sidebar.html" ignore missing without context %} والأمثلة التّاليّة خاطئة: {% include ['page1.html', 'page2.html'] with context ignore missing %} {% include "sidebar.html" with context ignore missing %} {% include "sidebar.html" without context ignore missing %} خاتمة تعرّفنا في هذا الدّرس على كيفيّة عمل السّياق في Jinja عند التّضمين والاستيراد، بعد قراءتك لهذا الدّرس، يجب أن تكون قادرا على إدارة السّياق باحترافيّة وتتجنّب المشاكل التي تقع في حالة إساءة فهم كيفيّة عمل السّياق، في الدّرس التّالي، سنتعرّف على تقنيات متقدّمة للتّعامل مع حلقات for من أجل تحسين تطبيقاتك وتكتب شفرة أكثر فعاليّة عند تطوير تطبيقات الويب باستعمال إطار العمل Flask أو أي إطار عمل يعتمد على Jinja.
-
مُقدّمة تعرّفنا في الدّرس السّابق على التعليمة include وكيفيّة استخدامها في مُحرّك Jinja لتضمين قالب HTML داخل قالب آخر، في هذا الدّرس سنتعرّف على ميّزة أخرى ستُساعدك على تفادي تكرار الشّيفرة، إذ سنتعرّف على تعليمة import لاستيراد مكوّن ما من قالب واستعماله في قالب آخر. ما الغرض من الاستيراد؟ تعرّفنا مُسبقا على خاصّية الماكرو في Jinja، وقلنا بأنّها مُفيدة لتفادي تكرار الشّفرة وأنّها تُستخدم كما تُستخدم الدّوال في لغة بايثون، لكنّنا لم نتطرّق بالتّفصيل إلى جزئيّة مُهمّة، ألا وهي كيفيّة جمع عدّة دوال في قالب واحد ليعمل مثل الوحدة (Module) في بايثون، بحيث يشمل عدّة دوال يُمكنك استيرادها بسطر واحد واستخدامها في أكثر من قالب واحد عوضا عن كتابة الماكرو أعلى الصّفحة. يُمكنك كذلك استخدام قالب HTML مُعيّن كمخزن لإعدادات تطبيق، مثل اسم التّطبيق والمُعاملات التّي يُمكنك بها التّحكّم في جزئيّات مُعيّنة ممّا يُعرض على مُتصفّح المُستخدم، ومن ثمّ يُمكنك استيراد المُعاملات من هذا القالب واستغلالها في بقيّة القوالب كما تشاء لتوفير ديناميكيّة أفضل لتطبيقك. التعليمة import لاستيراد مُتغيّر أو ماكرو من قالب مُعيّن كتابة شيفرة نماذج HTML أمر مُملّ، لذا قد ترغب في كتابة بعض الدّوال لتوليد النّماذج بشكل سريع اعتمادا على مُعاملات مُعيّنة. لنفترض بأنّ لدينا ما يلي في مُجلّد templates الخاصّ بتطبيق Flask: ملفّ base.html . ملفّ index.html للصّفحة الرّئيسية. ملفّ باسم forms.html. سيحتوي ملفّ base.html على شفرة HTML بسيطة: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"> <title>{% block title %}{% endblock %}</title> </head> <body> {% block body %} {% endblock %} </body> </html> أمّا ملفّ index.html فسيرث من الملفّ السّابق بالإضافة إلى عنوان داخل وسم <h1> لطلب تسجيل الدّخول من المُستخدم: {% extends 'base.html' %} {% block title %} My Application {% endblock %} {% block body %} <div class="container"> <h1>Please Login</h1> </div> {% endblock %} أما forms.html فسيحتوي على ماكرو خاصّ بعرض النّماذج من نوع input و ماكرو آخر لعرض نموذج لمساحة النّص (TextArea): {% macro input(name, value='', type='text', placeholder='') %} <input class="form-control" type="{{ type }}" value="{{ value|e }}" name="{{ name }}" placeholder="{{ placeholder }}"> {% endmacro %} {% macro textarea(name, value='', rows=10, cols=40, placeholder='') %} <textarea class="form-control" name="{{ name }}" rows="{{ rows }}" cols="{{ cols }}" placeholder="{{ placeholder }}">{{ value|e }}</textarea> {% endmacro %} ينشئ الماكرو الأول حقل إدخال من نوع input، يقبل الماكرو ثلاثة مُعاملات: name لاسم الحقل. value لقيمة الحقل المبدئية. type لنوع الحقل (نصّ عادي، كلمة مرور، …إلخ). placeholder للنّص المبدئي. تُستعمل هذه المُعاملات إن وفّرها المُطوّر للتّحكم بكيفيّة توليد الحقل، فمثلا القيمة المبدئية للحقل type هي text ما يعني بأنّك إن لم تُمرّر قيمةً لهذا المُعامل فسيُولّد الماكرو حقلا نصّيا عاديّا، ولو غيّرت القيمة مثلا إلى password فسيكون الحقل المُولّد عبارة عن حقل كلمة مرور. قد تُلاحظ بأنّنا نستخدم المُرشّح e الذي هو اختصار لكلمة escape والذي يقوم بحماية الحقل من الهجمات الخبيثة عبر منع ترجمة شفرات HTML وJavascript. أمّا بالنّسبة للماكرو textarea فهو مُشابه لما سبق، إلّا أنّه يُولّد وسما من النّوع textareaالذي يُعبّر عن حقل لنصّ مُتعدّد الأسطر، كما أنّه يقبل كلّا من المُعاملين rows و cols لتحديد حجم مساحة النّص التي ستظهر في صفحة HTML. استعمال التعليمة import لاستيراد الوحدة forms.html بعد أن أنشأنا قاعدة لمثالنا، وبعد أن كتبنا دوال خاصّة بتوليد حقول الإدخال وجمعها في ملفّ forms.html لتُمثّل وحدة كما في وحدات Python، لنستعملها في ملفّ index.html. أولا، ضع سطر الاستيراد في رأس صفحة index.html: {% import 'forms.html' as forms %} لاحظ بأنّنا استعملنا الكلمة المفتاحية as لإسناد اسم مُستعار للوحدة المُستوردة بحيث نتمكّن من الوصول إليها عبر الاسم forms. بعد استيراد الوحدة، يُمكنك استدعاء الدّوال المُتواجدة بداخلها كما يلي: forms.input() forms.textarea() لكن ستحتاج إلى تمرير مُعاملات حسب نوع الحقل الذي ترغب بتوليده. لنُضف الآن بضعة حقول إلى ملفّ index.html باستخدام الوحدة التّي قمنا باستيرادها: {{ forms.input('username', placeholder='Type in your username') }} <br> {{ forms.input('password', placeholder='Type in your password', type='password') }} <br> {{ forms.textarea('message', placeholder='Write your message here.') }} <br> <button class="btn btn-primary">Submit</button> في الشّيفرة أعلاه، نستعمل الماكرو forms.input لإنشاء حقل نصي لاسم المُستخدم، ونستخدمه مُجدّدا لإنشاء حقل لكلمة المرور الذي حدّدنا نوعه بالقيمة password، ثمّ بعد ذلك أنشأنا حقلا لمساحة النّص مع تعيين القيمة message كاسم للحقل، وفي آخر سطر أضفنا زرّا صغيرا لإرسال مُحتويات النّموذج. بعد إضافة الشّفرة السّابقة، ستكون مُحتويات الملفّ index.html كما يلي: {% import 'forms.html' as forms %} {% extends 'base.html' %} {% block title %} My Application {% endblock %} {% block body %} <div class="container"> <h1>Please Login</h1> {{ forms.input('username', placeholder='Type in your username') }} <br> {{ forms.input('password', placeholder='Type in your password', type='password') }} <br> {{ forms.textarea('message', placeholder='Write your message here.') }} <br> <button class="btn btn-primary">Submit</button> </div> {% endblock %} والصّفحة ستكون مُشابهة للمثال في الصّورة التّالية: مُلاحظة: الغرض من هذه الأمثلة هو شرح مبدأ الاستيراد وكيفية عمله في مُحرّك القوالب Jinja. إن أردتَ إدارة نماذج HTML على نحو أفضل وأكثر أمانا، فمن الأفضل استخدام مكتبة WTForms. استيراد مكوّن مُعيّن باستخدام التّعبير from import بالإضافة إلى إمكانيّة استيراد وحدة واستعمال الدّوال المتواجدة فيها كما يلي: import module module.macro() يُمكنك كذلك استخدام التّعبير from import لاستيراد ماكرو واحد فقط كما في المثال التّالي: {% from 'forms.html' import input %} هكذا ستتمكّن من استعمال الماكرو input عبر استدعائه، لكنّك لن تستطيع الوصول إلى الماكرو textarea حتى تستدعيه على نحو صريح. لاستدعاء أكثر من ماكرو واحد، افصل بينها بفاصلة كالتّالي: {% from 'forms.html' import input, textarea %} يُمكنك كذلك استعمال الكلمة المفتاحية as لمنح اسم مُستعار لكلّ ماكرو: {% from 'forms.html' import input as i, textarea as t %} بعد استيراد الماكرو باسم مُستعار، يُمكنك استخدام هذا الاسم المُستعار كما يلي: {{ i() }} {{ t() }} خاتمة بنهاية هذا الدّرس، يجب أن تكون قادرا على تصميم قوالب HTML بمرونة أكثر، بحيث تتمكّن من استغلال الجملة import لتفادي تكرار الشّيفرة والحصول على تجربة تطوير أفضل.
-
مُقدّمة تكرار الشّيفرة من أسوء المُمارسات التّي يُمكن لأي مُطوّر القيام بها، إذ تستهلك الكثير من الوقت الثّمين الّذي يُمكن صرفه في تطوير ميّزات أخرى وأمور أكثر أهميّة؛ سنتعرّف في هذا الدّرس على كيفيّة استغلال مُحرّك القوالب Jinja لتفادي تكرار الشّيفرة وتقسيم ملفّ HTML كبير إلى عدّة ملفّات صغيرة يُمكن إعادة استعمالها عبر الاعتماد على التعليمة include في Jinja. التعليمة include لتضمين قالب HTML داخل آخر تُستعمل التعليمة include لتضمين مُحتويات ملفّ HTML في مكان ما في قالب HTML آخر. على سبيل المثال، لنقل بأنّ لدينا خمسة قوالب HTML في مجلّد templates الخاصّ بنا كما يلي: ملفّ باسم base.html ملفّ باسم header.html ملفّ آخر باسم content.html ملفّ آخر باسم footer.html الملفّ الرّئيسي باسم main.html هكذا ستكون الصّورة العامّة للملفّات داخل المُجلّد templates كما يلي: └── templates ├── body.html ├── footer.html ├── header.html └── main.html └── base.html بالنّسبة للقالب base.html فهو معروف وطريقة استعماله واضحة، إذ يُعتبر القاعدة الأساسيّة لصفحات HTML التّي تعتمد عليه، ومُكوّناته ستكون كالتّالي: <!DOCTYPE html> <html lang="en"> <head> <link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"> <meta charset="UTF-8"> <title>{% block title %}{% endblock %}</title> </head> <body> {% block body %} {% endblock %} </body> </html> كما تُلاحظ هنا فإنّنا نقوم بوضع شِفرة HTML التّي تتكرّر في كلّ صفحة وفيها جزءان ديناميكيّان، الجزء الأول يُعنى بعنوان الصّفحة ويوجد داخل الوسم <title> وأمّا الجزء الثّاني فمسؤول عن الظّهور داخل جسم الصّفحة بالوسم <body>، وسنرى كيفيّة استغلال هذا القالب بعد لحظات. لاحظ كذلك بأنّنا أضفنا ملفّ CSS الخاص بإطار العمل Bootstrap في وسم <link> في الشّفرة أعلاه. أمّا عن قالب header.html فسيحمل شِفرة HTML التّي تكون عادة داخل الوسم <header> والتّي تكون مسؤولة عن الجزء العلوي من صفحة HTML كشريط التّصفّح وشعار التّطبيق وغير ذلك من المُكونات التّي تكون أعلى صفحات الويب. شيفرة هذا الملفّ ستكون عادة مُشابهة لما يلي: <header> <div class="navbar navbar-default"> <div class="container-fluid"> <ul class="nav navbar-nav"> <li><a class="navbar-brand" href="#"> Home </a></li> </ul> <ul class="nav navbar-nav navbar-right"> <li><a href="/about/"> <span class="glyphicon glyphicon-info-sign"></span> About </a></li> <li><a href="/contact/"> <span class="glyphicon glyphicon-envelope"></span> Contact </a></li> </ul> </div> </div> </header> إن لم تفهم الشّفرة أعلاه فلا تقلق، إذ سيتّضح كلّ شيء بعد أن نجمع الملفّات بعضها مع بعض. لكن عليك أن تفهم فقط بأنّ الشّفرة بالأعلى عبارة عن شفرة HTML مع تنسيقات من إطار العمل Bootstrap تُمثّل شريط تصفّح به ثلاثة أزرار، الزّر Home للرّبط بالصّفحة الرّئيسية، والزّر About لصفحة “عن التّطبيق” والزّر Contact لصفحة التّواصل. أمّا ملفّ footer.html فسيحمل الشّفرة التّي تكون عادة أسفل صفحات الوِب مثل حقوق المُحتوى ومعلومات عن الشّركة صاحبة الموقع أو عن فريق التّطوير الّذي ساهم في بناء التّطبيق، بالإضافة إلى إمكانيّة احتوائه على معلومات التّواصل مع أصحاب التّطبيق أو معلومات أخرى، وقد تُلاحظ أسفل هذا الموقع (موقع أكاديميّة حسوب)، جزءا خاصّا بمُنتجات شركة حسوب وقد تُلاحظ ذلك في الصّفحات الأخرى التّابعة لمُنتجات حسوب، وهذا ما أقصده بالضّبط. كمثال على مُحتوى هذا الملفّ، سنستعمل الشِّفرة التّاليّة لعرض حقوق الموقع واسم المُطوّر: <footer class="footer"> <div class="container"> <p>Built by Abdelhadi Dyouri</p> <p class="text-muted">All rights reserved.</p> </div> </footer> بالنّسبة للملفّ content.html، فسيكون المسؤول عمّا بداخل الوسم <div> ذي خاصيّة CSS المسمّاة container التّي يُوفّرها إطار العمل Bootstrap. سيشمل هذا الجزء المُكونات التي تُمثّل المُحتوى العام للموقع، فمثلا إن كان الموقع معنيّا بنشر المقالات، فالمُحتوى الّذي سيكون بداخل هذا الوسم هو لائحة المقالات وإن كانت الصّفحة خاصّة بعرض مقال واحد، فعنوان هذا المقال ومُحتواه وكاتبه والمعلومات العامّة حول المقال ستكون داخل هذا الوسم. ما يُميّز هذا الجزء من صفحات HTML هو أنّه يكون مُتغيّرا عكس كلّ من رأس الصّفحة وأسفلها، إذ يبقى مثلا شريط التّصفّح هو نفسه في جميع صفحات التّطبيق وكذلك مُكوّنات أسفل الصّفحة كالحقوق والمعلومات حول التّطبيق، إذ يُمكن للمقالات في وسط الصّفحة أن تتبدّل وأن يتغيّر نوع المحتوى من مقال إلى صفحة لاستعراض المعلومات الشّخصيّة للكاتب وما إلى ذلك. على سبيل المثال، سنستعمل المثال الّذي سبق وأن شرحناه في درس الدّالتين range وlipsum لملْء مُحتوى هذا الملفّ: <div class='container'> {% for i in range(8) %} <div class='col-md-3'> <h2> <a href='#'> {{ lipsum(1, min=3, max=5) }} </a> </h2> {{ lipsum(1, max=50) }} <button class='btn btn-default'>Read more...</button> </div> {% endfor %} </div> إن لم تقرأ الدّرس من قبل، فلا تقلق، المهم أن تفهم بأنّ الشّفرة أعلاه تُنتج ثمانيّة فقرات مع عنوان باستعمال نصّ lorem ipsum، ما يُتيح لنا الحصول على مُحتوى يُشبه المقالات دون كتابة شيفرة HTML كبيرة ومُكرّرة مكان استعمال دوال في Jinja للقيام بالأمر بكلّ بساطة. بالنّسبة للملفّ main.html، فهو الملفّ الرّئيسي الّذي سنستعمله لجمع هذه الملفّات لتظهر على شكل صفحة واحدة. الشّيء الّذي سنقوم به في الفقرة التّالية. جمع ملفّات HTML لتظهر في صفحة واحدة باستخدام التعليمة include بعد أن أصبح لدينا كلّ من الجزء العلوي للموقع في ملفّ header.html ومُحتوى الموقع في الملفّ content.html والجزء السّفلي للصّفحة داخل ملفّ footer.html، حان الوقت لجمع كلّ هذه المُكونات في ملفّ واحد لتجتمع في صفحة واحدة. سنستخدم الملفّ main.html لجمع جميع هذه المُكوّنات وسنستعمل التعليمة include كما شرحنا مُسبقا: {% extends 'base.html' %} {% block title %} My Application {% endblock %} {% block body %} {% include 'header.html' %} {% include 'content.html' %} {% include 'footer.html' %} {% endblock %} كما تُلاحظ، فقد استعملنا التعليمة extends للوراثة من الملفّ base.html الذي يُعتبر قاعدة الصّفحة، بعدها حدّدنا عنوانا للصّفحة عن طريق السّطر {% block title %} My Application {% endblock %}، بعدها حدّدنا جسم الصّفحة داخل الجزء body ومن ثمّ استعملنا التعليمة include لتضمين كلّ من الملفّات header.html، content.html وfooter.html داخل الصّفحة، لكن لاحظ بأنّ التّرتيب مُهمّ وهو نفس التّرتيب المنطقي الذي يجب اتّباعه، بحيث يكون تضمين الجزء العلوي أولا ثمّ الجزء المسؤول عن المُحتوى ثمّ الجزء السّفلي للصّفحة. يُمكنك أن تتخيّل بأنّ الجملة include غير مُتواجدة ويتواجد في مكانها كلّ الشّيفرة المتواجدة داخل ملفّ HTML المُضمّن، فعوضا عن السّطر {% include 'header.html' %} ستكون شيفرة الملفّ header.html وهكذا… بعد إنهاء تطبيق ما سبق، ستُلاحظ نتيجة مُشابهة لما في الصّورة التّاليّة: متى تستعمل التعليمة include ولماذا استعمال التعليمة include مُفيد لأنّه يُعطيك حرّية أكثر في العمل على أجزاء مُختلفة من صفحات الويب في تطبيقك، إذ يُمكنك تطوير الجزء السّفلي من الصّفحة دون الاضطرار إلى المرور بالشّفرة المسؤولة عن الأجزاء الأخرى، ما يُتيح لك تجربة تطوير أفضل، بالإضافة إلى أنّ تقسيم أجزاء الصّفحات إلى ملفّات HTML مُستقلّة أمر مُفيد إن كنت تعمل مع أحدهم على تطوير نفس التّطبيق أو حتّى فريق مُكوّن من أكثر من شخصين، إذ يُمكنك مثلا العمل على الجزء العلوي من الصّفحة وتترك صديقك يعمل على مُحتويات الصّفحة وصديق آخر يعمل على الجزء السّفلي بحيث لا يُعدّل الجميع نفس الملفّ في نفس الوقت. بالإضافة إلى أنّ التعليمة include تُساعد على تقسيم ملفّ HTML إلى عدّة ملفّات صغيرة لتسهيل عمليّة التّطوير، يُمكن كذلك أن تستفيد من هذه الملفّات عبر إعادة استعمالها في أكثر من ملفّ HTML لتفادي تكرار الشّيفرة واختصار الوقت. خاتمة تعرّفنا في هذا الدّرس على كيفيّة تقسيم صفحة HTML إلى عدّة ملفّات وكيفيّة جمعها داخل ملفّ واحد باستعمال الجملة include لتوفير تجربة تطوير أفضل، في الدّرس القادم، سنلقي نظرة على مبدأ الاستيرادات في مُحرّك القوالب Jinja وكيفيّة الاستفادة منه.
-
تمهيد إلى الآن، تعرّفنا على العديد من أساسيّات تطوير الويب باستعمال كلّ من إطار العمل Flask ومُحرّك القوالب Jinja، وفي الدّرس السّابق، تعرّفنا على ماهيّة الدّوال المُسبقة التّعريف في هذا الأخير، إذ تعرّفنا كبداية على كلّ من الدّالة range() والدّالة lipsum، وسنُكمل في هذا الدّرس ما بدأناه لنتعرّف على بقيّة الدّوال والأصناف المبنيّة مُسبقا في مُحرّك القوالب Jinja ليُساعدك ذلك على تطوير تطبيقات وِب أفضل. الدّالة dict في لغة بايثون، هناك نوع خاصّ من القيم يُسمّى القواميس Dictinaries، في كلّ قاموس نجد مفاتيح وقيّما لتُساعدنا على ترتيب البيانات وتسهيل البحث عن قيمة ما عبر مفتاحها، كما نستطيع الوصول إلى كلّ من القيم والمفاتيح باستعمال حلقة for بسيطة. في مُحرّك القوالب Jinja، القواميس مُشابهة لقواميس بايثون، إذ يُمكنك تعريف قاموس كما يلي: {% set dictionary = {'1': 'One', '2': 'Two'} %} في المثال أعلاه، كلّ من 1 و 2 عبارة عن مفاتيح، أمّا One و Two فهي قيم المفاتيح، بحيث One قيمة المفتاح 1 و Two قيمة المفتاح 2. للحصول على القيم في القاموس، يُمكن استخدام اسم المُتغيّر الذي يحمل القاموس مع المفاتيح كما يلي: {{ dictionary['1'] }} <br> {{ dictionary['2'] }} النّتيجة: One Two مُلاحظة: في المثال أعلاه، قُمنا باستخدام الكلمة المفتاحيّة set لتعريف مُتغيّر يحمل قاموسا كقيمة، لكن يُمكن كذلك أن تُمرّر مُتغيّرا يحمل قاموسا من ملفّات التّحكم – وهو الجزء الّذي يهتمّ به إطار العمل Flask - إلى قوالب HTML، لذا ففي هذه الحالة ستتمكّن من الوصول إلى القاموس مُباشرة من المُتغيّر دون تعريفه على نحو صريح باستعمال مُحرّك القوالب Jinja. يُمكنك في مُحرّك القوالب Jinja إنشاء قواميس بطريقة أفضل عبر الاستعانة بالدّالة dict()، إذ يُمكنك استعمال الدّالة لإنشاء قاموس تكون مفاتيحه هي أسماء المُعاملات وقيمة المُعامل هي نفسها قيمة المفتاح. التّالي مثال على كيفيّة استخدام الدّالة dict() لإنشاء قاموس بسيط: dict(key='value', another_key='another value') المثال أعلاه سيُنتج قاموسا كما يلي: {'another_key': 'another value', 'key': 'value'} تُستخدم القواميس عندما يكون لدينا نوع من القيم المُترابطة والتّي لها أصل واحد، فمثلا لنقل بأنّ القاموس يُمثّل بطاقة تعريف أحدهم، في القاموس سيتوجّب علينا تخزين الاسم الأوّل للشّخص، والاسم العائلي أو نسبُ نفس الشّخص. المثال التّالي توضيح على كيفيّة استخدام الدّالة dict لإنشاء قاموس يعمل على تخزين الاسم الكامل لشخص ما وعرض جملة ترحيبيّة مُخصّصة له: {% set dictionary = dict(firstname='أحمد', lastname='علي') %} مرحبا يا {{ dictionary['firstname'] }} {{ dictionary['lastname'] }}! النّتيجة ستكون الجملة التّاليّة: مرحبا يا أحمد علي! وبالطّبع فالنّتيجة ستتغيّر كلّما غيّرت أحد قيمتي المُعاملين firstname وlastname أثناء استدعاء الدّالة dict. من الأمور المُميّزة في الدّالة dict أنّها تُعطيك الحرّية في اختيار كيفيّة الوصول إلى قيمة كلّ مفتاح. سابقا، استعملنا الطّريقة التّقليديّة للوصول إلى قيم القاموس كما يلي: dictionary['firstname'] dictionary['lastname'] لكن يُمكننا كذلك الوصول إلى القيم بطريقة أسرع كما يلي: dictionary.firstname dictionary.lastname بهذه الطّريقة يُمكنك اعتبار المفاتيح خصائص كما في خصائص الأصناف في لغة بايثون، ما يعني بأنّك تستطيع استخدام المُرشّحات والاختبارات التّي تسمح لك باختبار قيم الخصائص مُباشرة. بالتّالي فالمثال السّابق سيكون كما يلي: مرحبا يا {{ dictionary.firstname }} {{ dictionary.lastname }}! ستُلاحظ بأنّ النّتيجة هي نفسها وبأنّ هذا المثال الأخير أفضل من المثال السّابق لأنّك تستطيع اختصار بعض الوقت عند الوصول إلى قيم المفاتيح بهذه الطّريقة. الصّنف cycler يُمكّنك الصّنف cycler من الدّوران حول مجموعة من العناصر بشكل لا نهائي، مع إمكانيّة إعادة الدّوران في وقت مُحدّد إن أردت ذلك. على سبيل المثال، لنقل بأنّك تُريد الدّوران حول قائمة من الأسماء بشكل لا نهائي بحيث تُعاد الدّورة في كلّ مرّة. لنضع الشّيفرة التّالية كمثال لكيفيّة استعمال الصّنف cycler: {% set names = cycler('Ahmed', 'Ali', 'Hassan') %} في هذا المثال، أنشأنا كائنا من الصّنف cycler وأسندناه إلى المُتغيّر names. يُمكننا الآن استخدام الكائن names للدّوران حول قائمة الأسماء التّي مرّرناها إلى الصّنف cycler. من المُهم أن نفهم بأنّ الكائن names سيحمل قيمة واحدة فقط في كلّ مرّة نستدعيه فيه باستخدام أحد توابع أو خصائص الصّنف cycler، إذ يُمكننا استخدام الخاصيّة current للحصول على القيمة الحاليّة، والتّابع next() للوصول إلى القيمة التّاليّة، بالإضافة إلى التّابع reset() لإعادة الدّوران من جديد والوصول إلى أول قيمة. القيمة التي سيحملها الكائن names عند استعمال أحد التّوابع ستكون أحد الأسماء في مجموعة الأسماء التّي حدّدناها من قبل فقط ولا يُمكن أن تكون قيمة أخرى. الوصول إلى قيمة الدّوران الحاليّة إن اعتبرنا المثال السّابق، فإنّنا نستطيع الوصول إلى قيمة الدّوران الحاليّة كما يلي: {% set names = cycler('Ahmed', 'Ali', 'Hassan') %} {{ names.current }} وبما أنّنا لم نقم بأي انتقال من قيمة إلى الّتي تليها عبر التّابع next()، فالقيمة البدئيّة ستكون أول عنصر من مجموعة العناصر المُمرّرة إلى الصّنف cycler، أي الاسم Ahmed في هذه الحالة. الوصول إلى قيمة الدّوران التّاليّة للوصول إلى القيمة التّاليّة وعرضها، يُمكنك استخدام التّابع next() على الكائن names كما يلي: {{ names.next() }} ستُلاحظ بأنّ النّتيجة هي نفسها النّتيجة التّي تظهر عند استخدام الخاصيّة current أي أنّ القيمة التّي ظهرت هي العنصر الأول من قائمة العناصر. هذا لأنّه يتوجّب علينا استخدام التّابع next() أكثر من مرّة ليظهر مفعوله: {{ names.next() }} <br> {{ names.next() }} <br> {{ names.next() }} <br> {{ names.next() }} <br> {{ names.next() }} <br> استخدمنا في المثال أعلاه التّابع next() خمس مرّات، والنّتيجة كما المُتوقّع ستكون كالتّالي: Ahmed Ali Hassan Ahmed Ali كما تُلاحظ، عندما وصلت القيمة إلى آخر عنصر (أي الاسم Hassan)، فقد عادت الدّورة إلى البداية من جديد لتستمرّ في الدّوران حول القيم، هذا يعني بأنّك تستطيع استخدام التّابع next() لعدد لا نهائي من المرّات مع أي مجموعة من العناصر. استخدام الصّنف cycler مع قائمة بايثون بما أنّ الصّنف cycler يعمل مع قيمٍ عدّة، فمن الطّبيعي أن ترغب في استخدامه مع قائمة عاديّة تحتوي على مجموعة من العناصر. لكن إن مرّرت قائمة إلى الصّنف cylcer كما يلي: set names = cycler(['Ahmed', 'Ali', 'Hassan']) فسيعتبر الصّنف هذه القائمة المُمرّرة مُجرّد قيمة واحدة، ولو استعملت مثلا التّابع next() على القائمة مرّتين فستحصل على النّتيجة التّالية: ['Ahmed', 'Ali', 'Hassan'] ['Ahmed', 'Ali', 'Hassan'] هذا يعني بأنّ الصّنف cycler لا يعتبر القائمة مجموعة من العناصر كما يجب. لحلّ هذه المُشكلة، استعمل الرّمز * قبل القائمة لتفكيكها وتمرير كلّ عنصر منها على شكل مُعامل كما يلي: set names = cycler(*['Ahmed', 'Ali', 'Hassan']) هذه الطّريقة تعمل حتى لو كانت القائمة مُسندة إلى مُتغيّر ما كذلك، على سبيل المثال إن كانت لديك قائمة باسم list وأردت تمرير عناصرها إلى الصّنف cycler فيُمكنك ذلك كما يلي: cycler(*list) خاتمة تعرّفنا في هذا الدّرس على كلّ من الدّالة dict والصّنف cycler لمُساعدتك على بناء صفحات HTML أفضل عند استعمال كلّ من إطار العمل Flask ومُحرّك القوالب Jinja، في الدّرس القادم سنتعرّف على جزء آخر لا يقلّ أهميّة عمّا درسناه في الدّروس السّابقة، ألا وهو كيفيّة التّعامل مع أكثر من ملفّ عبر تضمينها بعضها ببعض لتفادي تكرار الشّيفرة البرمجيّة.
-
مُقدّمة بعد أن تعرّفنا على المُرشّحات والاختبارات في مُحرّك القوالب Jinja، بقي أن نتعرّف على الدّوال التّي يُمكنك استعمالها لتسهيل عمليّة تطوير قوالب HTML، هذه الدّوال مُشابهة للدّوال التّي يُمكنك استخدامها في لغة بايثون، ما يعني بأنّك تستطيع استخدامها كما تُستخدم في لغة بايثون، أي باستدعائها وتمرير مُعاملات إليها إن كان ذلك مُمكنا. في هذا الدّرس، سنتعرّف على كلّ من الدّالة range والدّالة lipsum اللّتان تُعتبران دالّتين مُهمّتين لكل مُصمّم قوالب ولمُطوّري الويب الّذين يستخدمون إطار العمل Flask خصّيصا. ولتطبيق الأمثلة المذكورة في هذا الدّرس، يُمكنك الاستعانة بملفّ filters.html الّذي أنشأناه سابقا في بداية هذه السّلسة للتّحقق من نتائج أمثلة المُرشّحات. إن لم تتّبع سلسلة الدّروس منذ البداية، فلا مُشكلة، إذ يُمكنك إنشاء تطبيق بسيط بإطار العمل Flask بحيث يحتوي على موجّه واحد على الأقل يُقدّم صفحة HTML لتستطيع التّعامل معها عبر مُحرّك القوالب Jinja الّذي يُعتبر الأداة الافتراضيّة لإدارة قوالب HTML في تطبيقات الويب المبنيّة بإطار العمل Flask. بعد تجهيز قالب لنختبر به الأمثلة، لننتقل الآن إلى التّعرف على الدّوال المبنيّة مُسبقا في مُحرّك القوالب Jinja. الدّالة range الدّالة range() في Jinja تعمل بشكل مُشابه لطريقة عمل الدّالة التّي تحمل نفس الاسم في لغة Python. إذ تُساعدك على إنشاء قائمة أعداد حسب المُعاملات. افتراضيّا، الدّالة range تعمل إذا ما مرّرت إليها قيمة واحدة كمُعامل عددي، والنّتيجة تكون قائمة بالأعداد من 0 إلى العدد ما قبل العدد الذي مُرِّرَ كمُعامل. مثلا، استدعاء الدّالة range مع تمرير العدد 10 كمُعامل سيكون كما يلي: range(10) نتيجة المثال ستكون كما يلي: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] لاحظ بأنّ النّتيجة عبارة عن قائمة تحتوي على 10 عناصر من الصّفر إلى تسعة دون تضمين العدد 10 الذي يُعتبر المُعامل المُستخدم. مُلاحظة: إن طبّقت المثال أعلاه في ملفّ HTML فلن تظهر النّتيجة حتّى تُطبّق المُرشّح list كما يلي: {{ range(10) | list }} لكن عموما تستطيع استخدام حلقة for مع نتيجة الدّالة مُباشرة دون استخدام المُرشّح list. تحديد القيمة البدئيّة عندما نستعمل الدّالة range في ملفّات HTML مع تمرير مُعامل واحد فقط، فالقيمة البدئيّة تكون دائما العدد 0، لكن يُمكنك تغيير هذا الوضع بتمرير مُعامل قبل مُعامل القيمة القُصوى ليبدأ العدّ من هذه القيمة الجديدة إلى حين الوصول إلى العدد ما قبل القيمة القُصوى. كمثال على ذلك، لنعتبر بأنّنا نحتاج إلى قائمة تبدأ من العدد 5 وتنتهي بالعدد 15، في هذه الحالة سيتوجّب علينا استخدام الدّالة range كما يلي: {{ range(5, 15) | list }} هكذا ستكون النّتيجة كما يلي: [5, 6, 7, 8, 9, 10, 11, 12, 13, 14] كما تُلاحظ، فقد انتهت القائمة بالعدد 14 وليس بالعدد 15 كما قد تتوقّع، لذا لو أردت تضمين العدد 15 في القائمة فسيتوجّب عليك استخدام العدد 16 في المقام الأول كما يلي: range(5, 16) النّتيجة: [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] تحديد قيمة الزّيادة افتراضيّا، قيمة الزّيادة هي العدد 1، إذ يكون كلّ عنصر أكبر من العدد الذّي يسبقه في القائمة بواحد، وإن أردت أن تُغيّر قيمة الزّيادة فيُمكنك تمريرها كمُعامل ثالث. على سبيل المثال، لو أردنا الحصول على العشرات التّي تحتوي على رقمين فقط (20, 50 …) فسيُمكننا أن نقوم بتوليد قائمة عناصرها تبدأ من العدد 10 إلى العدد 100 بزيادة قيمتها العدد 10، ويُمكننا القيام بالأمر كما يلي: range(10, 100, 10) النّتيجة ستكون جميع العشرات من 10 إلى 90 في قائمة كالتّالي: [10, 20, 30, 40, 50, 60, 70, 80, 90] يُمكن لقيمة الزّيادة أن تكون سلبيّة كذلك، ما يسمح لك بالحصول على قائمة عكسيّة أو عدّ تنازلي. فمثلا، يُمكننا الحصول على قائمة الأعداد من 10 إلى 1 كما يلي: range(10, 0, -1) هنا نضع العدد 10 كقيمة بدئيّة والعدد 0 كقيمة قُصوى لأنّ آخر عدد قبل الصّفر هو العدد 1، وفي قيمة الزّيادة نضع العدد السّلبي -1. النّتيجة: [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] يُمكنك تغيير قيمة الزّيادة إلى أي عدد سلبي آخر حسب ظروف الاستعمال. أمثلة على استخدام الدّالة range في تطبيقات إطار العمل Flask بعد أن تعرّفنا على الهدف من الدّالة range وكيفيّة استعمالها في مُختلف الحالات مع عدّة مُعاملات، لننتقل الآن إلى أمثلة واقعيّة لكيفيّة استخدام هذه الدّالة في تطوير تطبيقات الويب. المثال الأول، تكرار نصّ ما عدّة مرّات في تطبيقات الويب، قد ترغب بتكرار نصّ ما عدّة مرّات لتوضيح فكرة أو للحصول على صورة عامّة للصّفحة مع مُحتوى وهمي. يُمكنك استخدام الدّالة range() لتكرار نصّ ما أو مقطع HTML عددا من المرّات حسب العدد المُمرّر كمُعامل، فمثلا، يُمكننا تكرار المقطع “مرحبا بالعالم!” 10 مرّات باستعمال الشّيفرة التّالية: {% for number in range(10) %} <p>مرحبا بالعالم!</p> {% endfor %} لاحظ بأنّنا قد تعاملنا مع الدّالة range كقائمة عاديّة دون الحاجة إلى استعمال المُرشّح list على نحو صريح. المثال الثّاني، توليد أرقام الصّفحات لنقل بأنّنا نمتلك العديد من السّجلات في قاعدة بياناتنا ونرغب بعرضها على شكل صَفحات، بالإضافة إلى أنّنا نريد توفير طريقة بسيطة لنُمكّن المُستخدم من التّنقل عبر الصّفحات بِحُرّيَة. يُمكننا في هذه الحالة أن نُنشئ شريطا لأرقام الصّفحات ونربط كلّ رقم برابط الصّفحة التّابعة له. التّالي مثال بسيط على كيفيّة إنشاء شريط تصفّح يُغطّي أرقام الصّفحات من 1 إلى 9: {% for number in range(10) %} <span> {{ number }} </span> {% endfor %} النّتيجة: 0 1 2 3 4 5 6 7 8 9 في المثال لم نقم سوى بعرض كل رقم داخل وسم <span> وإن أردت تطوير هذا المثال أكثر، فسيلزمك استخدام الدّالة url_for التي يوفّرها إطار العمل Flask لربط كلّ رقم بالصّفحة الخاصّة به. الدّالة lipsum إن كانت لك خلفيّة في التّصميم فلا بدّ أن تكون قد سمعت عن نصّ lorem ipsum، إن كان الأمر كذلك، فالدّالة lipsum() ستُساعدك على توليد مقاطع عشوائيّة من هذا النّص لتستعملها في ملفّات HTML الخاصّة بك. إن لم تسمع من قبل عن lorem ipsum، فهو ببساطة نصّ لاتيني يُستعمل في التّصاميم الطّباعيّة كمثال على نصّ صحيح، وذلك لكي تتوضّح الصّورة لدى المُصمّم ومن يرى التّصميم، وقد أصبح يُستعمل كثيرا في تصاميم الويب لتأديّة نفس الواجب، بحيث يُستعمل على شكل فقرات وعناوين في ملفّات HTML لتوضيح التّصميم والحصول على فكرة عن الصّورة العامّة للتّصميم عندما تكون النّصوص مُتوفّرة، فمثلا إن كنت تُصمّم صفحة ويب لعرض مقالات ما، فستنقصك المقالات لتتوضّح صورة التّصميم بالنّسبة إليك، لذا فاللجوء إلى توليد هذا النّص العشوائي أسرع طريقة للحصول على تصميم يحتوي على مقالات وهميّة. طريقة استعمال الدّالة هي كما يلي: {{ lipsum() }} افتراضيّا، تولّد الدّالة lipsum خمسة فقرات، كلّ فقرة تحتوي على ما بين 20 إلى 100 كلمة، ويُمكنك تغيير هذه الإعدادات عبر تمرير مُعاملات إلى الدّالة. لتغيير عدد الفقرات المُولّدة، يُمكنك تمرير العدد الذي ترغبه كمُعامل. لذا فإن كنت ترغب بالحصول على فقرتين فقط، فستحصل على هذه النّتيجة كالتّالي: {{ lipsum(2) }} الحدّ الأدنى لعدد الكلمات في كلّ فقرة هو 20 كلمة، إن كنت ترغب بتعديل هذا فمرّر العدد الأدنى كقيمة للمُعامل min. التّالي مثال على كيفيّة جعل الدّالة lipsum تولّد فقرة واحدة تحتوي على 60 كلمة أو أكثر دون النّزول عن هذا الحدّ: lipsum(1, min=60) الحدّ الأقصى لعدد الكلمات المُولّدة يكون 100 كلمة بشكل افتراضي، لكن تغييره يُمكنك تمرير العدد الّذي تودّ أن يكون العدد الأقصى إلى المُعامل max. على سبيل المثال، الشّيفرة التّاليّة ستقوم بتوليد فقرتين عدد كلمات كلّ واحدة منهما بين 50 إلى 80 كلمة: {{ lipsum(2, min=50, max=80) }} مثال يجمع كلّا من الدّالة range والدّالة lipsum بعد أن تعرّفنا على كلّ من الدّالة range والدّالة lipsum، حان الوقت لاستعمالهما في مثال واحد لتتّضح لك فكرة استعمالهما وتحصل على أفكار أخرى لكيفيّة استغلال ما تعلّمته في هذا الدّرس. في هذا المثال، سنقوم بعرض ثمانيّة فقرات من نصّ lorem ipsum مع عنوان يعمل على شكل رابط بالإضافة إلى زرّ لتمكين الزّائر من قراءة المزيد. وسنستعمل إطار العمل Bootstrap للحصول على تصميم جميل. شيفرة المثال ستكون كما يلي: {% for i in range(8) %} <div class='col-md-3'> <h2> <a href='#'> {{ lipsum(1, min=3, max=5) }} </a> </h2> {{ lipsum(1, max=50) }} <button class='btn btn-default'>Read more...</button> </div> {% endfor %} نقوم أولا باستعمال حلقة for مع الدّالة range مع تمرير العدد 8 كمُعامل، لذا فأي شيفرة داخل حلقة for هذه ستتكرّر ثماني مرّات. داخل حلقة for، نستعمل وسم <div> وخاصيّة CSS col-md-3 التّي يُوفّرها لنا إطار العمل Bootstrap لتقسيم صفحة الويب لأجزاء مُتساويّة. داخل وسم <div> نستعمل الدّالة lipsum لتوليد نصّ عشوائي صغير ليظهر على شكل عنوان للمقال الوهمي، بعدها نستعمل الدّالة مُجدّدا لتوليد فقرة واحدة عدد كلماتها لا يتعدّى 50 كلمة، ثمّ بعد ذلك نعرض زرّا لقراءة المزيد. بعد تطبيق هذا المثال، سنحصل على نتيجة مُشابهة للصّورة التّاليّة: ختاماً تعرّفنا في هذا الدّرس على جزء آخر من أساسيّات مُحرّك القوالب Jinja، يُمكنك الآن استخدام كلّ من الدّالة range والدّالة lipsum لتطوير تطبيقات أفضل واختصار الكثير من الوقت عند العمل مع إطار العمل Flask، إضافة إلى أنّك ستستطيع استخدامها مع تطبيق إدارة المُحتوى “كلمة” والذي سنُكمل تطويره بعد إنهاء أساسيّات مُحرّك القوالب Jinja.
-
مُقدّمة بعد أن تعرّفنا على ماهية ميّزة الاختبارات في مُحرّك القوالب Jinja، وتعرّفنا على بعض من أهمّ الاختبارات المبنيّة مُسبقا في Jinja والتّي يُمكنك استعمالها مُباشرة لتطوير قوالبHTML الخاصّة بك، قد لا تجد ما يسدّ حاجتك في الاختبارات التّي درسناها، وكما فعلنا مع المُرشّحات، يُمكننا سدّ هذه الحاجة بإنشاء اختبارات خاصّة بنا لاختبار القيم والتّحقق ممّا إذا كانت تتّبع نمطا مُعيّنا أو لا. مبدأ اختبارات Jinja المُخصّصة في إطار العمل Flask قبل الانتقال إلى أمثلة تطبيقيّة حول كيفيّة إنشاء اختبارات خاصّة بك لاستعمالها في قوالب Jinja مع إطار العمل Flask، سنتعرّف أولا على الشّكل المبدئي للاختبار. كما استعملنا المُزخرف template_filter لإنشاء مُرشّحات خاصّة بنا، فإنّنا نستعمل مُزخرفا آخر لإنشاء اختبار مُخصّص، إذ نستعمل المُزخرف template_test مع كائن التّطبيق app لتمكين جميع القوالب من استخدام الاختبار. شيفرة اختبار بسيط هي كالآتي: @app.template_test(name='test_name') def test_name_function(value): # Test value return True الشّيفرة شبيهة بشيفرة إنشاء مُرشّح مُخصّص، الفرق أنّنا نستعمل المُزخِرف template_test ونُمرّر اسم الاختبار إلى المُعامل name، ثمّ نمنح للدّالة المُزَخْرَفَةِ اسما مُعبّرا، الدّالة تستقبل قيمة افتراضيّة (القيمة المُختبَرَة) تتواجد داخل المُعامل value، داخل الدّالة ستقوم بإجراءات لاختبار القيمة value مكان التّعليق Test value ثمّ تُرجع إمّا القيمة المنطقيّة True أو القيمة المنطقيّة False حسب ما إذا اجتازت القيمة الاختبار أو لا. طريقة استعمال الاختبار الذي تقوم بإنشائه هي نفسُها طريقة استعمال الاختبارات المبنيّة مُسبقا: variable is test_name لاحظ بأنّ الاختبار يجب أن يُرجع قيمة منطقيّة، ولتبسيط الأمور، فالمثال يُرجع دائما القيمة المنطقيّة True. مثال على اختبار للتّحقق من أنّ طول قيمة ما لا يتجاوز حدا مُعيّنا في هذا المثال سنقوم بإنشاء اختبار للتّحقق ممّا إذا كانت سلسلة نصيّة مُعيّنة أكبر من 30 محرفا، الأمر الذي سيضمن لنا تنسيقا أفضل للصّفحات، فمثلا، لنقل بأنّك ترغب بأن تكون عناوين المقالات في تطبيقك قصيرة، ولنقل بأنّك لا ترغب في عرض أي عنوان تزيد عدد أحرفه عن الثّلاثين حرفا لسبب أو لآخر. المثال التّالي يُوضّح كيفيّة إنشاء اختبار Jinja مُخصّص للتّحقّق من أنّ قيمة مُعيّنة أكبر من 30 محرفا: @app.template_test(name='long') def is_long_test(value): is_long = len(value) > 30 return is_long # True if value is more than 30 characters long نقوم أولا باستخدام المُزخرف app.template_test مع تمرير الاسم long إلى المعامل name وبالتالي سنتمكّن من استعمال الاختبار بالاسم long، في الدّالة المُزخرفة is_long_test نقوم بإنشاء مُتغيّر is_long يحمل إحدى القيمتين True أو False حسب ما إذا كان عدد أحرف السّلسلة النّصيّة value المُمرّرة افتراضيّا إلى الاختبار أكبر من 30 أو لا، للتّحقق ممّا إذا كانت كذلك، نستعمل المُعامل > (أكبر من) للمُقارنة بين طول القيمة value الذي نحصل عليه باستخدام الدّالة len والقيمة 30، في الأخير نُرجع قيمة المُتغيّر is_long. بعد إنشاء الاختبار في الملفّ project/__init__.py ستتمكّن من اختباره كما يلي: {{ 'Building A Web Application Using The Flask Framework' is long }} <br> {{ 'An Introduction To Flask' is long }} النّتيجة: True False في المثال اختبرنا قيمتين، الأولى عدد عناصرها أكبر من 30 محرفا، والقيمة الثّانية عبارة عن عنوان قصير لا يتعدّى الطّول الأقصى، لذا فمن الطّبيعي أن تكون نتيجة الاختبار الأول القيمة True لأنّ العنوان طويل بالفعل، ونتيجة اختبار القيمة الثّانية هي القيمة False لأنّ العنوان أقصر من الثّلاثين حرفا. يُمكنك كذلك ترك تحديد أكبر قيمة للمُطوّر عبر جعلها مُعاملا كما هو مُوضّح في المثال التّالي: @app.template_test(name='longer_than') def is_long_test(value, number_of_characters=30): is_long = len(value) > number_of_characters return is_long في هذا المثال أنشأنا اختبارا باسم longer_than، الدّالة المسؤولة عن الاختبار تقبل مُعاملا واحدا بالإضافة إلى القيمة value التّي تُمرّر افتراضيّا والتّي تُعتبر القيمة التّي تُختبر، بالنّسبة للمُعامل فقد سمّيناه number_of_characters لأنّه يُمثّل عدد المحارف الذي سنعتبره الحد الأقصى لسلسلة نصيّة، افتراضيّا قيمة المُعامل number_of_characters هي 30، ما يعني بأنّ الاختبار سيعمل بنفس طريقة عمل الاختبار long في المثال السّابق إذا لم تُوفّر قيمة للمُعامل. داخل الدّالة، الأمر مُشابه لطريقة عمل الاختبار long، لكن عوضا عن مُقارنة طول قيمة المُعامل value مع العدد 30، أصبحنا نُقارن الطّول مع قيمة المُعامل number_of_characters الذي نحصل عليه من مُستخدم الاختبار. هكذا سيكون لدينا اختبار يسمح لنا بالتّحقق ممّا إذا كانت سلسلة نصّيّة ما أطول من عدد مُعيّن أو لا، عوضا عن التّحقق ممّا إن كانت أطول من العدد 30 فقط. المثال التّالي توضيح لكيفيّة استخدام الاختبار longer_than الذي أنشأناه للتوّ: {% set title = 'An Introduction To Flask' %} {% if title is longer_than 40 %} 'Title is too long' {% else %} {{ title }} {% endif %} في هذا المثال، نُعرّف مُتغيّرا title ونمنحه سلسلة نصيّة قصيرة، بعدها نختبر هذا المُتغيّر باستعمال الجملة الشّرطيّة if والاختبار longer_than، نتأكّد في الاختبار من أنّ السّلسلة النّصيّة المتواجدة بالمُتغيّر title أطول من 40 محرفا، إن كانت كذلك فإنّنا نعرض الرّسالة 'Title is too long'، وإن لم تكن كذلك فإنّنا نعرضها على صفحة HTML بشكل طبيعي. بما أنّ العنوان غير طويل، فالنّتيجة ستكون عرض العنوان في الصّفحة وليس العكس: An Introduction To Flask لاختبار العكس استبدل سطر تعريف المُتغيّر title بما يلي: {% set title = 'Building A Web Application Using The Flask Framework' %} في هذه الحالة، العنوان أطول من 40 محرفا، لذا فالنّتيجة ستكون الجملة 'Title is too long'. مثال على اختبار للتّحقق ممّا إذا كانت سلسلة نصيّة ما تنتهي بمقطع مُعيّن في لغة بايثون، يُمكننا التّحقق من أنّ سلسلة نصيّة تنتهي بمقطع ما باستعمال التّابع endswith مع تمرير المقطع كمُعامل. مثال على كيفيّة استخدام التّابع endswith داخل مُفسّر لغة بايثون: >>> 'An Introduction To Flask'.endswith('Flask') True >>> 'An Introduction To Flask'.endswith('Python') False المُهمّة هي إنشاء اختبار Jinja للحصول على نفس النّتيجة، بحيث سيكون لدينا اختبار باسم endswith ويقبل مُعاملا واحدا، ألا وهو المقطع الذي سنتحقّق ممّا إذا كان مُتواجدا بنهاية السّلسلة المُختَبَرَةِ أو لا. ما يلي مثال على كيفيّة إنشاء الاختبار endswith: @app.template_test(name='endswith') def endswith_test(value, end): return value.endswith(end) الاختبار بسيط جدّا، إذ كل ما نقوم به هو استعمال التّابع endswith على القيمة value مع تمرير قيمة المُعامل end التّي تُعتبر المقطع الذي نرغب بالتّحقق من أنّ السّلسلة تنتهي به، وبما أنّ نتيجة استعمال التّابع ستكون إمّا القيمة True أو القيمة False، فالاختبار سيعمل دون مشاكل، لأنّ شرط اختبار أن تُرجع إحدى القيمتين. المثال التّالي توضيح لكيفيّة استعمال الاختبار endswith: {% set title = 'Building A Web Application Using Flask' %} {% if title is endswith('Flask') %} 'Title ends with the word Flask' {% else %} 'Title does not end with the word Flask' {% endif %} في المثال، نقوم بالتّعريف الاعتيادي للمُتغيّر title، مع تعيين عنوان ينتهي بالكلمة Flask، بعدها نستعمل الاختبار للتّحقق ممّا إذا كان المُتغيّر title ينتهي بالمقطع Flask أو لا، فإن كان كذلك فإنّنا نعرض الرّسالة 'Title ends with the word Flask' وإن لم يكن كذلك فالرّسالة تُصبح 'Title does not end with the word Flask'. وبما أنّ العنوان ينتهي بالمقطع Flask فالنّتيجة هي الحالة الأولى: 'Title ends with the word Flask' لاختبار الحالة الثّانية، غيّر سطر تعريف المُتغيّر title إلى جملة لا تنتهي بالمقطع Flask. خاتمة بنهاية هذا الدّرس، يجب أن تكون قادرا على كتابة واستخدام اختباراتك الخاصّة عند العمل مع كل من مُحرّك القوالب Jinja وإطار العمل Flask، في الدّروس القادمة، سنُكمل التّعرف على أساسيات مُحرّك القوالب لتتمكّن من تطوير تطبيقات Flask أفضل.
-
مُقدّمة بعد التّعرف في الدّرس السّابق على مفهوم الاختبارات (Tests) في مُحرّك القوالب Jinja، سنتعرّف في هذا الدّرس على بعض من أهمّ الاختبارات التّي يجب عليك الإلمام بها لتطوير تطبيقات ويب أفضل بشكل أسهل وأسرع مع إطار العمل Flask، بعد التّعرف على هذه الاختبارات، يُمكنك استعمالها ببساطة للتّحقق من أنّ القيمة المتواجدة بمُتغيّر ما هي ما تُريده بالفعل، كما ستتمكّن من تنفيذ إجراءات حسب حالة المُتغيّر. الاختبار iterable قُلنا من قبل بأنّ الكائنات من النّوع iterable عبارة عن أي كائن يُمكنك الدّوران حوله باستخدام حلقة for، هذا يشمل مجموعات العناصر مثل القوائم والسّلاسل النّصيّة وأنواع أخرى من القيم، إذا لم تكن متأكّدا من أنّ كائنا ما عبارة عن كائن من النّوع Iterable فيُمكنك استخدام الاختبار iterable للتّحقق من الأمر بشكل بسيط. مثلا، يُمكن أن تجد بأنّك ستحتاج إلى الدّوران حول مُتغيّر لكنّك لست متأكّدا من أنّه سيكون دائما قابلا لذلك، لذا لتفادي عرض الأخطاء للمُستخدم، يُمكنك استخدام الاختبار iterable مع جملة if شرطيّة، وهكذا ستتمكّن من الدّوران حول المُتغيّر فقط إذا كان من النّوع Iterable أمّا إن لم يكن كذلك فسيتم تجاهله إلى أن تتغيّر قيمته إلى نوع قابل للدّوران عليه. المثال التّالي توضيح لكيفيّة استعمال الاختبار Iterable: {% set variable = 'ABCD' %} {% if variable is iterable %} <ul> {% for item in variable %} <li>{{ item }}</li> {% endfor %} </ul> {% else %} لا يُمكنك الدّوران حول قيمة المُتغيّر {% endif %} المثال بسيط وواضح، إذ نقوم بتعريف المُتغيّر variable ونتحقّق من أنّه من النّوع Iterable عن طريق كل من الجملة الشّرطيّة if والاختبار iterable، إذا كانت قيمة المُتغيّر قابلة للدّوران عليها، فإنّنا نقوم باستعمال الحلقة for للوصول إلى كل عنصر وعرضه على شكل عنصر من قائمة HTML، أمّا إن لم تجتز قيمة المُتغيّر الاختبار فإنّنا نعرض الجملة “لا يُمكنك الدّوران حول قيمة المُتغيّر”. بما أنّنا نستطيع الدّوران حول أي سلسلة نصيّة في لغة بايثون للوصول إلى عناصرها، فنتيجة المثال ستكون قائمة HTML لعرض عناصر السلسلة النّصيّة ABCD. لنتأكّد من أنّ المثال يعمل في كلتا الحالتين، غيّر السّطر الأول إلى ما يلي: {% set variable = 100 %} في هذه الحالة، قيمة المُتغيّر هي العدد الصّحيح 100، والأعداد الصّحيحة نوع لا يُمكن أن تدور حوله (إلّا إذا ما حوّلته إلى سلسلة نصيّة)، لذا فالنّتيجة هذه المرّة ستكون الجملة “لا يُمكنك الدّوران حول قيمة المُتغيّر ”. الاختبار lower عند التّعامل مع الأحرف اللاتينيّة، قد ترغب بالتّحقق من أنّ قيمة مُتغيّر عبارة عن أحرف صغيرة (Lowercase)، وهذا بالضّبط ما يُمكنك فعله بالاختبار lower. المثال التّالي يُوضّح طريقة عمل الاختبار lower: {% set words = ['ORM', 'walk', 'run', 'OOP'] %} {% for word in words %} {% if word is lower %} {{ word }} {% else %} {{ word }} is an acronym. {% endif %} <br> {% endfor %} في المثال أعلاه، نُعرّف قائمة باسم words تحتوي على بضعة كلمات، بعضها عبارة عن كلمات عاديّة والبعض الآخر عبارة عن اختصارات، وكما تعلم فهذه الأخيرة تُكتب بأحرف كبيرة لذا سنتمكّن من فصلها عن الكلمات الأخرى باستخدام الاختبار lower لأنّ الكلمات العاديّة مكتوبة بأحرف صغيرة، ما يعني بأنّها ستجتاز الاختبار، أمّا الاختصارات فلن تجتازه، لذا نقوم بالدّوران حول القائمة واختبار كل عنصر، فإن كانت الكلمة تحتوي على الأحرف الصّغيرة فقط فإنّنا نعرضها أمّا إن لم تكن كذلك فإنّنا نعرض رسالة تُفيد بأنّ العنصر عبارة عن اختصار لعبارة ما. نتيجة المثال أعلاه ستكون كما يلي: ORM is an acronym. walk run OOP is an acronym. الاختبار upper هذا الاختبار هو عكس الاختبار lower، إذ يُستعمل للتّحقق من أنّ كلمة ما مكتوبة بأحرف كبيرة، وطريقة استخدامه مُشابهة لطريقة استخدام الاختبار lower. مثال على كيفيّة استخدام الاختبار upper: {% set words = ['ORM', 'walk', 'run', 'OOP'] %} {% for word in words %} {% if word is upper %} {{ word }} <br> {% endif %} {% endfor %} النّتيجة: ORM OOP الاختبار undefined تعرّفنا من قبل على الاختبار defined، وقلنا بأنّه يُستعمل للتّحقق من أنّ مُتغيّرا ما معرّف أو لا، الاختبار undefined يقوم بالعكس، إذ يرجع القيمة المنطقيّة True إذا لم يكن المُتغيّر مُعرّفا، والقيمة False إذا كان المُتغيّر مُعرّفا. الاختبار none في لغة بايثون، إذا كان مُتغيّر ما يحمل القيمة None فهذا يعني بأنّ المُتغيّر مُعرّف لكنّه فارغ ولا يحمل أية قيمة، هذه القيمة مُهمّة جدّا إذا كنت تتعامل مع بعض المكتبات التّي تُساعد على التّعامل مع قواعد البيانات مثل مكتبة SQLAlchemy التّي سنعتمد عليها بشكل أساسيّ في مشروع “كلمة” الذي نبنيه في هذه الدّروس، لذا فمعرفة الغرض من القيمة None مهم. الاختبار none الذي يُمكنك استعماله في قوالب Jinja سيُساعدك على التّحقق ممّا إذا كانت قيمة الكائن الذي تتعامل معه فارغة أو لا، وبالتّالي ستتمكّن من عرض رسالة مفهومة للمُستخدم في حالة كانت نتيجة ما يبحث عنه هي القيمة None، فمثلا يُمكنك عرض الرّسالة “هذا المُستخدم لم يُضف نبذة عن نفسه بعد” في الصفحة الشّخصيّة للمُستخدمين الذين لم يقوموا بإضافة نبذة شخصيّة إلى حساباتهم، وهكذا سيعرف الزائر سبب غياب أي وصف في المكان المخصّص لذلك. المثال التّالي يُوضّح استعمال الاختبار none مع جملة شرطيّة لعرض رسالة تنبيه في حالة كانت قيمة العنصر فارغة: {% set list = [None, 'One', None, 'Two'] %} {% for item in list %} {% if item is none %} قيمة العنصر فارغة <br> {% else %} {{ item }} <br> {% endif %} {% endfor %} في المثال أعلاه، مزجنا بين السّلاسل النّصيّة والقيمة None في عناصر القائمة list، بعدها نقوم بالدّوران حول القائمة بحلقة for، ثمّ بعد ذلك نتحقّق ممّا إذا العنصر الحالي عبارة عن قيمة فارغة، فإن كان الأمر كذلك فإنّنا نعرض الرّسالة “قيمة العنصر فارغة”، أمّا إن لم يكن العنصر فارغا فإنّنا نعرضه. النّتيجة: قيمة العنصر فارغة One قيمة العنصر فارغة Two الاختبار number لمعرفة ما إذا كانت قيمة كائن ما عبارة عن عدد أو لا، يُمكنك استخدام الاختبار number، مع التّأكيد على أنّ الأعداد العشريّة تُعتبر أعدادا كذلك، لذا ستجتاز الاختبار بشكل عادي. المثال التّالي توضيح على كيفيّة استخدام الاختبار number: {% set list = [1, 'Flask', 2, 3, 3.2, 0.001] %} {% for item in list %} {% if item is number %} {{ item }} عبارة عن عدد <br> {% else %} {{ item }} ليس بعدد <br> {% endif %} {% endfor %} في هذا المثال، نقوم بتعريف قائمة تحتوي على أعداد وسلاسل نصيّة، ندور حول القائمة باستعمال حلقة for ثمّ نستعمل الاختبار number للتّحقّق ممّا إذا كان العنصر عددا أو لا، فإن كان عددا فإنّنا نعرض رسالة تُفيد بأنّ العنصر عبارة عن عدد بالفعل، أمّا إن لم يكن العنصر كذلك فإنّنا نعرض رسالة مفادها بأنّ القيمة ليست بعدد. نتيجة المثال: 1 عبارة عن عدد Flask ليس بعدد 2 عبارة عن عدد 3 عبارة عن عدد 3.2 عبارة عن عدد 0.001 عبارة عن عدد لاحظ في النّتيجة بأنّ الأعداد العشريّة تجتاز الاختبار كذلك. الاختبار string للتّحقق من أنّ قيمة ما عبارة عن سلسلة نصيّة، يُمكنك استخدام الاختبار string الذي يعمل بطريقة مُشابهة لطريقة عمل الاختبار number، ويُمكنك استعماله عند التّعامل مع البيانات التّي يُمكن أن تكون سلاسل نصيّة كما يُمكن أن تكون قيما أخرى في قاعدة البيانات. مثال على كيفيّة استخدام الاختبار string: {% set list = ['Flask', 2017, None] %} {% for item in list %} {% if item is number %} {{ item }} عبارة عن عدد <br> {% elif item is string %} {{ item }} عبارة عن سلسلة نصيّة <br> {% else %} {{ item }} ليس بعدد وليس سلسلة نصيّة <br> {% endif %} {% endfor %} في المثال أعلاه، نقوم بإنشاء قائمة ثمّ ندور عليها كالعادة، نختبر ما إذا كان كل عنصر عبارة عن عدد بالاختبار number لعرض رسالة تُفيد بذلك، إذا لم تكن القيمة عددا فإنّنا نستعمل الاختبار string لنرى إذا ما كانت القيمة سلسلة نصيّة لنعرض رسالة مفادها أنّ القيمة سلسلة نصيّة بالفعل، إذا لم تكن القيمة لا عددا ولا سلسلة نصيّة فإنّنا نعرض الجملة “ليس بعدد وليس سلسلة نصيّة ”. النّتيجة: Flask عبارة عن سلسلة نصيّة 2017 عبارة عن عدد None ليس بعدد وليس سلسلة نصيّة ختاما هكذا نكون قد تعرّفنا على ماهيّة الاختبارات في مُحرّك القوالب Jinja، وألقينا نظرة إلى بعض من أهمّ الاختبارات المبنيّة مُسبقا وعلى كيفيّة استخدامها في تطبيقات Flask أو أي مشروع بايثون آخر يعتمد على Jinja، سننتقل في الدّرس القادم إلى كيفيّة إنشاء اختبارات خاصّة بنا لاستعمالها في حالة لم تجد اختبارا يُلبي حاجتك في قائمة الاختبارات المبنيّة مُسبقا.
-
مقدّمة بعد أن تعرّفنا على بنية التّطبيق الذي سنبنيه سويا، سنبدأ بتهيئة وتنصيب بعض الحزم التّي سنعتمد عليها، وسنرى كيف يُمكننا تنظيم الملفّات لمرونة أكثر في التّعامل مع المشاريع الكبيرة، فعوضا عن وضع الشيفرة كاملة في ملفّ أو ملفّين، سنقوم باستعمال مبدأ المُخطّطات Blueprints الذي يوفّره لنا إطار العمل فلاسك لتنظيم التّطبيقات الأكثر تعقيدا. بنية التّطبيق المُعتادة سابقا، كنّا نستعمل ملفّا رئيسيّا واحدا ونقوم بتشغيله ليعمل الخادوم ونتمكّن من الوصول إلى التّطبيق عبر المُتصفّح من العنوان المُعتاد، وهكذا تكون جميع الموجّهات في ملفّ واحد، والملفّات السّاكنة في مجلّد واحد، وملفّات القوالب (ملفّات HTML) تكون جميعها في نفس المُجلّد. ملفّات التّطبيق المعتادة تكون على الشّكل التّالي: ├── app.py # ملفّ التّطبيق الرّئيسي ├── config.py # ملفّ الإعدادات ├── static # مجلّد الملفّات السّاكنة │ ├── css │ │ └── style.css │ ├── img │ │ └── logo.png │ └── js │ └── main.js ├── templates # مجلّد القوالب │ ├── index.html │ ├── posts.html مادام التّطبيق بسيطا فكلّ شيء سيكون على ما يرام، لكن ماذا لو كان التّطبيق يمتلك قسما لتسجيل المُستخدمين، عرض صفحاتهم الشّخصيّة، عرض المقالات، صفحات الأخطاء (مثل عدم تواجد صفحة أو خطأ بالخادوم …)، لوحة تحكّم للمُدير، لوحات التّحكم بأجزاء التّطبيق، واجهة برمجيّة للمُطوّرين API وغير ذلك من الخصائص التّي يتمتّع بها كل تطبيق كبير ذو وظائف مُتعدّدة. إن استعملت البنية المُشار إليها سابقا، ستواجه العديد من المشاكل، أولا سيكون الملفّ الرّئيسي مليئا بالموجّهات والدّوال، وقد يصل إلى آلاف الأسطر البرمجيّة ما سيجعل مهمّة الصّيانة وإضافة خصائص جديدة أمرا صعبا، كما أنّ ملفّات القوالب ستكون مجتمعة في مجلّد واحد، وإن كنت تعمل على تطوير التّطبيق مع فريق من المُطوّرين فسيجد كل شخص نفسك أمام ملفّات وشيفرات لا علاقة له بها، مثلا لو قُسّمت المهام بحيث يعمل كلّ مطوّر على قسم معيّن من التّطبيق، زيد هنا يعمل على نظام تسجيل للمُستخدمين، وعامر يعمل على إضافة قسم لعرض آخر التّعليقات المُضافة، وسعيد، مُصمّم الويب يعمل على تصميم صفحات أخطاء جميلة، وعبد القادر يعمل على لوحة تحكّم الإدارة، فكيف يُمكن لزيد أن يُعدّل الشيفرة التّي تقوم بتسجيل المُستخدمين دون المساس بالشّيفرة التّي تقوم بإعداد المقالات لعرضها على صفحة المقالات؟ المُخطّطات Blueprints المُخطّطات في فلاسك نظام لتجميع الموجّهات المُخصّصة لوظيفة مُعيّنة بعيدا عن المُوجّهات الأخرى، يُمكن كذلك تقسيم ملفّات القوالب والملفّات السّاكنة كذلك مع إتاحة خيار مُشاركة ملفّات مُحدّدة إن أردت ذلك. في هذا المشروع سنضع مجلّد قوالب لكلّ وظيفة، أمّا مُجلّد الملفّات السّاكنة فسيكون مُشتركا بين جميع القوالب، وهذا لأنّنا لن نحتاج إلى الكثير من ملفّات جافاسكريبت وملفّات css، وستكون الصّور مُشتركة كذلك. هناك نوعان معروفان من المُخطّطات، النّوع الأول يُقسّم ملفّات القوالب مع كلّ وظيفة ليكون كالتّالي: . ├── run.py ├── config.py ├── project │ ├── __init__.py │ ├── posts │ │ ├── __init__.py │ │ ├── templates │ │ │ ├── index.html │ │ │ ├── post.html │ │ │ ├── posts_by_category.html │ │ ├── views.py │ ├── static │ │ └── css │ │ └── style.css │ ├── templates │ │ ├── base.html │ └── users │ ├── __init__.py │ ├── templates │ │ ├── login.html │ │ └── register.html │ ├── views.py أمّا النّوع الثّاني فتجتمع فيه جميع ملفّات HTML في مُجلّد واحد كما يلي: . ├── run.py ├── config.py ├── project │ ├── __init__.py │ ├── posts │ │ ├── __init__.py │ │ ├── views.py │ └── users │ ├── __init__.py │ ├── views.py │ ├── templates │ │ ├── base.html │ │ ├── posts/ │ │ ├── users/ │ ├── static │ │ └── css │ │ └── style.css في المثالين السّابقين، لدينا مُخطّطان، مُخطّط خاصّ بالمقالات تحت مجلّد posts، وآخر خاصّ بالمُستخدمين في مجلّد users، ملفّ run.py مسؤول عن تشغيل التّطبيق وأمّا ملفّات __ini__.py فهي لجعل بايثون يفهم بأنّ كلّا من مجلّد المشروع project ومجلّدي المقالات والمُستخدمين عبارة عن حزم. أمّا ملفّا views.py فسيحتويان على الموجّهات المعنيّة بكلّ وظيفة، فمثلا مُوجّه عرض المقالات “/posts” سيكون في ملفّ posts/views.py أمّا موجّه تسجيل دخول المُستخدمين /login فسيكون في ملفّ users/views.py. لإخبار فلاسك بأنّ مجلّدي posts و users عبارة عن مُخطّطات، سنعرّف أولا كائنًا من صنف Blueprint في كل من ملفّات __init__.py في المُجلّدين، وبعدها سنقوم بتسجيلهما مع تطبيقنا الذي نُعرّفه في ملفّ __init__.py الأساسي -وهو المُتواجد في مجلّد project-. لك حريّة استخدام أي نمط تُريده، ففي النّهاية إطار فلاسك مُصمّم ليُوفّر لك كامل الحريّة في طريقة تنظيمك لملفّات التّطبيق، لكن اتّباع نمط مُتعارف عليه أفضل في مُعظم الحالات. سنستخدم في مشروعنا النّمط الأول، أي أنّ ملفّات HTML ستكون منفصلة حسب الوظيفة. بايثون 3 سنستعمل في إنشائنا لهذا التّطبيق النّسخة 3 من لغة بايثون، لن يتغيّر الكثير، فالاختلاف بين Python 2 و Python 3 بسيط جدا، وسأقوم بذكر أهم الاختلافات أثناء بناء التّطبيق في الدّروس القادمة. إذا كنت لا تزال تعتمد على بايثون 2 فأنصحك بأن تنتقل إلى بايثون 3 لأنّ الدّعم لهذه الأخيرة قد أصبح كبيرا ومُعظم مُطوري بايثون يستعملونها الآن، كما أنّها أحدث نُسخة. إذا أردت أن تتعلم كيفيّة تجهيز بايثون 3 على نظامك، يُمكنك اتّباع هذا الدّرس. تطبيق كلمة بما أنّ مشروعنا عبارة عن نظام لإدارة المُحتوى، قرّرت تسميّته “كلمة”، جميع الملفّات والمجلّدات الخاصّة بالتّطبيق ستكون بداخل مجلّد باسمkalima، لذا عليك إنشاؤه الآن في المكان المُفضّل لديك، المهم أن تعرف كيفيّة الوصول إليه عن طريق سطر الأوامر. في البداية، سننشئ الملفّات التّاليّة: . ├── run.py ├── project │ ├── __init__.py │ ├── posts │ │ ├── __init__.py │ │ ├── templates │ │ │ ├── posts.html │ │ ├── views.py │ ├── static │ │ └── css │ │ └── style.css │ ├── templates │ │ ├── base.html │ └── users │ ├── __init__.py │ ├── templates │ │ ├── users.html │ │ ├── login.html │ ├── views.py يُمكنك إمّا إنشاء المُجلّدات والملفّات يدويّا أو عبر نسخ الشيفرة التّالية وحفظها في ملفّ باسم create_app.py لإنشاء الملفّات والمُجلّدات باستعمال لغة بايثون تلقائيّا. import os import sys def mkapp(app_name, blueprints): dirs = ['{}', '{}/static', '{}/static/css', '{}/templates'] static_files = ['{s}/css/style.css'] templates_files = ['{t}/index.html', '{t}/base.html'] for d in dirs: os.mkdir(d.format(app_name)) open(app_name + '/' + "__init__.py", "w").close() # project/__init__.py for b in blueprints: os.mkdir(app_name + '/' + b) # project/posts os.mkdir(app_name + '/' + b + '/templates') # project/posts/templates open(app_name + '/' + b + '/' + "views.py", "w").close() # project/posts/views.py open(app_name + '/' + b + '/' + "__init__.py", "w").close() #project/posts/__init__.py open(app_name + '/' + b + '/templates/' + b + ".html", "w").close() # project/posts/templates/index.html for sf in static_files: static_file = app_name + '/' + sf.format(s='static') # project/static open(static_file, 'w').close() for tf in templates_files: templates_file = app_name + '/' + tf.format(t='templates') # project/templates open(templates_file, 'w').close() if __name__ == '__main__': app = sys.argv[1] blueprints = sys.argv[2:] mkapp(app, blueprints) يُمكنك تنفيذ السكربت بالأمر التّالي: $ python3 create_app.py project posts users بعدها أنشئ ملفّ login.html في مجلّد project/users/templates لأنّ السكربت لن يقوم بإنشائه. عليك كذلك إنشاء ملفّي run.py وconfig.py داخل مُجلّد kalima، الملفّ الأول مسؤول عن تشغيل الخادوم/ أمّا الملفّ الثّاني فمسؤول عن إعدادات التّطبيق. خاتمة تعرّفنا في هذا الدّرس على بنية التّطبيق الذي سنبنيه وكيفيّة توزيع ملفّاته إذ أنشأنا المُجلّدات والملفّات التّي ستكون مسؤولة عن الجانب العملي للتّطبيق، في الدّرس القادم، سنبدأ بتجهيز البيئة البرمجيّة وتنصيب أهم الحزم التّي سنحتاج إليها للبدء بتطوير التّطبيق.
-
مُقدّمة بعد أن تعرّفنا على بنية التّطبيق الذي سنبنيه سويًّا باستخدام إطار العمل Flask ولغة Python، سننتقل الآن إلى العمل على تجهيز بيئة وهميّة لبايثون 3 لنقوم بتنصيب إطار Flask عليها ونبدأ بتطوير التّطبيق بمعزل عن النّظام المتواجد في الحاسوب، بعد تهيئة البيئة البرمجيّة، سنتعرّف على كيفيّة استغلال مبدأ المُخطّطات Blueprints التّي يُوفرّها لنا إطار العمل Flask لتوزيع أفضل لملفّات ومُوجّهات التّطبيق لعزل كل جزء وظيفيّ عن الأجزاء الأخرى وبالتّالي تسهيل تطوير خصائص وميّزات وكذلك تقسيم المُهمّات على أفراد الفريق البرمجي في حالة كنت تعمل على تطبيق مع أكثر من مُطوّر. إنشاء بيئة وهميّة بأداة Pyvenv أولا، سنحتاج إلى إنشاء بيئة وهميّة لعزل اعتماديات التّطبيق عن اعتماديات النّظام، وبما أنّ لديك خبرة في لغة بايثون، فلا بد بأنّك تُفكّر في استخدام أداة virtualenv، يُمكنك استخدامها إن أردت، لكنّنا سنستعمل بايثون 3 في تطويرنا لهذا التّطبيق، وإذا اتّبعت الدّرس الذي أشرت إليه في الدّرس السّابق، فستعلم بأنّه من المُفضّل استعمال أداة pyvenv للتأكّد من أنّ البيئة الوهميّة ستكون خاصّة بلغة بايثون 3 وليس بايثون 2. اتّبع الطّريقة التّقليديّة لإنشاء بيئة وهميّة باستعمال أداة pyvenv عبر السّطر التّالي: pyvenv venv إذا كنت تستخدم نظام Windows فقد لا يعمل السّطر أعلاه، لكن يُمكنك استخدام الأمر التّالي عوضا عنه: python -m venv فقط تأكّد من أنّ الأمر python يوصلك إلى مُفسّر بايثون 3 وليس مُفسّر بايثون 2. بعد انتهاء الأمر من التّنفيذ، قم بتفعيل البيئة الوهميّة بالسّطر التّالي: source venv/bin/activate بعد تفعيل البيئة الوهميّة، قم بتنصيب إطار العمل Flask باستخدام أداة pip بالسّطر التّالي: pip install Flask إذا كان السّطر ما قبل الأخير من المُخرجات كما يلي، فهذا يعني بأنّ كل شيء بخير: Successfully installed Flask itsdangerous Jinja2 Werkzeug click MarkupSafe طريقة العمل بما أنّنا أصبحنا نتعامل مع الكثير من الملفّات، عليك أن تعرف أي شيفرة ستكون في أي ملف، ولهذا سأقوم بوضع مسار الملف في أعلى الشّيفرة على شكل تعليق. وفي معظم الأحيان، لن أضع محتويات الملف كاملًا، بل سأضع الشيفرة الجديدة مُحاطة بسطرين قديمين من الأعلى والأسفل لتُدرك أي مكان يجب عليك أن تضع به الشّيفرة. مثال على الشّيفرة سيكون كالآتي: # project/users/views.py @users.route('/login') def login(): . . . login_user(user) return redirect(url_for('users.posts_by_author')) لا تقلق إن لم تفهم الأجزاء الجديدة عليك من الشّيفرة، فهي مُجرّد مثال بسيط على كيفيّة استخدام مُوجّه لمُخطّط وكيفيّة الرّبط بين الموجّهات. إنشاء المُخطّطات لدينا الآن مُخطّطان في تطبيقنا، واحد في مجلد المُستخدمين والآخر في مُجلّد المقالات. سنقوم أولا بإنشاء المُخطّط الخاصّ بالمُستخدمين في ملفّ views.py المتواجد داخل المُجلّد users كما يلي: # project/users/views.py from flask import Blueprint users = Blueprint('users', __name__, template_folder='templates', url_prefix='/users') في السّطر الأول، نستدعي Blueprint من حزمة flask، ونقوم بإنشاء كائن منها كما كنّا نقوم بإنشاء كائن app من الصّنف Flask في الماضي. المعامل الأول عبارة عن اسم المُخطّط، والمعامل الثّالث هدفه إخبار فلاسك بأنّ مُجلّد القوالب سيكون في نفس مُجلّد المُخطّط، وسيكون باسم templates ويُمكنك تغيير هذا الاسم إلى ما تشاء، لكنّنا لن نقوم بذلك، ومن المُفضّل اتّباع المُمارسات الشّائعة في مثل هذه الأمور. أمّا مُعامل url_prefix فالهدف منه وضع موجّه رئيسي قبل الموجّه المعرّف. مثلًا لتعريف الموجّه الرّئيسي / سيكون كالآتي: @users.route('/') # /users ولتعريف موجّه التّسجيل: @users.route('/register') # /users/register وكما تُلاحظ، لا أحتاج إلى تعريف /users في بداية كل مُوجّه. لو لم نُخصّص مُجلّدًا للقوالب، لكان علينا جمع جميع ملفّات HTML في مجلّد project/templates. هناك كذلك خيار لتفريق مجلّد الملفّات السّاكنة بنفس الطّريقة: users = Blueprint('users', __name__, template_folder='templates', static_folder='static', url_prefix='/users') ولأنّنا سنجمع جميع الملفّات السّاكنة في مُجلّد project/static فهذا الخيار لن يكون مُفيدًا لنا. يُمكنك الآن إنشاء المُخطّط الخاصّ بالمقالات بنفس الطّريقة. # project/posts/views.py from flask import Blueprint posts = Blueprint('posts', __name__, template_folder='templates', url_prefix='/posts') تسجيل المُخطّطات مع التّطبيق الرّئيسي بعد أن قُمنا بإنشاء مُخطّطات، حان الوقت لإخبار إطار فلاسك بأنّ هذه المُخّططات تابعة للتّطبيق، وطريقة القيام بذلك بسيطة. افتح الملف project/__init__.py وضع به ما يلي: # project/__init__.py from flask import Flask app = Flask(__name__) from project.posts.views import posts from project.users.views import users app.register_blueprint(posts) app.register_blueprint(users) @app.route('/') def index(): return 'Project Index' السّطران الأولان معروف بأنّهما لإنشاء التّطبيق، أمّا الاستدعاءات فهي لاستدعاء المُتغيّرين اللذين يحملان المُخطّطان، أمّا الأسطر الأخيرة فهي لتسجيل المُخطّطات التّي استدعيناها. أمّا الموجّه الذي عرّفته في الأسفل فهو ليتمكّن المُستخدم من الوصول إلى الصّفحة الرّئيسية دون الحصول على خطأ من نوع 404. ذلك لأنّ الموجّهات التّي سنعرّفها في المُخطّطات ستكون كما يلي: /posts /posts/new /users /users/register . . . لذا إن دخل المُستخدم إلى العنوان 127.0.0.1:5000 فلن يجد شيئا لأنّنا لم نُحدّد الموجّه الرّئيسي /، وهذا هو السّبب الذي جعلنا نُضيف المُوجّه بملفّ __init__.py في مجلّد المشروع project. إنشاء مُوجّهين رئيسيّين قلنا سابقًا بأنّ المُوجّهات ستكون داخل ملفّات views.py فإن أردنا أن نعرّف مثلًا موجّهًا للمقالات الرّئيسية، فسنقوم بذلك في ملّف posts/views.py أمّا الخاص بالمُستخدمين فسيكون في ملفّ users/views.py وهكذا. سنقوم الآن بإنشاء مُوجّه رئيسي لكل مُخطّط. أولا موجّه /posts/ سيكون كما يلي (ضعه مُباشرة بعد سطر تعريف المُتغيّر posts): # project/posts/views.py @posts.route('/') # 127.0.0.1:5000/posts def index(): return 'Posts Index' أمّا المُوجّه الخاصّ بالمُستخدمين فسيكون كما يلي: # project/users/views.py @users.route('/') # 127.0.0.1:5000/users def index(): return 'Users Index' وبهذه التّغييرات سيُصبح الملفّ views.py الخاصّ بالمُستخدمين كما يلي: from flask import Blueprint users = Blueprint('users', __name__, template_folder='templates', url_prefix='/users') @users.route('/') # 127.0.0.1:5000/users def index(): return 'Users Index' وملفّ views.py الخاص بالمقالات سيكون كالآتي: from flask import Blueprint posts = Blueprint('posts', __name__, template_folder='templates', url_prefix='/posts') @posts.route('/') # 127.0.0.1:5000/posts def index(): return 'Posts Index' تشغيل الخادوم بعد أن قمنا بتسجيل المُخطّطات وأنهينا أساسيّات التّطبيق، علينا أن نقوم بتشغيل الخادوم لنتمكّن من مُعاينة ما قُمنا به، ولتشغيل التّطبيق، سنضع السّطر المسؤول عن الأمر في ملفّ باسم run.py في مجلّد kalima. محتويات الملفّ ستكون كالتّالي: # run.py from project import app app.run(debug=True) كما تُلاحظ، استدعينا الكائن app الذي سبق وأن عرّفناه في ملفّ project/__init__.py بعدها نقوم بتشغيل الخادوم بالطّريقة المُعتادة مع خيار تصحيح الأخطاء. بعد حفظ ملفّ run.py يُمكنك تشغيله عبر سطر الأوامر: $ python run.py ستُلاحظ بأنّ الخادوم قد بدأ بالاشتغال كما العادة، لكنّ طريقة تشغيل الخادوم بهذه الطّريقة غير منصوح بها لأنّ النّسخة رقم 0.11 من Flask التّي أطلقت في أواخر عام 2016 جاءت بطريقة جديدة لتشغيل الخادوم عبر تنفيذ الأمر flask run. لكن الأمر flask run لن يعمل إلّا بعد تحديد اسم الحزمة أو الوحدة التّي يتواجد بها تطبيق Flask، وذلك عبر تعريف مُتغيّر بيئة باسم FLASK_APP وإعطائه القيمة حسب مكان تواجد تطبيق Flask الخاص بك. لتعريف مُتغيّر البيئة يُمكنك تنفيذ الأمر التّالي من الطّرفيّة بعد التّأكد من أنّ البيئة الوهميّة لديك مُفعّلة: $ export FLASK_APP=app.py في نظام Windows يجب عليك استخدام الكلمة المفتاحيّة set عوضا عن export: > set FLASK_APP=app.py في المثال أعلاه، نفترض بأنّ شيفرة تطبيق Flask مُتواجدة في الملفّ app.py. بعد تعريف مُتغيّر البيئة يُمكنك تشغيل الخادوم عبر تنفيذ الأمر التّالي: flask run عوضًا عن الأمر: python app.py في مشروعنا، تطبيق Flask مُتواجد داخل ملفّ __init__.py في مُجلّد project لذا فطريقة تعيين مُتغيّر البيئة سيكون كالتّالي: $ export FLASK_APP=project/__init__.py بعد تحديد مُتغيّر البيئة، يُمكنك تجربة تشغيل الخادوم: flask run بعد تنفيذك للأمر أعلاه، يجب أن تحصل على مُخرج كما يلي: * Serving Flask app "project" * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) لتفعيل مُصحّح الأخطاء، كل ما عليك فعله هو تعريف مُتغيّر البيئة FLASK_DEBUG وتعيين القيمة 1 له: export FLASK_DEBUG=1 مُلاحظة: أذكّر بأنّ تفعيل مُصحّح الأخطاء في بيئة الإنتاج أمر خطير، ولا يجب عليك أبدًا أن تقوم بذلك حتى ولو كنت مُتأكّدًا من أنّ تطبيقك خال من الأخطاء، وعليك أن تُدرك بأنّ مُصحّح الأخطاء مصمّم لمُساعدتك على تطوير واختبار التّطبيق وليس لتفعيله بعد نشر التّطبيق على الأنترنت. ومُجدّدًا، استبدل export بالكلمة set إذا كنت تستخدم نظام Windows. بعد تشغيل الخادوم مع وضع تصحيح الأخطاء، فسيكون المُخرج مُشابها لما يلي: * Serving Flask app "project" * Forcing debug mode on * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) * Restarting with stat * Debugger is active! * Debugger pin code: 573-756-626 وإن أردت أن يصل الآخرون في شبكتك إلى موقعك، تستطيع إضافة --host=0.0.0.0 إلى أمر تشغيل الخادوم كما يلي: flask run --host=0.0.0.0 بعد تشغيل الخادوم ستتمكّن من الوصول إلى الموجّهات الثلاثة التّي سبق وأن عرّفناها. 127.0.0.1:5000/ 127.0.0.1:5000/posts 127.0.0.1:5000/users وستُلاحظ النّتائج: Project Index Posts Index Users Index خاتمة تعرّفنا في الدّرس على كيفيّة إنشاء مُخطّطات وتسجيلها لمرونة أكثر في التّطوير ولإدارة أفضل للتّطبيقات الكبيرة، وتعرّفنا كذلك على كيفيّة تشغيله وكيفيّة إضافة مُوجّه لكل مُخطّط، في الدّرس القادم سنتعرّف على كيفيّة التّعامل مع قوالب HTML وكيفيّة الرّبط بين مُختلف موجّهات التّطبيق.
-
مُقدّمة: بعد أن قمنا بتنظيم ملفّات التّطبيق على حسب الوظيفة باستعمال خاصيّة المُخطّطات، أصبح لدينا اختلاف بسيط في كيفيّة التّعامل مع ملفّات العرض التّي تحتوي على شيفرات HTML وشيفرات مُحرّك القوالب Jinja، وسيكون هناك اختلاف بسيط أيضا في طريقة استخدام الدّالة url_for للرّبط بين المُوجّهات. إنشاء صفحات HTML بما أنّنا أنشأنا ثلاثة مجلّدات باسم templates لتعدّد أغراضها، فسيتوجّب علينا أن نفهم أولا كل مجلّد ووظيفته. مسارات المُجلّدات التّي أنشأناها سابقا هي كالآتي: project/templates/ project/posts/templates/ project/users/templates/ المسار الأول عبارة عن مجلّد رئيسي للقوالب، يُمكنك أن تعتبره مُشتركا مع القوالب الأخرى، فمثلا يُمكن لكل من ملفّات المقالات والمُستخدمين أن يرثوا من ملفّ base.html كما سنوضّح بعد قليل، كما أنّ أي موجّه يُعرّف داخل الملفّ الرّئيسي project/__init__.py له الحق في تقديم الملفّات المُتواجدة في مجلّد القوالب الرّئيسي مُباشرة. أي أنّك تستطيع تعديل الموجّه الرّئيسي ليُقدّم القالب index.html المتواجد داخل مجلّد project/templates كالآتي: # project/__init__.py from flask import Flask, render_template #... #... @app.route('/') # 127.0.0.1:5000/ def index(): return render_template('index.html') # render project/templates/index.html لا تنس بأنّنا استردنا أولا الدّالة render_template ثمّ استدعيناها في المُوجّه لتقديم الملفّ index.html. بنفس الطّريقة سنقوم بتقديم كلّ من ملفّي posts.html الخاصّ بالمقالات و users.html الخاص بالمُستخدمين. حدّث المُوجّه في مُخطّط المقالات ليُصبح كما يلي: # project/posts/views.py from flask import Blueprint, render_template @posts.route('/') # 127.0.0.1:5000/posts def index(): return render_template('posts.html') # render project/posts/templates/posts.html وهكذا سيكون الموجّه الجديد في المُخطّطات الخاصّة بالمُستخدمين: # project/users/views.py from flask import Blueprint, render_template @users.route('/') # 127.0.0.1:5000/users def index(): return render_template('users.html') # render project/users/templates/users.html هكذا إن طلب الزّائر أي صفحة من الصّفحات الثّلاث فسيُقدّمُ ملفّ HTML المُناسب. استخدام إطار Bootsrap لتنسيق الصّفحات الهدف من هذه السّلسلة هو التّعريف بكيفيّة إنشاء تطبيقات ويب ديناميكيّة بلغة بايثون وإطار فلاسك ولن أتطرّق إلى لغات أساسيّة مثل HTML و css وJavascript، ولأنّ تطبيقات الويب في يومنا هذا أصبحت تعتمد كثيرا على المظهر الجميل والاستخدام المرن باستعمال كل من لغتي css وjavascript، فمن الضّروري أن نستعمل إطار عمل لسد هذا الفراغ وإتاحة تصميم أنيق لتطبيقنا. سنستخدم في هذا المشروع إطار bootsrap 3 الذي يُمكنك تحميله من الموقع الرّسمي وفك الضّغط عن ملفّ zip داخل مجلّد project/static، سأعتمد كذلك على مشروع Bootsrap-rtl لتعريب وتوجيه التّنسيق من اليمين إلى اليسار وسأضعه في مجلّد project/static/css كما سأضع مكتبة jquery داخل مجلّد project/static/css/bootstrap لذا تأكّد من أنّك قد جهّزت كلّ شيء قبل أن تبدأ في استدعاء الملفّات السّاكنة. بعد تجهيز مُجلّد static ستكون المُجلّدات والملفّات بداخله كما يلي: project/static/ ├── bootstrap │ ├── css │ │ └── bootstrap.min.css │ └── js │ ├── bootstrap.min.js │ └── jquery.js └── css ├── bootstrap-rtl.min.css └── style.css قد تكون هناك ملفّات إضافيّة حسب ما قُمت بتنزيله عندما نزّلت إطار العمل Bootstrap، لكنّ الملفّات أعلاه هي الأهم وهي التّي سنحتاج إليها حاليّا. إذا أردت إضافة ملفّات Javascript أخرى، فيُمكنك إمّا أن تضعها داخل مُجلّد js المُتواجد داخل المُجلّد bootstrap أو أن تُنشئ مُجلّدا جديدا باسم js داخل مُجلّد الملفّات السّاكنة static. الوراثة في قوالب Jinja2 لنقل بأنّنا نُريد عرض جملة واحدة في كل ملف من ملفات HTML الثلاثة. يُمكن أن أقوم بما يلي في كل ملفّ: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> Hello </body> </html> مع تعويض Hello بالجملة التّي ارغب في عرضها عند طلب كل موجّه. هذه الطّريقة تعمل بشكل جيّد، لكن ألا تُلاحظ بأنّنا سنُكرّر نفس الأسطر في كلّ ملف؟ عوضا عن تكرار الأسطر في كلّ ملف يُمكننا استعمال مبدأ الوراثة في مجرّك القوالب Jinja. وللاستفادة من هذا المبدأ في المثال السّابق، يُمكن أن نكتب الأسطر التّي تتكرّر في كل ملف في ملفّ رئيسي واحد يُعتبر القاعدة لجميع الملفّات الأخرى، وهذا هو دور ملفّ project/templates/base.html الذي أنشأناه سابقا، ففيه يُمكننا وضع الأسطر السّابقة مع تخصيص المكان الذي سنستطيع كتابة الجملة الخاصّة بكل ملف فيه. افتح ملفّ base.html وضع فيه ما يلي: {# project/templates/base.html #} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>{% block title %} {% endblock %}</title> <script src="{{ url_for("static", filename="bootstrap/js/jquery.js") }}"></script> <script src="{{ url_for("static", filename="bootstrap/js/bootstrap.min.js") }}"></script> <link rel="stylesheet" href="{{ url_for("static", filename="bootstrap/css/bootstrap.min.css") }}"> <link rel="stylesheet" href="{{ url_for("static", filename="css/style.css") }}"> <link rel="stylesheet" href="{{ url_for("static", filename="css/bootstrap-rtl.min.css") }}"> </head> <body> {% block content %} {% endblock %} </body> </html> احفظ الملف وأغلقه. لاحظ بأنّنا قمنا باستدعاء جميع الملفّات التّي سنحتاج إليها للتّنسيق وإضافة ميّزات أخرى بلغة Javascript، وقُمنا كذلك بوضع كتلة Block باسم title لتعويضها بالعنوان في كل ملفّ، أمّا كتلة content فهي التّي ستُعوّضُ بالمحتوى (كلمة Hello في مثالنا السّابق). يُمكنك إضافة ملفّات أخرى إلى مُجلّد static، لكن لا تنس أن تقوم باستيرادها في ملفّ base.html لكي تعمل في جميع الصّفحات. بعد تجهيز الملفّ base.html الذي سيُشكّل قاعدة للملفّات الأخرى، سنُنشئ الآن ثلاثة ملفّات HTML رئيسيّة، وهي كما يلي: ملفّ HTML للصّفحة الرّئيسية (مساره project/templates/index.html ). ملفّ HTML للصّفحة الرّئيسية للمُستخدمين (مساره project/users/templates/users.html). ملفّ المقالات الرّئيسي (مساره project/posts/templates/posts.html). لملء الملفّات، ضع الشّيفرة التّاليّة في كلّ ملفّ يُمكنك الاستعانة بالمسار المُشار إليه في التّعليق أعلى الشّيفرة للوصول إلى الملفّ بشكل أسرع. ملفّ الصّفحة الرّئيسية: {# project/templates/index.html #} {% extends 'base.html' %} {% block title %} كلمة – الصّفحة الرّئيسية {% endblock %} {% block content %} <h1> مرحبا بك في تطبيق كلمة </h1> {% endblock %} ملفّ الصّفحة الرّئيسية للمُستخدمين: {# project/users/templates/users.html #} {% extends 'base.html' %} {% block title %} كلمة – المُستخدمون {% endblock %} {% block content %} <h1> الصّفحة الرّئيسية للمُستخدمين </h1> {% endblock %} ملفّ الصّفحة الرّئيسية للمقالات: {# project/posts/templates/posts.html #} {% extends 'base.html' %} {% block title %} كلمة – المقالات {% endblock %} {% block content %} <h1> الصّفحة الرّئيسية للمقالات </h1> {% endblock %} كما ترى عوضا عن تكرار الكثير من شيفرات HTML قمنا ببساطة بإنشاء ملفّ واحد والوراثة منه باستخدام الجملة extends 'base.html' وفي كلّ مكان نضع المحتوى المناسب، وبهذه الطّريقة سنمتلك طريقة ديناميكية لتحديد عنوان ومحتوى كلّ موجّه مرتبط بقالب معيّن، بالإضافة إلى أنّ الملفّات السّاكنة مثل ملفّاتcss و js ستكون مُتاحة في جميع الملفّات التّي ترث من الملّف الرّئيسي base.html. مُلاحظة: في بعض المشاريع المكتوبة بلغة بايثون وإطار العمل فلاسك، يُمكن أن يُسمّى الملفّ المُشترك باسم layout.html عوضا عن base.html، لكنّ المبدأ هو نفسه. الآن إن عدت إلى الموجّهات السّابقة، فستُلاحظ بأنّ التّنسيق ومكوّنات كلّ صفحة قد تغيّرت. الرّبط بين المُوجّهات باستخدام الدّالة url_for بعد أن تعرّفنا على كيفيّة تقديم قوالب HTML، أصبح بإمكاننا عرض صفحات للمُستخدم حسب المُوجّه المطلوب، وبقي لنا التّعرف على كيفيّة الرّبط بين هذه الموجّهات. للرّبط بين موجّهات المُخطّطات باستخدام الدّالة url_for، نقوم أولا بوضع اسم المُخطّط ثمّ اسم الدّالة التّي يتم استدعاؤها عند الوصول إلى المُوجّه، وبما أنّنا سمينا هذه الدّوال باسم index فللرّبط بينها يُمكنك أن تقوم بشيء مُشابه لما يلي: url_for('index') # / url_for('posts.index') # /posts url_for('users.index') # /users لو كانت الدّالة تقبل مُعاملا لتوجّب علينا تمرير قيمته كالآتي: url_for('posts.post_by_id', post_id = 1) للرّبط بين الموجّهات الثّلاثة التّي سبق وأن أنشأناها سنُضيف ما يلي مُباشرة بعد وسم h1 في كل ملفّ من ملفّات HTML حسب المسار في بداية الشّيفرة. الرّوابط في الصّفحة الرّئيسية: {# project/templates/index.html #} <a href="{{ url_for("posts.index") }}"> اضغط هنا للوصول إلى صفحة المقالات </a> <br> <a href="{{ url_for("users.index") }}">اضغط هنا للوصول إلى صفحة المُستخدمين</a> رابط العودة إلى الصّفحة الرّئيسية في صفحة المقالات: {# project/posts/templates/posts.html #} <a href="{{ url_for("index") }}">اضغط هنا للعودة إلى الصّفحة الرّئيسيّة</a> رابط العودة إلى الصّفحة الرّئيسية في صفحة المُستخدمين: {# project/users/templates/users.html #} <a href="{{ url_for("index") }}">اضغط هنا للعودة إلى الصّفحة الرّئيسيّة</a> بعد حفظ الملفّات، ستجد بأنّ الصّفحة الرّئيسية أصبحت تحتوي على رابط لكلّ من صفحتي المقالات والمُستخدمين مع رابط للعودة إلى الصّفحة الرّئيسية في كل صفحة من الصّفحتين. الرّبط بين المُوجّهات مع تمرير مُعاملات للدالّة التّابعة لكل موجّه. لفهم مبدأ المعاملات أكثر، سنقوم بإضافة موجّه للوصول إلى مقال برقم مُعرّفه في ملفّ project/posts/views.py كما يلي: # project/posts/views.py #.. #.. @posts.route('/<int:id>') def post_by_id(id): return render_template('post.html', id = id) لاحظ بأنّنا خصّصنا المعامل id من نوع int أي عدد صحيح، فإن لم تكن قيمة المُعامل عند الوصول إلى الرّابط عددا صحيحا فإنّ ذلك سيُسبّب خطأ من نوع 404. بمعنى أدق، الرّابط /posts/1 صحيح، أمّا /posts/abc فسيرجع الخطأ ذو الرّقم 404. سنتعرّف على كيفيّة التّعامل مع هذه الأخطاء في درس قادم. سنضع ما يلي في ملفّ post.html الذي قدّمناه: {% extends 'base.html' %} {% block title %} كلمة – المقال ذو المُعرّف {{ id }} {% endblock %} {% block content %} <h1> صفحة المقال الواحد ذو رقم المُعرّف {{id}} </h1> <a href="{{ url_for("index") }}">اضغط هنا للعودة إلى الصّفحة الرّئيسيّة</a> {% endblock %} لنستخدم الآن الدّالة url_for داخل ملفّ posts.html لضرب مثال على كيفيّة تمرير مُعاملات إلى الدّالة. أضف ما يلي إلى ملفّ posts.html مُباشرة بعد الوسم <h1>: <a href="{{url_for("posts.post_by_id", id=1)}}"> <h2> رابط المقال الأول </h2> </a> <a href="{{url_for("posts.post_by_id", id=2)}}"> <h2> رابط المقال الثّاني </h2> </a> الآن، إن دخلت إلى صفحة المقالات فستلاحظ رابطين لمقالين وهميّين، الأول رقم معرّفه 1 والآخر رقم مُعرّفه 2، ورابط المقالين كالتّالي: http://127.0.0.1:5000/posts/1 http://127.0.0.1:5000/posts/2 يُمكنك كذلك زيارة صفحات أخرى عبر تغيير قيمة رقم المُعرّف إلى أي عدد صحيح، والتّالي بعض الأمثلة: http://127.0.0.1:5000/posts/99 http://127.0.0.1:5000/posts/232 في المقالات القادمة سوف نعوّض جملة “صفحة المقال ذي المُعرّف رقم * ” بعنوان المقال وسنعرض محتواه وقسمه أسفل العنوان مع الوسوم الخاصّة بالمقال، وذلك بجلب كل مقال حسب رقم مُعرّفه في قاعدة البيانات. خاتمة تعرّفنا في هذا الدّرس على جزء آخر من أساسيّات تطوير تطبيقات ويب أكثر تعقيدا وذات مهام مُتعدّدة، هذا الدّرس يُمثّل قسما صغيرا فقط ممّا يجب عليك معرفته حول كيفيّة التّعامل مع قوالب HTML ومحرّكها Jinja الذي يُرافق إطار العمل Flask افتراضيّا، لكنّك ستجده في مشاريع بايثون أخرى تتعلّق بتطوير الويب وبعض المجالات الأخرى ولو لم يكن إطار Flask حاضرا، لذا فتعلّم أساسيّات Jinja سيُخوّلك لتعلّم أساسيّات أطر عمل أخرى ومُختلف الأدوات التّي تعتمد عليه بشكل أسرع.
-
مُقدّمة تعلّمنا في الدّروس السّابقة كيفيّة إدارة مشروع مكتوب بإطار العمل فلاسك، وقد تعرّفنا في الدّرس السّابق على كيفيّة التّعامل مع قوالب HTML، في هذا الدّرس سنُكمل هذا الجزء من تطوير الويب بتعلّم أساسيّات مُحرّك القوالب Jinja الذي يأتي مُرفقا مع إطار العمل فلاسك افتراضيّا، والذّي تُعتبر أساسيّاته أمرا مُهمّا لكونه يُستعمل في العديد من مشاريع بايثون أخرى بالإضافة إلى إطار العمل فلاسك. الدّوال في Jinja يُمكننا تعريف دوال لتحمل شيفرة معيّنة مع معاملات على Jinja لإعادة استعمالها وتحقيق مبدأ DRY أو Don’t Repeat Yourself وترجمته إلى اللغة العربيّة هي “لا تكرّر نفسك” ويعني بأنّه لا يجب عليك تكرار كتابة نفس الشّيفرة، وكلّما وجدت نفسك تُكرّرها توقّف وفكّر في إيجاد حل لتجنّب هذا التّكرار، إما بوضعها في دالّة أو شيء من هذا القبيل لتسهل عليك إعادة استعمالها. في Jinja مبدأ الدّوال موجود في ما يُسمى بالماكرو macro ويُمكن تعريف واحدة كما يلي: {% macro name(param1, param2) %} . . . {% endmacro %} كما تُلاحظ، الأمر شبيه بكيفيّة إنشاء الدّوال في لغة بايثون، لاحظ فقط كيف نُنهي الجزء الخاص بالدّالة بكلمة endmacro. يُمكننا استدعاء الماكرو في مكان آخر من القالب كما نعرض قيمة مُتغيّر معيّن: {{ name('pram1_value', 'pram2_value' }} يُمكننا كذلك تعريف الماكرو دون أية مُعاملات. ومن الشّائع أن توضع في ملف باسم _helpers.html في مجلّد templates الرّئيسي، وبعدها لاستخدامه يجب استيراده في أعلى الملف كما يلي: {% from "_helpers.html" import macro_name %} مع استبدال macro_name باسم الماكرو، تأكّد فقط بأنّك تستدعيه في أعلى الملفّ عوضا عن أي مكان آخر. بعد الاستدعاء ستتمكّن من استخدامه كالمُعتاد. مثال على ماكرو هناك العديد من الاستخدامات لهذه الخاصيّة، وسنتعرّف على بعض منها أثناء تطويرنا للتّطبيق، وهذا ما جعلني أشرح الفكرة هنا لتُفهم أكثر. في هذا المثال سنقوم بإنشاء ماكرو لتحويل قائمة بايثون إلى قائمة HTML. فمثلا لو كانت لدينا القائمة التّالية: list = ['Abdelhadi', 'Ayman', 'Ibrahim'] فسنستطيع تحويلها إلى التّالي بسطر واحد: <ul> <li>Abdelhadi</li> <li>Ayman</li> <li>Ibrahim</li> </ul> أولا سنُنشئ الماكرو كما يلي: {% macro py_to_html(list) %} <ul> {% for item in list %} <li>{{ item }}</li> {% endfor %} </ul> {% endmacro %} ثمّ نستدعيه كما يلي (على فرض أنّ list عبارة عن قائمة بايثون عاديّة): {{ py_to_html(list) }} ما ستُلاحظه هو أنّ النّتيجة ستكون عبارة عن قائمة HTML تحتوي على عناصر القائمة list، وهذا طبيعي لأنّ ما يفعله الماكرو ببساطة هو الدّوران حول المُعامل list الذي يستقبله ويعرض كل عنصر من القائمة داخل وسمي <li></li>. وهذه هي ببساطة طريقة عمل الماكرو، ويُمكنك إعادة استعماله أكثر من مرّة مع تمرير مُعاملات أخرى دون الحاجة إلى إعادة الشّيفرة مرارا وتكرارا. يُمكنك كذلك إنشاء ماكرو بدون أيّة مُعاملات، فمثلا الماكرو التّالي سيقوم بعرض الجملة “مرحبا بالعالم” في كل مرّة يتمّ استدعاؤه فيه: {% macro hello_world() %} مرحبا بالعالم {% endmacro %} وطريقة استدعائه في قالب HTML ستكون كالتّالي: {{ hello_world() }} سنتعرّف أثناء تطويرنا لتطبيق “كلمة” على المزيد من الأمثلة لكيفيّة استعمال ميّزة الماكرو في مُحرّك القوالب Jinja، وإن تابعت سلسلة استعمال مكتبة WTForms مع إطار العمل Flask فلا شك بأنّك ستأخذ فكرة واضحة عن كيفيّة عمل الماكرو كما ستحصل على مثال واقعي لاستخدامه. المُرشّحات (filters) في Jinja2 المُرشّحات مبدأ جديد يتوجّب عليك فهمه لكتابة تطبيقات ويب أفضل. يُطبّق المُرشّح في مُحرّك القوالب Jinja على مُتغيّر لتغيير قيمته من حالة إلى أخرى. التّالي مثال على كيفيّة عرض قيمة المُتغيّر name في مُحرّك القوالب Jinja: {{ name }} إذا ما طبّقنا على هذا المتغيّر مُرشّحا فسيتوجّب علينا أن نقسم بين المُتغيّر والمُرشّح بعلامة |. المثال التّالي يُوضّح كيفيّة تطبيق مُرشّح وهمي سنُسمّيه filter على المُتغيّر name: {{ name | filter }} نستعمل المُرشّحات كما نستعمل في لغة Python دوال تأخذ قيمة وتقوم بعمليّة أو عدّة عمليّات على هذه القيمة وتُرجع القيمة النّهائيّة التّي تُمثّل النّتيجة. إذا ما طبقت مُرشّحا على مُتغيّر ما، فقيمة المُتغيّر يجب أن تتغيّر، وسنرى بعض أهم المُرشّحات التّي يُوفرّها مُحرّك القوالب Jinja فيما بعد. يُمكنك كذلك أن تُمرّر مُعاملات إلى مُرشّح عبر تحديد القيم داخل قوسين كما يلي: {{ name | filter(argument1, argument2, argument3) }} المُعاملات التّي تُمرّر تُحدّد نوعيّة العمليّات التّي ستخضع لها القيمة قبل أن تُرجع النّتيجة. يُمكن كذلك استخدام أكثر من مُرشّح عبر تقسيمها بالرمز | كما في المثال التّالي: {{ name | filter1() | filter2() | filter3() }} كل مُرشّح يجري عمليّة على القيمة التّي تسبقه، لذا فطريقة عمل المثال أعلاه هو كالتّالي: يأخذ المُرشّح filter1 قيمة المُتغيّر name ويُرجع نتيجة تُعتبر مُجرّد قيمة أخرى. تمرّ النّتيجة السّابقة على المُرشّح filter2 لنحصل على قيمة جديدة تُعتبر نتيجة لعمل المُرشّح filter2 على ما أرجعه المرشّح filter1. يأخذ المُرشّح filter3 القيمة التّي نحصل عليها في النّتيجة السّابقة ويُجري عليها عمليّة ثمّ يُرجع قيمة جديدة. وبما أنّ آخر قيمة حصلنا عليها هي ما أنتجه المُرشّح filter3 على ما سبقه من قيمة، فما سيظهر للمُستخدم الذي يزور صفحة HTML التّي حدث بها ما حدث هو القيمة الأخيرة. ما فائدة المُرشّحات؟ رغم أنّ المُرشّحات في Jinja يُمكن أن تُستبدل بعمليّات على المُتغيّرات قبل تقديم قالب HTML عند العمل مع إطار العمل Flask، إلّا أنّ استعمال المُرشّحات حل أفضل وأكثر مرونة، وفي بعض الحالات يُمكن أن تحفظ نفسك من تكرار الكثير من الشّيفرة إن استعملت مُرشّحا يُوفّره مُحرّك القوالب Jinja عوضا عن كتابة الشّيفرة باستعمال لغة Python في كل مرّة تحتاج فيها إلى تغيير قيمة إلى قيمة أخرى. بالإضافة إلى أنّك تستطيع إنشاء مُرشّحات خاصّة بك لتستعملها مع إطار العمل Flask وقوالب Jinja إن أردت ذلك، فمثلا يُمكنك أن تكتب مُرشّحا لتحويل تاريخ مُعيّن من الصّيغة العاديّة (1-1-2017) إلى صيغة (قبل كذا يوم/ قبل كذا شهر/ منذ X أشهر …). سنرى كذلك بعض الاستعمالات للمُرشّحات فيما بعد، فمثلا، سنرى كيفيّة استعمال مُرشّح تُوفّره لنا إضافة Flask-Gravatar (سنرى المزيد عن إضافات Flask فيما بعد) لتحويل بريد إلكتروني إلى الصّورة المُرتبطة به على خدمة Gravatar. مثال على استخدام مُرشّح لتحويل نصّ إلى أحرف كبيرة كمثال على كيفيّة استخدام مُرشّح مُتوفّر مُسبقا بحيث يُمكنك استخدامه في أي قالب HTML في مشروع Flask الخاصّ بك، سننظرُ إلى كيفيّة استخدام المُرشّح upper الذي يعمل بنفس طريقة عمل التّابع upper() في لغة بايثون، والذي يُمكنك استخدامه لتحويل نصّ عادي إلى أحرف كبيرة Uppercase. أولا، سنُضيف مُوجّها جديدا إلى ملفّ __init__.py داخل مُجلّد المشروع project، يُمكنك اعتبار هذا المُوجّه حقل تجارب لتختبر به مُختلف المُرشّحات التّي سأسردها فيما بعد ومن المُفضّل حذفه إذا انتهيت منه. سيكون المُوجّه كالتّالي: @app.route('/filters') def filters(): return render_template('filters.html') المُوجّه بسيط جدّا، والعمل الحقيقي سيكون داخل الملفّ filters.html الذي قدّمناه كجواب في الموجّه. أنشئ الملفّ filters.html داخل المُجلّد templates المُتواجد في مُجلّد المشروع project. داخل الملفّ filters.html، أضف ما يلي: {% extends 'base.html' %} {% block title %} كلمة – المُرشّحات {% endblock %} {% block content %} <h1> {{ "Hello World!" | upper() }} </h1> {% endblock %} لاحظ الجزء {{ "Hello World!" | upper() }}، إن تابعت ما سبق جيّدا، فستفهم بأنّنا قُمنا في هذا الجزء بتطبيق المُرشّح upper على السّلسلة النّصيّة Hello World ، وبالطّبع، فإنّك تستطيع أن تُعوّض السّلسلة النّصيّة بمُتغيّر يحمل نفس القيمة أو قيمة أخرى. إن زرت العنوان http://127.0.0.1:5000/filters فستُلاحظ النّص HELLO WORLD!، ما يعني بأنّ المُرشّح upper قد أدّى مهمّته. استعمال المُرشّحات مع نصّ مُتعدّد الأسطر في الأمثلة السّابقة، تعرّفنا على كيفيّة استعمال مُرشّح في سطر واحد كما يلي: {{ "Hello World!" | upper() }} لكن ماذا لو أردنا استعمال المُرشّح في جزء أكبر من الشّيفرة؟ كاستعمال المُرشّح upper مع فقرة كاملة أو نص متعدّد الفقرات. يُمكننا استخدام المُرشّح كما نستعمل الجملة الشّرطية if أو حلقة for في Jinja، وذلك عبر إحاطة ما نُريد تطبيق المُرشّح عليه بكل من {% filter upper %} و {% endfilter %}، مع تغيير upper بالمُرشّح الذي تُريد استعماله. المثال التّالي يُمثّل كيفيّة استعمال المُرشّح upper مع نصّ مُتعدّد الأسطر: {% filter upper() %} Hello World! {% endfilter %} خاتمة تعرّفنا في هذا الدّرس كيفيّة استعمال خاصيّة الماكرو في مُحرّك القوالب Jinja لتفادي تكرار شيفرة عدّة مرّات، كما تعرّفنا على ماهيّة مُرشّحات Jinja وكيفيّة استعمالها والهدف منها، في الدّرس التّالي، سنلقي نظرة على بعض من أهمّ المُرشّحات التّي تأتي مبنيّة مُسبقا في مُحرّك القوالب Jinja والتّي يُمكنك استعمالها مُباشرة مع تطبيقات فلاسك الخاصّة بك.
-
مُقدّمة بعد أن تعرّفنا على كيفيّة استخدام المُرشّحات في قوالب HTML، سنتعرّف في هذه الفقرة على بعض من أهم المُرشّحات المُتوفّرة في مُحرّك القوالب Jinja، والتّي يُمكنك استعمالها مُباشرة مع مشاريع Flask الخاصّة بك. المُرشّح default لتوفير قيمة افتراضيّة في بعض الأحيان، يُمكن أن يكون متغيّرٌ غير مُتوفّرٍ في القالب، لكن يُمكنك توفير قيمة افتراضيّة لتظهر في هذه الحالة. كمثال على ذلك، لنفترض بأنّك تعرض عنوان مقال باستخدام السّطر التّالي: <h1> {{ title }} </h1> في حالة لم يكن المُتغيّر title مُعرّفا فقد يُشوّه ذلك مظهر الصّفحة أو من المُمكن أن تحدث أخطاء غير مُتوقّعة، يُمكننا عوضا عن ذلك عرض النّص Title Not Found باستخدام السّطر التّالي عوضا عن السّطر السّابق: <h1> {{ title | default("Title Not Found") }} </h1> يُمكننا الآن التّأكّد من أنّ الجملة Title Not Found تظهر فقط في حالة لم يكن المُتغيّر title مُعرّفا، وذلك عبر تعريفه قبل استدعائه باستخدام السّطر التّالي: {% set title = "A title for a post" %} تأكّد فقط من تعريف المُتغيّر في سطر يسبق السّطر الذي تستدعي فيه قيمته. عند تعريف المُتغيّر ستظهر قيمته بشكل عادي، أمّا إن لم يكن مُعرّفا فالجملة “Title Not Found” ستظهر عوضا عن ذلك. المُرشّح capitalize لتحويل الحرف الأول من كلمة إلى حرف كبير في بعض اللغات اللاتينيّة، من المُهمّ أن تجعل الحرف الأول من بعض الأسماء حرفا كبيرا، فمثلا كتابة اسم على شكل Ali طريقة تعبير أفضل من كتابته على شكل ali. ولتحويل كل قيمة مُعيّنة إلى هذه الحالة يُمكننا استعمال المُرشّح capitalize كما يلي: <h1> {{ "academy" | capitalize() }} </h1> نتيجة المثال السّابق ستكون الكلمة academy مكتوبة على شكل Academy، لاحظ بأنّ الحرف الأول أصبح كبيرا وبقيّة الأحرف عاديّة. المُرشّح title لتحويل قيمة نصيّة إلى طريقة كتابة العناوين لا شك أنّك لاحظت بأنّ مواقع الأخبار الانجليزيّة والمُدوّنات تكتب عناوين مقالاتها بحيث يكون الحرف الأول من كل كلمة حرفا كبيرا. فعوضا عن كتابة عنوان بالطّريقة المُواليّة: how to use the flask framework to develop web applications فإنّ الطّريقة الصّحيحة هي بكتابته كما يلي: How To Use The Flask Framework To Develop Web Applications ولحسن الحظ، فإنّ مُحرّك القوالب Jinja يُسهّل علينا مهمّة تحويل جميع العناوين المُتواجدة في قاعدة البيانات إلى الشّكل الصّحيح للعناوين دون الاضطرار إلى تعديلها واحدا واحدا. يكفي استخدام المُرشّح title لتحويل أي عنوان كيف ما كان إلى الشّكل الصّحيح، والتّالي مثال على كيفيّة استخدام هذا المُرشّح: <h1> {{ "how to use the flask framework to develop web applications" | title() }} </h1> بالإضافة إلى كل من upper، capitalize و title فالمُرشّح lower يقوم بتحويل أي سلسلة نصّيّة إلى أحرف صغيرة. المُرشّح first لعرض أول عنصر من مجموعة عناصر إذا كنت تتعامل مع مُتغيّر يحمل مجموعة من القيم كقائمة بايثون تحتوي على العديد من العناصر، فإنّك تستطيع عرض أول عنصر دون العناصر الأخرى عبر استخدام المُرشّح first. المثال التّالي يُوضّح كيفيّة استخدام المُرشّح first: {% set list = ["One", "Two", "Three"] %} <h1> {{ list | first() }} </h1> في المثال أعلاه، نستخدم الكلمة المفتاحيّة set لتعريف مُتغيّر باسم list والذي يحمل بدوره قائمة من ثلاثة قيم، أمّا في السّطر الذي يليه، فنُطبّق المُرشّح first على القائمة list التّي أنشأناها قبل قليل. إذا عدت إلى الصّفحة الخاصّة بالمُرشّحات الآن، فستُلاحظ بأنّ ما بداخل الوسم <h1> هو القيمة One فقط، وذلك لأنّها أول قيمة في مجموعة القيم المُتواجدة داخل القائمة list. المُرشّح float لتحويل الأعداد إلى أعداد عشريّة هذا المُرشّح يعمل بنفس طريقة عمل الدّالة float() في لغة بايثون، إذ يقوم بتحويل أي عدد مهما كان نوعه إلى عدد عشري. يُمكنك استخدامه كما يلي: {{ 10 | float() }} ستُلاحظ بأنّ العدد 10 قد حوّل ليُصبح على شكل 10.0. المُرشّح يعمل مع السّلاسل النّصيّة كذلك، لذا فالمثال التّالي سيقوم بإنتاج 10.0 كذلك: {{ "10" | float() }} إذا لم يكن بالمقدور تحويل القيمة إلى عدد عشري فستظهر القيمة 0.0 عوضا عن ذلك. يُمكنك تجربة القيمة الافتراضيّة بمُحاولة تحويل سلسلة نصيّة إلى عدد عشري بمثال مُشابه لما يلي: {{ "Hello Word" | float() }} ستُلاحظ بأنّ النّتيجة هي 0.0، وهي القيمة الافتراضية التّي تظهر إذا ما كانت القيمة غير قابلة للتّحويل إلى عدد عشري. يُمكنك تغيير القيمة الافتراضيّة 0.0 إلى أي قيمة أخرى عبر تمرير القيمة الافتراضيّة الجديدة إلى المُعامل default كما يلي: {{ "Hello Word" | float(default="Error: value cannot be converted into a floating point number") }} بعد هذا التّغيير، ستجد بأنّ نتيجة تحويل قيمة لا يُمكن تحويلها إلى عدد عشري هي الجملة “Error: value cannot be converted into a floating point number” ويُمكنك تغيير هذه الرّسالة كيفما تشاء. المُرشّح int لتحويل القيم إلى أعداد صحيحة يعمل هذا المٌرشّح بطريقة مُشابهة لكيفيّة عمل المُرشّحfloat، إذ أنّ المُرشّح int يُحوّل أي قيمة إلى عدد صحيح، ويُمكنك استخدامه كما يلي: {{ 10.0 | int() }} وكما الحال مع المُرشّحfloat، فإنّ المُرشّح int يُحوّل أي قيمة غير قابلة إلى التّحويل إلى عدد صحيح إلى القيمة 0، ويُمكنك تعديل هذه القيمة الافتراضيّة عبر تمرير القيمة الجديدة إلى المُعامل default كما يلي: {{ "Hello Word" | int(default="Error: value cannot be converted into an integer number") }} المُرشّح join لضمّ عناصر مجموعة من القيم وجمعها لتكون قيمة واحدة في لغة بايثون البرمجيّة، يُمكننا جمع عناصر قائمة أو مجموعة من السّلاسل النّصيّة لتصبح سلسلة نصيّة واحدة باستخدام التّابع join. في مُحرّك القوالب Jinja، يُمكننا استخدام المُرشّح join للوصول إلى نفس النّتيجة. يُمكنك استخدام المُرشّح join كما يلي: {{ [1, 2, 3] | join() }} ستُلاحظ بأنّ النّتيجة في المُتصفّح هي 123. إليك مثالا آخر: {{ ["One", "Two", "Three"] | join() }} هذه المرّة ستكون النّتيجة القيمة OneTwoThree. يُمكنك كذلك الفصل بين العناصر بفاصل عبر تمريره إلى المُرشّح كمُعامل. المثال الأول: {{ [1, 2, 3] | join('|') }} في هذا المثال، ستُلاحظ بأنّ النّتيجة هي 1|2|3 عوضا عن 123 لأنّنا وضعنا فاصلا بين عناصر القائمة. المثال الثّاني: {{ ["One", "Two", "Three"] | join("-") }} هذه المرّة ستُلاحظ بأنّ النّتيجة هي One-Two-Three. المُرشّح last المُرشّح last يعمل بطريقة مُعاكسة للمُرشّح first، إذ أنّ هذا الأخير يعرض أول قيمة من مجموعة قيم، والمُرشّح last يعرض آخر قيمة من المجموعة. المثال التّالي يُوضّح كيفيّة استعمال المُرشّح last لعرض آخر قيمة من القائمة names: {% set names = ['Kamal', 'Ali', 'Ahmed', 'Khaled'] %} <h1> {{ names | last() }} </h1> إذا طبّقت المثال أعلاه، فسيتوجّب أن تحصل على الاسم Khaled كنتيجة لأنّه آخر عنصر من القائمة names. المثال التّالي يجمع كلّا من المُرشّح first والمُرشّح last: {% set names = ['Kamal', 'Ali', 'Ahmed', 'Khaled'] %} <h1> First: {{ names | first() }} </h1> <h1> Last: {{ names | last() }} </h1> النّتيجة ستكون كالتّالي: First: Kamal Last: Khaled ختاما تعرّفنا في هذا الدّرس على جزء من أهم مُرشّحات Jinja التّي يُمكنك أن تعتمد عليها في تطوير تطبيقات فلاسك الخاصّة بك، وسنستعمل بعضا منها في تطوير تطبيق “كلمة” في ما يلي من الدّروس، مُرشّحات Jinja كثيرة ولا يُمكن أن نذكرها جميعها في درس واحد، لذا فسنُكمل تغطيّة بقيّة المُرشّحات المُهمّة في دروس مُقبلة لنمرّ بعدها إلى جانب آخر من تطوير الويب أثناء تطويرنا لتطبيق كبير في هذه السّلسلة، لذا ترقّب بقيّة الدّروس.