<?xml version="1.0"?>
<rss version="2.0"><channel><title>&#x627;&#x644;&#x628;&#x631;&#x645;&#x62C;&#x629;: Flask</title><link>https://academy.hsoub.com/programming/python/flask/?d=2</link><description>&#x627;&#x644;&#x628;&#x631;&#x645;&#x62C;&#x629;: Flask</description><language>ar</language><item><title>&#x62F;&#x644;&#x64A;&#x644; &#x628;&#x646;&#x627;&#x621; &#x628;&#x648;&#x62A; &#x62A;&#x641;&#x627;&#x639;&#x644;&#x64A; &#x639;&#x644;&#x649; &#x62A;&#x64A;&#x644;&#x64A;&#x62C;&#x631;&#x627;&#x645; &#x62E;&#x637;&#x648;&#x629; &#x628;&#x62E;&#x637;&#x648;&#x629;</title><link>https://academy.hsoub.com/programming/python/flask/%D8%AF%D9%84%D9%8A%D9%84-%D8%A8%D9%86%D8%A7%D8%A1-%D8%A8%D9%88%D8%AA-%D8%AA%D9%81%D8%A7%D8%B9%D9%84%D9%8A-%D8%B9%D9%84%D9%89-%D8%AA%D9%8A%D9%84%D9%8A%D8%AC%D8%B1%D8%A7%D9%85-%D8%AE%D8%B7%D9%88%D8%A9-%D8%A8%D8%AE%D8%B7%D9%88%D8%A9-r2543/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2025_03/81.jpg.e51edb092551d3707d5d2a7d3ecfb07a.jpg" /></p>
<p>
	تليجرام من أشهر تطبيقات التراسل الفوري لديها عدد كبير من المستخدمين توفر مزايا كبيرة للمستخدمين والمطورين، أهمها برنامج الرد الآلي بوت Bot الذي يوفر واجهة بسيطة وسهلة للمستخدمين لاستقبال الرسائل منهم وأتمتة المهام برمجيًا.
</p>

<p>
	توفر المنصة للمطورين واجهة برمجة تطبيقات بوت تلجرام <a href="https://core.telegram.org/bots/api" rel="external nofollow">Telegram Bot <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr></a>، من خلالها يمكن استقبال الرسائل وتحليلها والرد عليها و إرسال إشعارات أو تنفيذ المهام برمجية.
</p>

<p>
	في هذا المقال سننشئ بوت تليجرام باستخدام بايثون Python وإطار العمل فلاسك Flask يعرض <a href="https://academy.hsoub.com/design/illustration/adobe-illustrator/%D8%AA%D8%B5%D9%85%D9%8A%D9%85-%D8%B5%D9%88%D8%B1%D8%A9-%D8%B1%D9%85%D8%B2%D9%8A%D8%A9-%D9%85%D8%B5%D8%BA%D8%B1%D8%A9-%D9%88%D9%85%D8%B3%D8%B7%D8%AD%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D9%84%D8%B3%D8%AA%D8%B1%D9%8A%D8%AA%D9%88%D8%B1-r306/" rel="">صورة رمزية مصغرة avatar</a> من موقع <a href="http://avatars.adorable.io/#demo" rel="external nofollow">Adorable Avatars</a>، وسننشره على Heroku.
</p>

<h2 id="-">
	تطوير تطبيق البوت
</h2>

<p>
	سنستخدم بيئة عمل افتراضية ضمن بايثون يمكن الرجوع إلى المقال <a href="https://academy.hsoub.com/programming/python/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%84%D9%84%D9%85%D8%B4%D8%A7%D8%B1%D9%8A%D8%B9-%D9%85%D8%B9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1751/" rel="">إعداد بيئة العمل للمشاريع مع بايثون</a>، لمعرفة الخطوات، وسنحتاج للأمور التالية:
</p>

<ul>
	<li>
		تثبيت نسخة <a href="https://www.python.org/downloads/" rel="external nofollow">بايثون python</a> ومعرفة <a href="https://academy.hsoub.com/programming/python/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-python-r2325/" rel="">أساسيات البرمجة باستخدام بايثون</a>.
	</li>
	<li>
		تثبيت نظام التحكم بالإصدارات <a href="https://git-scm.com/book/en/v2/Getting-Started-Installing-Git" rel="external nofollow">Git</a> ومعرفة أساسيات التعامل معه
	</li>
	<li>
		معرفة بأساسيات التعامل مع <a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r2201/" rel="">إطار العمل فلاسك Flask</a>
	</li>
	<li>
		وجود حساب على <a href="https://telegram.org/" rel="external nofollow">تليجرام</a>
	</li>
</ul>

<h3 id="-">
	إنشاء بوت تليجرام جديد
</h3>

<p>
	من حساب تليجرام وباستخدام بوت إدارة البوتات <a href="https://telegram.me/BotFather" rel="external nofollow">BotFather</a> الذي يوفره لنا تيليجرام يمكن إنشاء بوت جديد وتعديل جميع خصائصه كالصورة الشخصية له أو وصف البوت أو تغيير رمز الوصول، وحتى حذف البوت.
</p>

<p>
	لإنشاء بوت جديد نستخدم الأمر <code>newbot/</code> ليسألنا بعده عن تحديد اسم البوت الجديد سيظهر للمستخدمين واختيار اسم المستخدم له والذي يجب أن ينتهي بكلمة <code>bot</code>.
</p>

<p>
	سنحصل بعدها على رمز الوصول Access Token الخاص بالبوت الجديد، ومن الضروري الاحتفاظ بهذا الرمز وباسم المستخدم الذي اخترناه لاستخدامهما لاحقًا.
</p>

<p style="text-align: center;">
	<a data-fileext="png" data-fileid="169816" href="https://academy.hsoub.com/uploads/monthly_2025_03/001_BotFather.png.948afe0d870925bb08645637c6d021c9.png" rel=""><img alt="001 botfather" data-fileid="169816" src="https://academy.hsoub.com/uploads/monthly_2025_03/001_BotFather.thumb.png.d8d8e2efa7e94ebcee6d9a1838d76ada.png"></a>
</p>

<h3 id="-">
	إعداد المشروع في بايثون
</h3>

<p>
	نُنشئ بيئة عمل افتراضية جديدة لعزل متطلبات مشروعنا عن البيئة العامة للغة بايثون ونفعّلها ضمن مجلد اسمه <code>botenv</code> (يمكن اختيار أي اسم آخر) سيحتوي على جميع <a href="https://academy.hsoub.com/programming/python/%D8%A3%D9%87%D9%85-10-%D9%85%D9%83%D8%AA%D8%A8%D8%A7%D8%AA-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D8%AA%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-%D9%81%D9%8A-%D8%A7%D9%84%D9%85%D8%B4%D8%A7%D8%B1%D9%8A%D8%B9-%D8%A7%D9%84%D8%B5%D8%BA%D9%8A%D8%B1%D8%A9-r654/" rel="">مكتبات بايثون</a> التي سنستخدمها بتنفيذ الأوامر:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8080_7" style=""><span class="pln">python </span><span class="pun">-</span><span class="pln">m venv botenv</span><span class="pun">/</span><span class="pln">
botenv</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">activate</span></pre>

<p>
	سنحتاج للمكتبات التالية لبرمجة البوت:
</p>

<ul>
	<li>
		إطار عمل <a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r2201//" rel="">flask</a> لبناء تطبيقات الويب
	</li>
	<li>
		مكتبة بايثون <a href="https://github.com/python-telegram-bot/python-telegram-bot" rel="external nofollow">python-telegram-bot</a> توفر واجهة برمجية لتليجرام
	</li>
	<li>
		مكتبة بايثون <a href="https://pypi.org/project/requests/" rel="external nofollow">requests</a> الشهيرة للتعامل مع <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-http-r73/" rel="">طلبات HTTP</a>
	</li>
</ul>

<p>
	ننزلها باستخدام الأمر <code>pip install</code> ونثبتها ضمن الملف <code>requirements.txt</code> بالاستعانة بالأمر <code>pip freeze</code> ليتمكن خادم البوت من تحضريها بعد نشر البوت، وذلك بتنفيذ الأوامر التالية داخل مجلد المشروع:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_8080_9" style=""><span class="pln">pip install flask python-telegram-bot requests
pip freeze &gt; requirements.txt</span></pre>

<p>
	ستكون هيكلة الملفات فى مجلد مشروعنا كالتالي::
</p>

<ul>
	<li>
		<strong>app.py </strong>: يحوي الشيفرة الرئيسية لعمل البوت
	</li>
	<li>
		<strong>telebot/ </strong>: مجلد يحتوي على الملفات الخاصة بالبوت
	</li>
	<li>
		<strong>credentials.py </strong>: نخزن في هذا الملف المتغيرات الحساسة كاسم المستخدم ورمز الوصول
	</li>
	<li>
		<strong><strong>init</strong>.py </strong>: ملف خاص في بايثون يحدد أن المجلد الذي يحتوي عليه هو حزمة Package
	</li>
</ul>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_8080_11" style=""><span class="pln">├── app.py 
├── telebot 
│   ├── credentials.py 
│   |   . 
│   |   you can build your engine here 
│   |   . 
│   └── __init__.py 
└── botenv</span></pre>

<p>
	نضيف في الملف <code>credentials.py</code> المتغيرات التي حصلنا عليها أثناء إنشاء البوت في BotFather واللازمة للاستيثاق Authentication أثناء التعامل مع واجهة تيليجرام البرمجية للبوتات، كما يجب تعريف عنوان النطاق لخادم البوت الذي يمكن تبديله لاحقًا بعد إعداد خادم استضافة البوت:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_8080_13" style=""><span class="pln">bot_token = "رمز الوصول الخاص بالبوت الجديد" 
bot_user_name = "اسم المستخدم للبوت الجديد"
URL = "عنوان نطاق خادم البوت"</span></pre>

<p>
	ضمن ملف التطبيق الرئيس <code>app.py</code> نستورد المكتبات التي سنحتاجها، وننشئ الكائن <code>bot</code> من مكتبة <code>python-telegram-bot</code> للتفاعل مع البوت عبر الواجهة البرمجية، وننشئ الكائن <code>app</code> من مكتبة <code>flask</code> لتعريف التطبيق:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8080_15" style=""><span class="com"># استيراد المكتبات</span><span class="pln">
</span><span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> request 
</span><span class="kwd">import</span><span class="pln"> telegram </span><span class="kwd">from</span><span class="pln"> telebot</span><span class="pun">.</span><span class="pln">credentials 
</span><span class="kwd">import</span><span class="pln"> bot_token</span><span class="pun">,</span><span class="pln"> bot_user_name</span><span class="pun">,</span><span class="pln"> URL </span><span class="com"># متغيرات سنستخدمها لاحقًا</span><span class="pln">

</span><span class="kwd">global</span><span class="pln"> bot 
</span><span class="kwd">global</span><span class="pln"> TOKEN 
TOKEN </span><span class="pun">=</span><span class="pln"> bot_token </span><span class="com"># رمز الوصول</span><span class="pln">
bot </span><span class="pun">=</span><span class="pln"> telegram</span><span class="pun">.</span><span class="typ">Bot</span><span class="pun">(</span><span class="pln">token</span><span class="pun">=</span><span class="pln">TOKEN</span><span class="pun">)</span><span class="pln"> </span><span class="com"># كائن للتفاعل مع البوت</span><span class="pln">
app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln"> </span><span class="com"># كائن تطبيق الويب</span><span class="pln">

</span><span class="com">##</span><span class="pln">
</span><span class="com">## سنضيف موجهات التطبيق هنا</span><span class="pln">
</span><span class="com">##</span><span class="pln">

</span><span class="kwd">if</span><span class="pln"> __name__ </span><span class="pun">==</span><span class="pln"> </span><span class="str">'__main__'</span><span class="pun">:</span><span class="pln">
    app</span><span class="pun">.</span><span class="pln">run</span><span class="pun">(</span><span class="pln">threaded</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span></pre>

<h3 id="-">
	تعريف موجهات التطبيق
</h3>

<p>
	نعرف <a href="https://www.google.com/url?q=https://academy.hsoub.com/programming/python/flask/%25D8%25A3%25D8%25B3%25D8%25A7%25D8%25B3%25D9%258A%25D8%25A7%25D8%25AA-%25D8%25A5%25D8%25B7%25D8%25A7%25D8%25B1-%25D8%25A7%25D9%2584%25D8%25B9%25D9%2585%25D9%2584-flask-%25D8%25A7%25D9%2584%25D9%2585%25D9%2588%25D8%25AC%25D9%2587%25D8%25A7%25D8%25AA-r337/&amp;sa=D&amp;source=docs&amp;ust=1728649547728465&amp;usg=AOvVaw1V-BZ5PjF1qMS7wgT4GVXX" rel="external nofollow">موجه Route</a> من الشكل <code>{token}/</code> سيستدعيه تيليجرام لكل رسالة جديدة تُرسل للبوت وفيه سنستخرج محتوى الرسالة ونرد عليها، فإذا كان نصها أمر البدء <code>start/</code> وهو زر افتراضي يظهر للمستخدمين في أول مرة يتعاملون فيها مع أي بوت سنرسل رسالة ترحيب وشرح للمستخدم عن ما يفعله هذا البوت.
</p>

<p>
	أما إذا كانت رسالة عادية فنستخرج من نصّها الأحرف والأرقام فقط لإنشاء رابط للصورة المقابلة للنص الذي أرسلة المستخدم عبر الواجهة البرمجية للموقع <code>adorable.io</code> ونرسل تلك الصورة للمستخدم عبر البوت.
</p>

<p>
	وفي حال حدوث أي خطأ يجب إعلام المستخدم بذلك حتى يعلم أن البوت يتفاعل معه ويرسل لنا إسم آخر لتوليد صورة منه، لتكون دالة الموجه كالتالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8080_17" style=""><span class="com"># موجه استقبال الرسائل</span><span class="pln">
</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/{}'</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="pln">TOKEN</span><span class="pun">),</span><span class="pln"> methods</span><span class="pun">=[</span><span class="str">'POST'</span><span class="pun">])</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> respond</span><span class="pun">():</span><span class="pln">
   </span><span class="com"># تحويل جسم الطلب الوارد إلى كائن تلغرام</span><span class="pln">
   update </span><span class="pun">=</span><span class="pln"> telegram</span><span class="pun">.</span><span class="typ">Update</span><span class="pun">.</span><span class="pln">de_json</span><span class="pun">(</span><span class="pln">request</span><span class="pun">.</span><span class="pln">get_json</span><span class="pun">(</span><span class="pln">force</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">),</span><span class="pln"> bot</span><span class="pun">)</span><span class="pln">

   chat_id </span><span class="pun">=</span><span class="pln"> update</span><span class="pun">.</span><span class="pln">message</span><span class="pun">.</span><span class="pln">chat</span><span class="pun">.</span><span class="pln">id
   msg_id </span><span class="pun">=</span><span class="pln"> update</span><span class="pun">.</span><span class="pln">message</span><span class="pun">.</span><span class="pln">message_id

   </span><span class="com"># استخراج نص الرسالة القادمة</span><span class="pln">
   text </span><span class="pun">=</span><span class="pln"> update</span><span class="pun">.</span><span class="pln">message</span><span class="pun">.</span><span class="pln">text</span><span class="pun">.</span><span class="pln">encode</span><span class="pun">(</span><span class="str">'utf-8'</span><span class="pun">).</span><span class="pln">decode</span><span class="pun">()</span><span class="pln">

   </span><span class="kwd">print</span><span class="pun">(</span><span class="str">"got text message :"</span><span class="pun">,</span><span class="pln"> text</span><span class="pun">)</span><span class="pln">

   </span><span class="com"># إرسال رسالة ترحيب عند بدء المستخدم التفاعل مع البوت</span><span class="pln">
   </span><span class="kwd">if</span><span class="pln"> text </span><span class="pun">==</span><span class="pln"> </span><span class="str">"/start"</span><span class="pun">:</span><span class="pln">
       </span><span class="com"># رسالة الترحيب</span><span class="pln">
       bot_welcome </span><span class="pun">=</span><span class="pln"> </span><span class="str">"""
       مرحبًا بك، أرسل لي اسمك وسأولد لك صورة شخصية مناسبة
       """</span><span class="pln">
       </span><span class="com"># إرسال الرسالة</span><span class="pln">
       bot</span><span class="pun">.</span><span class="pln">sendMessage</span><span class="pun">(</span><span class="pln">chat_id</span><span class="pun">=</span><span class="pln">chat_id</span><span class="pun">,</span><span class="pln"> text</span><span class="pun">=</span><span class="pln">bot_welcome</span><span class="pun">,</span><span class="pln"> reply_to_message_id</span><span class="pun">=</span><span class="pln">msg_id</span><span class="pun">)</span><span class="pln">

   </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
       </span><span class="kwd">try</span><span class="pun">:</span><span class="pln">
           </span><span class="com"># استخراج الأحرف والأرقام من نص الرسالة</span><span class="pln">
           text </span><span class="pun">=</span><span class="pln"> re</span><span class="pun">.</span><span class="pln">sub</span><span class="pun">(</span><span class="pln">r</span><span class="str">"\W"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"_"</span><span class="pun">,</span><span class="pln"> text</span><span class="pun">)</span><span class="pln">
           </span><span class="com"># إنشاء رابط الصورة بناءًا على الرسالة</span><span class="pln">
           url </span><span class="pun">=</span><span class="pln"> </span><span class="str">"https://api.adorable.io/avatars/285/{}.png"</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="pln">text</span><span class="pun">.</span><span class="pln">strip</span><span class="pun">())</span><span class="pln">
           </span><span class="com"># إرسال الصورة للمستخدم</span><span class="pln">
           bot</span><span class="pun">.</span><span class="pln">sendPhoto</span><span class="pun">(</span><span class="pln">chat_id</span><span class="pun">=</span><span class="pln">chat_id</span><span class="pun">,</span><span class="pln"> photo</span><span class="pun">=</span><span class="pln">url</span><span class="pun">,</span><span class="pln"> reply_to_message_id</span><span class="pun">=</span><span class="pln">msg_id</span><span class="pun">)</span><span class="pln">
       </span><span class="kwd">except</span><span class="pln"> </span><span class="typ">Exception</span><span class="pun">:</span><span class="pln">
           </span><span class="com"># إرسال رسالة في حالة حدوث خطأ من طرفنا</span><span class="pln">
           bot</span><span class="pun">.</span><span class="pln">sendMessage</span><span class="pun">(</span><span class="pln">chat_id</span><span class="pun">=</span><span class="pln">chat_id</span><span class="pun">,</span><span class="pln"> text</span><span class="pun">=</span><span class="str">"حدث خطأ، جرب اسمًا آخر لأولد لك صورة مقابله"</span><span class="pun">,</span><span class="pln"> reply_to_message_id</span><span class="pun">=</span><span class="pln">msg_id</span><span class="pun">)</span><span class="pln">

   </span><span class="kwd">return</span><span class="pln"> </span><span class="str">'ok'</span></pre>

<p>
	يمكننا الاستعلام عن الرسائل الجديدة الواردة للبوت عبر دالة من الواجهة البرمجية <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> في فترات زمنية ثابتة، ولكن يتيح تيلجرام طريقة أسهل عبر خطافات الويب <a href="https://en.wikipedia.org/wiki/Webhook" rel="external nofollow">Webhooks</a>، فبدلًا من إرسال طلبات للسؤال من طرفنا سيعلمنا تيلجرام بالرسائل الجديدة عبر إرسال طلب من النوع <code>POST</code> لخادمنا يتضمن معلومات عن الرسالة.
</p>

<p>
	يجب إعلام تيليجرام ولمرة واحدة بالرابط المحدد ضمن تطبيقنا لإرسال إشعارات بالرسائل الجديدة إليه، لذا سننشئ موجه جديد سنستدعيه يدويًا لمرة واحدة يستدعي بدوره الدالة <code>setWebhook</code> لإعداد الخطاف كالتالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8080_21" style=""><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/setwebhook'</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=[</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">])</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> set_webhook</span><span class="pun">():</span><span class="pln">
    </span><span class="com"># إخبار تيليجرام برابط الخطاف في تطبيقنا</span><span class="pln">
    s </span><span class="pun">=</span><span class="pln"> bot</span><span class="pun">.</span><span class="pln">setWebhook</span><span class="pun">(</span><span class="str">'{URL}/{TOKEN}'</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="pln">URL</span><span class="pun">=</span><span class="pln">URL</span><span class="pun">,</span><span class="pln"> TOKEN</span><span class="pun">=</span><span class="pln">TOKEN</span><span class="pun">))</span><span class="pln">

    </span><span class="com"># فحص إتمام العملية بنجاح</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> s</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="str">"تم إعداد الخطاف بنجاح"</span><span class="pln">
    </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="str">"فشلت عملية إعداد الخطاف"</span></pre>

<p>
	أصبح بذلك تطبيق البوت جاهزًا وبقي نشره على الخادم ثم الذهاب لموجه إعداد الخطاف ليبدأ البوت بالعمل، وبذلك يصبح محتوى ملف <code>app.py</code> كاملًا كالتالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8080_23" style=""><span class="com"># استيراد المكتبات</span><span class="pln">
</span><span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> request 
</span><span class="kwd">import</span><span class="pln"> telegram </span><span class="kwd">from</span><span class="pln"> telebot</span><span class="pun">.</span><span class="pln">credentials 
</span><span class="kwd">import</span><span class="pln"> bot_token</span><span class="pun">,</span><span class="pln"> bot_user_name</span><span class="pun">,</span><span class="pln"> URL  

</span><span class="kwd">global</span><span class="pln"> bot 
</span><span class="kwd">global</span><span class="pln"> TOKEN 
TOKEN </span><span class="pun">=</span><span class="pln"> bot_token </span><span class="com"># رمز الوصول</span><span class="pln">
bot </span><span class="pun">=</span><span class="pln"> telegram</span><span class="pun">.</span><span class="typ">Bot</span><span class="pun">(</span><span class="pln">token</span><span class="pun">=</span><span class="pln">TOKEN</span><span class="pun">)</span><span class="pln"> </span><span class="com"># كائن للتفاعل مع البوت</span><span class="pln">

app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln">

</span><span class="com"># موجه تلقي الاشعارات</span><span class="pln">
</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/{}'</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="pln">TOKEN</span><span class="pun">),</span><span class="pln"> methods</span><span class="pun">=[</span><span class="str">'POST'</span><span class="pun">])</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> respond</span><span class="pun">():</span><span class="pln">
   </span><span class="com"># تحويل جسم الطلب الوارد إلى كائن تلغرام</span><span class="pln">
   update </span><span class="pun">=</span><span class="pln"> telegram</span><span class="pun">.</span><span class="typ">Update</span><span class="pun">.</span><span class="pln">de_json</span><span class="pun">(</span><span class="pln">request</span><span class="pun">.</span><span class="pln">get_json</span><span class="pun">(</span><span class="pln">force</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">),</span><span class="pln"> bot</span><span class="pun">)</span><span class="pln">

   chat_id </span><span class="pun">=</span><span class="pln"> update</span><span class="pun">.</span><span class="pln">message</span><span class="pun">.</span><span class="pln">chat</span><span class="pun">.</span><span class="pln">id
   msg_id </span><span class="pun">=</span><span class="pln"> update</span><span class="pun">.</span><span class="pln">message</span><span class="pun">.</span><span class="pln">message_id

   </span><span class="com"># استخراج نص الرسالة القادمة</span><span class="pln">
   text </span><span class="pun">=</span><span class="pln"> update</span><span class="pun">.</span><span class="pln">message</span><span class="pun">.</span><span class="pln">text</span><span class="pun">.</span><span class="pln">encode</span><span class="pun">(</span><span class="str">'utf-8'</span><span class="pun">).</span><span class="pln">decode</span><span class="pun">()</span><span class="pln">

   </span><span class="kwd">print</span><span class="pun">(</span><span class="str">"got text message :"</span><span class="pun">,</span><span class="pln"> text</span><span class="pun">)</span><span class="pln">

   </span><span class="com"># إرسال رسالة ترحيب عند بدء المستخدم التفاعل مع البوت</span><span class="pln">
   </span><span class="kwd">if</span><span class="pln"> text </span><span class="pun">==</span><span class="pln"> </span><span class="str">"/start"</span><span class="pun">:</span><span class="pln">
       </span><span class="com"># رسالة الترحيب</span><span class="pln">
       bot_welcome </span><span class="pun">=</span><span class="pln"> </span><span class="str">"""
       مرحبًا بك، أرسل لي اسمك وسأولد لك صورة شخصية مناسبة
       """</span><span class="pln">
       </span><span class="com"># إرسال الرسالة</span><span class="pln">
       bot</span><span class="pun">.</span><span class="pln">sendMessage</span><span class="pun">(</span><span class="pln">chat_id</span><span class="pun">=</span><span class="pln">chat_id</span><span class="pun">,</span><span class="pln"> text</span><span class="pun">=</span><span class="pln">bot_welcome</span><span class="pun">,</span><span class="pln"> reply_to_message_id</span><span class="pun">=</span><span class="pln">msg_id</span><span class="pun">)</span><span class="pln">

   </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
       </span><span class="kwd">try</span><span class="pun">:</span><span class="pln">
           </span><span class="com"># استخراج الأحرف والأرقام من نص الرسالة</span><span class="pln">
           text </span><span class="pun">=</span><span class="pln"> re</span><span class="pun">.</span><span class="pln">sub</span><span class="pun">(</span><span class="pln">r</span><span class="str">"\W"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"_"</span><span class="pun">,</span><span class="pln"> text</span><span class="pun">)</span><span class="pln">
           </span><span class="com"># إنشاء رابط الصورة بناءًا على الرسالة</span><span class="pln">
           url </span><span class="pun">=</span><span class="pln"> </span><span class="str">"https://api.adorable.io/avatars/285/{}.png"</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="pln">text</span><span class="pun">.</span><span class="pln">strip</span><span class="pun">())</span><span class="pln">
           </span><span class="com"># إرسال الصورة للمستخدم</span><span class="pln">
           bot</span><span class="pun">.</span><span class="pln">sendPhoto</span><span class="pun">(</span><span class="pln">chat_id</span><span class="pun">=</span><span class="pln">chat_id</span><span class="pun">,</span><span class="pln"> photo</span><span class="pun">=</span><span class="pln">url</span><span class="pun">,</span><span class="pln"> reply_to_message_id</span><span class="pun">=</span><span class="pln">msg_id</span><span class="pun">)</span><span class="pln">
       </span><span class="kwd">except</span><span class="pln"> </span><span class="typ">Exception</span><span class="pun">:</span><span class="pln">
           </span><span class="com"># إرسال رسالة في حالة حدوث خطأ من طرفنا</span><span class="pln">
           bot</span><span class="pun">.</span><span class="pln">sendMessage</span><span class="pun">(</span><span class="pln">chat_id</span><span class="pun">=</span><span class="pln">chat_id</span><span class="pun">,</span><span class="pln"> text</span><span class="pun">=</span><span class="str">"حدث خطأ، جرب اسمًا آخر لأولد لك صورة مقابله"</span><span class="pun">,</span><span class="pln"> reply_to_message_id</span><span class="pun">=</span><span class="pln">msg_id</span><span class="pun">)</span><span class="pln">

   </span><span class="kwd">return</span><span class="pln"> </span><span class="str">'ok'</span><span class="pln">

</span><span class="com"># موجه إعداد الخطاف</span><span class="pln">
</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/setwebhook'</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=[</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">])</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> set_webhook</span><span class="pun">():</span><span class="pln">
    </span><span class="com"># إخبار تيليجرام برابط الخطاف في تطبيقنا</span><span class="pln">
    s </span><span class="pun">=</span><span class="pln"> bot</span><span class="pun">.</span><span class="pln">setWebhook</span><span class="pun">(</span><span class="str">'{URL}/{TOKEN}'</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="pln">URL</span><span class="pun">=</span><span class="pln">URL</span><span class="pun">,</span><span class="pln"> TOKEN</span><span class="pun">=</span><span class="pln">TOKEN</span><span class="pun">))</span><span class="pln">

    </span><span class="com"># فحص إتمام العملية بنجاح</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> s</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="str">"تم إعداد الخطاف بنجاح"</span><span class="pln">
    </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="str">"فشلت عملية إعداد الخطاف"</span><span class="pln">

</span><span class="kwd">if</span><span class="pln"> __name__ </span><span class="pun">==</span><span class="pln"> </span><span class="str">'__main__'</span><span class="pun">:</span><span class="pln">
   app</span><span class="pun">.</span><span class="pln">run</span><span class="pun">(</span><span class="pln">threaded</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span></pre>

<h2 id="-heroku-">
	تحضير خادم Heroku ونشر البوت
</h2>

<p>
	يمكن استضافة البوت في أي منصة توفر خوادم ببيئة بايثون وسنستخدم في هذا المقال منصة هيروكو <a href="https://heroku.com/" rel="external nofollow">Heroku</a> لإنشاء الخادم و<a href="https://academy.hsoub.com/devops/deployment/%D9%86%D8%B4%D8%B1-%D8%A7%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%88%D8%AA%D9%88%D8%B2%D9%8A%D8%B9%D9%87%D8%A7-%D9%88%D9%81%D9%82-%D9%86%D9%87%D8%AC-%D8%A7%D9%84%D8%AA%D8%B3%D9%84%D9%8A%D9%85-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-r646/" rel="">نشر التطبيق</a> عليه، وتأكد أولًا من تثبيت أداة سطر الأوامر الخاصة بالمنصة <a href="https://devcenter.heroku.com/articles/heroku-cli#install-the-heroku-cli" rel="external nofollow">Heroku CLI</a> والتي سنستخدمها لتسجيل الدخول ونشر المشروع على المنصة.
</p>

<p>
	نبدأ بإنشاء ملف جديد بالاسم <code>Procfile</code> وهو ملف خاص بمنصة هيروكو نعرّف فيه بطريقة تشغيل التطبيق بإضافة السطر التالي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_8080_25" style=""><span class="pln">web: gunicorn app:app</span></pre>

<p>
	نذهب لحسابنا على المنصة ومن لوحة التحكم Dashboard وننشئ تطبيق جديد بالضغط على Create new app بعدها سيعاد توجيهنا إلى صفحة النشر Deploy page، وفيها ننتقل لعلامة التبويب Settings وننسخ رابط نطاق التطبيق Domain ونضعه ضمن الملف <code>credentials.py</code> كقيمة للمتغير <code>URL</code>.
</p>

<p>
	<a data-fileext="png" data-fileid="169817" href="https://academy.hsoub.com/uploads/monthly_2025_03/002_Heroku.png.f2a9b6db22f90a639c5bef2cd5260f13.png" rel=""><img alt="002 heroku" data-fileid="169817" src="https://academy.hsoub.com/uploads/monthly_2025_03/002_Heroku.png.f2a9b6db22f90a639c5bef2cd5260f13.png"></a>
</p>

<p>
	نعود لمجلد المشروع وننفذ ضمن الطرفية أمر تسجيل الدخول إلى المنصة كالتالي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_8080_27" style=""><span class="pln">heroku login -i</span></pre>

<h3 id="-git">
	تحضير مستودع Git
</h3>

<p>
	نهيئ مستودع Git في مجلد المشروع ونثبت التغييرات ضمنه ونحدد الفرع الوجهة لدفع المستودع إليه للنشر، وذلك بتنفيذ الأمر التالي مع استبدال اسم المشروع بالاسم الذي حددناه أثناء إنشاء التطبيق على المنصة:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_8080_29" style=""><span class="pln">git init
git add . 
git commit -m "first commit"
heroku git:remote -a {اسم-المشروع}</span></pre>

<p>
	يمكن أيضًا إضافة ملف <code>gitignore.</code> إلى مجلد الجذر للمشروع قبل الدفع كي لا تُرفع الملفات الغير مستخدمة إلى المستودع وضمن الخادم.
</p>

<h3 id="-">
	نشر المشروع
</h3>

<p>
	نحتاج فقط لدفع تغييرات مستودع المشروع في كل مرة نعدل فيها على التطبيق، وذلك بدفع الفرع الحالي بتنفيذ الامر:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_8080_31" style=""><span class="pln">git push heroku master</span></pre>

<p>
	ستظهر رسائل تقدم البناء في <a href="https://academy.hsoub.com/devops/linux/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%B7%D8%B1%D9%81%D9%8A%D9%91%D8%A9-%D9%84%D9%8A%D9%86%D9%83%D8%B3-linux-terminal-r18/" rel="">الطرفية terminal</a> وفي حال عدم وجود مشاكل سنرى خرج مشابه للتالي عند انتهائها سيكون التطبيق تم رفعه ونشره وأصبح جاهز للاستخدام:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_8080_33" style=""><span class="pln">remote: -----&gt; Launching...
remote:        Released v6
remote:        https://project-name.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.</span></pre>

<h3 id="-webhook">
	إعداد الخطاف Webhook
</h3>

<p>
	جهزنا سابقًا رابط ضمن التطبيق لإعداد الخطاف للبوت مع تيليجرام يجب نزوره لمرة واحدة ضمن المتصفح لإتمام الإعداد عندها ستظهر رسالة النجاح <code>تم إعداد الخطاف بنجاح</code> يصبح بعدها البوت جاهزًا للتحدث معه، وهو عنوان نطاق التطبيق على المنصة مضافًا في نهايته <code>setwebhook/</code> ويكون مشابهًا للتالي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_8080_35" style=""><span class="pln">https://appname.herokuapp.com/setwebhook</span></pre>

<h2 id="-">
	تجربة البوت
</h2>

<p>
	ضمن تطبيق تيليجرام نبحث عن اسم المستخدم للبوت الخاص بنا وبعد الضغط على زر البدء start، يمكننا إرسال أي اسم وسيرد بإرسال صورة مقابلة لذلك الاسم كما هو موضح في الفيديو أدناه:
</p>

<p style="text-align: center;">
	<a data-fileext="gif" data-fileid="169818" href="https://academy.hsoub.com/uploads/monthly_2025_03/003_-.gif.dc6d60541f8e2d6387ad904488f97f75.gif" rel=""><img alt="التحدث مع البوت" data-fileid="169818" src="https://academy.hsoub.com/uploads/monthly_2025_03/003_-.thumb.gif.3dc77c6d6f4881e4984aa21d1e5b883f.gif"></a>
</p>

<h2 id="-">
	إضافة ميزات للبوت
</h2>

<p>
	يمكن إضافة أي مزايا جديدة لاحقًا للبوت عبر تعديل الشيفرة ضمن المشروع ودفع التعديلات إلى المنصة، فمثلًا يمكن إضافة ميزة إعلام المستخدم أن البوت يكتب الآن رسالة بإضافة تحديث للحالة قبل سطر إرسال الرسالة كالتالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_142_8" style=""><span class="com"># إعلام المستخدم أن البوت يكتب رسالة</span><span class="pln">
bot</span><span class="pun">.</span><span class="pln">sendChatAction</span><span class="pun">(</span><span class="pln">chat_id</span><span class="pun">=</span><span class="pln">chat_id</span><span class="pun">,</span><span class="pln"> action</span><span class="pun">=</span><span class="str">"typing"</span><span class="pun">)</span><span class="pln">

</span><span class="com"># إرسال الرسالة</span><span class="pln">
bot</span><span class="pun">.</span><span class="pln">sendMessage</span><span class="pun">(</span><span class="pln">chat_id</span><span class="pun">=</span><span class="pln">chat_id</span><span class="pun">,</span><span class="pln"> text</span><span class="pun">=</span><span class="pln">bot_welcome</span><span class="pun">,</span><span class="pln"> reply_to_message_id</span><span class="pun">=</span><span class="pln">msg_id</span><span class="pun">)</span><span class="pln">

</span><span class="com">#أو أن البوت يرسل لك صورة الآن كي يعلم أن البوت يتفاعل معه بتحديث حالة البوت قبل سطر إرسال الصورة كالتالي</span><span class="pln">
</span><span class="com"># إعلام المستخدم أن البوت يرسل صورة</span><span class="pln">
bot</span><span class="pun">.</span><span class="pln">sendChatAction</span><span class="pun">(</span><span class="pln">chat_id</span><span class="pun">=</span><span class="pln">chat_id</span><span class="pun">,</span><span class="pln"> action</span><span class="pun">=</span><span class="str">"upload_photo"</span><span class="pun">)</span><span class="pln">

</span><span class="com"># إرسال الصورة للمستخدم</span><span class="pln">
bot</span><span class="pun">.</span><span class="pln">sendPhoto</span><span class="pun">(</span><span class="pln">chat_id</span><span class="pun">=</span><span class="pln">chat_id</span><span class="pun">,</span><span class="pln"> photo</span><span class="pun">=</span><span class="pln">url</span><span class="pun">,</span><span class="pln"> reply_to_message_id</span><span class="pun">=</span><span class="pln">msg_id</span><span class="pun">)</span></pre>

<p>
	ومثلًا يمكن توليد الردود للبوت عبر الذكاء الاصطناعي، أو أن يستخدم البوت أدوات معينة برمجيا مثلا الاطلاع على حالة الطقس أو الأسعار أو نتائج المباريات ويجيب المستخدم بما يريد، ويمكنك الاطلاع على بوتات أخرى منشورة أو أمثلة عن بوتات تستخدم مزايا مختلفة من تيليجرام مطورة باستخدام بايثون من صفحة <a href="https://github.com/python-telegram-bot/python-telegram-bot/tree/master/examples" rel="external nofollow">python-telegram-bot على GitHub</a>.
</p>

<h2 id="-">
	الخاتمة
</h2>

<p>
	يوفر تيليجرام ميزات غنية وطريقة سهلة لإنشاء بوتات ضمنه تتفاعل مع المستخدمين، شرحنا في هذا المقال خطوات إنشاء بوت تليجرام بلغة بايثون python يعرض صورة رمزية avatar باستخدام إطار عمل فلاسك Flask وبينا طريقة نشره على سيرفر مجاني من Heroku.
</p>

<p>
	ترجمة وبتصرف للمقال <a href="https://www.toptal.com/python/telegram-bot-tutorial-python" rel="external nofollow">Building Your First Telegram Bot: A Step by Step Guide</a> لكاتبه <a href="https://www.toptal.com/resume/ali-abdel-aal" rel="external nofollow">Ali Abdel Aal</a>
</p>

<h2 id="-5">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/php/laravel/%D8%A8%D9%86%D8%A7%D8%A1-%D8%A8%D9%88%D8%AA-bot-%D8%AA%D9%84%D8%BA%D8%B1%D8%A7%D9%85-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%A7%D8%B1%D8%A7%D9%81%D9%8A%D9%84-%D9%88%D8%A8%D9%88%D8%AA%D9%85%D8%A7%D9%86-botman-r1740/" rel="">بناء بوت Bot تلغرام باستخدام لارافيل وبوتمان BotMan</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%B9%D9%84%D9%85-%D8%A8%D9%86%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9%D9%83-%D8%A7%D9%84%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1715/" rel="">تعلم بناء موقعك الإلكتروني الأول باستخدام إطار عمل فلاسك Flask بلغة بايثون</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A3%D8%AE%D8%B7%D8%A7%D8%A1-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1738/" rel="">كيفية التعامل مع الأخطاء في تطبيقات فلاسك Flask</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-python-r2325/" rel="">أساسيات لغة بايثون Python</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2543</guid><pubDate>Mon, 24 Mar 2025 12:00:00 +0000</pubDate></item><item><title>&#x62A;&#x639;&#x631;&#x641; &#x639;&#x644;&#x649; &#x625;&#x637;&#x627;&#x631; &#x627;&#x644;&#x639;&#x645;&#x644; &#x641;&#x644;&#x627;&#x633;&#x643; Flask</title><link>https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r2201/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_12/--Flask.png.a941e13c9acefb3030f99289ecc1c6f6.png" /></p>
<p>
	نعرفك في هذا المقال على إطار عمل تطوير الويب فلاسك Flask وهو إطار عمل صغير الحجم ومنظم ومرن للغاية مبني باستخدام لغة بايثون Python ويعد خيارًا مناسبًا للمبتدئين الذين يبدؤون خطواتهم الأولى في عالم برمجة تطبيقات الويب.
</p>

<p>
	فإذا كنت مهتمًا بمعرفة مفهوم إطار العمل فلاسك Flask، وما هي أبرز استخداماته وما هي مميزاته وعيوبه، وبماذا يختلف عن أطر بايثون الأخرى مثل جانغو Django واكتشاف أهم الخطوات التي تساعدك على تعلم فلاسك من الصفر حتى الاحتراف فتابع قراءة المقال للنهاية.
</p>

<h2 id="flask">
	ما هو إطار العمل فلاسك Flask
</h2>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="140203" href="https://academy.hsoub.com/uploads/monthly_2023_12/--Flask.png.187697c5b9df90289c2b7f46c9c2a6fa.png" rel=""><img alt="ماهو إطار flask" class="ipsImage ipsImage_thumbnailed" data-fileid="140203" data-ratio="62.60" data-unique="bjuacs432" style="width: 500px; height: auto;" width="500" src="https://academy.hsoub.com/uploads/monthly_2023_12/--Flask.thumb.png.0cdd4cb00e6a3f2eb65349d02cd64f92.png"> </a>
</p>

<p>
	إطار العمل فلاسك Flask هو إطار عمل مفتوح المصدر للغة البرمجة بايثون <a href="https://wiki.hsoub.com/Python" rel="external" target="_blank">Python</a> طوره المبرمج النمساوي أرمين روناشر Armin Ronacher عام 2010 كإطار مخصص لتطوير تطبيقات الويب
</p>

<p>
	يتميز فلاسك Flask بكونه إطار ويب مصغر micro web framework و<a href="https://academy.hsoub.com/programming/general/%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-framework/" rel="">إطار العمل في البرمجة</a> هو عبارة عن مجموعة من المكتبات التي تساعد المبرمجين على إنشاء وتطوير التطبيقات بسرعة أكبر وموثوقية أعلى، ومعنى مصغر هنا هو أنه يتضمن فقط المكتبات والمكونات الأساسية التي يحتاجها المطور من أجل إنشاء تطبيقات ويب بسيطة وسريعة لجعله صغير الحجم ما أمكن، لكنه بذات الوقت إطار مرن وقابل للتوسيع وإضافة مزيد من المكتبات عندما تستدعي الحاجة لتطوير تطبيقات ويب أكثر تعقيدًا.
</p>

<p>
	وعلى سهولته وبساطته فهو يحظى بشعبية وشهرة كبيرة ونال على المرتبة رقم 15 بين أفضل أطر عمل وتقنيات تطوير الويب المستخدمة من قبل المطورين والمبرمجين وفقًا لاستطلاع الرأي الذي أجراه الموقع الشهير ستاك أوفر فلو <a href="https://survey.stackoverflow.co/2023/#section-most-popular-technologies-web-frameworks-and-technologies" rel="external nofollow" target="_blank">Stack Overflow</a> لعام 2023 متفوقًا بذلك على أطر عمل ويب مشهورة أخرى مثل إطار جانغو <a href="https://academy.hsoub.com/programming/python/django/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-r2041/" rel="">Django</a> وإطار لارافيل <a href="https://academy.hsoub.com/programming/php/laravel/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D8%A7%D9%84%D8%B4%D9%87%D9%8A%D8%B1-%D9%84%D8%A7%D8%B1%D8%A7%D9%81%D9%8A%D9%84-laravel-r2093/" rel="">Laravel</a>.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="140201" href="https://academy.hsoub.com/uploads/monthly_2023_12/-.png.9a4b93d6a60b182c13b3896be8b1d6aa.png" rel=""><img alt="استبيان فلاسك" class="ipsImage ipsImage_thumbnailed" data-fileid="140201" data-ratio="97.40" data-unique="6x08233cc" style="width: 500px; height: auto;" width="500" src="https://academy.hsoub.com/uploads/monthly_2023_12/-.thumb.png.20631b7c433d78c049ac403209e4c6be.png"> </a>
</p>

<h2 id="flask-1">
	استخدامات إطار فلاسك Flask
</h2>

<p>
	يستخدم إطار عمل Flask بشكل أساسي من قبل مطوري الويب المختصين بلغة البرمجة بايثون <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D9%85%D8%B1%D8%AC%D8%B9-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%AA%D8%B9%D9%84%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r735/" rel="">Python</a> وهو يناسب المطورين المبتدئين ويساعدهم على تطوير مواقع وتطبيقات الويب الديناميكية وبشكل محدد أكثر يستخدم فلاسك لتطوير الواجهة الخلفية لمواقع الويب <a href="https://academy.hsoub.com/programming/general/%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D8%AE%D9%84%D9%81%D9%8A%D8%A9-backend-web-development/" rel="">Backend Web Development</a> ولاسيما التطبيقات الصغيرة والمتوسطة التي لا تتضمن الكثير من الوظائف والمهام المعقدة.
</p>

<p>
	هذا لا يعني أن فلاسك Flask لا يصلح لتطوير تطبيقات الويب المعقدة مثل المتاجر الإلكترونية الضخمة أو واجهات برمجة التطبيقات فهو كما ذكرنا سابقًا إطار عمل مرن وقابل للتوسيع أي يمكن لمطور الويب تثبيت أي مكتبات إضافية يحتاجها عندما يحتاج لتضمين مهام أو وظائف متقدمة في موقعه الإلكتروني وتخصيص موقعه كما يشاء.
</p>

<p>
	كما يصلح إطار فلاسك لتطوير تطبيقات ويب لتعلم الآلة ويتيح لك تحميل البيانات ومعالجتها وتدريب الخوارزميات عليها بالاستفادة من قوة مكتبات لغة بايثون التي تعد من أهم <a href="https://academy.hsoub.com/programming/artificial-intelligence/%D9%84%D8%BA%D8%A7%D8%AA-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A7%D9%84%D8%B0%D9%83%D8%A7%D8%A1-%D8%A7%D9%84%D8%A7%D8%B5%D8%B7%D9%86%D8%A7%D8%B9%D9%8A/" rel="">لغات برمجة الذكاء الاصطناعي</a>، ويستخدم إطار العمل Flask أيضًا لتطوير واجهة برمجة التطبيقات <a href="https://academy.hsoub.com/programming/general/%D8%B4%D8%B1%D8%AD-%D9%81%D9%84%D8%B3%D9%81%D8%A9-restful-%D8%AA%D8%B9%D9%84%D9%85-%D9%83%D9%8A%D9%81-%D8%AA%D8%A8%D9%86%D9%8A-%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-rest-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r635/" rel="">RESTful</a> التي تتيح الاتصال بين الأنظمة البرمجية المختلفة حيث يوفر مجموعة من المكتبات والأدوات اللازمة للتعامل مع طلبات واستجابات HTTP.
</p>

<h2 id="flask-2">
	إيجابيات إطار العمل Flask
</h2>

<p>
	يعتبر إطار العمل فلاسك واحدًا من أفضل أطر عمل تطوير الواجهات الخلفية للويب الويب ومن أبرز مميزات وإيجابيات فلاسك Flask نذكر:
</p>

<ul>
	<li>
		سهل التعلم والاستخدام ويملك توثيقًا جيدًا ما يجعله مثاليًا للمطورين المبتدئين.
	</li>
	<li>
		يعتمد على لغة البرمجة بايثون Python التي تعد واحدة من أقوى وأشهر لغات البرمجة وأكثرها تفضيلًا بين المبرمجين.
	</li>
	<li>
		يوفر العديد من أدوات التصحيح والاختبار التي تسهل عليك تصحيح أخطاء التطبيقات.
	</li>
	<li>
		صغير الحجم ما يجعله مثاليًا لتطوير مواقع وتطبيقات ويب سريعة وعالية الأداء.
	</li>
	<li>
		شائع الاستخدام ويتمتع بشهرة كبيرة بين أوساط مطوري الويب ويوفر الكثير من التوثيقات الشاملة ومصادر التعلم.
	</li>
	<li>
		يستخدم من قبل العديد من الشركات المعروفة مثل Reddit و Netflix.
	</li>
	<li>
		مرن وقابل للتوسع ويسمح بتثبيت أي عدد من المكتبات الإضافية المعززة لتطوير تطبيقات أكثر تقدمًا.
	</li>
	<li>
		يستخدم محرك قوالب يسمى جينجا <a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-jinja-%D8%A7%D9%84%D8%AF%D9%88%D8%A7%D9%84-%D9%88%D8%A7%D9%84%D9%85%D8%B1%D8%B4%D8%AD%D8%A7%D8%AA-r440/" rel="">Jinja 2</a> لعرض أو إخراج عناصر الواجهة الأمامية للموقع المصممة باستخدام HTML و CSS بطريقة ديناميكية ويسهل ربط الواجهة الأمامية لتطبيقات الويب بالواجهة الخلفية.
	</li>
	<li>
		آمن و<a href="https://academy.hsoub.com/programming/general/%D8%A7%D8%AD%D9%85-%D9%85%D9%88%D9%82%D8%B9%D9%83-%D9%85%D9%86-%D8%A7%D9%84%D8%A7%D8%AE%D8%AA%D8%B1%D8%A7%D9%82-r864/" rel="">يحمي تطبيقات الويب من الاختراق</a> ومن هجمات XSS كما أنه يستخدم ميزة ملفات تعريف الارتباط الآمنة.
	</li>
	<li>
		يوفر <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">خادم ويب</a> مدمج لاستضافة التطبيق في بيئة التطوير ومصحح أخطاء سريع يسهل اكتشاف الأخطاء، لكن تجدر الملاحظة بأن هذا الخادم مخصص للتطوير فقط وليس لنشر التطبيق فعند النشر عليك استخدام خادم آخر.
	</li>
	<li>
		يسمح بإنشاء واجهات برمجة تطبيقات <a href="https://academy.hsoub.com/programming/general/%D8%B4%D8%B1%D8%AD-%D9%81%D9%84%D8%B3%D9%81%D8%A9-restful-%D8%AA%D8%B9%D9%84%D9%85-%D9%83%D9%8A%D9%81-%D8%AA%D8%A8%D9%86%D9%8A-%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-rest-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r635/" rel="">RESTful</a> فعالة وصغيرة الحجم.
	</li>
</ul>

<h2 id="flask-3">
	سلبيات إطار العمل Flask
</h2>

<p>
	يعاني إطار العمل فلاسك من بعض جوانب القصور ومن بينها:
</p>

<ul>
	<li>
		لا يوفر فلاسك Flask الكثير من الأدوات والمكتبات المضمنة مما يضطر المطور لإضافة أي مكتبة خارجية يحتاجها يدويًا وهذا قد يتسبب في إبطاء التطبيق في حال كثرة المكتبات الخارجية.
	</li>
	<li>
		يحتاج المطور في إطار فلاسك لكتابة جميع تعليمات SQL الخاصة بالاتصال بقاعدة البيانات ومعالجة بياناتها والتحقق من صحة بيانات النموذج يدويًا في حين توفر أطر العمل الأخرى هذه الوظائف بشكل مضمن عادة.
	</li>
	<li>
		قد يصبح استخدام فلاسك Flask صعب التعامل والصيانة عند تطوير تطبيقات كبيرة الحجم ومليئة بالميزات.
	</li>
	<li>
		لا يوفر فلاسك Flask موقع مدمج لمدير الموقع يمكنه من التعامل مع سجلات قاعدة البيانات بسهولة كما في معظم أطر عمل تطوير الويب الشهيرة الأخرى.
	</li>
</ul>

<h2 id="flaskdjango">
	الفرق بين إطار فلاسك Flask وجانغو Django
</h2>

<p>
	إذا فكرت في تطوير تطبيق ويب أو موقع إلكتروني باستخدام لغة بايثون فستجد العديد من المكتبات وأطر العمل المتاحة لتنفيذ هذه المهمة لكن إطار عمل جانغو Django وفلاسك Flask هما الخياران الأكثر شيوعًا وقوة لتحقيق هذه المهمة ولمساعدتك على اختيار الإطار المناسب إليك قائمة بأبرز الفروقات بين فلاسك وجانغو:
</p>

<ul>
	<li>
		يعد إطار العمل فلاسك Flask أسهل في التعلم ويمكنك تطوير التطبيقات باستخدامه بسرعة وسهولة في حين أن تعلم جانغو Django أكثر صعوبة وستحتاج لزمن أطول لتطوير تطبيقك الأول باستخدامه.
	</li>
	<li>
		يعد إطار العمل جانغو Django كبير الحجم مقارنة بإطار العمل فلاسك Flask لكون إطار جانغو متكامل الميزات في حين أن فلاسك مصغر ومحدود الميزات ولا يحتوي على مكتبات مضمنة فيه كما ذكرنا سابقًا.
	</li>
	<li>
		يتضمن إطار عمل جانغو Django ميزة تسجيل الدخول ويوفر آلية للاستيثاق أو المصادقة Authentication والتحقق من صحة بيانات النموذج غيرها من المميزات التي تسهل عمل مطور تطبيقات الويب، في حين لا يوفر إطار عمل فلاسك هذه المميزات بشكل ضمني وإنما تحتاج لاستيراد مكتبات خارجية لتضمينها.
	</li>
	<li>
		يسهل إطار عمل جانغو إدارة الموقع ويوفر لوحة تحكم افتراضية تمكن مدير التطبيق من التعامل مع سجلات قواعد البيانات بسهولة دون الحاجة إلى كتابة أي استعلامات برمجية مخصصة، في حين لا يتضمن فلاسك هذه الميزة ضمنيًا لكن يمكنك تثبيت مكتبة تسمى flask-admin لتضمينها.
	</li>
	<li>
		يدعم جانغو نموذج Model View Templates أو اختصارًا MVT مما يسهل على المطورين تنظيم هيكلية التطبيق والتركيز على مهام برمجة التطبيق، في حين لا يقيدك إطار العمل فلاسك بأي نموذج محدد للتطبيق ويمكنك هيكلة ملفات مشروعك بالطريقة التي تناسبك.
	</li>
	<li>
		يتعامل جانغو مع قواعد بيانات التطبيق باستخدام ميزة Object-Relational Mapper أو اختصارًا ORM المضمنة فيه وهي طريقة للتفاعل مع قواعد البيانات من خلال واجهة برمجة تطبيقات <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> تعالج البيانات باستخدام تعليمات لغة بايثون، بينما لا يدعم فلاسك هذه الميزة وسيتعين عليك كتابة استعلامات SQL يدويًا لتنفيذ عمليات معالجة قواعد البيانات.
	</li>
	<li>
		ينصح بالاعتماد على إطار العمل جانغو في حال كانت لديك خبرة جيدة في تطوير الويب باستخدام بايثون وكنت ترغب في تطوير مشاريع ضخمة بميزات متقدمة.
	</li>
</ul>

<p>
	يمكن أن نختصر ما سبق بأن أبرز فرق بين إطار فلاسك وجانغو هو أن كلًا من جانغو Django وفلاسك هما إطارا ويب للغة بايثون Python لكن لكل منهما فلسفة مختلفة حيث يتبع فلاسك Flask فلسفة البساطة والمرونة وحين تحتاج لمكتبات خارجية عليك تضمينها يدويًا، في حين يتبع Django فلسفة تسمى فلسفة البطاريات المضمنة Battery Included Philosophy التي تعتمدها لغة بايثون أي أنه إطار عمل كامل المواصفات واستخدامه يقلل وقت تطوير تطبيقات الويب بشكل كبير.<br>
	وللمزيد من المعلومات حول الفرق بين إطار فلاسك Flask وجانغو Django أنصحك بمشاهدة الفيديو التالي:
</p>

<p>
	<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="" frameborder="0" height="450" id="ips_uid_4395_5" src="https://academy.hsoub.com/applications/core/interface/index.html" title="مقارنة إطار جانغو Django وفلاسك Flask" width="800" data-embed-src="https://www.youtube.com/embed/s-3Us8C2_NE"></iframe>
</p>

<h2 id="flask-4">
	خطوات تعلم إطار العمل Flask
</h2>

<p>
	إن تعلم إطار العمل فلاسك سهل ولا يستغرق الكثير من الوقت لتعلمه، فإذا التزمت بخطة منهجية فستتعلم التعامل معه بغضون عدة أسابيع وبعدها عليك الاهتمام ببناء مجموعة تطبيقات ومشاريع عملية تعزز تعلمك، وإليك أهم خطوات تعلم إطار العمل فلاسك Flask:
</p>

<ol>
	<li>
		تعلم مبادئ لغة <a href="https://academy.hsoub.com/programming/html/" rel="">HTML</a> الأساسية لإنشاء قوالب الموقع أو التطبيق وتعلم أساسيات لغة <a href="https://academy.hsoub.com/programming/css/" rel="">CSS</a> لتنسيق صفحات الويب الخاصة بك وجعلها جذابة بصريًا.
	</li>
	<li>
		افهم <a href="https://academy.hsoub.com/programming/python/" rel="">لغة بايثون</a> جيدًا وتعلم مفاهيم المتغيرات وأنواعها <a href="https://academy.hsoub.com/programming/general/%D9%87%D9%8A%D8%A7%D9%83%D9%84-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-data-structures/" rel="">وهياكل البيانات</a> وتعريف الدوال <a href="https://academy.hsoub.com/programming/python/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%AF%D9%88%D8%A7%D9%84-functions-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r292/" rel="">Functions</a> ومبادئ البرمجة كائنية التوجه <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%83%D8%A7%D8%A6%D9%86%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D9%88%D8%AC%D9%87-object-oriented-programming-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-r309/" rel=""><abbr title="Object-Oriented Programming | البرمجة كائنية التوجه"><abbr title="Object-Oriented Programming | البرمجة كائنية التوجه">OOP</abbr></abbr></a> …إلخ.
	</li>
	<li>
		افهم <a href="https://academy.hsoub.com/programming/general/%D9%83%D9%8A%D9%81-%D9%8A%D8%B9%D9%85%D9%84-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-web%D8%9F-r1690/" rel="">كيف يعمل الويب</a> وما هي أهم بروتوكولات الإنترنت وتعرف على <a href="https://academy.hsoub.com/programming/general/%D8%AA%D8%B9%D9%84%D9%85-%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%A7%D9%84%D9%88%D9%8A%D8%A8/" rel="">مبادئ تطوير الويب</a>.
	</li>
	<li>
		تعرف على مبدأ عمل أ<a href="https://academy.hsoub.com/devops/servers/%D8%A3%D8%B7%D8%B1-%D8%B9%D9%85%D9%84-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D9%85%D9%86-%D8%B7%D8%B1%D9%81-%D8%A7%D9%84%D8%AE%D8%A7%D8%AF%D9%85-r784/" rel="">طر تطوير الويب</a> بشكل عام وركز على فهم طريقة عمل إطار فلاسك Flask ومفاهيمه الأساسية مثل الموجهات routes والعرض view.
	</li>
	<li>
		تعلم طريقة إعداد بيئة التطوير الخاصة بك وخطوات تثبيت فلاسك على جهازك.
	</li>
	<li>
		تعلم كيفية إنشاء واستخدام بيئة افتراضية معزولة لتطبيقات فلاسك المختلفة فهذا يساعد على إدارة وتنظيم تبعيات التطبيقات المختلفة.
	</li>
	<li>
		أنشئ <a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%B9%D9%84%D9%85-%D8%A8%D9%86%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9%D9%83-%D8%A7%D9%84%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1715/" rel="">موقعك الأول على فلاسك Flask</a> الخاص وتعرف على طريقة إضافة الوظائف للموقع واستخدام الوجهات ودوال العرض والقوالب .
	</li>
	<li>
		تعلم طريقة التعامل مع النماذج في فلاسك واستخدمها لقبول المدخلات من مستخدم الموقع أو التطبيق.
	</li>
	<li>
		تعلم التعامل قواعد البيانات بمختلف أنواعها مثل MySQL أو PostgreSQL أو MongoDB ضمن تطبيقات فلاسك Flask وتعلم كيف تشفر البيانات الحساسة قبل تخزينها وكيف تعالج البيانات المخزنة باستخدام استعلامات SQL وكيف تستعمل مكتبات خارجية تسهل عليك كتابة استعلامات مثل SQLAlchemy.
	</li>
	<li>
		انتقل لبناء تطبيقات ويب بميزات متقدمة مثل ميزة استيثاق المستخدمين Authentication وأذوناتهم Permissions أو ميزة قبول المدفوعات أو ميزة تحليل البيانات وكيف تستورد المكتبات المناسبة لتحقيق هذه الميزات.
	</li>
	<li>
		تعلم كيفية إنشاء ونشر واجهات برمجة تطبيقات باستخدام فلاسك Flask.
	</li>
	<li>
		تعرف على خطوات نشر تطبيق فلاسك Flask الخاص بك على حاوية Docker أو خادم لينكس Linux أو على منصة سحابية مثل AWS.
	</li>
	<li>
		عند إتمامك لكل الخطوات السابقة ستكون جاهزًا الآن للبحث عن فرص عمل مناسبة في شركة تطوير ويب لتعزز ما تعلمته من خلال بناء تطبيقات فعلية للمستخدمين، كما يمكنك العمل بشكل مستقل وتقديم خدماتك في تطوير المواقع والتطبيقات باستخدام فلاسك للعملاء على مواقع العمل الحر مثل <a href="https://mostaql.com/" rel="external" target="_blank">مستقل</a> أو <a href="https://khamsat.com/" rel="external" target="_blank">خمسات</a>.
	</li>
</ol>

<h2 id="flask-5">
	مصادر تعلم فلاسك Flask
</h2>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="140202" href="https://academy.hsoub.com/uploads/monthly_2023_12/-.png.15227750e7d7969165e7692afb647a3c.png" rel=""><img alt="تعلم-فلاسك.png" class="ipsImage ipsImage_thumbnailed" data-fileid="140202" data-ratio="62.65" data-unique="br9rdq7er" style="width: 498px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2023_12/-.thumb.png.3d168bd9fe7fb948329c4d817f102413.png"></a>
</p>

<p>
	إذا كنت مهتمًا بتطوير الويب وتبحث عن مصادر عربية عالية الجودة لتعلم لغة البرمجة بايثون وإطار عملها فلاسك وفهم كيفية بناء تطبيقات ويب من خلالها بالخطوات التفصيلية فستجد في أكاديمية حسوب الكثير من الموارد المجانية التي تساعدك في رحلة التعلم من دروس ومقالات تنشر بصورة دورية، كما ستجد في <a href="https://wiki.hsoub.com/%D8%A7%D9%84%D8%B5%D9%81%D8%AD%D8%A9_%D8%A7%D9%84%D8%B1%D8%A6%D9%8A%D8%B3%D9%8A%D8%A9" rel="external" target="_blank">موسوعة حسوب</a> توثيقات شاملة للغة بايثون وغيرها الكثير من اللغات والتقنيات التي يحتاجها أي مطور ويب للبدء. وإذا كنت تفضل التعلم من الكتب فقد وفرت أكاديمية حسوب العديد من كتب البرمجة العربية المميزة التي تساعدك على التعلم التي يمكنك تحميلها مجانًا والتعلم منها.
</p>
<iframe allowfullscreen="" class="ipsEmbed_finishedLoading" data-controller="core.front.core.autosizeiframe" data-embedauthorid="3889" data-embedcontent="" data-embedid="embed6159646690" src="https://academy.hsoub.com/files/15-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86/?do=embed" style="overflow: hidden; height: 469px; max-width: 500px; margin:auto;"></iframe>

<p>
	وإذا كنت تشعر بالتشتت من كثرة المصادر وتنوعها وتريد مصدرًا تعليميًا منظمًا يساعدك على التعلم بطريقة منهجية ضمن خطة تعلم واضحة ويوفر لك الدعم والتوجيه ويجيبك على أي تساؤلات أو مشكلات تواجهك من قبل مدربين أكفاء ويمكنك من بناء معرض أعمال احترافي بفترة وجيزة ويعزز فرصتك في الحصول على عمل فقد وفرت أكاديمية حسوب <a href="https://academy.hsoub.com/learn/python-application-development/" rel="">دورة تطوير التطبيقات باستخدام لغة بايثون Python</a> التي تساعدك على تحقيق هذه الأهداف دون الحاجة لوجود أي معرفة برمجية مسبقة فمن خلالها ستبدأ من الصفر وتتعلم كل ما تحتاجه لبناء تطبيقات متكاملة بلغة بايثون وإطار عملها فلاسك Flask وغيره من أطر العمل الشهيرة.
</p>

<p>
	<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="" frameborder="0" height="450" id="ips_uid_4395_6" src="https://academy.hsoub.com/applications/core/interface/index.html" title="دورة تطوير التطبيقات بلغة بايثون - أكاديمية حسوب" width="800" data-embed-src="https://www.youtube.com/embed/1niwEWY7CN4"></iframe>
</p>

<h2 id="">
	الخلاصة
</h2>

<p>
	بهذا نكون قد وصلنا لنهاية مقالنا الذي عرفناك فيه على إطار العمل فلاسك Flask لتطوير تطبيقات الويب بلغة البرمجة بايثون Python وبينا أبرز مميزات هذا الإطار واستخداماته المختلفة وخطوات تعلمه بالتفصيل كما وضحنا أبرز الفروقات بينه وبين إطار العمل جانغو Django، وإذا كان لديك أي تساؤلات أو مشكلات بخصوص فلاسك Flask لا تتردد في مشاركتها معنا في قسم التعليقات.
</p>

<h2 id="-1">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A3%D8%B7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%81%D9%8A-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D9%81%D9%84%D8%A7%D8%B3%D9%83-%D9%86%D9%85%D9%88%D8%B0%D8%AC%D8%A7-r1547/" rel="">استخدام أطر العمل في برمجة تطبيقات الويب: فلاسك نموذجا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D9%85%D8%A7%D8%B1%D9%83%D8%AF%D8%A7%D9%88%D9%86-%D9%85%D8%B9-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-%D9%88%D9%85%D8%AD%D8%B1%D9%83-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-sqlite-r1714/" rel="">استخدام مكتبة بايثون-ماركداون مع إطار عمل فلاسك ومحرك قواعد البيانات SQLite</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-sqlalchemy-%D9%81%D9%8A-%D9%81%D9%84%D8%A7%D8%B3%D9%83-%D9%84%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D8%AF%D9%88%D9%86%D8%A9-%D9%85%D8%B9-%D8%B9%D9%84%D8%A7%D9%82%D8%A7%D8%AA-many-to-many-r1924/" rel="">استخدام SQLAlchemy في فلاسك لإنشاء مدونة مع علاقات many-to-many</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%86%D8%B8%D8%A7%D9%85-%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D9%85%D9%88%D8%B8%D9%81%D9%8A%D9%86-%D9%88%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%A9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-flask-sqlalchemy-r1925/" rel="">إنشاء نظام إدارة موظفين ومعالجة البيانات باستخدام إضافة Flask-SQLAlchemy</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-flask-sqlalchemy-%D9%84%D9%84%D8%AA%D8%AE%D8%A7%D8%B7%D8%A8-%D9%85%D8%B9-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-r1842/" rel="">استخدام الإضافة Flask-SQLAlchemy للتخاطب مع قواعد البيانات في تطبيقات فلاسك</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2201</guid><pubDate>Sun, 31 Dec 2023 11:07:02 +0000</pubDate></item><item><title>&#x625;&#x646;&#x634;&#x627;&#x621; &#x646;&#x638;&#x627;&#x645; &#x625;&#x62F;&#x627;&#x631;&#x629; &#x645;&#x648;&#x638;&#x641;&#x64A;&#x646; &#x648;&#x645;&#x639;&#x627;&#x644;&#x62C;&#x629; &#x627;&#x644;&#x628;&#x64A;&#x627;&#x646;&#x627;&#x62A; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x625;&#x636;&#x627;&#x641;&#x629; Flask-SQLAlchemy</title><link>https://academy.hsoub.com/programming/python/flask/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%86%D8%B8%D8%A7%D9%85-%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D9%85%D9%88%D8%B8%D9%81%D9%8A%D9%86-%D9%88%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%A9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-flask-sqlalchemy-r1925/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_03/Flask-SQLAlchemy.jpg.0be023a04071c78c4a3d3bbdb6ab4e3d.jpg" /></p>
<p>
	يُعد <span ipsnoautolink="true">فلاسك</span> إطار عمل للويب مبني بلغة <span ipsnoautolink="true">بايثون</span>، ويتميز بكونه صغير الحجم وسهل المعالجة، ويوفّر أيضًا عدة أدوات وميزات من شأنها إنشاء تطبيقات ويب في لغة بايثون.
</p>

<p>
	أمّا <a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%AC%D9%87%D9%8A%D8%B2-%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-flask-sqlalchemy-r508/" rel="">SQLAlchemy</a> فهي أداة في محرك قواعد البيانات SQL تؤمن وصولًا فعالًا وعالي الأداء إلى قواعد البيانات العلاقية، كما توفّر طرقًا للتخاطب مع العديد من محركات قواعد البيانات، مثل SQLite و MySQL و PostgreSQ، مانحةً إمكانية الوصول إلى آليات SQL الخاصة <a href="https://academy.hsoub.com/devops/servers/databases/%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-database/" rel="">بقواعد البيانات</a>، كما توفّر رابط الكائن بالعلاقات Object Relational Mapper -أو اختصارًا ORM- الذي يتيح إمكانية إنشاء الاستعلامات والتعامل مع البيانات باستخدام توابع وكائنات بسيطة <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D9%85%D8%B1%D8%AC%D8%B9-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%AA%D8%B9%D9%84%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r735/" rel="">في بايثون</a>.
</p>

<p>
	تعدّ Flask-SQLAlchemy إضافةً لفلاسك تسهّل استخدام SQLAlchemy ضمنها وتؤمّن الأدوات والوسائل المناسبة للتعامل مع قاعدة البيانات في تطبيقات فلاسك من خلال SQLAlchemy.
</p>

<p>
	سنستخدم في هذا المقال كلًا من <span ipsnoautolink="true">إطار العمل فلاسك</span> و<a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%AC%D9%87%D9%8A%D8%B2-%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-flask-sqlalchemy-r508/" rel="">الإضافة Flask-SQLAlchemy</a> لإنشاء نظام إدارة موظفين مع قاعدة بيانات تضم جدولًا خاصًّا بكل موظف، ليتضمّن هذا الجدول المعرّف الفريد ID للموظف واسمه الأوّل ونسبته وبريده الإلكتروني الفريد، إضافةً إلى عددٍ صحيح يدل على عمره وقيمة تاريخ تشير إلى تاريخ انضمامه إلى الشركة (تاريخ تعيّينه) وقيمةٍ منطقية تُحدّد ما إذا كان هذا الموظف متواجد حاليًا في مكتبه أم لا.
</p>

<p>
	إذ سنستخدم صَدَفَة فلاسك للاستعلام عن جدول ما والحصول على سجلاتٍ معيّنة وفقًا لقيمة عمود منه (مثل البريد الإلكتروني)، أي سنجلب سجلات الموظفين المُحقّقة لشروط مُحدّدة، كأن نجلب فقط الموظفين المتواجدين حاليًا أو أن نُنشئ قائمةً بالموظفين غير المتواجدين، كما سنرتّب النتائج وفقًا لقيم أحد الأعمدة، بما يتضمّن تحديد نتائج الاستعلام وحساب عددها، وسنستخدم نهايةً الترقيم Pagination بغية عرض عدد مُحدّد من الموظفين في الصفحة الواحدة ضمن تطبيق الويب.
</p>

<h2>
	مستلزمات العمل
</h2>

<p>
	قبل المتابعة في هذا المقال لا بدّ من:
</p>

<ul>
	<li>
		توفُّر <a href="https://academy.hsoub.com/programming/python/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%AA%D9%87%D8%A7-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r714/" rel="">بيئة برمجة بايثون 3 محلية</a>، مثبّتة على حاسوبك، وسنفترض في مقالنا أن اسم مجلد المشروع هو "flask_app".
	</li>
	<li>
		الفهم الجيد لأساسيات فلاسك، مثل مفهوم الوجهات ودوال العرض، ويمكنك في هذا الصدد الاطلاع على المقالين <a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%B9%D9%84%D9%85-%D8%A8%D9%86%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9%D9%83-%D8%A7%D9%84%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1715/" rel="">كيفية بناء موقعك الإلكتروني الأول باستخدام إطار عمل فلاسك Flask من لغة بايثون</a> و<a href="https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1737/" rel="">كيفية استخدام القوالب في تطبيقات فلاسك Flask</a> لفهم مبادئ فلاسك.
	</li>
	<li>
		فهم <a href="https://academy.hsoub.com/programming/html/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D9%84%D8%BA%D8%A9-html-r1687/" rel="">أساسيات لغة HTML</a>.
	</li>
	<li>
		فهم أساسيات الإضافة Flask-SQLAlchemy، مثل إعداد قاعدة البيانات وإنشاء نماذجها وإدخال البيانات إليها، وننصحك في هذا الصدد بقراءة المقال <a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-flask-sqlalchemy-%D9%84%D9%84%D8%AA%D8%AE%D8%A7%D8%B7%D8%A8-%D9%85%D8%B9-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-r1842/" rel="">استخدام الإضافة Flask-SQLAlchemy للتخاطب مع قواعد البيانات في تطبيقات فلاسك</a> لفهم مبادئ هذه الإضافة.
	</li>
</ul>

<h2>
	الخطوة 1: إعداد قاعدة البيانات والنموذج
</h2>

<p>
	سنثبّت في هذه الخطوة كافّة الحزم اللازمة، كما سنعدّ كلًا من تطبيق فلاسك وقاعدة البيانات Flask-SQLAlchemy ونموذج الموظف المُمثّل للجدول <code>employee</code>، حيث سنخزّن بيانات الموظف، كما سنضيف وجهة وصفحة، حيث سنعرض كافّة الموظفين ضمن الصفحة الرئيسية للتطبيق.
</p>

<p>
	لذا، وبعد التأكّد من كون البيئة الافتراضية مُفعّلة، سنستخدم أمر تثبيت الحزم <code>pip</code> لتثبيت كل من فلاسك والإضافة Flask-SQLAlchemy على النحو التالي:
</p>

<pre class="ipsCode">(env)user@localhost:$ pip install Flask Flask-SQLAlchemy
</pre>

<p>
	بمجرّد انتهاء التثبيت بنجاح، سيظهر في السطر الأخير من الخرج ما يشبه التالي:
</p>

<pre class="ipsCode">Successfully installed Flask-2.1.2 Flask-SQLAlchemy-2.5.1 Jinja2-3.1.2 MarkupSafe-2.1.1 SQLAlchemy-1.4.37 Werkzeug-2.1.2 click-8.1.3 greenlet-1.1.2 itsdangerous-2.1.2
</pre>

<p>
	الآن وبعد تثبيت الحزم اللازمة، سننشئ ملفًا جديدًا باسم "app.py" ضمن المجلد "flask.app" والذي سيحتوي على الشيفرات الخاصة بإعداد قاعدة البيانات ووجهات فلاسك اللازمة لعمل التطبيق:
</p>

<pre class="ipsCode">(env)user@localhost:$ nano app.py
</pre>

<p>
	ونكتب ضمنه الشيفرة التالية، التي ستُعِدّ <a href="https://academy.hsoub.com/devops/servers/databases/%d9%83%d9%8a%d9%81-%d9%88%d9%85%d8%aa%d9%89-%d9%86%d8%b3%d8%aa%d8%ae%d8%af%d9%85-sqlite-r111/" rel="">قاعدة بياناتٍ من نوع SQLite</a> ونموذج قاعدة بيانات للموظف مُمثّلًا للجدول <code>employee</code> الذي سنستخدمه لتخزين بيانات كل موظف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_6" style=""><span class="kwd">import</span><span class="pln"> os
</span><span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> render_template</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">,</span><span class="pln"> url_for</span><span class="pun">,</span><span class="pln"> redirect
</span><span class="kwd">from</span><span class="pln"> flask_sqlalchemy </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">SQLAlchemy</span><span class="pln">


basedir </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">abspath</span><span class="pun">(</span><span class="pln">os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">dirname</span><span class="pun">(</span><span class="pln">__file__</span><span class="pun">))</span><span class="pln">

app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">config</span><span class="pun">[</span><span class="str">'SQLALCHEMY_DATABASE_URI'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln">\
        </span><span class="str">'sqlite:///'</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="pln">basedir</span><span class="pun">,</span><span class="pln"> </span><span class="str">'database.db'</span><span class="pun">)</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">config</span><span class="pun">[</span><span class="str">'SQLALCHEMY_TRACK_MODIFICATIONS'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">False</span><span class="pln">

db </span><span class="pun">=</span><span class="pln"> </span><span class="typ">SQLAlchemy</span><span class="pun">(</span><span class="pln">app</span><span class="pun">)</span><span class="pln">


</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
    id </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Integer</span><span class="pun">,</span><span class="pln"> primary_key</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
    firstname </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">String</span><span class="pun">(</span><span class="lit">100</span><span class="pun">),</span><span class="pln"> nullable</span><span class="pun">=</span><span class="kwd">False</span><span class="pun">)</span><span class="pln">
    lastname </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">String</span><span class="pun">(</span><span class="lit">100</span><span class="pun">),</span><span class="pln"> nullable</span><span class="pun">=</span><span class="kwd">False</span><span class="pun">)</span><span class="pln">
    email </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">String</span><span class="pun">(</span><span class="lit">100</span><span class="pun">),</span><span class="pln"> unique</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">,</span><span class="pln"> nullable</span><span class="pun">=</span><span class="kwd">False</span><span class="pun">)</span><span class="pln">
    age </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Integer</span><span class="pun">,</span><span class="pln"> nullable</span><span class="pun">=</span><span class="kwd">False</span><span class="pun">)</span><span class="pln">
    hire_date </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Date</span><span class="pun">,</span><span class="pln"> nullable</span><span class="pun">=</span><span class="kwd">False</span><span class="pun">)</span><span class="pln">
    active </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Boolean</span><span class="pun">,</span><span class="pln"> nullable</span><span class="pun">=</span><span class="kwd">False</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> __repr__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> f</span><span class="str">'&lt;Employee {self.firstname} {self.lastname}&gt;'</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	استوردنا في الشيفرة السابقة الوحدة <code>os</code> التي تُمكنّنا من الوصول إلى واجهات <a href="https://academy.hsoub.com/files/24-%D8%A3%D9%86%D8%B8%D9%85%D8%A9-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D9%84%D9%84%D9%85%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D9%86/" rel="">نظام التشغيل</a> المختلفة، والتي سنستخدمها في بناء مسار ملف قاعدة البيانات database.db، كما استوردنا من حزمة فلاسك كافّة المُساعدات اللازمة للتطبيق، مثل الصنف فلاسك <code>Flask</code> المُستخدم في إنشاء النسخة الفعلية من التطبيق، والدالة <code>()render_template</code> لتصيير قوالب <a href="https://wiki.hsoub.com/HTML" rel="external">HTML</a>، والكائن <code>request</code> المسؤول عن التعامل مع الطلبات، والدالة <code>()url_for</code> لبناء روابط الوجهات، والدالة <code>()redirect</code> لإعادة توجيه المُستخدمين من صفحة لأُخرى.
</p>

<p>
	استوردنا بعد ذلك الصنف <code>SQLAlchemy</code> من الإضافة Flask-SQLAlchemy، والذي يسمح لنا بالوصول إلى جميع الدوال والأصناف الخاصة بالأداة SQLAlchemy، إضافةً إلى المُساعدات والآليات التي تدعم عمل فلاسك مع هذه الأداة، والتي سنستخدمها لإنشاء كائن قاعدة بيانات يتصل مع تطبيق فلاسك.
</p>

<p>
	بهدف إنشاء مسار لملف قاعدة البيانات، حدّدنا المجلد الحالي ليكون المجلد الرئيسي، كما استخدمنا الدالة <code>()os.path.abspath</code> للحصول على المسار المطلق لمجلد الملف الحالي، إذ يتضمّن المتغير الخاص <code>__file__</code> اسم مسار الملف الحالي app.py، وخزنّا المسار المطلق للمجلد الأساسي ضمن المتغير <code>basedir</code>.
</p>

<p>
	بعد ذلك، أنشأنا نسخةً فعليةً من تطبيق فلاسك باسم <code>app</code> والتي سنستخدمها لضبط مفتاحي إعدادات خاصّين بالإضافة Flask-SQLAlchemy، هما:
</p>

<ul>
	<li>
		<code>SQLALCHEMY_DATABASE_URI</code>: وهو معرّف الموارد الموحّد الخاص بقاعدة البيانات database URI، وهو مسؤول عن تحديد قاعدة البيانات المراد إنشاء اتصال معها، وفي هذه الحالة تكون صيغة هذا المعرّف على النحو <code>sqlite://path/to/database.db</code>، إذ تُستخدم الدالة <a href="https://docs.python.org/3.8/library/os.path.html#os.path.join" rel="external nofollow"><code>()op.path.join</code></a> لتحقيق الربط المدروس ما بين المجلد الأساسي الذي أنشاناه وخزنّا مساره في المتغير <code>basedir</code>، وبين اسم الملف <code>database.db</code>، الأمر الذي يسمح بالاتصال مع ملف قاعدة البيانات "database.db" الموجود في المجلد "flask.app"، إذ سيُنشَئ هذا الملف فور تهيئة قاعدة البيانات.
	</li>
	<li>
		<code>SQLALCHEMY_TRACK_MODIFICATIONS</code>: وهو إعداد لتمكين أو إلغاء تمكين ميزة تتبع تغيرات الكائنات، وقد عيّناه إلى القيمة <code>false</code> لإلغاء التتبع وبالتالي تحقيق استخدام أقل لموارد الذاكرة.
	</li>
</ul>

<p>
	بعد الانتهاء من ضبط إعدادات الأداة SQLAIchemy من خلال تعيين معرّف الموارد الموحّد لقاعدة البيانات وإلغاء تمكين ميزة تتبّع التغييرات، أنشأنا كائن قاعدة بيانات باستخدام صنف الأداة SQLAIchemy، ممررين إليه نسخة التطبيق بغية ربط تطبيق فلاسك مع الأداة SQLAIchemy، مخزنين كائن قاعدة البيانات هذا ضمن متغير باسم <code>db</code> والذي سنستخدمه في التخاطب مع قاعدة البيانات.
</p>

<p>
	وبعد إعداد كل من نسخة التطبيق الفعلية وكائن قاعدة البيانات، أنشأنا نموذج قاعدة بيانات باسم <code>Employee</code> الوارث للصنف <code>db.Model</code>، إذ يُمثّل هذا النموذج جدول الموظفين <code>employee</code> المُتضمّن للأعمدة التالية:
</p>

<ul>
	<li>
		<code>id</code>: وهو معرّف الموظف، ويحتوي على بياناتٍ من نوع رقم صحيح، ويمثّل المفتاح الأساسي.
	</li>
	<li>
		<code>firstname</code>: الاسم الأول للموظف، وهو سلسلة نصية بعدد محارف أعظمي يساوي 100، وتشير التعليمة <code>nullable=False</code> إلى أن هذا العمود لا يجب أن يحتوي على قيمٍ فارغة.
	</li>
	<li>
		<code>lastname</code>: الاسم الأخير للموظف، وهو سلسلة نصية بعدد محارف أعظمي يساوي 100، وتشير التعليمة <code>nullable=False</code> إلى أن هذا العمود لا يجب أن يحتوي على قيمٍ فارغة.
	</li>
	<li>
		<code>email</code>: عنوان البريد الإلكتروني للموظف، وهو سلسلة نصية بعدد محارف أعظمي يساوي 100، وتشير التعليمة <code>unique=True</code> إلى أنّ البريد الإلكتروني يجب أن يكون فريدًا لكل موظف، كما تشير التعليمة <code>nullable=False</code> إلى أن هذا العمود لا يجب أن يحتوي على قيمٍ فارغة.
	</li>
	<li>
		<code>age</code>: عمر الموظف، ويحتوي على بياناتٍ من نوع رقم صحيح.
	</li>
	<li>
		<code>hire_date</code>: يحتوي على تاريخ تعيين الموظف، وقد عيّنا نمط العمود ليكون <code>db.date</code> بغية التصريح عن كون هذا العمود يحتوي على تواريخ.
	</li>
	<li>
		<code>active</code>: يتضمّن قيمة منطقية تدل على ما إذا كان الموظف متواجد حاليًا في مكتبه أم لا.
	</li>
</ul>

<p>
	تمكّننا الدالة الخاصة <code>__repr__</code> من تمثيل كل كائن بصيغة سلسلة نصية، الأمر الذي يساعد في التعرّف على هذا الكائن لأغراض التنقيح، واستخدمنا في حالتنا الاسم الأوّل والأخير للموظف لتمثيل كائنات الموظفين.
</p>

<p>
	الآن وبعد الانتهاء من إعداد كل من الاتصال مع قاعدة البيانات ونموذج الموظف، سنكتب برنامجًا في بايثون لإنشاء قاعدة البيانات والجدول <code>employee</code> وملء هذا الجدول ببعضٍ من بيانات الموظفين.
</p>

<p>
	لذا، سننشئ ملفًا جديدًا باسم "init_db.py" ضمن المجلد "flask_app":
</p>

<pre class="ipsCode">(env)user@localhost:$ nano init_db.py
</pre>

<p>
	ونكتب ضمنه الشيفرة التالية بغية حذف أي جداول حالية في قاعدة البيانات والبدء بقاعدة بيانات فارغة، كما سننشئ الجدول <code>employee</code> وسنملؤه ببيانات تسعة موظفين:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_8" style=""><span class="kwd">from</span><span class="pln"> datetime </span><span class="kwd">import</span><span class="pln"> date
</span><span class="kwd">from</span><span class="pln"> app </span><span class="kwd">import</span><span class="pln"> db</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Employee</span><span class="pln">

db</span><span class="pun">.</span><span class="pln">drop_all</span><span class="pun">()</span><span class="pln">
db</span><span class="pun">.</span><span class="pln">create_all</span><span class="pun">()</span><span class="pln">

e1 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">(</span><span class="pln">firstname</span><span class="pun">=</span><span class="str">'John'</span><span class="pun">,</span><span class="pln">
              lastname</span><span class="pun">=</span><span class="str">'Doe'</span><span class="pun">,</span><span class="pln">
              email</span><span class="pun">=</span><span class="str">'jd@example.com'</span><span class="pun">,</span><span class="pln">
              age</span><span class="pun">=</span><span class="lit">32</span><span class="pun">,</span><span class="pln">
              hire_date</span><span class="pun">=</span><span class="pln">date</span><span class="pun">(</span><span class="lit">2012</span><span class="pun">,</span><span class="pln"> </span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="lit">3</span><span class="pun">),</span><span class="pln">
              active</span><span class="pun">=</span><span class="kwd">True</span><span class="pln">
              </span><span class="pun">)</span><span class="pln">

e2 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">(</span><span class="pln">firstname</span><span class="pun">=</span><span class="str">'Mary'</span><span class="pun">,</span><span class="pln">
              lastname</span><span class="pun">=</span><span class="str">'Doe'</span><span class="pun">,</span><span class="pln">
              email</span><span class="pun">=</span><span class="str">'md@example.com'</span><span class="pun">,</span><span class="pln">
              age</span><span class="pun">=</span><span class="lit">38</span><span class="pun">,</span><span class="pln">
              hire_date</span><span class="pun">=</span><span class="pln">date</span><span class="pun">(</span><span class="lit">2016</span><span class="pun">,</span><span class="pln"> </span><span class="lit">6</span><span class="pun">,</span><span class="pln"> </span><span class="lit">7</span><span class="pun">),</span><span class="pln">
              active</span><span class="pun">=</span><span class="kwd">True</span><span class="pln">
              </span><span class="pun">)</span><span class="pln">

e3 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">(</span><span class="pln">firstname</span><span class="pun">=</span><span class="str">'Jane'</span><span class="pun">,</span><span class="pln">
              lastname</span><span class="pun">=</span><span class="str">'Tanaka'</span><span class="pun">,</span><span class="pln">
              email</span><span class="pun">=</span><span class="str">'jt@example.com'</span><span class="pun">,</span><span class="pln">
              age</span><span class="pun">=</span><span class="lit">32</span><span class="pun">,</span><span class="pln">
              hire_date</span><span class="pun">=</span><span class="pln">date</span><span class="pun">(</span><span class="lit">2015</span><span class="pun">,</span><span class="pln"> </span><span class="lit">9</span><span class="pun">,</span><span class="pln"> </span><span class="lit">12</span><span class="pun">),</span><span class="pln">
              active</span><span class="pun">=</span><span class="kwd">False</span><span class="pln">
              </span><span class="pun">)</span><span class="pln">

e4 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">(</span><span class="pln">firstname</span><span class="pun">=</span><span class="str">'Alex'</span><span class="pun">,</span><span class="pln">
              lastname</span><span class="pun">=</span><span class="str">'Brown'</span><span class="pun">,</span><span class="pln">
              email</span><span class="pun">=</span><span class="str">'ab@example.com'</span><span class="pun">,</span><span class="pln">
              age</span><span class="pun">=</span><span class="lit">29</span><span class="pun">,</span><span class="pln">
              hire_date</span><span class="pun">=</span><span class="pln">date</span><span class="pun">(</span><span class="lit">2019</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">3</span><span class="pun">),</span><span class="pln">
              active</span><span class="pun">=</span><span class="kwd">True</span><span class="pln">
              </span><span class="pun">)</span><span class="pln">

e5 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">(</span><span class="pln">firstname</span><span class="pun">=</span><span class="str">'James'</span><span class="pun">,</span><span class="pln">
              lastname</span><span class="pun">=</span><span class="str">'White'</span><span class="pun">,</span><span class="pln">
              email</span><span class="pun">=</span><span class="str">'jw@example.com'</span><span class="pun">,</span><span class="pln">
              age</span><span class="pun">=</span><span class="lit">24</span><span class="pun">,</span><span class="pln">
              hire_date</span><span class="pun">=</span><span class="pln">date</span><span class="pun">(</span><span class="lit">2021</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">4</span><span class="pun">),</span><span class="pln">
              active</span><span class="pun">=</span><span class="kwd">True</span><span class="pln">
              </span><span class="pun">)</span><span class="pln">

e6 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">(</span><span class="pln">firstname</span><span class="pun">=</span><span class="str">'Harold'</span><span class="pun">,</span><span class="pln">
              lastname</span><span class="pun">=</span><span class="str">'Ishida'</span><span class="pun">,</span><span class="pln">
              email</span><span class="pun">=</span><span class="str">'hi@example.com'</span><span class="pun">,</span><span class="pln">
              age</span><span class="pun">=</span><span class="lit">52</span><span class="pun">,</span><span class="pln">
              hire_date</span><span class="pun">=</span><span class="pln">date</span><span class="pun">(</span><span class="lit">2002</span><span class="pun">,</span><span class="pln"> </span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="lit">6</span><span class="pun">),</span><span class="pln">
              active</span><span class="pun">=</span><span class="kwd">False</span><span class="pln">
              </span><span class="pun">)</span><span class="pln">

e7 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">(</span><span class="pln">firstname</span><span class="pun">=</span><span class="str">'Scarlett'</span><span class="pun">,</span><span class="pln">
              lastname</span><span class="pun">=</span><span class="str">'Winter'</span><span class="pun">,</span><span class="pln">
              email</span><span class="pun">=</span><span class="str">'sw@example.com'</span><span class="pun">,</span><span class="pln">
              age</span><span class="pun">=</span><span class="lit">22</span><span class="pun">,</span><span class="pln">
              hire_date</span><span class="pun">=</span><span class="pln">date</span><span class="pun">(</span><span class="lit">2021</span><span class="pun">,</span><span class="pln"> </span><span class="lit">4</span><span class="pun">,</span><span class="pln"> </span><span class="lit">7</span><span class="pun">),</span><span class="pln">
              active</span><span class="pun">=</span><span class="kwd">True</span><span class="pln">
              </span><span class="pun">)</span><span class="pln">

e8 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">(</span><span class="pln">firstname</span><span class="pun">=</span><span class="str">'Emily'</span><span class="pun">,</span><span class="pln">
              lastname</span><span class="pun">=</span><span class="str">'Vill'</span><span class="pun">,</span><span class="pln">
              email</span><span class="pun">=</span><span class="str">'ev@example.com'</span><span class="pun">,</span><span class="pln">
              age</span><span class="pun">=</span><span class="lit">27</span><span class="pun">,</span><span class="pln">
              hire_date</span><span class="pun">=</span><span class="pln">date</span><span class="pun">(</span><span class="lit">2019</span><span class="pun">,</span><span class="pln"> </span><span class="lit">6</span><span class="pun">,</span><span class="pln"> </span><span class="lit">9</span><span class="pun">),</span><span class="pln">
              active</span><span class="pun">=</span><span class="kwd">True</span><span class="pln">
              </span><span class="pun">)</span><span class="pln">

e9 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">(</span><span class="pln">firstname</span><span class="pun">=</span><span class="str">'Mary'</span><span class="pun">,</span><span class="pln">
              lastname</span><span class="pun">=</span><span class="str">'Park'</span><span class="pun">,</span><span class="pln">
              email</span><span class="pun">=</span><span class="str">'mp@example.com'</span><span class="pun">,</span><span class="pln">
              age</span><span class="pun">=</span><span class="lit">30</span><span class="pun">,</span><span class="pln">
              hire_date</span><span class="pun">=</span><span class="pln">date</span><span class="pun">(</span><span class="lit">2021</span><span class="pun">,</span><span class="pln"> </span><span class="lit">8</span><span class="pun">,</span><span class="pln"> </span><span class="lit">11</span><span class="pun">),</span><span class="pln">
              active</span><span class="pun">=</span><span class="kwd">True</span><span class="pln">
              </span><span class="pun">)</span><span class="pln">

db</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">add_all</span><span class="pun">([</span><span class="pln">e1</span><span class="pun">,</span><span class="pln"> e2</span><span class="pun">,</span><span class="pln"> e3</span><span class="pun">,</span><span class="pln"> e4</span><span class="pun">,</span><span class="pln"> e5</span><span class="pun">,</span><span class="pln"> e6</span><span class="pun">,</span><span class="pln"> e7</span><span class="pun">,</span><span class="pln"> e8</span><span class="pun">,</span><span class="pln"> e9</span><span class="pun">])</span><span class="pln">

db</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span></pre>

<p>
	استوردنا في الشيفرة السابقة الصنف <code>()date</code> من الوحدة <code>datetime</code> لاستخدامه في ضبط تواريخ تعيّين الموظفين.
</p>

<p>
	استوردنا أيضًا كلًا من كائن قاعدة البيانات والنموذج <code>Employee</code>، واستدعينا الدالة <code>()db.drop_all</code> بغية حذف كافّة الجداول الموجودة أصلًا في قاعدة البيانات متجنبين بذلك فرصة وجود جدول مملوء باسم "employee" سابقًا فيها، الأمر الذي قد يسبب أخطاءً.
</p>

<p>
	من الجدير بالملاحظة أنّ البرنامج "init_db.py" سيحذف كامل محتويات قاعدة البيانات في كل مرة نشغّله فيها، وللمزيد حول كيفية إنشاء وتعديل وحذف جداول قاعدة البيانات، ننصحك بقراءة المقال <a href="https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%86%D9%85%D8%A7%D8%B0%D8%AC-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1739/" rel="">استخدام الإضافة Flask-SQLAlchemy للتخاطب مع قواعد البيانات في تطبيقات فلاسك</a>.
</p>

<p>
	استنسخنا بعد ذلك النموذج <code>Emolyee</code> عدّة مرات بغية تمثيل كافّة الموظفين الذين سنستعلم عنهم في هذا المقال، ثم أضفنا هذه النسخ إلى قاعدة البيانات باستخدام الدالة <code>()db.session.add_all</code>، ونهايةً استخدمنا التابع <code>()db.session.commit</code> لتأكيد العمليات وتطبيق التغييرات على قاعدة البيانات.
</p>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	سننفذ الآن البرنامج "init_db.py":
</p>

<pre class="ipsCode">(env)user@localhost:$ python init_db.py
</pre>

<p>
	للاطلاع على البيانات المُضافة إلى قاعدة البيانات، نفتح صَدفة فلاسك بعد التأكّد من كون البيئة الافتراضية مُفعّلة بغية الاستعلام عن كافّة الموظفين وعرض بيانات كل منهم، على النحو التالي:
</p>

<pre class="ipsCode">(env)user@localhost:$ flask shell
</pre>

<p>
	ثمّ نشغّل الشيفرة التالية المسؤولة عن الاستعلام عن كافّة الموظفين وعرض بياناتهم:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_10" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">from</span><span class="pln"> app </span><span class="kwd">import</span><span class="pln"> db</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Employee</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> employees </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> employee </span><span class="kwd">in</span><span class="pln"> employees</span><span class="pun">:</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">     </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">employee</span><span class="pun">.</span><span class="pln">firstname</span><span class="pun">,</span><span class="pln"> employee</span><span class="pun">.</span><span class="pln">lastname</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">     </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Email:'</span><span class="pun">,</span><span class="pln"> employee</span><span class="pun">.</span><span class="pln">email</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">     </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Age:'</span><span class="pun">,</span><span class="pln"> employee</span><span class="pun">.</span><span class="pln">age</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">     </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Hired:'</span><span class="pun">,</span><span class="pln"> employee</span><span class="pun">.</span><span class="pln">hire_date</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">     </span><span class="kwd">if</span><span class="pln"> employee</span><span class="pun">.</span><span class="pln">active</span><span class="pun">:</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">         </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Active'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">     </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">         </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Out of Office'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">     </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'----'</span><span class="pun">)</span></pre>

<p>
	استخدمنا في الشيفرة السابقة التابع <code>()all</code> من السمة <code>query</code> للحصول على كافّة الموظفين، واستخدمنا حلقةً تكراريةً للمرور على كافّة نتائج الاستعلام؛ أمّا بالنسبة للعمود "active"، فقد استخدمنا جملةً شرطيةً لإظهار الحالة الراهنة للموظف، سواءٌ كان متواجدًا "Active" أم خارج مكتبه "Out of Office".
</p>

<p>
	نحصل على خرجٍ على النحو التالي:
</p>

<pre class="ipsCode">John Doe
Email: jd@example.com
Age: 32
Hired: 2012-03-03
Active
----
Mary Doe
Email: md@example.com
Age: 38
Hired: 2016-06-07
Active
----
Jane Tanaka
Email: jt@example.com
Age: 32
Hired: 2015-09-12
Out of Office
----
Alex Brown
Email: ab@example.com
Age: 29
Hired: 2019-01-03
Active
----
James White
Email: jw@example.com
Age: 24
Hired: 2021-02-04
Active
----
Harold Ishida
Email: hi@example.com
Age: 52
Hired: 2002-03-06
Out of Office
----
Scarlett Winter
Email: sw@example.com
Age: 22
Hired: 2021-04-07
Active
----
Emily Vill
Email: ev@example.com
Age: 27
Hired: 2019-06-09
Active
----
Mary Park
Email: mp@example.com
Age: 30
Hired: 2021-08-11
Active
----
</pre>

<p>
	وبذلك نجد أنه قد جرى عرض كافة الموظفين المُضافين إلى قاعدة البيانات على نحوٍ سليم.
</p>

<p>
	نُغلق الآن صَدَفة فلاسك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_13" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> exit</span><span class="pun">()</span></pre>

<p>
	أمّا الآن، فسننشئ وجهة فلاسك المسؤولة عن عرض الموظفين، لذا سنفتح الملف app.py لتحريره:
</p>

<pre class="ipsCode">(env)user@localhost:$ nano app.py
</pre>

<p>
	ونكتب الوجهة التالية في نهايته:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_15" style=""><span class="pun">...</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> index</span><span class="pun">():</span><span class="pln">
    employees </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'index.html'</span><span class="pun">,</span><span class="pln"> employees</span><span class="pun">=</span><span class="pln">employees</span><span class="pun">)</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	ستستعلم الوجهة السابقة عن كافّة الموظفين، وتُصيّر قالب HTML للصفحة الرئيسية للتطبيق باسم "index.html" مُمررةً إليه نتائج الاستعلام.
</p>

<p>
	ننشئ الآن مجلدًا للقوالب باسم "templates" وقالبًا رئيسيًا باسم "base.html":
</p>

<pre class="ipsCode">(env)user@localhost:$ mkdir templates
(env)user@localhost:$ nano templates/base.html
</pre>

<p>
	ونكتب التالي ضمن القالب الرئيسي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1338_17" style=""><span class="dec">&lt;!DOCTYPE html&gt;</span><span class="pln">
</span><span class="tag">&lt;html</span><span class="pln"> </span><span class="atn">lang</span><span class="pun">=</span><span class="atv">"en"</span><span class="tag">&gt;</span><span class="pln">
</span><span class="tag">&lt;head&gt;</span><span class="pln">
    </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">"UTF-8"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;title&gt;</span><span class="pln">{% block title %} {% endblock %} - FlaskApp</span><span class="tag">&lt;/title&gt;</span><span class="pln">
    </span><span class="tag">&lt;style&gt;</span><span class="pln">
        </span><span class="pun">.</span><span class="pln">title </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">margin</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5px</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        </span><span class="pun">.</span><span class="pln">content </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">margin</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">width</span><span class="pun">:</span><span class="pln"> </span><span class="lit">100%</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">display</span><span class="pun">:</span><span class="pln"> flex</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">flex-direction</span><span class="pun">:</span><span class="pln"> row</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">flex-wrap</span><span class="pun">:</span><span class="pln"> wrap</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        </span><span class="pun">.</span><span class="pln">employee </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">flex</span><span class="pun">:</span><span class="pln"> </span><span class="lit">20%</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">padding</span><span class="pun">:</span><span class="pln"> </span><span class="lit">10px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">margin</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">background-color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#f3f3f3</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">inline-size</span><span class="pun">:</span><span class="pln"> </span><span class="lit">100%</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        </span><span class="pun">.</span><span class="pln">name </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#00a36f</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">text-decoration</span><span class="pun">:</span><span class="pln"> none</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        nav a </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#d64161</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">font-size</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3em</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">margin-left</span><span class="pun">:</span><span class="pln"> </span><span class="lit">50px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">text-decoration</span><span class="pun">:</span><span class="pln"> none</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        </span><span class="pun">.</span><span class="pln">pagination </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">margin</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> auto</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        </span><span class="pun">.</span><span class="pln">pagination span </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">font-size</span><span class="pun">:</span><span class="pln"> </span><span class="lit">2em</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">margin-right</span><span class="pun">:</span><span class="pln"> </span><span class="lit">10px</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        </span><span class="pun">.</span><span class="pln">page-number </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#d64161</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">padding</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">text-decoration</span><span class="pun">:</span><span class="pln"> none</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        </span><span class="pun">.</span><span class="pln">current-page-number </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#666</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

    </span><span class="tag">&lt;/style&gt;</span><span class="pln">
</span><span class="tag">&lt;/head&gt;</span><span class="pln">
</span><span class="tag">&lt;body&gt;</span><span class="pln">
    </span><span class="tag">&lt;nav&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('index') }}"</span><span class="tag">&gt;</span><span class="pln">FlaskApp</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"#"</span><span class="tag">&gt;</span><span class="pln">About</span><span class="tag">&lt;/a&gt;</span><span class="pln">
    </span><span class="tag">&lt;/nav&gt;</span><span class="pln">
    </span><span class="tag">&lt;hr&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"content"</span><span class="tag">&gt;</span><span class="pln">
        {% block content %} {% endblock %}
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
</span><span class="tag">&lt;/body&gt;</span><span class="pln">
</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	استخدمنا في الشيفرة السابقة كتلة عنوان، كما أضفنا بعضًا من تنسيقات <a href="https://academy.hsoub.com/programming/html/html-%D9%88-css-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86-%D9%83%D9%8A%D9%81-%D8%AA%D8%B5%D9%85%D9%85-%D8%A3%D9%88%D9%84-%D8%B5%D9%81%D8%AD%D8%A9-%D9%88%D9%8A%D8%A8-%D9%84%D9%83-r242/" rel="">CSS</a> لتنسيق مظهر المكونات.
</p>

<p>
	أضفنا أيضًا شريط تصفّح بعنصرين، الأوّل للصفحة الرئيسية للتطبيق، والثاني لصفحة عرض معلومات حول التطبيق إلا أننا سنتركها غير فعالة حاليًا، إذ سيُستخدم شريط التصفّح هذا في التطبيق في كافّة القوالب الوارثة للقالب الرئيسي، كما ستُستبدل كتلة المحتوى من القالب الرئيسي بالمحتوى الموافق لكل صفحة موظف.
</p>

<p>
	نفتح الآن قالب الصفحة الرئيسية "index.html" الذي صيّرناه في الملف "app.py":
</p>

<pre class="ipsCode">(env)user@localhost:$ nano templates/index.html
</pre>

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

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1338_19" style=""><span class="pln">{% extends 'base.html' %}

{% block content %}
    </span><span class="tag">&lt;h1</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"title"</span><span class="tag">&gt;</span><span class="pln">{% block title %} Employees {% endblock %}</span><span class="tag">&lt;/h1&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"content"</span><span class="tag">&gt;</span><span class="pln">
        {% for employee in employees %}
            </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"employee"</span><span class="tag">&gt;</span><span class="pln">
                </span><span class="tag">&lt;p&gt;&lt;b&gt;</span><span class="pln">#{{ employee.id }}</span><span class="tag">&lt;/b&gt;&lt;/p&gt;</span><span class="pln">
                </span><span class="tag">&lt;b&gt;</span><span class="pln">
                    </span><span class="tag">&lt;p</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"name"</span><span class="tag">&gt;</span><span class="pln">{{ employee.firstname }} {{ employee.lastname }}</span><span class="tag">&lt;/p&gt;</span><span class="pln">
                </span><span class="tag">&lt;/b&gt;</span><span class="pln">
                </span><span class="tag">&lt;p&gt;</span><span class="pln">{{ employee.email }}</span><span class="tag">&lt;/p&gt;</span><span class="pln">
                </span><span class="tag">&lt;p&gt;</span><span class="pln">{{ employee.age }} years old.</span><span class="tag">&lt;/p&gt;</span><span class="pln">
                </span><span class="tag">&lt;p&gt;</span><span class="pln">Hired: {{ employee.hire_date }}</span><span class="tag">&lt;/p&gt;</span><span class="pln">
                {% if employee.active %}
                    </span><span class="tag">&lt;p&gt;&lt;i&gt;</span><span class="pln">(Active)</span><span class="tag">&lt;/i&gt;&lt;/p&gt;</span><span class="pln">
                {% else %}
                    </span><span class="tag">&lt;p&gt;&lt;i&gt;</span><span class="pln">(Out of Office)</span><span class="tag">&lt;/i&gt;&lt;/p&gt;</span><span class="pln">
                {% endif %}
            </span><span class="tag">&lt;/div&gt;</span><span class="pln">
        {% endfor %}
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
{% endblock %}</span></pre>

<p>
	مررنا في الشيفرة السابقة على كافّة الموظفين باستخدام حلقةٍ تكرارية، لنعرض معلومات كل منهم، إذ أضفنا العنوان التوضيحي <strong>"Active"</strong> في حال كون الموظف متواجد حاليًا، وفيما عدا ذلك يُعرَض العنوان <strong>"Out of Office"</strong> دلالةً على عدم تواجده.
</p>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	الآن ومع وجودنا ضمن المجلد "flask_app" ومع كون البيئة الافتراضية مُفعّلة، سنُعلم فلاسك بالتطبيق المراد تشغيله (وهو في حالتنا الملف "app.py") باستخدام متغير البيئة <code>FLASK_APP</code>، في حين يحدّد متغير البيئة <code>FLASK_ENV</code> وضع التشغيل وهنا قد اخترنا وضع <code>development</code> ما يعني أنّ التطبيق سيعمل في وضع التطوير مع تشغيل مُنقّح الأخطاء، وللمزيد من المعلومات حول مُنقّح الأخطاء في فلاسك ننصحك بقراءة المقال <a href="https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A3%D8%AE%D8%B7%D8%A7%D8%A1-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1738/" rel="">كيفية التعامل مع الأخطاء في تطبيقات فلاسك</a>، ولتنفيذ ما سبق سنشغّل الأوامر التالية:
</p>

<pre class="ipsCode">(env)user@localhost:$ export FLASK_APP=app
(env)user@localhost:$ export FLASK_ENV=development
</pre>

<p>
	والآن سنشغّل التطبيق باستخدام الأمر <code>flask run</code>:
</p>

<pre class="ipsCode">(env)user@localhost:$ flask run
</pre>

<p>
	وبعد التأكد من كون <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">خادم</a> التطوير ما يزال قيد التشغيل، نذهب إلى الرابط التالي باستخدام المتصفح:
</p>

<pre class="ipsCode">http://127.0.0.1:5000/
</pre>

<p>
	فيظهر الموظفون المضافون إلى قاعدة البيانات في صفحة مشابهة للصورة التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="120691" href="https://academy.hsoub.com/uploads/monthly_2023_03/01_Employees_Page.png.36d2bcba1b76a908a292b20a9da8186f.png" rel=""><img alt="01_Employees_Page.png" class="ipsImage ipsImage_thumbnailed" data-fileid="120691" data-unique="f5d32c6ld" src="https://academy.hsoub.com/uploads/monthly_2023_03/01_Employees_Page.thumb.png.119a6062783b123f05e273dd30217583.png"> </a>
</p>

<p>
	أما الآن، نترك الخادم قيد التشغيل، ونفتح نافذة طرفية جديدة استعدادًا للخطوة التالية، ومع نهاية هذه الخطوة نكون قد عرضنا الموظفين المدخلين في قاعدة البيانات على الصفحة الرئيسية للتطبيق، وسنستخدم في الخطوة التالية صَدَفة فلاسك للاستعلام عن الموظفين باستخدام توابع مختلفة.
</p>

<h2>
	الخطوة 2: الاستعلام عن السجلات
</h2>

<p>
	سنستخدم في هذه الخطوة صَدَفة فلاسك للاستعلام عن السجلات، لنجلب النتائج ونرشّحها باستخدام توابع مختلفة تحت شروط مختلفة.
</p>

<p>
	سنضبط بدايةً متغيرات البيئة <code>FLASK_APP</code> و <code>FLASK_ENV</code> أثناء كون البيئة البرمجية مُفعّلة، ثم نفتح صَدَفة فلاسك:
</p>

<pre class="ipsCode">(env)user@localhost:$ export FLASK_APP=app
(env)user@localhost:$ export FLASK_ENV=development
(env)user@localhost:$ flask shell
</pre>

<p>
	ثم نستورد كلًا من الكائن <code>db</code> والنموذج <code>Employee</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_21" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">from</span><span class="pln"> app </span><span class="kwd">import</span><span class="pln"> db</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Employee</span></pre>

<h3>
	استرجاع كافة السجلات
</h3>

<p>
	كما رأينا في الخطة السابقة، من الممكن استخدام التابع <code>()all</code> من السمة <code>query</code> للحصول على كافّة سجلات الجدول، على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_23" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> all_employees </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">all_employees</span><span class="pun">)</span></pre>

<p>
	وعندها سيكون الخرج قائمةً بالكائنات المُمثلّة لكل الموظفين:
</p>

<pre class="ipsCode">[&lt;Employee John Doe&gt;, &lt;Employee Mary Doe&gt;, &lt;Employee Jane Tanaka&gt;, &lt;Employee Alex Brown&gt;, &lt;Employee James White&gt;, &lt;Employee Harold Ishida&gt;, &lt;Employee Scarlett Winter&gt;, &lt;Employee Emily Vill&gt;, &lt;Employee Mary Park&gt;]
</pre>

<h3>
	استرجاع السجل الأول
</h3>

<p>
	من المُمكن استخدام التابع <code>()first</code> للحصول على السجل الأوّل بصورةٍ مشابهة لما سبق، على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_25" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> first_employee </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">first</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">first_employee</span><span class="pun">)</span></pre>

<p>
	وعندها سيكون الخرج هو الكائن المُتضمّن لبيانات الموظف الأوّل:
</p>

<pre class="ipsCode">&lt;Employee John Doe&gt;
</pre>

<h3>
	استرجاع سجل وفق معرفه
</h3>

<p>
	تُعرَّفُ السجلات في معظم جداول قواعد البيانات باستخدام معرّفٍ فريد ID، وتتيح الإضافة Flask-SQLAlchemy جلبَ سجلٍ ما وفقًا لمعرّفه باستخدام التابع <code>()get</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_27" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> employee5 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="lit">5</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> employee3 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="lit">3</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">f</span><span class="str">'{employee5} | ID: {employee5.id}'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">f</span><span class="str">'{employee3} | ID: {employee3.id}'</span><span class="pun">)</span></pre>

<p>
	ويكون الخرج بالشكل:
</p>

<pre class="ipsCode">&lt;Employee James White&gt; | ID: 5
&lt;Employee Jane Tanaka&gt; | ID: 3
</pre>

<h3>
	استرجاع سجل أو عدة سجلات وفقا لقيمة عمود
</h3>

<p>
	من الممكن استخدام تابع الترشيح <code>()filter_by</code> بغية الحصول على سجل محدّد وفقًا لقيمة أحد أعمدته، فمثلًا لجلب سجل ما وفقًا لقيمة معرفّه بما يشبه مبدأ عمل التابع <code>()get</code>، نكتب:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_29" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> employee </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">filter_by</span><span class="pun">(</span><span class="pln">id</span><span class="pun">=</span><span class="lit">1</span><span class="pun">).</span><span class="pln">first</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">employee</span><span class="pun">)</span></pre>

<p>
	ويكون الخرج بالشكل:
</p>

<pre class="ipsCode">&lt;Employee John Doe&gt;
</pre>

<p>
	استخدمنا التابع <code>()first</code> في الشيفرة السابقة لأنّ التابع <code>()filter_by</code> قد يعيد عدّة نتائج، وبذلك نجلب الأولى منها فقط.
</p>

<p>
	<strong>ملاحظة:</strong> في حال رغبتك بجلب سجلٍ ما وفقًا لمعرّفه، فإن المنهجية الأفضل هي استخدام التابع <code>()get</code>.
</p>

<p>
	من المُمكن أيضًا جلب موظف ما وفقًا لعمره، كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_31" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> employee </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">filter_by</span><span class="pun">(</span><span class="pln">age</span><span class="pun">=</span><span class="lit">52</span><span class="pun">).</span><span class="pln">first</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">employee</span><span class="pun">)</span></pre>

<p>
	فيكون الخرج بالشكل:
</p>

<pre class="ipsCode">&lt;Employee Harold Ishida&gt;
</pre>

<p>
	لنأخذ مثالًا على حالة تتضمن فيها نتائج الاستعلام عدّة سجلات، إذ سنستعلم عندها وفقًا لاسم الموظف الأول <code>firstname</code>، وبقيمة "Mary"، وهنا توجد موظفتان بهذا الاسم في قاعدة البيانات لدينا:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_33" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> mary </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">filter_by</span><span class="pun">(</span><span class="pln">firstname</span><span class="pun">=</span><span class="str">'Mary'</span><span class="pun">).</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">mary</span><span class="pun">)</span></pre>

<p>
	فيكون الخرج بالشكل:
</p>

<pre class="ipsCode">[&lt;Employee Mary Doe&gt;, &lt;Employee Mary Park&gt;]
</pre>

<p>
	استخدمنا التابع <code>()all</code> في الشيفرة السابقة للحصول على قائمة نتائج الاستعلام كاملةً، كما من الممكن استخدام التابع <code>()first</code> للحصول على النتيجة الأولى فقط، على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_35" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> mary </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">filter_by</span><span class="pun">(</span><span class="pln">firstname</span><span class="pun">=</span><span class="str">'Mary'</span><span class="pun">).</span><span class="pln">first</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">mary</span><span class="pun">)</span></pre>

<p>
	ويكون الخرج عندها بالشكل:
</p>

<pre class="ipsCode">&lt;Employee Mary Doe&gt;
</pre>

<p>
	وبذلك نكون قد جلبنا سجلاتٍ وفقًا لقيم أعمدة فيها، أما في الخطوة التالية فسنستعلم عن السجلات مُستخدمين شروطًا منطقية.
</p>

<h2>
	الخطوة 3: ترشيح السجلات باستخدام الشروط المنطقية
</h2>

<p>
	نحتاج غالبًا في تطبيقات الويب المعقدة كاملة الميزات إلى الاستعلام عن السجلات من قاعدة البيانات تحت شروطٍ معقدة، كأن نجلب الموظفين اعتمادًا على مجموعة من الشروط التي تأخذ في الحسبان مواقعهم وتوافرهم ودورهم الوظيفيّ ومسؤولياتهم.
</p>

<p>
	ستتدرب في هذه الخطوة على كيفية استخدام المعاملات الشرطية، كما ستستخدم تابع الترشيح <code>()filter</code> من سمة الاستعلام <code>query</code> بغية ترشيح نتائج الاستعلامات وفقًا لشروط منطقية تربط فيما بينها معاملاتٍ مُختلفة، إذ يمكن مثلًا استخدام المعاملات المنطقية لجلب قائمة بالموظفين غير المتواجدين في مكاتبهم حاليًا، أو أولئك الذين يستحقون ترقيةً، أو حتى لتوفير جدولة لمواعيد إجازات الموظفين وغيرها.
</p>

<h3>
	معامل التساوي
</h3>

<p>
	لعلّ أبسط معامل منطقي يمكن أن نستخدمه هو معامل المساواة <code>==</code>، والذي يعمل بآلية مُشابهة للتابع <code>()filter_by</code>، فللحصول مثلًا على كافّة السجلات التي قيمة عمود الاسم الأول <code>firstname</code> فيها تساوي "Mary"، يمكننا استخدام التابع <code>()filter</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_37" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> mary </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">firstname </span><span class="pun">==</span><span class="pln"> </span><span class="str">'Mary'</span><span class="pun">).</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">mary</span><span class="pun">)</span></pre>

<p>
	استخدمنا في الشيفرة السابقة التعليمة <code>Model.column == value</code> وسيطًا للتابع <code>()filter</code>، ويعدّ التابع <code>()filter_by</code> اختصارًا لهذه الطريقة، وستكون النتيجة نفسها كما في حالة استخدام التابع <code>()filter_by</code> تحت نفس الشرط، وهي:
</p>

<pre class="ipsCode">[&lt;Employee Mary Doe&gt;, &lt;Employee Mary Park&gt;]
</pre>

<p>
	وكما هو الحال لدى استخدام التابع <code>()filter_by</code>، من الممكن جلب نتيجة الاستعلام الأولى فقط باستخدام التابع <code>()first</code>، على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_39" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> mary </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">firstname </span><span class="pun">==</span><span class="pln"> </span><span class="str">'Mary'</span><span class="pun">).</span><span class="pln">first</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">mary</span><span class="pun">)</span></pre>

<p>
	فيصبح الخرج على الشّكل التالي:
</p>

<pre class="ipsCode">&lt;Employee Mary Doe&gt;
</pre>

<h3>
	معامل عدم التساوي
</h3>

<p>
	يتيح التابع <code>()filter</code> إمكانية استخدام معامل عدم التساوي <code>=!</code> من بايثون في الحصول على السجلات، إذ يمكن مثلًا استخدام المنهجية التالية بغية الحصول على قائمة بالموظفين غير المتواجدين في مكاتبهم حاليًا:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_41" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> out_of_office_employees </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">active </span><span class="pun">!=</span><span class="pln"> </span><span class="kwd">True</span><span class="pun">).</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">out_of_office_employees</span><span class="pun">)</span></pre>

<p>
	فيكون الخرج بالشكل التالي:
</p>

<pre class="ipsCode">[&lt;Employee Jane Tanaka&gt;, &lt;Employee Harold Ishida&gt;]
</pre>

<p>
	استخدمنا في الشيفرة السابقة الشرط <code>Employee.active != True</code> لترشيح نتائج الاستعلام.
</p>

<h3>
	معامل الأصغر تماما
</h3>

<p>
	من الممكن استخدام المعامل <code>&gt;</code> للحصول على سجل وفقًا لشرط كون قيمة أحد أعمدته أصغر تمامًا من قيمة معينة، إذ يمكن مثلًا الحصول على قائمة بالموظفين الذين أعمارهم أصغر تمامًا من 32 عامًا على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_43" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> employees_under_32 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">age </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">32</span><span class="pun">).</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> employee </span><span class="kwd">in</span><span class="pln"> employees_under_32</span><span class="pun">:</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">employee</span><span class="pun">.</span><span class="pln">firstname</span><span class="pun">,</span><span class="pln"> employee</span><span class="pun">.</span><span class="pln">lastname</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Age: '</span><span class="pun">,</span><span class="pln"> employee</span><span class="pun">.</span><span class="pln">age</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'----'</span><span class="pun">)</span></pre>

<p>
	فيكون الخرج بالشكل التالي:
</p>

<pre class="ipsCode">Alex Brown
Age:  29
----
James White
Age:  24
----
Scarlett Winter
Age:  22
----
Emily Vill
Age:  27
----
Mary Park
Age:  30
----
</pre>

<p>
	كما من الممكن استخدام المعامل <code>=&gt;</code> للحصول على السجلات الأصغر من قيمة معينة أو تساويها، فمثلًا لتضمين الموظفين الذين أعمارهم تساوي 32 عامًا بنتائج الاستعلام السابق، نكتب:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_45" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> employees_32_or_younger </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">age </span><span class="pun">&lt;=</span><span class="lit">32</span><span class="pun">).</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> employee </span><span class="kwd">in</span><span class="pln"> employees_32_or_younger</span><span class="pun">:</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">employee</span><span class="pun">.</span><span class="pln">firstname</span><span class="pun">,</span><span class="pln"> employee</span><span class="pun">.</span><span class="pln">lastname</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Age: '</span><span class="pun">,</span><span class="pln"> employee</span><span class="pun">.</span><span class="pln">age</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'----'</span><span class="pun">)</span></pre>

<p>
	فيصبح الخرج بالشّكل:
</p>

<pre class="ipsCode">John Doe
Age:  32
----
Jane Tanaka
Age:  32
----
Alex Brown
Age:  29
----
James White
Age:  24
----
Scarlett Winter
Age:  22
----
Emily Vill
Age:  27
----
Mary Park
Age:  30
----
</pre>

<h3>
	معامل الأكبر تماما
</h3>

<p>
	من الممكن استخدام المعامل <code>&lt;</code> للحصول على سجل وفقًا لشرط كون قيمة أحد أعمدته أكبر تمامًا من قيمة معينة. فعلى سبيل المثال، للحصول على قائمة بالموظفين الذين أعمارهم أكبر تمامًا من 32 عامًا، نكتب:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_47" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> employees_over_32 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">age </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">32</span><span class="pun">).</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> employee </span><span class="kwd">in</span><span class="pln"> employees_over_32</span><span class="pun">:</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">employee</span><span class="pun">.</span><span class="pln">firstname</span><span class="pun">,</span><span class="pln"> employee</span><span class="pun">.</span><span class="pln">lastname</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Age: '</span><span class="pun">,</span><span class="pln"> employee</span><span class="pun">.</span><span class="pln">age</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'----'</span><span class="pun">)</span></pre>

<p>
	فيكون الخرج بالشكل:
</p>

<pre class="ipsCode">Mary Doe
Age:  38
----
Harold Ishida
Age:  52
----
</pre>

<p>
	كما من الممكن استخدام المعامل <code>=&lt;</code> للحصول على السجلات الأكبر من قيمة معينة أو تساويها. فمثلًا لتضمين الموظفين الذين أعمارهم تساوي 32 عامًا بنتائج الاستعلام السابق، نكتب:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_49" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> employees_32_or_older </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">age </span><span class="pun">&gt;=</span><span class="lit">32</span><span class="pun">).</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> employee </span><span class="kwd">in</span><span class="pln"> employees_32_or_older</span><span class="pun">:</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">employee</span><span class="pun">.</span><span class="pln">firstname</span><span class="pun">,</span><span class="pln"> employee</span><span class="pun">.</span><span class="pln">lastname</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Age: '</span><span class="pun">,</span><span class="pln"> employee</span><span class="pun">.</span><span class="pln">age</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'----'</span><span class="pun">)</span></pre>

<p>
	فيصبح الخرج بالشّكل:
</p>

<pre class="ipsCode">John Doe
Age:  32
----
Mary Doe
Age:  38
----
Jane Tanaka
Age:  32
----
Harold Ishida
Age:  52
----
</pre>

<h3>
	تابع الانتماء In
</h3>

<p>
	تتيح الإضافة SQLAlchemy إمكانية الحصول على السجلات التي توافق قيمة أحد أعمدتها قيمةً ضمن قائمة معطاة وذلك باستخدام التابع <code>()_in</code> على العمود المطلوب على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_51" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> names </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">'Mary'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Alex'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Emily'</span><span class="pun">]</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> employees </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">firstname</span><span class="pun">.</span><span class="pln">in_</span><span class="pun">(</span><span class="pln">names</span><span class="pun">)).</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">employees</span><span class="pun">)</span></pre>

<p>
	فيكون الخرج بالشكل:
</p>

<pre class="ipsCode">[&lt;Employee Mary Doe&gt;, &lt;Employee Alex Brown&gt;, &lt;Employee Emily Vill&gt;, &lt;Employee Mary Park&gt;]
</pre>

<p>
	استخدمنا في الشيفرة السابقة شرطًا وفق الصيغة <code>(Model.column.in_(iterable</code>، إذ تشير العبارة <code>iterable</code> لأي نوع تكراري من الكائنات (أي كائن يمكن المرور على عناصره، مثل القائمة). لنأخذ مثال آخر، إذ من الممكن استخدام دالة بايثون <code>()range</code> للحصول على الموظفين المُنتمين لمجال معيّن من العمر، والاستعلام التالي يجلب كافّة الموظفين الذين أعمارهم في الثلاثينات:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_53" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> employees_in_30s </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">age</span><span class="pun">.</span><span class="pln">in_</span><span class="pun">(</span><span class="pln">range</span><span class="pun">(</span><span class="lit">30</span><span class="pun">,</span><span class="pln"> </span><span class="lit">40</span><span class="pun">))).</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> employee </span><span class="kwd">in</span><span class="pln"> employees_in_30s</span><span class="pun">:</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">employee</span><span class="pun">.</span><span class="pln">firstname</span><span class="pun">,</span><span class="pln"> employee</span><span class="pun">.</span><span class="pln">lastname</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Age: '</span><span class="pun">,</span><span class="pln"> employee</span><span class="pun">.</span><span class="pln">age</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'----'</span><span class="pun">)</span></pre>

<p>
	فيكون الخرج بالشّكل:
</p>

<pre class="ipsCode">John Doe
Age:  32
----
Mary Doe
Age:  38
----
Jane Tanaka
Age:  32
----
Mary Park
Age:  30
----
</pre>

<h3>
	تابع عدم الانتماء Not in
</h3>

<p>
	من الممكن استخدام تابع عدم الانتماء <code>()not_in</code> بصورةٍ مشابهة لآلية استخدام التابع السابق <code>()_in</code> بغية الحصول على السجلات التي لا تنتمي قيمة أحد أعمدتها لمجموعة قيم ضمن كائن تكراري ما، كما في المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_55" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> names </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">'Mary'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Alex'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Emily'</span><span class="pun">]</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> employees </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">firstname</span><span class="pun">.</span><span class="pln">not_in</span><span class="pun">(</span><span class="pln">names</span><span class="pun">)).</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">employees</span><span class="pun">)</span></pre>

<p>
	فيكون الخرج بالشّكل:
</p>

<pre class="ipsCode">[&lt;Employee John Doe&gt;, &lt;Employee Jane Tanaka&gt;, &lt;Employee James White&gt;, &lt;Employee Harold Ishida&gt;, &lt;Employee Scarlett Winter&gt;]
</pre>

<p>
	إذ حصلنا على كافّة الموظفين عدا أولئك الذي أسماؤهم الأولى موجودة ضمن القائمة المُسمّاة <code>names</code>.
</p>

<h3>
	معامل And
</h3>

<p>
	من الممكن ربط عدّة شروط منطقية مع بعضها بعضًا باستخدام الدالة <code>()_db.and</code> التي تعمل بنفس آلية المعامل <code>and</code> في بايثون.
</p>

<p>
	لنفرض مثلًا أننا نريد الحصول على جميع الموظفين الذين تبلغ أعمارهم 32 عامًا والمتواجدين في مكاتبهم حاليًا، ففي هذه الحالة، سنتحقق بدايةً من الموظفين البالغة أعمارهم 32 عامًا باستخدام التابع <code>()filter_by</code> (أو التابع <code>()filter</code> إذا أردت)، على النحو:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_57" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> employee </span><span class="kwd">in</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">filter_by</span><span class="pun">(</span><span class="pln">age</span><span class="pun">=</span><span class="lit">32</span><span class="pun">).</span><span class="pln">all</span><span class="pun">():</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">employee</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Age:'</span><span class="pun">,</span><span class="pln"> employee</span><span class="pun">.</span><span class="pln">age</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Active:'</span><span class="pun">,</span><span class="pln"> employee</span><span class="pun">.</span><span class="pln">active</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'-----'</span><span class="pun">)</span></pre>

<p>
	فيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">&lt;Employee John Doe&gt;
Age: 32
Active: True
-----
&lt;Employee Jane Tanaka&gt;
Age: 32
Active: False
-----
</pre>

<p>
	حصلنا في الخرج السابق على موظَفيْن تبلغ أعمارهما 32 عامًا، أحدهما متواجد في مكتبه حاليًا والآخر قد غادره، وللحصول على الموظفين البالغة أعمارهم 32 عامًا والمتواجدين في مكاتبهم حاليًا، فلا بدّ من استخدام شرطين ضمن التابع <code>()filter</code>، هما:
</p>

<ul>
	<li>
		<code>Employee.age == 32</code>
	</li>
	<li>
		<code>Employee.active == True</code>
	</li>
</ul>

<p>
	ولربط الشرطين معًا، من الممكن استخدام الدالة <code>()_db.and</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_59" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> active_and_32 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="pln">and_</span><span class="pun">(</span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">age </span><span class="pun">==</span><span class="pln"> </span><span class="lit">32</span><span class="pun">,</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">                                      </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">active </span><span class="pun">==</span><span class="pln"> </span><span class="kwd">True</span><span class="pun">)).</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">active_and_32</span><span class="pun">)</span></pre>

<p>
	فيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">[&lt;Employee John Doe&gt;]
</pre>

<p>
	استخدمنا في الشيفرة السابقة الصيغة <code>((filter(db.and_(condition1, condition2</code> للربط بين الشرطين.
</p>

<p>
	ومع تطبيق التابع <code>()all</code> على نتائج الاستعلام نحصل على قائمة بكافّة النتائج المُحقّقة لكلا الشرطين، كما من الممكن استخدام التابع <code>()first</code> للحصول على النتيجة الأولى فقط من نتائج الاستعلام.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_61" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> active_and_32 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="pln">and_</span><span class="pun">(</span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">age </span><span class="pun">==</span><span class="pln"> </span><span class="lit">32</span><span class="pun">,</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">                                      </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">active </span><span class="pun">==</span><span class="pln"> </span><span class="kwd">True</span><span class="pun">)).</span><span class="pln">first</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">active_and_32</span><span class="pun">)</span></pre>

<p>
	فيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">&lt;Employee John Doe&gt;
</pre>

<p>
	لنأخذ مثالًا أكثر تعقيدًا، لنفرض أننا نريد الاستعلام عن الموظفين الذين جرى تعيينهم خلال فترة زمنية معينة، عندها من الممكن استخدام الدالة <code>()db.and</code> مع الدالة <code>()date</code>، كما في المثال التالي الهادف للحصول على كافّة الموظفين المُعينين خلال عام 2019:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_63" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">from</span><span class="pln"> datetime </span><span class="kwd">import</span><span class="pln"> date
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> hired_in_2019 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="pln">and_</span><span class="pun">(</span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">hire_date </span><span class="pun">&gt;=</span><span class="pln"> date</span><span class="pun">(</span><span class="pln">year</span><span class="pun">=</span><span class="lit">2019</span><span class="pun">,</span><span class="pln"> month</span><span class="pun">=</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> day</span><span class="pun">=</span><span class="lit">1</span><span class="pun">),</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">hire_date </span><span class="pun">&lt;</span><span class="pln"> date</span><span class="pun">(</span><span class="pln">year</span><span class="pun">=</span><span class="lit">2020</span><span class="pun">,</span><span class="pln"> month</span><span class="pun">=</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> day</span><span class="pun">=</span><span class="lit">1</span><span class="pun">))).</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> 
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> employee </span><span class="kwd">in</span><span class="pln"> hired_in_2019</span><span class="pun">:</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">     </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">employee</span><span class="pun">,</span><span class="pln"> </span><span class="str">' | Hired: '</span><span class="pun">,</span><span class="pln"> employee</span><span class="pun">.</span><span class="pln">hire_date</span><span class="pun">)</span></pre>

<p>
	فيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">&lt;Employee Alex Brown&gt;  | Hired:  2019-01-03
&lt;Employee Emily Vill&gt;  | Hired:  2019-06-09
</pre>

<p>
	استوردنا في الشيفرة السابقة الدالة <code>()date</code>، كما رشّحنا نتائج الاستعلام باستخدام الدالة <code>()_db.and</code> بغية ربط شرطين، هما:
</p>

<ul>
	<li>
		<code>(Employee.hire_date &gt;= date(year=2019, month=1, day=1</code>: وهو مُحقّق "True" من أجل الموظفين المُعينين بدءًا من الأول من كانون الثاني لعام 2019 فما بعد.
	</li>
	<li>
		<code>(Employee.hire_date &lt; date(year=2020, month=1, day=1</code>: وهو مُحقّق "True" من أجل الموظفين المُعينين قبل الأول من كانون الثاني لعام 2020.
	</li>
</ul>

<p>
	حصلنا من خلال الربط بين الشرطين على الموظفين المُعينين بدءًا من بداية عام 2019 وحتى نهايته.
</p>

<h3>
	معامل Or
</h3>

<p>
	تعمل الدالة <code>()_db.or</code> على ربط شرطين منطقيين كما تفعل الدالة <code>()_db.and</code>، إلا أنها تشابه آلية عمل المعامل <code>or</code> في بايثون، إذ تعمل على جلب كافّة السجلات التي تحقق شرطًا على الأقل من الشرطين المُمررين إليها، فعلى سبيل المثال، للحصول على الموظفين البالغة أعمارهم 32 عامًا أو 52 عامًا، من الممكن الربط بين هذين الشرطين باستخدام الدالة <code>()_db.or</code> بالشكل:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_65" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> employees_32_or_52 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="pln">or_</span><span class="pun">(</span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">age </span><span class="pun">==</span><span class="pln"> </span><span class="lit">32</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">age </span><span class="pun">==</span><span class="pln"> </span><span class="lit">52</span><span class="pun">)).</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> 
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> e </span><span class="kwd">in</span><span class="pln"> employees_32_or_52</span><span class="pun">:</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">     </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">e</span><span class="pun">,</span><span class="pln"> </span><span class="str">'| Age:'</span><span class="pun">,</span><span class="pln"> e</span><span class="pun">.</span><span class="pln">age</span><span class="pun">)</span></pre>

<p>
	فيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">&lt;Employee John Doe&gt; | Age: 32
&lt;Employee Jane Tanaka&gt; | Age: 32
&lt;Employee Harold Ishida&gt; | Age: 52
</pre>

<p>
	يمكن استخدام التابعين <code>()startswith</code> (الذي يشترط على السلسة النصية البدء بمحرف معيّن) و <code>()endswith</code> (الذي يشترط على <a href="https://academy.hsoub.com/programming/python/%d8%a7%d9%84%d8%aa%d8%b9%d8%a7%d9%85%d9%84-%d9%85%d8%b9-%d8%a7%d9%84%d9%82%d9%88%d8%a7%d8%a6%d9%85-%d9%88%d8%a7%d9%84%d8%b3%d9%84%d8%a7%d8%b3%d9%84-%d8%a7%d9%84%d9%86%d8%b5%d9%8a%d8%a9-%d9%81%d9%8a-%d9%84%d8%ba%d8%a9-%d8%a8%d8%a7%d9%8a%d8%ab%d9%88%d9%86-r223/" rel="">السلسة النصية</a> الانتهاء بمحرف معيّن) على السلاسل النصية في الشروط المُمرّرة إلى التابع <code>()filter</code>، فعلى سبيل المثال، لجلب كافة الموظفين الذين يبدأ اسمهم الأول بالحرف <code>'M'</code> وأولئك الذين ينتهي اسمهم الأخير بالحرف <code>'e'</code> نكتب:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_67" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> employees </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="pln">or_</span><span class="pun">(</span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">firstname</span><span class="pun">.</span><span class="pln">startswith</span><span class="pun">(</span><span class="str">'M'</span><span class="pun">),</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">lastname</span><span class="pun">.</span><span class="pln">endswith</span><span class="pun">(</span><span class="str">'e'</span><span class="pun">))).</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> 
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> e </span><span class="kwd">in</span><span class="pln"> employees</span><span class="pun">:</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">e</span><span class="pun">)</span></pre>

<p>
	فنحصل على الخرج التالي:
</p>

<pre class="ipsCode">&lt;Employee John Doe&gt;
&lt;Employee Mary Doe&gt;
&lt;Employee James White&gt;
&lt;Employee Mary Park&gt;
</pre>

<p>
	ربطنا في الشيفرة السابقة الشرطين التاليين:
</p>

<ul>
	<li>
		<code>('Employee.firstname.startswith('M</code>: للحصول على الموظفين الذين يبدأ اسمهم الأول بالحرف <code>'M'</code>.
	</li>
	<li>
		<code>('Employee.lastname.endswith('e</code>: للحصول على الموظفين الذين ينتهي اسمهم الأخير بالحرف <code>'e'</code>.
	</li>
</ul>

<p>
	وبذلك أصبح من الممكن الآن ترشيح نتائج الاستعلام باستخدام الشروط المنطقية في تطبيقات Flask-SQLAlchemy، أمّا في الخطوة التالية فسنعمل على ترتيب وتحديد وحساب عدد النتائج التي نحصل عليها من <a href="https://academy.hsoub.com/programming/sql/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%B9%D9%86-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-r584/" rel="">قاعدة البيانات</a>.
</p>

<h2>
	الخطوة 4: ترتيب وتحديد وحساب عدد النتائج
</h2>

<p>
	غالبًا ما نضطر إلى ترتيب السجلات عند عرضها في تطبيقات الويب، فمن الممكن مثلًا أن يكون لدينا صفحةٌ لعرض التعيينات الأخيرة في كل قسم، وهذا يجعل باقي أعضاء فريق العمل يتعرّفون على زملائهم الجدد؛ كما من الممكن ترتيب عرض الموظفين بحيث يظهر الموظفين الأقدم أولًا بهدف معرفة أطول الموظفين خدمةً؛ وقد تحتاج أيضًا في بعض الحالات إلى تحديد النتائج، كما في حال رغبتك بعرض أحدث ثلاث موظفين ضمن شريط جانبي صغير، وغالبًا ما نضطر إلى حساب عدد نتائج الاستعلام، لعرض مثلًا عدد الموظفين المتواجدين في مكاتبهم حاليًا. ستتعرف في هذه الخطوة على كيفية ترتيب وتحديد وحساب عدد نتائج الاستعلامات.
</p>

<h3>
	ترتيب النتائج
</h3>

<p>
	نستخدم التابع <code>()order_by</code> لترتيب النتائج وفقًا لقيم عمود ما، فعلى سبيل المثال، لترتيب النتائج حسب الاسم الأول للموظف نكتب:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_69" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> employees </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">order_by</span><span class="pun">(</span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">firstname</span><span class="pun">).</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">employees</span><span class="pun">)</span></pre>

<p>
	فنحصل على الخرج التالي:
</p>

<pre class="ipsCode">[&lt;Employee Alex Brown&gt;, &lt;Employee Emily Vill&gt;, &lt;Employee Harold Ishida&gt;, &lt;Employee James White&gt;, &lt;Employee Jane Tanaka&gt;, &lt;Employee John Doe&gt;, &lt;Employee Mary Doe&gt;, &lt;Employee Mary Park&gt;, &lt;Employee Scarlett Winter&gt;]
</pre>

<p>
	نلاحظ من الخرج السابق أنّ النتائج مُرتّبة أبجديًا وفق الاسم الأول للموظف، كما من الممكن ترتيب النتائج وفقًا لقيم عمود آخر، كأن نستخدم الاسم الأخير للترتيب، كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_71" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> employees </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">order_by</span><span class="pun">(</span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">lastname</span><span class="pun">).</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">employees</span><span class="pun">)</span></pre>

<p>
	فيصبح الخرج على النحو التالي:
</p>

<pre class="ipsCode">[&lt;Employee Alex Brown&gt;, &lt;Employee John Doe&gt;, &lt;Employee Mary Doe&gt;, &lt;Employee Harold Ishida&gt;, &lt;Employee Mary Park&gt;, &lt;Employee Jane Tanaka&gt;, &lt;Employee Emily Vill&gt;, &lt;Employee James White&gt;, &lt;Employee Scarlett Winter&gt;]
</pre>

<p>
	كما من الممكن ترتيب الموظفين وفقًا لتواريخ تعيّينهم، على النحو:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_73" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> em_ordered_by_hire_date </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">order_by</span><span class="pun">(</span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">hire_date</span><span class="pun">).</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> 
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> employee </span><span class="kwd">in</span><span class="pln"> em_ordered_by_hire_date</span><span class="pun">:</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">employee</span><span class="pun">.</span><span class="pln">firstname</span><span class="pun">,</span><span class="pln"> employee</span><span class="pun">.</span><span class="pln">lastname</span><span class="pun">,</span><span class="pln"> employee</span><span class="pun">.</span><span class="pln">hire_date</span><span class="pun">)</span></pre>

<p>
	فيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">Harold Ishida 2002-03-06
John Doe 2012-03-03
Jane Tanaka 2015-09-12
Mary Doe 2016-06-07
Alex Brown 2019-01-03
Emily Vill 2019-06-09
James White 2021-02-04
Scarlett Winter 2021-04-07
Mary Park 2021-08-11
</pre>

<p>
	نلاحظ من الخرج السابق ترتيب تواريخ التعيين تصاعديًا من الأقدم إلى الأحدث، ومن الممكن عكس هذا الترتيب وجعله تنازلي أي من الأحدث إلى الأقدم باستخدام التابع <code>()desc</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_75" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> em_ordered_by_hire_date_desc </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">order_by</span><span class="pun">(</span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">hire_date</span><span class="pun">.</span><span class="pln">desc</span><span class="pun">()).</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> 
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> employee </span><span class="kwd">in</span><span class="pln"> em_ordered_by_hire_date_desc</span><span class="pun">:</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">     </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">employee</span><span class="pun">.</span><span class="pln">firstname</span><span class="pun">,</span><span class="pln"> employee</span><span class="pun">.</span><span class="pln">lastname</span><span class="pun">,</span><span class="pln"> employee</span><span class="pun">.</span><span class="pln">hire_date</span><span class="pun">)</span></pre>

<p>
	فيصبح الخرج كما يلي:
</p>

<pre class="ipsCode">Mary Park 2021-08-11
Scarlett Winter 2021-04-07
James White 2021-02-04
Emily Vill 2019-06-09
Alex Brown 2019-01-03
Mary Doe 2016-06-07
Jane Tanaka 2015-09-12
John Doe 2012-03-03
Harold Ishida 2002-03-06
</pre>

<p>
	كما من الممكن ربط كلا تابعي الترشيح <code>()filter</code> والترتيب <code>()order_by</code> معًا بغية ترتيب النتائج بعد ترشيحها، إذ تجلب الشيفرة التالية مثلًا كافّة الموظفين المُعينين خلال عام 2021 ثمّ ترتبهم حسب أعمارهم:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_77" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">from</span><span class="pln"> datetime </span><span class="kwd">import</span><span class="pln"> date
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> hired_in_2021 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="pln">and_</span><span class="pun">(</span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">hire_date </span><span class="pun">&gt;=</span><span class="pln"> date</span><span class="pun">(</span><span class="pln">year</span><span class="pun">=</span><span class="lit">2021</span><span class="pun">,</span><span class="pln"> month</span><span class="pun">=</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> day</span><span class="pun">=</span><span class="lit">1</span><span class="pun">),</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">hire_date </span><span class="pun">&lt;</span><span class="pln"> date</span><span class="pun">(</span><span class="pln">year</span><span class="pun">=</span><span class="lit">2022</span><span class="pun">,</span><span class="pln"> month</span><span class="pun">=</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> day</span><span class="pun">=</span><span class="lit">1</span><span class="pun">))).</span><span class="pln">order_by</span><span class="pun">(</span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">age</span><span class="pun">).</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> 
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> employee </span><span class="kwd">in</span><span class="pln"> hired_in_2021</span><span class="pun">:</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">employee</span><span class="pun">.</span><span class="pln">firstname</span><span class="pun">,</span><span class="pln"> employee</span><span class="pun">.</span><span class="pln">lastname</span><span class="pun">,</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">          employee</span><span class="pun">.</span><span class="pln">hire_date</span><span class="pun">,</span><span class="pln"> </span><span class="str">'| Age'</span><span class="pun">,</span><span class="pln"> employee</span><span class="pun">.</span><span class="pln">age</span><span class="pun">)</span></pre>

<p>
	فيكون الخرج كما يلي:
</p>

<pre class="ipsCode">Scarlett Winter 2021-04-07 | Age 22
James White 2021-02-04 | Age 24
Mary Park 2021-08-11 | Age 30
</pre>

<p>
	استخدمنا في الشيفرة السابقة الدالة <code>()_db.and</code> للربط بين شرطين مطلوب تحققهما معًا، الأول لتحديد الموظفين المُعينين بدءًا من الأول من كانون الثاني لعام 2021 فما بعد:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_79" style=""><span class="typ">Employee</span><span class="pun">.</span><span class="pln">hire_date </span><span class="pun">&gt;=</span><span class="pln"> date</span><span class="pun">(</span><span class="pln">year</span><span class="pun">=</span><span class="lit">2021</span><span class="pun">,</span><span class="pln"> month</span><span class="pun">=</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> day</span><span class="pun">=</span><span class="lit">1</span><span class="pun">)</span></pre>

<p>
	والثاني لتحديد الموظفين المُعينين قبل اليوم الأول من عام 2022:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_81" style=""><span class="typ">Employee</span><span class="pun">.</span><span class="pln">hire_date </span><span class="pun">&lt;</span><span class="pln"> date</span><span class="pun">(</span><span class="pln">year</span><span class="pun">=</span><span class="lit">2022</span><span class="pun">,</span><span class="pln"> month</span><span class="pun">=</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> day</span><span class="pun">=(</span><span class="lit">1</span><span class="pun">)</span></pre>

<p>
	ومن ثمّ استخدمنا التابع <code>()order_by</code> لترتيب الموظفين المُحققين للشرطين السابقين وفق أعمارهم.
</p>

<h3>
	تحديد النتائج
</h3>

<p>
	قد نحصل في معظم حالات الاستعلام عن جداول قواعد البيانات في التطبيقات الحقيقية على ملايين النتائج الموافقة، ما يجعل من تحديد عدد النتائج برقم معيّن أمرًا ضروريًا، ولتحقيق ذلك في تطبيقات Flask-SQLAlchemy، نستخدم التابع <code>()limit</code>. يبين المثال التالي كيفية الاستعلام عن البيانات في جدول الموظفين <code>employee</code>، ليعيد أول ثلاث نتائج موافقة فقط:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_84" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> employees </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">limit</span><span class="pun">(</span><span class="lit">3</span><span class="pun">).</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">employees</span><span class="pun">)</span></pre>

<p>
	فيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">[&lt;Employee John Doe&gt;, &lt;Employee Mary Doe&gt;, &lt;Employee Jane Tanaka&gt;]
</pre>

<p>
	كما من الممكن استخدام التابع <code>()limit</code> مع توابع أُخرى، مثل <code>filter</code> و <code>order_by</code>، إذ يمكن مثلًا الحصول على آخر موظَفيْن مُعيَنيْن لعام 2021 باستخدام التابع <code>()limit</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_86" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">from</span><span class="pln"> datetime </span><span class="kwd">import</span><span class="pln"> date
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> hired_in_2021 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="pln">and_</span><span class="pun">(</span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">hire_date </span><span class="pun">&gt;=</span><span class="pln"> date</span><span class="pun">(</span><span class="pln">year</span><span class="pun">=</span><span class="lit">2021</span><span class="pun">,</span><span class="pln"> month</span><span class="pun">=</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> day</span><span class="pun">=</span><span class="lit">1</span><span class="pun">),</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">hire_date </span><span class="pun">&lt;</span><span class="pln"> date</span><span class="pun">(</span><span class="pln">year</span><span class="pun">=</span><span class="lit">2022</span><span class="pun">,</span><span class="pln"> month</span><span class="pun">=</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> day</span><span class="pun">=</span><span class="lit">1</span><span class="pun">))).</span><span class="pln">order_by</span><span class="pun">(</span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">age</span><span class="pun">).</span><span class="pln">limit</span><span class="pun">(</span><span class="lit">2</span><span class="pun">).</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> 
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> employee </span><span class="kwd">in</span><span class="pln"> hired_in_2021</span><span class="pun">:</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">     </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">employee</span><span class="pun">.</span><span class="pln">firstname</span><span class="pun">,</span><span class="pln"> employee</span><span class="pun">.</span><span class="pln">lastname</span><span class="pun">,</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">           employee</span><span class="pun">.</span><span class="pln">hire_date</span><span class="pun">,</span><span class="pln"> </span><span class="str">'| Age'</span><span class="pun">,</span><span class="pln"> employee</span><span class="pun">.</span><span class="pln">age</span><span class="pun">)</span></pre>

<p>
	فنحصل على الخرج التالي:
</p>

<pre class="ipsCode">Scarlett Winter 2021-04-07 | Age 22
James White 2021-02-04 | Age 24
</pre>

<p>
	استخدمنا في الشيفرة السابقة نفس الاستعلام الوارد في القسم السابق ولكن مع استدعاء للتابع <code>(limit(2</code>.
</p>

<h3>
	حساب عدد النتائج
</h3>

<p>
	يمكننا استخدام التابع <code>()count</code> لحساب عدد نتائج استعلام ما، فعلى سبيل المثال، للحصول على عدد الموظفين الموجودين في قاعدة البيانات نكتب:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_88" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> employee_count </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">count</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">employee_count</span><span class="pun">)</span></pre>

<p>
	فنحصل على الخرج التالي:
</p>

<pre class="ipsCode">9
</pre>

<p>
	كما من الممكن استخدام التابع <code>()count</code> جنبًا إلى جنب مع توابع استعلام أُخرى مثل <code>()limit</code>. للحصول على عدد الموظفين المُعينين خلال عام 2021 مثلًا نكتب:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_90" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">from</span><span class="pln"> datetime </span><span class="kwd">import</span><span class="pln"> date
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> hired_in_2021_count </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="pln">and_</span><span class="pun">(</span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">hire_date </span><span class="pun">&gt;=</span><span class="pln"> date</span><span class="pun">(</span><span class="pln">year</span><span class="pun">=</span><span class="lit">2021</span><span class="pun">,</span><span class="pln"> month</span><span class="pun">=</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> day</span><span class="pun">=</span><span class="lit">1</span><span class="pun">),</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">hire_date </span><span class="pun">&lt;</span><span class="pln"> date</span><span class="pun">(</span><span class="pln">year</span><span class="pun">=</span><span class="lit">2022</span><span class="pun">,</span><span class="pln"> month</span><span class="pun">=</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> day</span><span class="pun">=</span><span class="lit">1</span><span class="pun">))).</span><span class="pln">order_by</span><span class="pun">(</span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">age</span><span class="pun">).</span><span class="pln">count</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">hired_in_2021_count</span><span class="pun">)</span></pre>

<p>
	فنحصل على الخرج التالي:
</p>

<pre class="ipsCode">3
</pre>

<p>
	استخدمنا في هذه الشيفرة نفس الاستعلام السابق الخاص بالحصول على الموظفين المُعينين خلال عام 2021 ولكن مع استخدام التابع <code>()count</code> للحصول على عددهم (عدد السجلات الموافقة) وهو 3.
</p>

<p>
	ومع نهاية هذه الخطوة نكون قد تعرّفنا على كيفية ترتيب وتحديد وحساب عدد نتائج استعلام في Flask-SQLAlchemy، أمّا في الخطوة التالية فسنتعرف على كيفية تقسيم نتائج الاستعلام على صفحاتٍ مُتعدّدة، إضافةً إلى كيفية إنشاء نظام ترقيم في تطبيقات فلاسك.
</p>

<h2>
	الخطوة 5: كيفية عرض قوائم السجلات الطويلة على صفحات متعددة
</h2>

<p>
	سنعمل في هذه الخطوة على تعديل الوجهة الرئيسية للتطبيق لتجعل الصفحة الرئيسية تعرض سجلات الموظفين على صفحاتٍ مُتعدّدة، ما يجعل من تصفّح قائمة الموظفين أسهل.
</p>

<p>
	سنستخدم بدايةً صَدَفة فلاسك لنوضّح كيفية استخدام ميزة الترقيم pagination في الإضافة Flask-SQLAlchemy، لذا نفتح صَدَفة فلاسك (إن لم تكن مفتوحة أصلًا):
</p>

<pre class="ipsCode">(env)user@localhost:$ flask shell
</pre>

<p>
	وبفرض أنّنا نريد توزيع سجلات جدول الموظفين على صفحاتٍ مُتعدّدة، من خلال عرض عنصرين اثنين فقط في كل صفحة، فمن الممكن إنجاز ذلك باستخدام تابع الاستعلام <code>()paginate</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_92" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> page1 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">paginate</span><span class="pun">(</span><span class="pln">page</span><span class="pun">=</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> per_page</span><span class="pun">=</span><span class="lit">2</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">page1</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">page1</span><span class="pun">.</span><span class="pln">items</span><span class="pun">)</span></pre>

<p>
	فيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">&lt;flask_sqlalchemy.Pagination object at 0x7f1dbee7af80&gt;
[&lt;Employee John Doe&gt;, &lt;Employee Mary Doe&gt;]
</pre>

<p>
	استخدمنا في الشيفرة السابقة معامل الصفحة <code>page</code> من تابع الاستعلام <code>()paginate</code> بغية تحديد الصفحة المراد الوصول إليها، وهي الصفحة الأولى في حالتا، أمّا المعامل <code>per_page</code> فيُحدّد عدد العناصر المعروضة في كل صفحة، وقد عيّنا قيمته لتكون "2" لتعرض كل صفحة عنصرين فقط، أمّا المتغير <code>page1</code> فما هو إلا كائن ترقيم يمكّننا من الوصول إلى السمات والتوابع المُستخدمة في إدارة عملية الترقيم.
</p>

<p>
	استخدمنا السمة <code>items</code> للوصول إلى عناصر الصفحة؛ أما للوصول إلى الصفحة التالية، فمن الممكن استخدام التابع <code>()next</code> من كائن الترقيم على النحو التالي، إذ ستكون النتيجة المُعادة كائن ترقيم أيضًا:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_94" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> page2 </span><span class="pun">=</span><span class="pln"> page1</span><span class="pun">.</span><span class="pln">next</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> 
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">page2</span><span class="pun">.</span><span class="pln">items</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">page2</span><span class="pun">)</span></pre>

<p>
	فيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">[&lt;Employee Jane Tanaka&gt;, &lt;Employee Alex Brown&gt;]

&lt;flask_sqlalchemy.Pagination object at 0x7f1dbee799c0&gt;
</pre>

<p>
	كما من الممكن الحصول على كائن ترقيم خاص بالصفحة السابقة باستخدام التابع <code>()prev</code>، ففي المثال التالي، نحصل على كائن الترقيم الخاص بالصفحة الرابعة، ومنه نصل إلى كائن ترقيم الصفحة السابقة، أي الصفحة رقم 3، على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_96" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> page4 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">paginate</span><span class="pun">(</span><span class="pln">page</span><span class="pun">=</span><span class="lit">4</span><span class="pun">,</span><span class="pln"> per_page</span><span class="pun">=</span><span class="lit">2</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">page4</span><span class="pun">.</span><span class="pln">items</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> page3 </span><span class="pun">=</span><span class="pln"> page4</span><span class="pun">.</span><span class="pln">prev</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">page3</span><span class="pun">.</span><span class="pln">items</span><span class="pun">)</span></pre>

<p>
	فيكون الخرج كما يلي:
</p>

<pre class="ipsCode">[&lt;Employee Scarlett Winter&gt;, &lt;Employee Emily Vill&gt;]

[&lt;Employee James White&gt;, &lt;Employee Harold Ishida&gt;]
</pre>

<p>
	كما من الممكن الوصول إلى رقم الصفحة الحالية باستخدام السمة <code>page</code> على النحو:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_98" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">page1</span><span class="pun">.</span><span class="pln">page</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">page2</span><span class="pun">.</span><span class="pln">page</span><span class="pun">)</span></pre>

<p>
	فنحصل على الخرج التالي:
</p>

<pre class="ipsCode">1
2
</pre>

<p>
	كما من الممكن استخدام السمة <code>pages</code> من كائن الترقيم للحصول على العدد الإجمالي من الصفحات، ففي المثال التالي، تعيد كلًا من التعليمتين <code>page1.pages</code> و <code>page2.pages</code> نفس القيمة لأن العدد الإجمالي للصفحات ثابت:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_100" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">page1</span><span class="pun">.</span><span class="pln">pages</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">page2</span><span class="pun">.</span><span class="pln">pages</span><span class="pun">)</span></pre>

<p>
	ويكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">5
5
</pre>

<p>
	أمّا للحصول على العدد الكلي من العناصر، فنستخدم السمة <code>total</code> من كائن الترقيم، على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_102" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">page1</span><span class="pun">.</span><span class="pln">total</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">page2</span><span class="pun">.</span><span class="pln">total</span><span class="pun">)</span></pre>

<p>
	فنحصل على الخرج التالي:
</p>

<pre class="ipsCode">9
9
</pre>

<p>
	نلاحظ في الخرج السابق وطالما أنّنا استعلمنا عن كافّة الموظفين، سيكون العدد الإجمالي للعناصر في كائن الترقيم هو 9، نظرًا لوجود تسعة موظفين في قاعدة البيانات.
</p>

<p>
	وفيما يلي قائمة ببعض السمات الأخرى التي تملكها كائنات الترقيم:
</p>

<ul>
	<li>
		<code>prev_num</code>: رقم الصفحة السابقة.
	</li>
	<li>
		<code>next_num</code>: رقم الصفحة التالية.
	</li>
	<li>
		<code>has_next</code>: تعيد القيمة "True" في حال وجود صفحة تالية.
	</li>
	<li>
		<code>has_prev</code>: تعيد القيمة "True" في حال وجود صفحة سابقة.
	</li>
	<li>
		<code>per_page</code>: عدد العناصر في الصفحة.
	</li>
</ul>

<p>
	كما يمتلك كائن الترقيم تابعًا باسم <code>()iter_pages</code> للمرور على أرقام الصفحات، إذ من الممكن مثلًا طباعة أرقام الصفحات على النحو:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_104" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pagination </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">paginate</span><span class="pun">(</span><span class="pln">page</span><span class="pun">=</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> per_page</span><span class="pun">=</span><span class="lit">2</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> 
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> page_num </span><span class="kwd">in</span><span class="pln"> pagination</span><span class="pun">.</span><span class="pln">iter_pages</span><span class="pun">():</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">     </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">page_num</span><span class="pun">)</span></pre>

<p>
	فيكون الخرج كما يلي:
</p>

<pre class="ipsCode">1
2
3
4
5
</pre>

<p>
	وفيما يلي مثال لتوضيح كيفية الوصول إلى جميع الصفحات وعناصرها باستخدام كل من كائن الترقيم والتابع <code>()iter_pages</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_106" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pagination </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">paginate</span><span class="pun">(</span><span class="pln">page</span><span class="pun">=</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> per_page</span><span class="pun">=</span><span class="lit">2</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> 
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> page_num </span><span class="kwd">in</span><span class="pln"> pagination</span><span class="pun">.</span><span class="pln">iter_pages</span><span class="pun">():</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">     </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'PAGE'</span><span class="pun">,</span><span class="pln"> pagination</span><span class="pun">.</span><span class="pln">page</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">     </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'-'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">     </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">pagination</span><span class="pun">.</span><span class="pln">items</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">     </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'-'</span><span class="pun">*</span><span class="lit">20</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">     pagination </span><span class="pun">=</span><span class="pln"> pagination</span><span class="pun">.</span><span class="pln">next</span><span class="pun">()</span></pre>

<p>
	فنحصل على الخرج التالي:
</p>

<pre class="ipsCode">PAGE 1
-
[&lt;Employee John Doe&gt;, &lt;Employee Mary Doe&gt;]
--------------------
PAGE 2
-
[&lt;Employee Jane Tanaka&gt;, &lt;Employee Alex Brown&gt;]
--------------------
PAGE 3
-
[&lt;Employee James White&gt;, &lt;Employee Harold Ishida&gt;]
--------------------
PAGE 4
-
[&lt;Employee Scarlett Winter&gt;, &lt;Employee Emily Vill&gt;]
--------------------
PAGE 5
-
[&lt;Employee Mary Park&gt;]
--------------------
</pre>

<p>
	أنشأنا في الشيفرة السابقة كائن ترقيم يبدأ من الصفحة الأولى، ثمّ مررنا على الصفحات باستخدام حلقة <code>for</code> تكرارية عن طريق تابع الترقيم <code>()iter_pages</code>، لنطبع رقم وعناصر الصفحة، ثم أسندنا قيمة كائن الترقيم الخاص بالصفحة التالية إلى كائن الترقيم <code>pagination</code> باستخدام التابع <code>()next</code>.
</p>

<p>
	يمكن استخدام كلًا من تابعي الترشيح <code>()filter</code> والترتيب <code>()order_by</code> جنبًا إلى جنب مع تابع الترقيم <code>()paginate</code> لترقيم نتائج الاستعلام بعد ترشيحها وترتيبها، فعلى سبيل المثال، يمكننا الحصول على الموظفين ممن تجاوزوا الثلاثين من عمرهم ونرتّب النتائج وفقًا للعمر وننفّذ على النتائج عملية ترقيم كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_108" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pagination </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">age </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">30</span><span class="pun">).</span><span class="pln">order_by</span><span class="pun">(</span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">age</span><span class="pun">).</span><span class="pln">paginate</span><span class="pun">(</span><span class="pln">page</span><span class="pun">=</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> per_page</span><span class="pun">=</span><span class="lit">2</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> 
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> page_num </span><span class="kwd">in</span><span class="pln"> pagination</span><span class="pun">.</span><span class="pln">iter_pages</span><span class="pun">():</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">     </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'PAGE'</span><span class="pun">,</span><span class="pln"> pagination</span><span class="pun">.</span><span class="pln">page</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">     </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'-'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">     </span><span class="kwd">for</span><span class="pln"> employee </span><span class="kwd">in</span><span class="pln"> pagination</span><span class="pun">.</span><span class="pln">items</span><span class="pun">:</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">         </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">employee</span><span class="pun">,</span><span class="pln"> </span><span class="str">'| Age: '</span><span class="pun">,</span><span class="pln"> employee</span><span class="pun">.</span><span class="pln">age</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">     </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'-'</span><span class="pun">*</span><span class="lit">20</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">     pagination </span><span class="pun">=</span><span class="pln"> pagination</span><span class="pun">.</span><span class="pln">next</span><span class="pun">()</span></pre>

<p>
	فنحصل على خرجٍ بالشّكل التالي:
</p>

<pre class="ipsCode">PAGE 1
-
&lt;Employee John Doe&gt; | Age:  32
&lt;Employee Jane Tanaka&gt; | Age:  32
--------------------
PAGE 2
-
&lt;Employee Mary Doe&gt; | Age:  38
&lt;Employee Harold Ishida&gt; | Age:  52
--------------------
</pre>

<p>
	الآن وبعد أن تعرفنا على آلية عمل ميزة الترقيم في Flask-SQLAlchemy بوضوح، سنحرّر صفحة التطبيق الرئيسية لعرض الموظفين على صفحاتٍ مُتعدّدة، وهذا ما يجعل تصفحهم أسهل على المُستخدم.
</p>

<p>
	نخرج من صَدَفة فلاسك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_110" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> exit</span><span class="pun">()</span></pre>

<p>
	سنستخدم معاملات <a href="https://academy.hsoub.com/programming/general/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%B9%D9%86%D9%88%D8%A7%D9%86-url-%D9%88%D8%A3%D9%86%D9%88%D8%A7%D8%B9%D9%87-r1435/" rel="">الروابط URL</a> المعروفة باسم سلاسل الاستعلام عن الروابط URL query strings للوصول إلى صفحاتِ الموظفين المُختلفة، وهي طريقة لتمرير المعلومات إلى التطبيق من خلال الروابط، إذ تُمرّر المعاملات إلى التطبيق بعد الرمز <code>?</code> في الرابط. على سبيل المثال، من الممكن استخدام الروابط التالية لتمرير معامل الصفحة <code>page</code> بقيمٍ مُختلفة:
</p>

<pre class="ipsCode">http://127.0.0.1:5000/?page=1
http://127.0.0.1:5000/?page=3
</pre>

<p>
	إذ يمرِّر هنا الرابط الأوّل القيمة "1" إلى معامل الرابط <code>page</code>، بينما يمرّر الرابط الثاني القيمة "3" لنفس المعامل.
</p>

<p>
	نفتح الآن الملف app.py:
</p>

<pre class="ipsCode">(env)user@localhost:$ nano app.py
</pre>

<p>
	ونعدّل الوجهة الرئيسية <code>index</code> لتصبح على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_112" style=""><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> index</span><span class="pun">():</span><span class="pln">
    page </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">args</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="str">'page'</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> type</span><span class="pun">=</span><span class="pln">int</span><span class="pun">)</span><span class="pln">
    pagination </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">order_by</span><span class="pun">(</span><span class="typ">Employee</span><span class="pun">.</span><span class="pln">firstname</span><span class="pun">).</span><span class="pln">paginate</span><span class="pun">(</span><span class="pln">
        page</span><span class="pun">,</span><span class="pln"> per_page</span><span class="pun">=</span><span class="lit">2</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'index.html'</span><span class="pun">,</span><span class="pln"> pagination</span><span class="pun">=</span><span class="pln">pagination</span><span class="pun">)</span></pre>

<p>
	حصلنا في الشيفرة السابقة على قيمة معامل الرابط للصفحة المسمّى <code>page</code> باستخدام كل من الكائن <code>request.args</code> وتابعه <code>()get</code>. فعلى سبيل المثال، نأخذ من الجزء <code>page=1?/</code> من الرابط القيمة "1" الخاصّة بمعامل الرابط للصفحة المسمّى <code>page</code>، لتُمرّر هذه القيمة "1" مثل قيمة افتراضية، ونمرّر نمط البيانات <code>int</code> من بايثون مثل وسيط لمعامل النوع <code>type</code> لضمان كون القيمة المُمررة من النوع عدد صحيح.
</p>

<p>
	ثمّ أنشأنا الكائن <code>pagination</code>، ورتّبنا نتائج الاستعلام وفق الاسم الأول للموظف، كما مرّرنا قيمة معامل الرابط <code>page</code> إلى التابع <code>()paginate</code>، ووزّعنا النتائج على عنصرين لكل صفحة عبر تمرير القيمة "2" إلى المعامل <code>per_page</code>، ومرّرنا نهايةً كائن الترقيم المُنشأ <code>pagination</code> إلى القالب المُصيّر index.html.
</p>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	أمّا الآن، سنحرر القالب index.html بغية عرض عناصر الترقيم:
</p>

<pre class="ipsCode">(env)user@localhost:$ nano templates/index.html
</pre>

<p>
	سنعدّل وسم التقسيم <code>div</code> الخاص بالمحتوى ليتضمّن عنوانًا من المستوى الثاني <code>h2</code> للدلالة على الصفحة الحالية، كما سنعدّل <a href="https://academy.hsoub.com/programming/python/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AD%D9%84%D9%82%D8%A7%D8%AA-%D8%AA%D9%83%D8%B1%D8%A7%D8%B1-for-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-r513/" rel="">حلقة for التكرارية</a> لتمر على الكائن <code>pagination.items</code> بدلًا من الكائن <code>employees</code> الذي لم يعد موجودًا.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1338_114" style=""><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"content"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;h2&gt;</span><span class="pln">(Page {{ pagination.page }})</span><span class="tag">&lt;/h2&gt;</span><span class="pln">
    {% for employee in pagination.items %}
        </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"employee"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;p&gt;&lt;b&gt;</span><span class="pln">#{{ employee.id }}</span><span class="tag">&lt;/b&gt;&lt;/p&gt;</span><span class="pln">
            </span><span class="tag">&lt;b&gt;</span><span class="pln">
                </span><span class="tag">&lt;p</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"name"</span><span class="tag">&gt;</span><span class="pln">{{ employee.firstname }} {{ employee.lastname }}</span><span class="tag">&lt;/p&gt;</span><span class="pln">
            </span><span class="tag">&lt;/b&gt;</span><span class="pln">
            </span><span class="tag">&lt;p&gt;</span><span class="pln">{{ employee.email }}</span><span class="tag">&lt;/p&gt;</span><span class="pln">
            </span><span class="tag">&lt;p&gt;</span><span class="pln">{{ employee.age }} years old.</span><span class="tag">&lt;/p&gt;</span><span class="pln">
            </span><span class="tag">&lt;p&gt;</span><span class="pln">Hired: {{ employee.hire_date }}</span><span class="tag">&lt;/p&gt;</span><span class="pln">
            {% if employee.active %}
                </span><span class="tag">&lt;p&gt;&lt;i&gt;</span><span class="pln">(Active)</span><span class="tag">&lt;/i&gt;&lt;/p&gt;</span><span class="pln">
            {% else %}
                </span><span class="tag">&lt;p&gt;&lt;i&gt;</span><span class="pln">(Out of Office)</span><span class="tag">&lt;/i&gt;&lt;/p&gt;</span><span class="pln">
            {% endif %}
        </span><span class="tag">&lt;/div&gt;</span><span class="pln">
    {% endfor %}
</span><span class="tag">&lt;/div&gt;</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	الآن، سنضبط كلًا من متغيري البيئة <code>FLASK_APP</code> و <code>FLASK_ENV</code>، كما سنشغّل خادم التطوير (وذلك في حال عدم إنجاز هذه الخطوات مُسبقًا):
</p>

<pre class="ipsCode">(env)user@localhost:$ export FLASK_APP=app
(env)user@localhost:$ export FLASK_ENV=development
(env)user@localhost:$ flask run
</pre>

<p>
	ننتقل الآن إلى الصفحة الرئيسية للتطبيق باستخدام قيمٍ مُختلفة لمعامل الرابط <code>page</code>، على النحو التالي:
</p>

<pre class="ipsCode">http://127.0.0.1:5000/
http://127.0.0.1:5000/?page=2
http://127.0.0.1:5000/?page=4
http://127.0.0.1:5000/?page=19
</pre>

<p>
	فسيظهر في كل مرة صفحة جديدة، يتضمّن كل منها عنصرين مُختلفين بما يُشبه النتيجة التي رأيناها سابقًا ضمن صَدَفة فلاسك.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="120690" href="https://academy.hsoub.com/uploads/monthly_2023_03/02_Paginated_Index.png.e2cc43350c5cfb447d76dcbd0d95d8bc.png" rel=""><img alt="02_Paginated_Index.png" class="ipsImage ipsImage_thumbnailed" data-fileid="120690" data-unique="0llf8t5ct" src="https://academy.hsoub.com/uploads/monthly_2023_03/02_Paginated_Index.thumb.png.6e234b3b22fe326e75e7b84eb3851ea8.png"> </a>
</p>

<p>
	وفي حال كون الرقم المُمرّر إلى معامل الرابط غير موجود، فعندها سيُعرض <a href="https://academy.hsoub.com/programming/general/%d9%85%d8%af%d8%ae%d9%84-%d8%a5%d9%84%d9%89-http-r73/" rel="">خطأ HTTP</a> من النوع "‎404 Not Found"، كما هو الحال مع الرابط الأخير من قائمة الروابط أعلاه.
</p>

<p>
	أمّا الآن، فسننشئ عنصر واجهة مستخدم widget خاص بالترقيم لنسمح للمستخدم بالتنقّل بين الصفحات، إذ سنستخدم بعض سمات وتوابع كائن الترقيم بغية عرض أرقام كافّة الصفحات، إذ سيمثّل كل رقم رابطًا إلى صفحته الموافقة، كما سنعرض الزر <code>&gt;&gt;&gt;</code> للرجوع في حال كانت الصفحة الحالية تمتلك صفحة سابقة، وزر <code>&lt;&lt;&lt;</code> للانتقال إلى الصفحة التالية في حال كانت الصفحة الحالية تمتلك صفحة تليها.
</p>

<p>
	وسيبدو عنصر <a href="https://academy.hsoub.com/design/user-interface/%D8%A7%D9%84%D8%AF%D9%84%D9%8A%D9%84-%D8%A5%D9%84%D9%89-%D8%AA%D9%87%D9%8A%D8%A6%D8%A9-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-ui-r652/" rel="">واجهة المستخدم</a> الخاص بالترقيم بالشكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="120689" href="https://academy.hsoub.com/uploads/monthly_2023_03/03_Pagination_Widget.png.8ce6da236842a8ebd512b3b17505dfcc.png" rel=""><img alt="03_Pagination_Widget.png" class="ipsImage ipsImage_thumbnailed" data-fileid="120689" data-unique="5umzzfb0j" src="https://academy.hsoub.com/uploads/monthly_2023_03/03_Pagination_Widget.thumb.png.a3dc14d36d4fd8a8f78b4ffca9c40df4.png"> </a>
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="120688" href="https://academy.hsoub.com/uploads/monthly_2023_03/04_Pagination_Widget2.png.0c61a3c34a522c5558395f8a75b075b4.png" rel=""><img alt="04_Pagination_Widget2.png" class="ipsImage ipsImage_thumbnailed" data-fileid="120688" data-unique="n5o0oxyrr" src="https://academy.hsoub.com/uploads/monthly_2023_03/04_Pagination_Widget2.thumb.png.607426b63e5f6a60a1f44ae2325356af.png"> </a>
</p>

<p>
	ولإضافته، نفتح الملف index.html:
</p>

<pre class="ipsCode">(env)user@localhost:$ nano templates/index.html
</pre>

<p>
	ونعدّله بإضافة وسم تقسيم <code>div</code> جديد أسفل وسم <code>div</code> السابق الخاص بالمحتوى، على النحو:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1338_116" style=""><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"content"</span><span class="tag">&gt;</span><span class="pln">
    {% for employee in pagination.items %}
        </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"employee"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;p&gt;&lt;b&gt;</span><span class="pln">#{{ employee.id }}</span><span class="tag">&lt;/b&gt;&lt;/p&gt;</span><span class="pln">
            </span><span class="tag">&lt;b&gt;</span><span class="pln">
                </span><span class="tag">&lt;p</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"name"</span><span class="tag">&gt;</span><span class="pln">{{ employee.firstname }} {{ employee.lastname }}</span><span class="tag">&lt;/p&gt;</span><span class="pln">
            </span><span class="tag">&lt;/b&gt;</span><span class="pln">
            </span><span class="tag">&lt;p&gt;</span><span class="pln">{{ employee.email }}</span><span class="tag">&lt;/p&gt;</span><span class="pln">
            </span><span class="tag">&lt;p&gt;</span><span class="pln">{{ employee.age }} years old.</span><span class="tag">&lt;/p&gt;</span><span class="pln">
            </span><span class="tag">&lt;p&gt;</span><span class="pln">Hired: {{ employee.hire_date }}</span><span class="tag">&lt;/p&gt;</span><span class="pln">
            {% if employee.active %}
                </span><span class="tag">&lt;p&gt;&lt;i&gt;</span><span class="pln">(Active)</span><span class="tag">&lt;/i&gt;&lt;/p&gt;</span><span class="pln">
            {% else %}
                </span><span class="tag">&lt;p&gt;&lt;i&gt;</span><span class="pln">(Out of Office)</span><span class="tag">&lt;/i&gt;&lt;/p&gt;</span><span class="pln">
            {% endif %}
        </span><span class="tag">&lt;/div&gt;</span><span class="pln">
    {% endfor %}
</span><span class="tag">&lt;/div&gt;</span><span class="pln">

</span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"pagination"</span><span class="tag">&gt;</span><span class="pln">
    {% if pagination.has_prev %}
        </span><span class="tag">&lt;span&gt;</span><span class="pln">
            </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">'page-number'</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('index', page=pagination.prev_num) }}"</span><span class="tag">&gt;</span><span class="pln">
                {{ '&lt;&lt;&lt;' }}
            </span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;/span&gt;</span><span class="pln">
    {% endif %}

    {% for number in pagination.iter_pages() %}
        {% if pagination.page != number %}
            </span><span class="tag">&lt;span&gt;</span><span class="pln">
                    </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">'page-number'</span><span class="pln">
                        </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('index', page=number) }}"</span><span class="tag">&gt;</span><span class="pln">
                    {{ number }}
                    </span><span class="tag">&lt;/a&gt;</span><span class="pln">
            </span><span class="tag">&lt;/span&gt;</span><span class="pln">
        {% else %}
            </span><span class="tag">&lt;span</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">'current-page-number'</span><span class="tag">&gt;</span><span class="pln">{{ number }}</span><span class="tag">&lt;/span&gt;</span><span class="pln">
        {% endif %}
    {% endfor %}

    {% if pagination.has_next %}
        </span><span class="tag">&lt;span&gt;</span><span class="pln">
            </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">'page-number'</span><span class="pln">
                </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('index', page=pagination.next_num) }}"</span><span class="tag">&gt;</span><span class="pln">
                {{ '&gt;&gt;&gt;' }}
            </span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;/span&gt;</span><span class="pln">
    {% endif %}
</span><span class="tag">&lt;/div&gt;</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	استخدمنا في الشيفرة السابقة الشرط <code>if pagination.has_prev</code> بغية إضافة رابط لزر الرجوع إلى الصفحة السابقة <code>&gt;&gt;&gt;</code> وذلك فقط في حال لم تكن الصفحة الحالية هي الصفحة الأولى، إذ ربطنا الزر مع الصفحة السابقة من خلال استدعاء الدالة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_120" style=""><span class="pln">url_for</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">,</span><span class="pln"> page</span><span class="pun">=</span><span class="pln">pagination</span><span class="pun">.</span><span class="pln">prev_num</span><span class="pun">)</span></pre>

<p>
	وفيها يُربط الزر بدالة العرض الرئيسية <code>index</code> ممررين القيمة <code>pagination.prev_num</code> إلى معامل الرابط <code>page</code>.
</p>

<p>
	لعرض روابط لكل أرقام الصفحات المتوفرة، مررنا باستخدام <a href="https://academy.hsoub.com/programming/general/%D8%A7%D9%84%D8%AD%D9%84%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D8%AA%D9%83%D8%B1%D8%A7%D8%B1%D9%8A%D8%A9-%D9%81%D9%8A-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-r1306/" rel="">حلقة تكرارية</a> على كافّة عناصر التابع <code>()pagination.iter_pages</code> الذي يزودنا برقم صفحة عند كل تكرار، واستخدمنا الشرط <code>if pagination.page != number</code> لنتحقق ما إذا كان رقم الصفحة الحالي مغاير للرقم في التكرار الحالي؛ فعند تحقق الشرط، ننشئ رابطًا يُمكّن المستخدم من تغيّير الصفحة الحالية إلى صفحة أخرى، وإلّا فإنّنا نعرض رقم الصفحة دون رابط، إذ تكون الصفحة الحالية في هذه الحالة موافقة للرقم الحالي في التكرار، وهذا ما سيساعد المستخدمين على معرفة رقم الصفحة الحالية من خلال عنصر واجهة المستخدم الخاص بالترقيم.
</p>

<p>
	استخدمنا نهايةً الشرط <code>pagination.has_next</code> لنتحقّق من وجود صفحة تلي الصفحة الحالية، وفي حال تحقُّق هذا الشرط، نُنشئ رابطًا إليها من خلال استدعاء التابع وربطه بالزر <code>&lt;&lt;&lt;</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1338_122" style=""><span class="pln">url_for</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">,</span><span class="pln"> page</span><span class="pun">=</span><span class="pln">pagination</span><span class="pun">.</span><span class="pln">next_num</span><span class="pun">)</span></pre>

<p>
	الآن وبالانتقال إلى الصفحة الرئيسية للتطبيق عبر المتصفح باستخدام الرابط:
</p>

<pre class="ipsCode">http://127.0.0.1:5000/
</pre>

<p>
	سنجد عنصر واجهة المستخدم الخاص بالترقيم يعمل بنجاح، كما في الشكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="120687" href="https://academy.hsoub.com/uploads/monthly_2023_03/05_Pagination_Widget.png.dd5e115ad55f0b656880a4f9346dc912.png" rel=""><img alt="05_Pagination_Widget.png" class="ipsImage ipsImage_thumbnailed" data-fileid="120687" data-unique="ixqysesf8" src="https://academy.hsoub.com/uploads/monthly_2023_03/05_Pagination_Widget.thumb.png.6c3efb728d2eca745dfc5d18d0c6d000.png"> </a>
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="120686" href="https://academy.hsoub.com/uploads/monthly_2023_03/06_Pagination_Widget2.png.c00791206e7b93f09e2a1795ba3dc665.png" rel=""><img alt="06_Pagination_Widget2.png" class="ipsImage ipsImage_thumbnailed" data-fileid="120686" data-unique="t31ivjufw" src="https://academy.hsoub.com/uploads/monthly_2023_03/06_Pagination_Widget2.thumb.png.9e274ebd7c17e9b25d4cd83a14fd2ec5.png"> </a>
</p>

<p>
	استخدمنا هنا الرمز <code>&lt;&lt;&lt;</code> للدلالة على الانتقال إلى الصفحة التالية، والرمز <code>&gt;&gt;&gt;</code> للدلالة على الانتقال إلى الصفحة السابقة، ولكن من الممكن استخدام أي رمز آخر مثل <code>&gt;</code> و <code>&lt;</code>، أو حتى صور باستخدام <a href="https://wiki.hsoub.com/HTML/img" rel="external">وسم الصورة &lt;img&gt;</a>.
</p>

<p>
	مع نهاية هذه الخطوة نكون قد عرضنا بيانات الموظفين على صفحاتٍ مُتعدّدة، وتعلّمنا كيفية التعامل مع مفهوم الترقيم في Flask-SQLAlchemy، ومن الآن أصبح بإمكانك استخدام عنصر واجهة المستخدم الخاص بالترقيم الذي أنشأناه في أي تطبيقات فلاسك أخرى تُنشئها.
</p>

<h2>
	الخاتمة
</h2>

<p>
	استخدمنا في هذا المقال الإضافة Flask-SQLAlchemy لإنشاء نظام إدارة موظفين، إذ استعرضنا كيفية الاستعلام عن جدول وترشيح نتائجه وفق قيم أحد أعمدته أو وفق شروطٍ منطقية بسيطة ومُركّبة، كما بيّنا كيفية ترتيب نتائج الاستعلامات وتحديدها وحساب عددها، وأنشأنا أيضًا نظام ترقيم بغية عرض عددٍ مُحدّد من السجلات في كل صفحة من صفحات تطبيق الويب، مع إمكانية التنقّل بين هذه الصفحات.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-query-tables-and-paginate-data-in-flask-sqlalchemy#step-3-filtering-records-using-logical-conditions" rel="external nofollow">How To Query Tables and Paginate Data in Flask-SQLAlchemy</a> لصاحبه Abdelhadi Dyouri.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D9%85%D9%84%D8%A1-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%A8%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%AA%D8%AC%D8%B1%D9%8A%D8%A8%D9%8A%D8%A9-%D9%88%D8%A7%D9%84%D8%B7%D8%B1%D9%82-%D8%A7%D9%84%D9%85%D8%A8%D8%AF%D8%A6%D9%8A%D8%A9-%D9%84%D9%84%D8%AD%D8%B5%D9%88%D9%84-%D8%B9%D9%84%D9%8A%D9%87%D8%A7-%D9%81%D9%8A-sqlalchemy-r827/" rel="">ملء قاعدة البيانات ببيانات تجريبية والطرق المبدئية للحصول عليها في SQLAlchemy</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AC%D8%AF%D9%88%D9%84%D9%8A-%D8%A7%D9%84%D9%85%D9%82%D8%A7%D9%84%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D9%85%D9%8F%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D9%8A%D9%86-%D9%81%D9%8A-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-r514/" rel="">إنشاء جدولي المقالات والمُستخدمين في قاعدة البيانات</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D9%85%D9%84%D8%A1-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%A8%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%AA%D8%AC%D8%B1%D9%8A%D8%A8%D9%8A%D8%A9-%D9%88%D8%A7%D9%84%D8%B7%D8%B1%D9%82-%D8%A7%D9%84%D9%85%D8%A8%D8%AF%D8%A6%D9%8A%D8%A9-%D9%84%D9%84%D8%AD%D8%B5%D9%88%D9%84-%D8%B9%D9%84%D9%8A%D9%87%D8%A7-%D9%81%D9%8A-sqlalchemy-r827/" rel="">تجهيز جدولي المقالات والمستخدمين باستعمال إضافة Flask-SQLAlchemy</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1925</guid><pubDate>Mon, 20 Mar 2023 13:03:00 +0000</pubDate></item><item><title>&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; SQLAlchemy &#x641;&#x64A; &#x641;&#x644;&#x627;&#x633;&#x643; &#x644;&#x625;&#x646;&#x634;&#x627;&#x621; &#x645;&#x62F;&#x648;&#x646;&#x629; &#x645;&#x639; &#x639;&#x644;&#x627;&#x642;&#x627;&#x62A; many-to-many</title><link>https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-sqlalchemy-%D9%81%D9%8A-%D9%81%D9%84%D8%A7%D8%B3%D9%83-%D9%84%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D8%AF%D9%88%D9%86%D8%A9-%D9%85%D8%B9-%D8%B9%D9%84%D8%A7%D9%82%D8%A7%D8%AA-many-to-many-r1924/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_03/--------many-to-many---SQLAlchemy--.jpg.850c658439e87b728355329c0ce5b19e.jpg" /></p>
<p>
	يُعد <span ipsnoautolink="true">فلاسك</span> إطار عمل للويب مبني بلغة <span ipsnoautolink="true">بايثون</span>، ويتميز بكونه صغير الحجم وسهل المعالجة، ويوفّر أيضًا عدة أدوات وميزات من شأنها إنشاء تطبيقات ويب في لغة بايثون.
</p>

<p>
	أمّا <a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%AC%D9%87%D9%8A%D8%B2-%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-flask-sqlalchemy-r508/" rel="">SQLAlchemy</a>، فهي أداةٌ في محرك <a href="https://academy.hsoub.com/programming/sql/%d9%85%d8%af%d8%ae%d9%84-%d8%a5%d9%84%d9%89-sql-r844/" rel="">قواعد البيانات SQL</a> تؤمن وصولًا فعالًا وعالي الأداء إلى قواعد البيانات العلاقية، كما توفّر طرقًا للتخاطب مع العديد من محركات قواعد البيانات، مثل SQLite و MySQL و PostgreSQ، مانحةً إمكانية الوصول إلى آليات SQL الخاصة بقواعد البيانات، كما توفّر رابط الكائنات بالعلاقات Object Relational Mapper -أو اختصارًا ORM- الذي يتيح إمكانية إنشاء الاستعلامات والتعامل مع البيانات باستخدام توابع وكائنات بسيطة في <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D9%85%D8%B1%D8%AC%D8%B9-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%AA%D8%B9%D9%84%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r735/" rel="">بايثون</a>.
</p>

<p>
	تعدّ Flask-SQLAlchemy إضافة لفلاسك تسهّل من استخدام SQLAlchemy ضمن فلاسك مؤمنةً الأدوات والوسائل المناسبة للتعامل مع <a href="https://academy.hsoub.com/programming/sql/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%B9%D9%86-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-r584/" rel="">قاعدة البيانات</a> في تطبيقات فلاسك من خلال SQLAlchemy.
</p>

<p>
	تُعرَّف علاقة قاعدة البيانات متعدّد-إلى-متعدّد many-to-many على أنها علاقةٌ بين جدولين، إذ يمكن لأي سجلٍ من الجدولين الارتباط مع عدّة سجلات من الآخر، فمثلًا في تطبيق مدونة يمكن لجدول التدوينات posts أن يرتبط بعلاقة من نوع متعدّد-إلى-متعدّد مع جدول أسماء المؤلفين (كُتَّاب التدوينات)، بمعنى أن كل تدوينة قد ترتبط بعدّة أسماء مؤلفين، وأيضًا قد يرتبط كل اسم مؤلف بعدّة تدوينات، وبالتالي فإنّ العلاقة ما بين التدوينات وأسماء المؤلفين هي علاقة متعدّد-إلى-متعدّد. أيُّ تطبيقٍ آخر من تطبيقات التواصل الاجتماعي هو مثالٌ آخر، إذ أن كل منشور قد يحتوي عدّة إشارات مرجعية، وكل إشارة مرجعية قد تتضمّن عدة منشورات.
</p>

<p>
	سنعمل في هذا المقال على تعديل تطبيق مبني باستخدام كل من فلاسك والإضافة SQLAlchemy فيه، وذلك بإضافة علاقة من نوع متعدّد-إلى-متعدّد إليه، إذ سننشئ علاقة ما بين التدوينات والوسوم tags، بحيث يمكن لكل تدوينة أن تتضمّن عدّة وسوم، ويمكن أن يُشار للوسم الواحد في عدّة تدوينات.
</p>

<p>
	يمكنك قراءة هذا المقال مُنفصلًا، إلّا أنّه تكملة لمقالنا السابق <a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B9%D9%84%D8%A7%D9%82%D8%A9-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-one-to-many-%D9%85%D8%B9-%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-sqlalchemy-%D9%81%D9%8A-%D9%81%D9%84%D8%A7%D8%B3%D9%83-r1843/" rel=""> كيفية استخدام SQLAlchemy في فلاسك</a>، وفيه قد بنينا قاعدة بيانات مُتعدّدة الجداول بعلاقة من نواع واحد إلى مُتعدّد one-to-many ما بين التدوينات والتعليقات عليها وذلك ضمن تطبيق مدونة.
</p>

<p>
	سيتضمّن تطبيقك -مع نهاية هذا المقال- ميزةً جديدة تتمثّل بإمكانية إضافة الوسوم إلى التدوينات، بحيث يُشار إلى التدوينة الواحدة في عدّة وسوم، لتعرض صفحة الوسم الواحد كافّة التدوينات المُشار إليها من خلاله.
</p>

<h2>
	مستلزمات العمل
</h2>

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

<ul>
	<li>
		توفُّر <a href="https://academy.hsoub.com/programming/python/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%AA%D9%87%D8%A7-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r714/" rel="">بيئة برمجة بايثون 3 محلية</a>، مثبّتة على حاسوبك، وسنفترض في مقالنا أن اسم مجلد المشروع هو "flask_app".
	</li>
	<li>
		الفهم الجيد لأساسيات فلاسك، مثل مفهوم الوجهات ودوال العرض، وفي هذا الصدد يمكنك الاطلاع على المقالين <a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%B9%D9%84%D9%85-%D8%A8%D9%86%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9%D9%83-%D8%A7%D9%84%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1715/" rel="">كيفية بناء موقعك الإلكتروني الأول باستخدام إطار عمل فلاسك Flask من لغة بايثون</a> و<a href="https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1737/" rel="">كيفية استخدام القوالب في تطبيقات فلاسك Flask</a> لفهم مبادئ فلاسك.
	</li>
	<li>
		فهم <a href="https://academy.hsoub.com/programming/html/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D9%84%D8%BA%D8%A9-html-r1687/" rel="">أساسيات لغة HTML</a>.
	</li>
	<li>
		(مُتطلّب اختياري) في الخطوة الأولى ستنسخ تطبيق المدونة الذي ستعمل عليه في هذا المقال، ولكن يمكنك بناءه من الصفر بدلًا من نسخه باتباعك للخطوات الواردة في <a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B9%D9%84%D8%A7%D9%82%D8%A9-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-one-to-many-%D9%85%D8%B9-%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-sqlalchemy-%D9%81%D9%8A-%D9%81%D9%84%D8%A7%D8%B3%D9%83-r1843/" rel="">المقال السابق</a>، كما يمكنك الوصول إلى الشيفرة الكاملة للتطبيق من <a href="https://github.com/do-community/flask-slqa-bloggy" rel="external nofollow">flask-slqa-bloggy</a>.
	</li>
</ul>

<h2>
	الخطوة 1 - إعداد تطبيق الويب
</h2>

<p>
	سنعمل في هذه الخطوة على إعداد تطبيق المدونة ليكون جاهزًا للتعديل عليه، كما سنستعرض نماذج قاعدة بيانات Flask-SQLAlchemy ووجهات فلاسك لضمان فهمك الجيد لبنية التطبيق.
</p>

<p>
	يمكنك تجاوز هذه الخطوة في حال اتباعك للمقال السابق المذكور آنفًا في فقرة مستلزمات العمل واحتفاظك بالشيفرة والبيئة الافتراضية على حاسوبك.
</p>

<p>
	سنستخدم شيفرة التطبيق المبني في المقال السابق (راجع فقرة مستلزمات العمل) لشرح آلية إضافة علاقة من النوع متعدّد-إلى-متعدّد لتطبيق ويب مبني باستخدام فلاسك مع الإضافة Flask-SQLAlchemy؛ وهذا التطبيق هو مدونة يتيح إمكانية إضافة وعرض التدوينات والتعليق عليها، إضافةً إلى إمكانية قراءة التعليقات الموجودة وحذفها.
</p>

<p>
	سننسخ الآن ملف الشيفرة من المستودع ونعيد تسميته من "flask-slqa-bloggy" ليصبح "flask_app" وذلك باستخدام الأمر التالي:
</p>

<pre class="ipsCode">user@localhost:$ git clone https://github.com/do-community/flask-slqa-bloggy flask_app
</pre>

<p>
	ننتقل الآن إلى الملف "flask_app" على النحو التالي:
</p>

<pre class="ipsCode">user@localhost:$ cd flask_app
</pre>

<p>
	ثمّ سننشئ بيئة افتراضية جديدة:
</p>

<pre class="ipsCode">user@localhost:$ python -m venv env
</pre>

<p>
	ونفعّل هذه البيئة على النحو التالي:
</p>

<pre class="ipsCode">user@localhost:$ source env/bin/activate
</pre>

<p>
	ونثبّت الآن كلًا من فلاسك والإضافة Flask-SQLAlchemy باستخدام الأمر:
</p>

<pre class="ipsCode">(env)user@localhost:$ pip install Flask Flask-SQLAlchemy
</pre>

<p>
	ثمّ نضبط متغيرات البيئة التالية:
</p>

<pre class="ipsCode">(env)user@localhost:$ export FLASK_APP=app
(env)user@localhost:$ export FLASK_ENV=development
</pre>

<p>
	يشير متغيّر البيئة <code>FLASK_APP</code> إلى التطبيق الذي نعمل على تطويره حاليًا، وهو "app.py" في حالتنا، أمّا المتغير <code>FLASK_ENV</code> فيحدّد وضع التشغيل، وقد اخترناه ليكون وضع التطوير <code>development</code> الذي يوفّر إمكانية تنقيح الأخطاء في التطبيق، مع الانتباه إلى عدم استخدام وضع التشغيل هذا في بيئة الإنتاج.
</p>

<p>
	والآن، سنفتح صَدَفَة فلاسك لإنشاء جداول قاعدة البيانات:
</p>

<pre class="ipsCode">(env)user@localhost:$ flask shell
</pre>

<p>
	ومن ثمّ سنستورد الكائن <code>db</code> الخاص بقاعدة بيانات Flask-SQLAlchemy، بالإضافة إلى النموذج <code>post</code> الخاص بالتدوينات، والنموذج <code>Comment</code> الخاص بالتعليقات، لننشئ بعدها جداول قاعدة البيانات الموافقة للنماذج السابقة باستخدام الدالة <code>()db.create_all</code>، كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_6" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">from</span><span class="pln"> app </span><span class="kwd">import</span><span class="pln"> db</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Comment</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> db</span><span class="pun">.</span><span class="pln">create_all</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> exit</span><span class="pun">()</span></pre>

<p>
	ومن ثمّ سنملأ قاعدة البيانات باستخدام البرنامج من الملف "init_db.py":
</p>

<pre class="ipsCode">(env)user@localhost:$ python init_db.py
</pre>

<p>
	والذي سيضيف ثلاث تدوينات وأربعة تعليقات إلى قاعدة البيانات.
</p>

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

<pre class="ipsCode">(env)user@localhost:$ flask run
</pre>

<p>
	الآن وبالذهاب إلى المتصفّح، يصبح من الممكن الوصول إلى التطبيق عبر الرابط:
</p>

<pre class="ipsCode">http://127.0.0.1:5000/
</pre>

<p>
	فتظهر صفحة شبيهة بما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="120680" href="https://academy.hsoub.com/uploads/monthly_2023_03/1st.png.2f4c748bc0ae93d3d16ec8d3a02c8b4b.png" rel=""><img alt="1st.png" class="ipsImage ipsImage_thumbnailed" data-fileid="120680" data-unique="jq1jmok7l" src="https://academy.hsoub.com/uploads/monthly_2023_03/1st.thumb.png.296694de59a6d70fe48bf4a814c0175b.png"> </a>
</p>

<p>
	وفي حال ظهور خطأ، تأكّد من اتباعك الخطوات السابقة على النحو الصحيح.
</p>

<p>
	يمكنك إيقاف تشغيل خادم التطوير بالضغط على مفتاحي "CTRL+C" في لوحة المفاتيح.
</p>

<p>
	فيما يلي سنمر على نماذج قاعدة البيانات Flask-SQLAlchemy بغية فهم العلاقات الحالية ما بين الجداول فيها، فإذا كانت محتويات الملف "app.py" مألوفةً يالنسبة لك من المقال السابق، فيمكنك تجاوز هذه الخطوة.
</p>

<p>
	نفتح الآن الملف "app.py":
</p>

<pre class="ipsCode">(env)user@localhost:$ nano app.py
</pre>

<p>
	فتظهر محتوياته على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_8" style=""><span class="kwd">import</span><span class="pln"> os
</span><span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> render_template</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">,</span><span class="pln"> redirect</span><span class="pun">,</span><span class="pln"> url_for
</span><span class="kwd">from</span><span class="pln"> flask_sqlalchemy </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">SQLAlchemy</span><span class="pln">

basedir </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">abspath</span><span class="pun">(</span><span class="pln">os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">dirname</span><span class="pun">(</span><span class="pln">__file__</span><span class="pun">))</span><span class="pln">

app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">config</span><span class="pun">[</span><span class="str">'SQLALCHEMY_DATABASE_URI'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln">\
           </span><span class="str">'sqlite:///'</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="pln">basedir</span><span class="pun">,</span><span class="pln"> </span><span class="str">'database.db'</span><span class="pun">)</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">config</span><span class="pun">[</span><span class="str">'SQLALCHEMY_TRACK_MODIFICATIONS'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">False</span><span class="pln">


db </span><span class="pun">=</span><span class="pln"> </span><span class="typ">SQLAlchemy</span><span class="pun">(</span><span class="pln">app</span><span class="pun">)</span><span class="pln">


</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
    id </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Integer</span><span class="pun">,</span><span class="pln"> primary_key</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
    title </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">String</span><span class="pun">(</span><span class="lit">100</span><span class="pun">))</span><span class="pln">
    content </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Text</span><span class="pun">)</span><span class="pln">
    comments </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="pln">relationship</span><span class="pun">(</span><span class="str">'Comment'</span><span class="pun">,</span><span class="pln"> backref</span><span class="pun">=</span><span class="str">'post'</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> __repr__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> f</span><span class="str">'&lt;Post "{self.title}"&gt;'</span><span class="pln">


</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Comment</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
    id </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Integer</span><span class="pun">,</span><span class="pln"> primary_key</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
    content </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Text</span><span class="pun">)</span><span class="pln">
    post_id </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Integer</span><span class="pun">,</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">ForeignKey</span><span class="pun">(</span><span class="str">'post.id'</span><span class="pun">))</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> __repr__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> f</span><span class="str">'&lt;Comment "{self.content[:20]}..."&gt;'</span><span class="pln">


</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> index</span><span class="pun">():</span><span class="pln">
    posts </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'index.html'</span><span class="pun">,</span><span class="pln"> posts</span><span class="pun">=</span><span class="pln">posts</span><span class="pun">)</span><span class="pln">


</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/&lt;int:post_id&gt;/'</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=(</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">))</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> post</span><span class="pun">(</span><span class="pln">post_id</span><span class="pun">):</span><span class="pln">
    post </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">get_or_404</span><span class="pun">(</span><span class="pln">post_id</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">method </span><span class="pun">==</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">:</span><span class="pln">
        comment </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Comment</span><span class="pun">(</span><span class="pln">content</span><span class="pun">=</span><span class="pln">request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">],</span><span class="pln"> post</span><span class="pun">=</span><span class="pln">post</span><span class="pun">)</span><span class="pln">
        db</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="pln">comment</span><span class="pun">)</span><span class="pln">
        db</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> redirect</span><span class="pun">(</span><span class="pln">url_for</span><span class="pun">(</span><span class="str">'post'</span><span class="pun">,</span><span class="pln"> post_id</span><span class="pun">=</span><span class="pln">post</span><span class="pun">.</span><span class="pln">id</span><span class="pun">))</span><span class="pln">

    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'post.html'</span><span class="pun">,</span><span class="pln"> post</span><span class="pun">=</span><span class="pln">post</span><span class="pun">)</span><span class="pln">


</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/comments/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> comments</span><span class="pun">():</span><span class="pln">
    comments </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Comment</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">order_by</span><span class="pun">(</span><span class="typ">Comment</span><span class="pun">.</span><span class="pln">id</span><span class="pun">.</span><span class="pln">desc</span><span class="pun">()).</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'comments.html'</span><span class="pun">,</span><span class="pln"> comments</span><span class="pun">=</span><span class="pln">comments</span><span class="pun">)</span><span class="pln">


</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/comments/&lt;int:comment_id&gt;/delete'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> delete_comment</span><span class="pun">(</span><span class="pln">comment_id</span><span class="pun">):</span><span class="pln">
    comment </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Comment</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">get_or_404</span><span class="pun">(</span><span class="pln">comment_id</span><span class="pun">)</span><span class="pln">
    post_id </span><span class="pun">=</span><span class="pln"> comment</span><span class="pun">.</span><span class="pln">post</span><span class="pun">.</span><span class="pln">id
    db</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">delete</span><span class="pun">(</span><span class="pln">comment</span><span class="pun">)</span><span class="pln">
    db</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> redirect</span><span class="pun">(</span><span class="pln">url_for</span><span class="pun">(</span><span class="str">'post'</span><span class="pun">,</span><span class="pln"> post_id</span><span class="pun">=</span><span class="pln">post_id</span><span class="pun">))</span></pre>

<p>
	وفيه لدينا نموذجي قاعدة بيانات يمثلان جدولين، هما:
</p>

<ul>
	<li>
		الجدول <code>Post</code>: والذي يتضمّن عمودًا لكل من معرّف التدوينة وعنوانها ومضمونها، إضافةً إلى علاقة من النوع واحد-إلى-مُتعدّد مع جدول التعليقات.
	</li>
	<li>
		الجدول <code>Comment</code>: والذي يتضمّن عمودًا لكل من معرّف التعليق ومحتواه، إضافةً إلى عمود لمعرّف التدوينة <code>post_id</code> للإشارة إلى التدوينة التي يتبع لها التعليق.
	</li>
</ul>

<p>
	كما يتضمّن الملف بعد النموذجين كل من الوجهات التالية:
</p>

<ul>
	<li>
		<code>/</code>: الصفحة الرئيسية للتطبيق، والتي تعرض كافّة التدوينات الموجودة في قاعدة البيانات.
	</li>
	<li>
		<code>/&lt;int:post_id&gt;/</code>: الصفحة الخاصّة بكل تدوينة، فمثلًا يعرض الرابط "/http://127.0.0.1:5000/2" تفاصيل التدوينة الثانية من قاعدة البيانات مع التعليقات عليها.
	</li>
	<li>
		<code>/comments/</code>: صفحة لعرض كافّة التعليقات من قاعدة البيانات مع روابط التدوينات التي يتبع لها كل تعليق.
	</li>
	<li>
		<code>comments/&lt;int:comment_id&gt;/delete/</code>: وجهة مُخصّصة لحذف التعليقات باستخدام زر أوامر باسم <strong>حذف تعليق Delete Comment</strong>.
	</li>
</ul>

<p>
	نغلق الآن الملف app.py.
</p>

<p>
	سنستخدم في الخطوة التالية علاقة من نوع مُتعدّد-إلى-مُتعدّد للربط بين الجدولين المذكورين سابقًا.
</p>

<h2>
	الخطوة 2 - إعداد نماذج قاعدة بيانات لإنشاء علاقة من نوع متعدد-إلى-متعدد
</h2>

<p>
	سنضيف في هذه الخطوة نموذج قاعدة البيانات المُمثّل لجدول الوسوم، والذي سنربطه مع جدول التدوينات الحالي باستخدام "جدول ارتباط association table"، والذي يعرّف بأنّه جدول وسيط يربط بين جدولين بعلاقة مُتعدّد-إلى-مُتعدّد.
</p>

<p>
	تربط العلاقة مُتعدّد-إلى-مُتعدّد بين جدولين، بحيث يتعلّق كل عنصر من أحدهما بعدّة عناصر من الآخر، وتشير كل تدوينة في جدول الارتباط إلى وسومها، كما يشير كل وسم إلى كافّة التدوينات المُتضمّنة له.
</p>

<p>
	سنضيف أيضًا في هذه الخطوة بعضًا من التدوينات والوسوم إلى قاعدة البيانات، لنتمكن لاحقًا من عرض التدوينات مع وسوم كل منها أو الوسوم مع التدوينات المُتضمّنة لكل منها.
</p>

<p>
	ليكن لدينا جدول تدوينات بسيط على النحو التالي:
</p>

<pre class="ipsCode">Posts
+----+-----------------------------------+
| id | content                           |
+----+-----------------------------------+
| 1  | A post on life and death          |
| 2  | A post on joy                     |
+----+-----------------------------------+
</pre>

<p>
	وجدولًا للوسوم كما يلي:
</p>

<pre class="ipsCode">Tags
+----+-------+
| id | name  |
+----+-------+
| 1  | life  |
| 2  | death |
| 3  | joy   |
+----+-------+
</pre>

<p>
	يمكننا مثلًا إضافة كل من الوسمين "life" و "death" إلى التدوينة "A post on life and death" عن طريق إضافة عمود جديد إلى جدول التدوينات، ليصبح بالشّكل:
</p>

<pre class="ipsCode">Posts
+----+-----------------------------------+------+
| id | content                           | tags |
+----+-----------------------------------+------+
| 1  | A post on life and death          | 1, 2 |
| 2  | A post on joy                     |      |
+----+------------------------------------------+
</pre>

<p>
	لن تعمل هذه المنهجية لأنّ كل عمود يجب أن يتضمّن قيمة واحدة، وفي حال وجود عدّة قيم للعمود الواحد من السجل، ستصبح العمليات الأساسية مثل إضافة البيانات وتحديثها مرهقة وبطيئة، وسنستخدم بدلًا من ذلك جدولًا إضافيًا ثالثًا ليشير إلى المفاتيح الأساسية ويربط بينها وبين الجداول المرتبطة، وهو ما ندعوه بجدول الارتباط أو جدول الصلة join table، والذي سيخزّن معرّفات كل عنصر من كل جدول.
</p>

<p>
	وفيما يلي مثال لجدول ارتباط يربط بين جدولي التدوينات والوسوم:
</p>

<pre class="ipsCode">post_tag
+----+---------+-------------+
| id | post_id | tag_id      |
+----+---------+-------------+
| 1  | 1       | 1           |
| 2  | 1       | 2           |
+----+---------+-------------+
</pre>

<p>
	ترتبط التدوينة ذات المعرّف رقم "1" (وهي "A post on life and death") مع الوسم ذي المعرّف رقم "1" (وهو "life") في السجل الأوّل من الجدول السابق؛ أمّا في السجل الثاني، فترتبط التدوينة الأولى نفسها أيضًا مع الوسم ذو المعرّف رقم "2" (وهو "death")، ما يعني أنّ التدوينة الأولى تتضمّن كلا الوسمين "life" و "death"، ويمكن ربط كل تدوينة بوسومات متعدّدة بنفس الآلية.
</p>

<p>
	سنعدّل الآن الملف app.py لإضافة نموذج قاعدة بيانات جديد ليمثّل الجدول الذي سنستخدمه لتخزين الوسوم، كما سنضيف جدول ارتباط باسم "post_tag" لربط التدوينات بالوسوم.
</p>

<p>
	لذا، سنفتح بدايةً الملف app.py بغية إنشاء العلاقة ما بين التدوينات والوسوم:
</p>

<pre class="ipsCode">(env)user@localhost:$ nano app.py
</pre>

<p>
	سنضيف الآن جدول <code>post_tag</code> ونموذج باسم <code>Tag</code> بعد كائن قاعدة البيانات <code>db</code> وقبل النموذج <code>Post</code>، ومن ثمّ سنضيف للنموذج <code>Post</code> عمودًا علاقيًا زائفًا للوسوم باسم <code>tags</code> لنمكّن لاحقًا من الوصول إلى وسوم التدوينة باستخدام التعليمة <code>post.tags</code> أو الوصول إلى التدوينات المُتضمّنة وسمًا ما باستخدام التعليمة <code>tag.posts</code>.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_10" style=""><span class="com"># ...</span><span class="pln">

db </span><span class="pun">=</span><span class="pln"> </span><span class="typ">SQLAlchemy</span><span class="pun">(</span><span class="pln">app</span><span class="pun">)</span><span class="pln">


post_tag </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Table</span><span class="pun">(</span><span class="str">'post_tag'</span><span class="pun">,</span><span class="pln">
                    db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="str">'post_id'</span><span class="pun">,</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Integer</span><span class="pun">,</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">ForeignKey</span><span class="pun">(</span><span class="str">'post.id'</span><span class="pun">)),</span><span class="pln">
                    db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="str">'tag_id'</span><span class="pun">,</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Integer</span><span class="pun">,</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">ForeignKey</span><span class="pun">(</span><span class="str">'tag.id'</span><span class="pun">))</span><span class="pln">
                    </span><span class="pun">)</span><span class="pln">


</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Tag</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
    id </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Integer</span><span class="pun">,</span><span class="pln"> primary_key</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
    name </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">String</span><span class="pun">(</span><span class="lit">50</span><span class="pun">))</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> __repr__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> f</span><span class="str">'&lt;Tag "{self.name}"&gt;'</span><span class="pln"> 



</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
    id </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Integer</span><span class="pun">,</span><span class="pln"> primary_key</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
    title </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">String</span><span class="pun">(</span><span class="lit">100</span><span class="pun">))</span><span class="pln">
    content </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Text</span><span class="pun">)</span><span class="pln">
    comments </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="pln">relationship</span><span class="pun">(</span><span class="str">'Comment'</span><span class="pun">,</span><span class="pln"> backref</span><span class="pun">=</span><span class="str">'post'</span><span class="pun">)</span><span class="pln">
    tags </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="pln">relationship</span><span class="pun">(</span><span class="str">'Tag'</span><span class="pun">,</span><span class="pln"> secondary</span><span class="pun">=</span><span class="pln">post_tag</span><span class="pun">,</span><span class="pln"> backref</span><span class="pun">=</span><span class="str">'posts'</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> __repr__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> f</span><span class="str">'&lt;Post "{self.title}"&gt;'</span></pre>

<p>
	احفظ الملف واغلقه.
</p>

<p>
	استخدمنا في الشيفرة السابقة الدالة <code>()db.Table</code> لإنشاء جدول بعمودين، إذ أنّ الإجراء الأفضل بالنسبة لجدول الارتباط هو استخدام جدول بدلًا من نموذج قاعدة بيانات، وهنا يمتلك الجدول post_tag عمودين يمثلان مفتاحين خارجيين foreign keys أي المفتاحين المستخدمين للإشارة إلى أعمدة المفاتيح الأساسية في الجداول الأصل (المفتاح الأساسي لجدول ما هو مفتاح أجنبي عند تواجده في جدول آخر):
</p>

<ul>
	<li>
		<code>post_id</code>: وهو مفتاح خارجي من نوع عدد صحيح يمثّل معرّف التدوينة ويشير إلى عمود المُعرّف ID في جدول التدوينات <code>post</code>.
	</li>
	<li>
		<code>tag_id</code>: وهو مفتاح خارجي من نوع عدد صحيح يمثّل معرّف الوسم ويشير إلى عمود المُعرّف ID في جدول الوسوم <code>tag</code>.
	</li>
</ul>

<p>
	ستُنشئ هذه المفاتيح العلاقات ما بين الجداول.
</p>

<p>
	وبعد الانتهاء من الجدول <code>post_tag</code>، أنشأنا النموذج <code>Tag</code> الممثّل للجدول الذي سنخزّن ضمنه الوسوم، ويحتوي هذا الجدول على عمودين:
</p>

<ul>
	<li>
		<code>id</code>: معرّف الوسم.
	</li>
	<li>
		<code>name</code>: اسم الوسم.
	</li>
</ul>

<p>
	استخدمنا اسم الوسم ضمن التابع الخاص <code>()__repr__</code> بغية إعطاء كل كائن وسم تمثيلًا محرفيًا واضحًا لأسباب تنقيحية، كما أضفنا متغير صنف للوسوم باسم <code>tags</code> ضمن النموذج <code>Post</code>، وذلك باستخدام التابع <code>()db.relationship</code> ممررين إليه اسم نموذج الوسوم، وهو <code>Tag</code> في حالتنا.
</p>

<p>
	مرّرنا جدول الارتباط <code>post_tag</code> إلى المعامل الثاني <code>secondary</code> من التابع بغية إنشاء علاقة من نوع مُتعدّد-إلى-مُتعدّد ما بين التدوينات والوسوم. استخدمنا المعامل <code>backref</code> لإضافة مرجعٍ عكسي back reference يلعب ما يشبه دور العمود في النموذج <code>Tag</code>، وبذلك يصبح من الممكن الوصول تدوينات الوسم المُحدّد عبر التعليمة <code>tag.posts</code> ووسوم تدوينة ما باستخدام التعليمة <code>post.tags</code>، وسنعرض مثالًا يوضّح هذه الفكرة لاحقًا.
</p>

<p>
	أمّا الآن فسنحرّر برنامج بايثون "init_db.py" لتعديل قاعدة البيانات بإضافة كل من جدول الارتباط <code>post_tag</code> وجدول الوسوم المبني استنادًا إلى نموذج الوسوم <code>Tag</code>، على النحو التالي:
</p>

<pre class="ipsCode">(env)user@localhost:$ nano init_db.py
</pre>

<p>
	ونعدّل الملف ليصبح كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_12" style=""><span class="kwd">from</span><span class="pln"> app </span><span class="kwd">import</span><span class="pln"> db</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Comment</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Tag</span><span class="pln">

db</span><span class="pun">.</span><span class="pln">drop_all</span><span class="pun">()</span><span class="pln">
db</span><span class="pun">.</span><span class="pln">create_all</span><span class="pun">()</span><span class="pln">

post1 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">(</span><span class="pln">title</span><span class="pun">=</span><span class="str">'Post The First'</span><span class="pun">,</span><span class="pln"> content</span><span class="pun">=</span><span class="str">'Content for the first post'</span><span class="pun">)</span><span class="pln">
post2 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">(</span><span class="pln">title</span><span class="pun">=</span><span class="str">'Post The Second'</span><span class="pun">,</span><span class="pln"> content</span><span class="pun">=</span><span class="str">'Content for the Second post'</span><span class="pun">)</span><span class="pln">
post3 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">(</span><span class="pln">title</span><span class="pun">=</span><span class="str">'Post The Third'</span><span class="pun">,</span><span class="pln"> content</span><span class="pun">=</span><span class="str">'Content for the third post'</span><span class="pun">)</span><span class="pln">

comment1 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Comment</span><span class="pun">(</span><span class="pln">content</span><span class="pun">=</span><span class="str">'Comment for the first post'</span><span class="pun">,</span><span class="pln"> post</span><span class="pun">=</span><span class="pln">post1</span><span class="pun">)</span><span class="pln">
comment2 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Comment</span><span class="pun">(</span><span class="pln">content</span><span class="pun">=</span><span class="str">'Comment for the second post'</span><span class="pun">,</span><span class="pln"> post</span><span class="pun">=</span><span class="pln">post2</span><span class="pun">)</span><span class="pln">
comment3 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Comment</span><span class="pun">(</span><span class="pln">content</span><span class="pun">=</span><span class="str">'Another comment for the second post'</span><span class="pun">,</span><span class="pln"> post_id</span><span class="pun">=</span><span class="lit">2</span><span class="pun">)</span><span class="pln">
comment4 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Comment</span><span class="pun">(</span><span class="pln">content</span><span class="pun">=</span><span class="str">'Another comment for the first post'</span><span class="pun">,</span><span class="pln"> post_id</span><span class="pun">=</span><span class="lit">1</span><span class="pun">)</span><span class="pln">

tag1 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Tag</span><span class="pun">(</span><span class="pln">name</span><span class="pun">=</span><span class="str">'animals'</span><span class="pun">)</span><span class="pln">
tag2 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Tag</span><span class="pun">(</span><span class="pln">name</span><span class="pun">=</span><span class="str">'tech'</span><span class="pun">)</span><span class="pln">
tag3 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Tag</span><span class="pun">(</span><span class="pln">name</span><span class="pun">=</span><span class="str">'cooking'</span><span class="pun">)</span><span class="pln">
tag4 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Tag</span><span class="pun">(</span><span class="pln">name</span><span class="pun">=</span><span class="str">'writing'</span><span class="pun">)</span><span class="pln">

post1</span><span class="pun">.</span><span class="pln">tags</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">tag1</span><span class="pun">)</span><span class="pln">  </span><span class="com"># Tag the first post with 'animals'</span><span class="pln">
post1</span><span class="pun">.</span><span class="pln">tags</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">tag4</span><span class="pun">)</span><span class="pln">  </span><span class="com"># Tag the first post with 'writing'</span><span class="pln">
post3</span><span class="pun">.</span><span class="pln">tags</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">tag3</span><span class="pun">)</span><span class="pln">  </span><span class="com"># Tag the third post with 'cooking'</span><span class="pln">
post3</span><span class="pun">.</span><span class="pln">tags</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">tag2</span><span class="pun">)</span><span class="pln">  </span><span class="com"># Tag the third post with 'tech'</span><span class="pln">
post3</span><span class="pun">.</span><span class="pln">tags</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">tag4</span><span class="pun">)</span><span class="pln">  </span><span class="com"># Tag the third post with 'writing'</span><span class="pln">


db</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">add_all</span><span class="pun">([</span><span class="pln">post1</span><span class="pun">,</span><span class="pln"> post2</span><span class="pun">,</span><span class="pln"> post3</span><span class="pun">])</span><span class="pln">
db</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">add_all</span><span class="pun">([</span><span class="pln">comment1</span><span class="pun">,</span><span class="pln"> comment2</span><span class="pun">,</span><span class="pln"> comment3</span><span class="pun">,</span><span class="pln"> comment4</span><span class="pun">])</span><span class="pln">
db</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">add_all</span><span class="pun">([</span><span class="pln">tag1</span><span class="pun">,</span><span class="pln"> tag2</span><span class="pun">,</span><span class="pln"> tag3</span><span class="pun">,</span><span class="pln"> tag4</span><span class="pun">])</span><span class="pln">

db</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span></pre>

<p>
	احفظ الملف واغلقه.
</p>

<p>
	استوردنا في الشيفرة السابقة النموذج <code>Tag</code>، كما حذفنا كل محتويات قاعدة البيانات باستخدام الدالة <code>()db.drop_all</code> بغية إضافة كل من جدول الوسوم والجدول <code>post_tag</code> بأمان، متجنبين بذلك وقوع أي من المشاكل الشائعة التي قد تحدث لدى إضافة جداول جديدة إلى قاعدة البيانات. أنشأنا بعد ذلك جميع الجداول مُجدّدًا باستخدام الدالة <code>()db.create_all</code>.
</p>

<p>
	يمكننا -بعد تصريح الشيفرة من المقال السابق عن التدوينات والتعليقات- استخدام نموذج الوسوم <code>Tag</code> لإنشاء أربعة وسوم، ومن ثم إضافة الوسوم إلى التدوينات باستخدام السمة <code>tags</code>، التي أُضيفت عبر السطر البرمجي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_14" style=""><span class="pln">tags </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="pln">relationship</span><span class="pun">(</span><span class="str">'Tag'</span><span class="pun">,</span><span class="pln"> secondary</span><span class="pun">=</span><span class="pln">post_tag</span><span class="pun">,</span><span class="pln"> backref</span><span class="pun">=</span><span class="str">'posts'</span><span class="pun">)</span></pre>

<p>
	من الملف app.py. كما خصّصنا الوسوم للتدوينات باستخدام تابع الإسناد <code>()append</code> الشبيه <a href="https://academy.hsoub.com/programming/python/%d8%a7%d9%84%d8%aa%d8%b9%d8%a7%d9%85%d9%84-%d9%85%d8%b9-%d8%a7%d9%84%d9%82%d9%88%d8%a7%d8%a6%d9%85-%d9%88%d8%a7%d9%84%d8%b3%d9%84%d8%a7%d8%b3%d9%84-%d8%a7%d9%84%d9%86%d8%b5%d9%8a%d8%a9-%d9%81%d9%8a-%d9%84%d8%ba%d8%a9-%d8%a8%d8%a7%d9%8a%d8%ab%d9%88%d9%86-r223/" rel="">بقوائم بايثون</a>.
</p>

<p>
	أُضيفت بعد ذلك الوسوم المُنشأة إلى جلسة قاعدة البيانات باستخدام الدالة <code>()db.session.add_all</code>.
</p>

<p>
	<strong>ملاحظة:</strong> لا تعيد الدالة <code>()db.create_all</code> إنشاء جدول موجود أصلًا ولا تحدثّه، فعلى سبيل المثال، لو عدّلنا أحد النماذج بإضافة عمود جديد إليه، ثمّ استخدمنا الدالة <code>()db.create_all</code> فلن يُطبَّق هذا التغيير على الجدول في حال كان هذا الجدول موجود أصلًا. ويكون الحل بحذف كافّة الجداول الموجودة في قاعدة البيانات باستخدام الدالة <code>()db.drop_all</code> ومن ثمّ إعادة إنشائها باستخدام الدلة <code>()db.create_all</code> كما هو موضّح في الملف "init_db.py".
</p>

<p>
	تضمن هذه الآلية تطبيق التغييرات المُنفّذة على النماذج إلّا أنّها ستتسبب بحذف كافّة البيانات الموجودة في الجداول. من الممكن تهجير ملف تخطيط قاعدة البيانات Schema migration بغية تحديث قاعدة البيانات مع الحفاظ على البيانات الموجودة ضمنها، وهذا التهجير سيسمح بتعديل الجداول مع الحفاظ على البيانات فيها، إذ يمكننا استخدام الإضافة <a href="https://flask-migrate.readthedocs.io/en/latest/index.html" rel="external nofollow"><code>Flask-Migrate</code></a> لإجراء ترحيل لملف تخطيط قاعدة بيانات SQLAlchemy عن طريق واجهة سطر أوامر فلاسك.
</p>

<p>
	سنشغّل الآن البرنامج init_db.py لتطبيق التغييرات على قاعدة البيانات:
</p>

<pre class="ipsCode">(env)user@localhost:$ python init_db.py
</pre>

<p>
	يُفترض في هذه المرحلة أن يُنفّذ البرنامج بنجاح دون ظهور أي خرج، أمّا في حال ظهور رسالة خطأ، فتأكّد من كونك قد أجريت التغييرات الصحيحة على الملف init_db.py.
</p>

<p>
	ولإلقاء نظرة على التدوينات والوسوم الموجودة حاليًا في قاعدة البيانات، سنفتح صَدَفَة فلاسك:
</p>

<pre class="ipsCode">(env)user@localhost:$ flask shell
</pre>

<p>
	ثمّ سننفّذ الشيفرة التالية التي ستمر على كافّة التدوينات والوسوم:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_16" style=""><span class="kwd">from</span><span class="pln"> app </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Post</span><span class="pln">

posts </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">all</span><span class="pun">()</span><span class="pln">

</span><span class="kwd">for</span><span class="pln"> post </span><span class="kwd">in</span><span class="pln"> posts</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">post</span><span class="pun">.</span><span class="pln">title</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">post</span><span class="pun">.</span><span class="pln">tags</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'---'</span><span class="pun">)</span></pre>

<p>
	استوردنا في الشيفرة السابقة نموذج التدوينات <code>Post</code> من الملف app.py، واستعلمنا عن جدول التدوينات جالبين منه كافّة التدوينات الموجودة في قاعدة البيانات، ومررنا بعدها على كل تدوينة لنعرض عنوانها وقائمة الوسوم المرتبطة بها، وسيظهر الخرج كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_18" style=""><span class="typ">Post</span><span class="pln"> </span><span class="typ">The</span><span class="pln"> </span><span class="typ">First</span><span class="pln">
</span><span class="pun">[&lt;</span><span class="typ">Tag</span><span class="pln"> </span><span class="str">"animals"</span><span class="pun">&gt;,</span><span class="pln"> </span><span class="pun">&lt;</span><span class="typ">Tag</span><span class="pln"> </span><span class="str">"writing"</span><span class="pun">&gt;]</span><span class="pln">
</span><span class="pun">---</span><span class="pln">
</span><span class="typ">Post</span><span class="pln"> </span><span class="typ">The</span><span class="pln"> </span><span class="typ">Third</span><span class="pln">
</span><span class="pun">[&lt;</span><span class="typ">Tag</span><span class="pln"> </span><span class="str">"cooking"</span><span class="pun">&gt;,</span><span class="pln"> </span><span class="pun">&lt;</span><span class="typ">Tag</span><span class="pln"> </span><span class="str">"tech"</span><span class="pun">&gt;,</span><span class="pln"> </span><span class="pun">&lt;</span><span class="typ">Tag</span><span class="pln"> </span><span class="str">"writing"</span><span class="pun">&gt;]</span><span class="pln">
</span><span class="pun">---</span><span class="pln">
</span><span class="typ">Post</span><span class="pln"> </span><span class="typ">The</span><span class="pln"> </span><span class="typ">Second</span><span class="pln">
</span><span class="pun">[]</span><span class="pln">
</span><span class="pun">---</span></pre>

<p>
	يمكنك الوصول إلى أسماء الوسوم باستخدام الأمر <code>tag.name</code> كما هو مبين في المثال التالي، والذي يمكنك تشغيله باستخدام صَدَفَة فلاسك:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1965_20" style=""><span class="pln">from app </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Post</span><span class="pln">

posts </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">all</span><span class="pun">()</span><span class="pln">

</span><span class="kwd">for</span><span class="pln"> post in posts</span><span class="pun">:</span><span class="pln">
    print</span><span class="pun">(</span><span class="str">'TITLE: '</span><span class="pun">,</span><span class="pln"> post</span><span class="pun">.</span><span class="pln">title</span><span class="pun">)</span><span class="pln">
    print</span><span class="pun">(</span><span class="str">'-'</span><span class="pun">)</span><span class="pln">
    print</span><span class="pun">(</span><span class="str">'TAGS:'</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> tag in post</span><span class="pun">.</span><span class="pln">tags</span><span class="pun">:</span><span class="pln">
        print</span><span class="pun">(</span><span class="str">'&gt; '</span><span class="pun">,</span><span class="pln"> tag</span><span class="pun">.</span><span class="pln">name</span><span class="pun">)</span><span class="pln">
    print</span><span class="pun">(</span><span class="str">'-'</span><span class="pun">*</span><span class="lit">30</span><span class="pun">)</span></pre>

<p>
	وفي هذه الحالة وبالإضافة إلى طباعة عنوان كل تدوينة، نمرّ على وسوم كل منها ونطبع أسماءها، لنحصل على خرجٍ على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_22" style=""><span class="pln">TITLE</span><span class="pun">:</span><span class="pln">  </span><span class="typ">Post</span><span class="pln"> </span><span class="typ">The</span><span class="pln"> </span><span class="typ">First</span><span class="pln">
</span><span class="pun">-</span><span class="pln">
TAGS</span><span class="pun">:</span><span class="pln">
</span><span class="pun">&gt;</span><span class="pln">  animals
</span><span class="pun">&gt;</span><span class="pln">  writing
</span><span class="pun">------------------------------</span><span class="pln">
TITLE</span><span class="pun">:</span><span class="pln">  </span><span class="typ">Post</span><span class="pln"> </span><span class="typ">The</span><span class="pln"> </span><span class="typ">Third</span><span class="pln">
</span><span class="pun">-</span><span class="pln">
TAGS</span><span class="pun">:</span><span class="pln">
</span><span class="pun">&gt;</span><span class="pln">  cooking
</span><span class="pun">&gt;</span><span class="pln">  tech
</span><span class="pun">&gt;</span><span class="pln">  writing
</span><span class="pun">------------------------------</span><span class="pln">
TITLE</span><span class="pun">:</span><span class="pln">  </span><span class="typ">Post</span><span class="pln"> </span><span class="typ">The</span><span class="pln"> </span><span class="typ">Second</span><span class="pln">
</span><span class="pun">-</span><span class="pln">
TAGS</span><span class="pun">:</span><span class="pln">
</span><span class="pun">------------------------------</span></pre>

<p>
	نلاحظ مما سبق أنّ الوسوم المُضافة إلى التدوينات في البرنامج init_db.py متموضعة بمكانها الصحيح بالنسبة للتدوينات.
</p>

<p>
	وللاطلاع على مثال لتوضيح كيفية الوصول إلى التدوينات التابعة لكل وسم باستخدام التعليمة <code>tag.posts</code>، سنشغّل الشيفرة التالية في صّدّفّة فلاسك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_24" style=""><span class="kwd">from</span><span class="pln"> app </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Tag</span><span class="pln">

writing_tag </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Tag</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">filter_by</span><span class="pun">(</span><span class="pln">name</span><span class="pun">=</span><span class="str">'writing'</span><span class="pun">).</span><span class="pln">first</span><span class="pun">()</span><span class="pln">

</span><span class="kwd">for</span><span class="pln"> post </span><span class="kwd">in</span><span class="pln"> writing_tag</span><span class="pun">.</span><span class="pln">posts</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">post</span><span class="pun">.</span><span class="pln">title</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'-'</span><span class="pun">*</span><span class="lit">6</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">post</span><span class="pun">.</span><span class="pln">content</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'-'</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">([</span><span class="pln">tag</span><span class="pun">.</span><span class="pln">name </span><span class="kwd">for</span><span class="pln"> tag </span><span class="kwd">in</span><span class="pln"> post</span><span class="pun">.</span><span class="pln">tags</span><span class="pun">])</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'-'</span><span class="pun">*</span><span class="lit">20</span><span class="pun">)</span></pre>

<p>
	استوردنا في الشيفرة السابقة النموذج <code>Tag</code>، ثمّ استخدمنا تابع الترشيح <code>()filter_by</code> على سمة الاستعلام <code>query</code> ممررين إليه معامل الاسم <code>name</code> ليحصل على الوسم <code>writing</code>، ونحصل على أوّل نتيجة باستخدام التابع <code>()first</code>، ثم خزّنا كائن الوسم الناتج ضمن متغير باسم <code>writing_tag</code>. للمزيد حول تابع الترشيح <code>filter_by</code> ننصحك بقراءة الخطوة 4 من المقال <a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-flask-sqlalchemy-%D9%84%D9%84%D8%AA%D8%AE%D8%A7%D8%B7%D8%A8-%D9%85%D8%B9-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-r1842/" rel="">استخدام الإضافة Flask-SQLAlchemy للتخاطب مع قواعد البيانات في تطبيقات فلاسك</a>.
</p>

<p>
	مررنا بعد ذلك على جميع التدوينات المُتضمّنة للوسم <code>writing</code>، والتي وصلنا إليها باستخدام التعليمة <code>writing_tag.posts</code>، لنطبع عنوان كل تدوينة ومحتواها وقائمة بأسماء الوسوم الخاصّة بها والتي أنشأناها باستخدام طريقة <a href="https://academy.hsoub.com/programming/python/%D9%81%D9%87%D9%85-%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84-list-comprehensions-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-r515/" rel="">استيعاب القائمة list comprehension</a> المعتمدة على وسوم التدوينة، التي يمكننا الوصول إليها باستخدام الأمر <code>post.tags</code>.
</p>

<p>
	ويكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">Post The Third
------
Content for the third post
-
['cooking', 'tech', 'writing']
--------------------
Post The First
------
Content for the first post
-
['animals', 'writing']
--------------------
</pre>

<p>
	نرى في الخرج السابق التدوينتان المُتضمنتان للوسم <code>writing</code> مع قائمة بايثون بأسماء كافّة وسوم كل من التدوينتين.
</p>

<p>
	ومع نهاية هذه الخطوة أصبح من الممكن الوصول إلى التدوينات ووسوم كل منها أو إلى التدوينات المُتضمّنة لوسم معيّن، إذ أضفنا نموذج قاعدة بيانات ليمثّل جدول الوسوم، وربطنا ما بين جدولي التدوينات والوسوم باستخدام جدول ارتباط، كما أدخلنا عددًا من الوسوم إلى قاعدة البيانات واستخدمناها للإشارة إلى التدوينات، وأصبح من الممكن الوصول إلى التدوينات ووسوم كل منها أو إلى التدوينات المُتضمّنة لوسم ما.
</p>

<p>
	سنستخدم في الخطوة التالية صَدَفَة فلاسك لإضافة تدوينات ووسوم جديدة والربط فيما بينها، كما سنتعرّف على كيفية حذف وسوم من تدوينة ما.
</p>

<h2>
	الخطوة 3 - إدارة البيانات في العلاقة متعدد-إلى-متعدد
</h2>

<p>
	سنستخدم في هذه الخطوة صَدَفَة فلاسك لإضافة تدوينات ووسوم جديدة إلى قاعدة البيانات والربط فيما بينها، إذ سنتمكّن من الوصول إلى كل تدوينة مع وسومها، وسنتعرّف على كيفية فك ارتباط عنصرين في العلاقة مُتعدّد-إلى-مُتعدّد.
</p>

<p>
	بدايةً وفي حال كون صَدَفَة فلاسك غير مفتوحة أصلًا، نفتحها مع التأكّد من كون البيئة البرمجية مُفعّلة، كما يلي:
</p>

<pre class="ipsCode">(env)user@localhost:$ flask shell
</pre>

<p>
	سنضيف بعد ذلك بضعة تدويناتٍ ووسوم:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_27" style=""><span class="kwd">from</span><span class="pln"> app </span><span class="kwd">import</span><span class="pln"> db</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Tag</span><span class="pln">

life_death_post </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">(</span><span class="pln">title</span><span class="pun">=</span><span class="str">'A post on life and death'</span><span class="pun">,</span><span class="pln"> content</span><span class="pun">=</span><span class="str">'life and death'</span><span class="pun">)</span><span class="pln">
joy_post </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">(</span><span class="pln">title</span><span class="pun">=</span><span class="str">'A post on joy'</span><span class="pun">,</span><span class="pln"> content</span><span class="pun">=</span><span class="str">'joy'</span><span class="pun">)</span><span class="pln">

life_tag </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Tag</span><span class="pun">(</span><span class="pln">name</span><span class="pun">=</span><span class="str">'life'</span><span class="pun">)</span><span class="pln">
death_tag </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Tag</span><span class="pun">(</span><span class="pln">name</span><span class="pun">=</span><span class="str">'death'</span><span class="pun">)</span><span class="pln">
joy_tag </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Tag</span><span class="pun">(</span><span class="pln">name</span><span class="pun">=</span><span class="str">'joy'</span><span class="pun">)</span><span class="pln">

life_death_post</span><span class="pun">.</span><span class="pln">tags</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">life_tag</span><span class="pun">)</span><span class="pln">
life_death_post</span><span class="pun">.</span><span class="pln">tags</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">death_tag</span><span class="pun">)</span><span class="pln">
joy_post</span><span class="pun">.</span><span class="pln">tags</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">joy_tag</span><span class="pun">)</span><span class="pln">

db</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">add_all</span><span class="pun">([</span><span class="pln">life_death_post</span><span class="pun">,</span><span class="pln"> joy_post</span><span class="pun">,</span><span class="pln"> life_tag</span><span class="pun">,</span><span class="pln"> death_tag</span><span class="pun">,</span><span class="pln"> joy_tag</span><span class="pun">])</span><span class="pln">

db</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span></pre>

<p>
	وهذا ما سيُنشئ بالنتيجة تدوينتين وثلاثة وسوم. إذ أشرنا إلى التدوينات باستخدام الوسوم الموافقة، كما استخدمنا التابع <code>()add_all</code> لإضافة العناصر المُنشأة حديثًا إلى جلسة قاعدة البيانات. ونهايةً أكدنا التغييرات وطبّقناها على قاعدة البيانات باستخدام التابع <code>()commit</code>.
</p>

<p>
	فيما يلي سنستخدم صَدَفة فلاسك للحصول على كافّة التدوينات ووسومها كما فعلنا في الخطوة السابقة، على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_29" style=""><span class="pln">posts </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">all</span><span class="pun">()</span><span class="pln">

</span><span class="kwd">for</span><span class="pln"> post </span><span class="kwd">in</span><span class="pln"> posts</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">post</span><span class="pun">.</span><span class="pln">title</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">post</span><span class="pun">.</span><span class="pln">tags</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'---'</span><span class="pun">)</span></pre>

<p>
	فنحصل على خرجٍ شبيه بما يلي:
</p>

<pre class="ipsCode">Post The First
[&lt;Tag "animals"&gt;, &lt;Tag "writing"&gt;]
---
Post The Third
[&lt;Tag "cooking"&gt;, &lt;Tag "tech"&gt;, &lt;Tag "writing"&gt;]
---
Post The Second
[]
---
A post on life and death
[&lt;Tag "life"&gt;, &lt;Tag "death"&gt;]
---
A post on joy
[&lt;Tag "joy"&gt;]
---
</pre>

<p>
	وبذلك نجد أنّ التدوينات قد أُضيفت مع وسوم كل منها.
</p>

<p>
	الآن وبغية توضيح كيفية فك ارتباط عنصرين ضمن علاقة قاعدة البيانات من النوع مُتعدّد-إلى-مُتعدّد، سنفرض على سبيل المثال أنّ التدوينة الثالثة <code>Post The Third</code> لم تعد معنية بالطبخ، وبالتالي يجب أن نحذف الوسم <code>cooking</code> منها.
</p>

<p>
	لتحقيق ذلك، سنجلب بدايةً التدوينة مع الوسم المراد حذفه، كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_32" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">from</span><span class="pln"> app </span><span class="kwd">import</span><span class="pln"> db</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Tag</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> post </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">filter_by</span><span class="pun">(</span><span class="pln">title</span><span class="pun">=</span><span class="str">'Post The Third'</span><span class="pun">).</span><span class="pln">first</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> tag </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Tag</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">filter_by</span><span class="pun">(</span><span class="pln">name</span><span class="pun">=</span><span class="str">'cooking'</span><span class="pun">).</span><span class="pln">first</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">post</span><span class="pun">.</span><span class="pln">title</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">post</span><span class="pun">.</span><span class="pln">tags</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">tag</span><span class="pun">.</span><span class="pln">posts</span><span class="pun">)</span></pre>

<p>
	جلبنا في الشيفرة السابقة التدوينة ذات العنوان <code>Post The Third</code> باستخدام التابع <code>()filter_by</code>، كما جلبنا الوسم <code>cooking</code>، لنطبع كل من عنوان التدوينة ووسومها وكافّة التدوينات المُتضمّنة للوسم <code>cooking</code>.
</p>

<p>
	يعيد التابع <code>()filter_by</code> كائن استعلام، وباستخدام التابع <code>()all</code> نحصل على نتائج هذا الاستعلام ضمن قائمة، ولكن وبما أنّنا نتوقّع الحصول على نتيجة واحدة من الاستعلام في هذه الحالة، فاستخدمنا التابع <code>()first</code> للحصول فقط على النتيجة الأولى، وللمزيد حول كل من التابعين <code>()first</code> و <code>()all</code>، ننصحك بقراءة الخطوة 4 من المقال <a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-flask-sqlalchemy-%D9%84%D9%84%D8%AA%D8%AE%D8%A7%D8%B7%D8%A8-%D9%85%D8%B9-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-r1842/" rel="">استخدام الإضافة Flask-SQLAlchemy للتخاطب مع قواعد البيانات في تطبيقات فلاسك</a>.
</p>

<p>
	ويكون الخرج في هذه الحالة على النحو التالي:
</p>

<pre class="ipsCode">Post The Third


[&lt;Tag "cooking"&gt;, &lt;Tag "tech"&gt;, &lt;Tag "writing"&gt;]


[&lt;Post "Post The Third"&gt;]
</pre>

<p>
	وفيه نرى عنوان التدوينة ووسومها، مع قائمة بكافّة التدوينات المُتضمّنة للوسم <code>cooking</code>.
</p>

<p>
	الآن وبغية حذف الوسم <code>cooking</code> من التدوينة، سنستخدم التابع <code>()remove</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_35" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> post</span><span class="pun">.</span><span class="pln">tags</span><span class="pun">.</span><span class="pln">remove</span><span class="pun">(</span><span class="pln">tag</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> db</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">post</span><span class="pun">.</span><span class="pln">tags</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">tag</span><span class="pun">.</span><span class="pln">posts</span><span class="pun">)</span></pre>

<p>
	استخدمنا في الشيفرة السابقة التابع <code>()remove</code> لفصل الوسم <code>cooking</code> عن التدوينة، ثمّ استخدمنا التابع <code>()db.session.commit</code> لتطبيق التغييرات على قاعدة البيانات، فنحصل على خرجٍ يؤكّد حذف الوسم من التدوينة، على النحو التالي:
</p>

<pre class="ipsCode" id="ips_uid_1965_37">[&lt;Tag "tech"&gt;, &lt;Tag "writing"&gt;]

[]
</pre>

<p>
	وبذلك نجد أنّ الوسم <code>cooking</code> قد حُذف فعلًا من القائمة <code>post.tags</code>، كما أنّ التدوينة لم تعد موجودةً ضمن قائمة التدوينات <code>tag.posts</code> المُتضمّنة لهذا الوسم.
</p>

<p>
	نُغلق الآن صَدَفَة فلاسك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_39" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> exit</span><span class="pun">()</span></pre>

<p>
	ومع نهاية هذه الخطوة نكون قد أضفنا تدويناتٍ ووسوم جديدة، كما أضفنا الوسوم إلى التدوينات وحذفناها منها.
</p>

<p>
	سنعمل في الخطوة التالية على عرض الوسوم الخاصّة بكل تدوينة في صفحة تطبيق المدونة الرئيسية.
</p>

<h2>
	الخطوة 4 - عرض الوسوم أسفل كل تدوينة
</h2>

<p>
	سنحرّر في هذه الخطوة قالب الصفحة الرئيسية لعرض الوسوم الخاصّة بكل تدوينة أسفلها. لذا، اطلع بدايةً على البنية الحالية للصفحة الرئيسية للتطبيق.
</p>

<p>
	أعلم فلاسك بالتطبيق المطلوب تشغيله وهو في حالتنا الملف app.py باستخدام متغير البيئة <code>FLASK_APP</code> وذلك بعد التأكد من أن البيئة البرمجية مُفعّلة، واضبط متغير البيئة <code>FLASK_ENV</code> على وضع التطوير <code>development</code>، على النحو التالي:
</p>

<pre class="ipsCode">(env)user@localhost:$ export FLASK_APP=app
(env)user@localhost:$ export FLASK_ENV=development
</pre>

<p>
	شغّل البرنامج الآن:
</p>

<pre class="ipsCode">(env)user@localhost:$ flask run
</pre>

<p>
	وأثناء كون خادم التطوير قيد التشغيل، انتقل إلى الرابط التالي باستخدام المُتصفح:
</p>

<pre class="ipsCode">http://127.0.0.1:5000/
</pre>

<p>
	فستظهر لك صفحة شبيهة بالشّكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="120679" href="https://academy.hsoub.com/uploads/monthly_2023_03/2nd.png.cc7ece486e15587898e3081d9b65cdf7.png" rel=""><img alt="2nd.png" class="ipsImage ipsImage_thumbnailed" data-fileid="120679" data-unique="mhzefqhki" src="https://academy.hsoub.com/uploads/monthly_2023_03/2nd.thumb.png.6b1297a69785f71c44070bf6fd66aeeb.png"> </a>
</p>

<p>
	نترك خادم التطوير قيد التشغيل ونفتح نافذة سطر أوامر جديدة.
</p>

<p>
	نريد في هذه المرحلة عرض كافّة وسوم كل تدوينة ضمن صفحتين من صفحات التطبيق: الأولى في الصفحة الرئيسية أسفل كل تدوينة، والثانية في الصفحة المُخصّصة لكل تدوينة لتظهر الوسوم أسفل محتواها، إذ سنستخدم في الحالتين نفس الشيفرة لعرض الوسوم، لذا ولتجنّب التكرار سنستخدم <a href="https://academy.hsoub.com/tags/jinja2" rel="">ماكرو جينجا jinja macro</a>، الذي يعمل مثل أي دالة في بايثون ويتضمّن شيفرة HTML ديناميكية قابلة للاستخدام في أي مكان يُستدعى فيه الماكرو؛ وفي حال التعديل عليه، سيُعدّل تلقائيًا في كافّة الأماكن المُستدعى فيها ما يجعل الشيفرة قابلة لإعادة الاستخدام.
</p>

<p>
	لذا، سننشئ ملفًا جديدًا باسم "macros.html" ضمن مجلد القوالب templates، على النحو التالي:
</p>

<pre class="ipsCode">(env)user@localhost:$ nano templates/macros.html
</pre>

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

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1965_41" style=""><span class="pln">{% macro display_tags(post) %}
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"tags"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;p&gt;</span><span class="pln">
            </span><span class="tag">&lt;h4&gt;</span><span class="pln">Tags:</span><span class="tag">&lt;/h4&gt;</span><span class="pln">
            {% for tag in post.tags %}
                </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"#"</span><span class="pln"> </span><span class="atn">style</span><span class="pun">=</span><span class="atv">"</span><span class="kwd">text-decoration</span><span class="pun">:</span><span class="pln"> none</span><span class="pun">;</span><span class="pln"> </span><span class="kwd">color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#dd5b5b</span><span class="atv">"</span><span class="tag">&gt;</span><span class="pln">
                    {{ tag.name }}
                </span><span class="tag">&lt;/a&gt;</span><span class="pln">
                |
            {% endfor %}
        </span><span class="tag">&lt;/p&gt;</span><span class="pln">
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
{% endmacro %}</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	استخدمنا في الشيفرة السابقة الكلمة المفتاحية <code>macro</code> للتصريح عن ماكرو باسم <code>()display_tags</code> ذو معامل باسم <code>post</code>، إذ استخدمنا الوسم <code>&lt;div&gt;</code> ليعرض عنوانًا من المستوى الرابع <code>&lt;h4&gt;</code>، كما استخدمنا حلقة <code>for</code> تكرارية بغية المرور على كافّة وسوم كائن التدوينة المُمرّرة مثل وسيط إلى الماكرو المُستدعى، كما هو الحال لدى تمرير أي وسيط لدالة بايثون اعتيادية مُراد استدعاؤها.
</p>

<p>
	حصلنا على وسوم كل تدوينة باستخدام التعليمة <code>post.tags</code>، لنعرض اسم الوسم ضمن وسم رابط <code>&lt;a&gt;</code>، إذ سنعدّل لاحقًا قيمة السمة <code>href</code> لوسم الرابط لنجعله يشير إلى صفحة مُخصّصة للوسم سننشئها لاحقًا لتعرض كافّة التدوينات المُتضمّنة للوسم المُحدّد. نهايةً، استخدمنا الكلمة المفتاحية <code>endmacro</code> للدلالة على انتهاء الماكرو.
</p>

<p>
	الآن، وبغية عرض كافّة الوسوم التابعة للتدوينة أسفلها في الصفحة الرئيسية للتطبيق، سنفتح ملف القالب index.html:
</p>

<pre class="ipsCode">(env)user@localhost:$ nano templates/index.html
</pre>

<p>
	وسنستورد بدايةً الماكرو <code>()display_tags</code> من الملف "macros.html"، لذا سنضيف الاستيرادات التالية إلى بداية الملف قبل السطر <code>{% extends 'base.html' %}</code>، كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_43" style=""><span class="pun">{%</span><span class="pln"> </span><span class="kwd">from</span><span class="pln"> </span><span class="str">'macros.html'</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> display_tags </span><span class="pun">%}</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> extends </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span></pre>

<p>
	سنعدّل بعد ذلك الحلقة التكرارية <code>for post in posts</code> لتتضمّن استدعاءً للماكرو <code>()display_tags</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1965_45" style=""><span class="pln">{% for post in posts %}
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"post"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;p&gt;&lt;b&gt;</span><span class="pln">#{{ post.id }}</span><span class="tag">&lt;/b&gt;&lt;/p&gt;</span><span class="pln">
        </span><span class="tag">&lt;b&gt;</span><span class="pln">
            </span><span class="tag">&lt;p</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"title"</span><span class="tag">&gt;</span><span class="pln">
                </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('post', post_id=post.id)}}"</span><span class="tag">&gt;</span><span class="pln">
                    {{ post.title }}
                </span><span class="tag">&lt;/a&gt;</span><span class="pln">
            </span><span class="tag">&lt;/p&gt;</span><span class="pln">
        </span><span class="tag">&lt;/b&gt;</span><span class="pln">
        </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"content"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;p&gt;</span><span class="pln">{{ post.content }}</span><span class="tag">&lt;/p&gt;</span><span class="pln">
        </span><span class="tag">&lt;/div&gt;</span><span class="pln">

        {{ display_tags(post) }}

        </span><span class="tag">&lt;hr&gt;</span><span class="pln">
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
{% endfor %}</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	استدعينا في الشيفرة السابقة الشيفرة الجامعة <code>()display_tags</code> ممررين إليها الكائن <code>post</code>، وهذا ما سيعرض بالنتيجة أسماء الوسوم أسفل كل تدوينة.
</p>

<p>
	الآن، بتحديث الصفحة الرئيسية للتطبيق، نرى الوسوم أسفل كل تدوينة كما في الشّكل:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="120678" href="https://academy.hsoub.com/uploads/monthly_2023_03/3rd.png.cc73d1e09e7b040b53e7d4e9a1e55990.png" rel=""><img alt="3rd.png" class="ipsImage ipsImage_thumbnailed" data-fileid="120678" data-unique="4to41pzmm" src="https://academy.hsoub.com/uploads/monthly_2023_03/3rd.thumb.png.18dd07e1f824eb4b95e09f017737e009.png"> </a>
</p>

<p>
	أمّا الآن فسنعمل على إضافة الوسوم أسفل محتوى التدوينة في الصفحة المُخصّصة لها. لذا، نفتح ملف القالب post.html:
</p>

<pre class="ipsCode">(env)user@localhost:$ nano templates/post.html
</pre>

<p>
	ونستورد في بدايته الماكرو <code>display_tags</code> بالشّكل:
</p>

<pre class="ipsCode">{% from 'macros.html' import display_tags %}
{% extends 'base.html' %}
</pre>

<p>
	ونستدعي بعد ذلك الماكرو <code>()display_tags</code> ممررين إليه الكائن <code>post</code> أسفل محتوى التدوينة وقبل وسم الفاصل الأفقي <code><a href="https://wiki.hsoub.com/HTML/hr" rel="external">&lt;hr&gt;</a></code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1965_47" style=""><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"post"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;p&gt;&lt;b&gt;</span><span class="pln">#{{ post.id }}</span><span class="tag">&lt;/b&gt;&lt;/p&gt;</span><span class="pln">
    </span><span class="tag">&lt;b&gt;</span><span class="pln">
        </span><span class="tag">&lt;p</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"title"</span><span class="tag">&gt;</span><span class="pln">{{ post.title }}</span><span class="tag">&lt;/p&gt;</span><span class="pln">
    </span><span class="tag">&lt;/b&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"content"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;p&gt;</span><span class="pln">{{ post.content }}</span><span class="tag">&lt;/p&gt;</span><span class="pln">
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">

    {{ display_tags(post) }}

    </span><span class="tag">&lt;hr&gt;</span><span class="pln">
    </span><span class="tag">&lt;h3&gt;</span><span class="pln">Comments</span><span class="tag">&lt;/h3&gt;</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	الآن وبالانتقال إلى صفحة التدوينة (الثانية مثلًا) باستخدام الرابط:
</p>

<pre class="ipsCode">http://127.0.0.1:5000/2
</pre>

<p>
	نرى الوسوم معروضة أسفل محتوى التدوينة بنفس طريقة عرضها في الصفحة الرئيسية للتطبيق.
</p>

<p>
	سنعمل في الخطوة التالية على إضافة وجهة جديدة إلى تطبيق فلاسك هذا لتعرض كافّة التدوينات المُتضمّنة لوسم معيّن، كما سنجعل رابط الوسم الذي أضفناه في هذه الخطوة فعّالًا.
</p>

<h2>
	الخطوة 5 - عرض الوسوم وتدويناتها
</h2>

<p>
	سنضيف في هذه الخطوة وجهةً وقالبًا إلى التطبيق لعرض الوسوم الموجودة في قاعدة البيانات مع التدوينات المُتضمّنة لكل منها.
</p>

<p>
	لذا، سنضيف بدايةً وجهةً لعرض التدوينات المُتضمّنة لكل وسم، فعلى سبيل المثال، ستعرض الوجهة <code>/tags/tag_name/</code> صفحةً تحتوي كافّة التدوينات المُتضمّنة للوسم المُحدّد في الجزء المُخصّص لاسم الوسم المطلوب <code>tag_name</code> من الوجهة.
</p>

<p>
	نقتح الآن الملف app.py لتحريره:
</p>

<pre class="ipsCode">(env)user@localhost:$ nano app.py
</pre>

<p>
	ونضيف الوجهة التالية إلى نهايته:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_49" style=""><span class="com"># ...</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/tags/&lt;tag_name&gt;/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> tag</span><span class="pun">(</span><span class="pln">tag_name</span><span class="pun">):</span><span class="pln">
    tag </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Tag</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">filter_by</span><span class="pun">(</span><span class="pln">name</span><span class="pun">=</span><span class="pln">tag_name</span><span class="pun">).</span><span class="pln">first_or_404</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'tag.html'</span><span class="pun">,</span><span class="pln"> tag</span><span class="pun">=</span><span class="pln">tag</span><span class="pun">)</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	استخدمنا في الشيفرة السابقة متغير الرابط <code>tag_name</code> المسؤول عن تحديد كل من الوسم والتدوينات المُتضمّنة لهذا الوسم والتي ستُعرض ضمن صفحة الوسم، إذ يُمرّر اسم الوسم إلى الدالة <code>()tag</code> العاملة في فلاسك باستخدام المعامل <code>tag_name</code> المُستخدم أيضًا ضمن تابع الترشيح <code>()filter_by</code> للاستعلام عن الوسم المطلوب.
</p>

<p>
	استخدمنا التابع <code>()first_or_404</code> للحصول على كائن الوسم، فإمّا أن يُخزّن في حال وجوده ضمن متغير باسم <code>tag</code>، وإلّا سنحصل على رسالة الخطأ "‎404 Not Found" في حال عدم وجود وسم في قاعدة البيانات باسم مطابق للاسم المطلوب. ثمّ صيّرنا ملف قالب باسم "tag.html" مُمررين إليه الكائن <code>tag</code>.
</p>

<p>
	سنفتح الآن القالب الجديد "templates/tag.html" بغية تحريره:
</p>

<pre class="ipsCode">(env)user@localhost:$ nano templates/tag.html
</pre>

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

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1965_51" style=""><span class="pln">{% from 'macros.html' import display_tags %}
{% extends 'base.html' %}

{% block content %}
    </span><span class="tag">&lt;span</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"title"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;h1&gt;</span><span class="pln">{% block title %} Posts Tagged with "{{ tag.name }}" {% endblock %}</span><span class="tag">&lt;/h1&gt;</span><span class="pln">
    </span><span class="tag">&lt;/span&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"content"</span><span class="tag">&gt;</span><span class="pln">
        {% for post in tag.posts %}
        </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"post"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;p&gt;&lt;b&gt;</span><span class="pln">#{{ post.id }}</span><span class="tag">&lt;/b&gt;&lt;/p&gt;</span><span class="pln">
            </span><span class="tag">&lt;b&gt;</span><span class="pln">
                </span><span class="tag">&lt;p</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"title"</span><span class="tag">&gt;</span><span class="pln">
                    </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('post', post_id=post.id)}}"</span><span class="tag">&gt;</span><span class="pln">
                        {{ post.title }}
                    </span><span class="tag">&lt;/a&gt;</span><span class="pln">
                </span><span class="tag">&lt;/p&gt;</span><span class="pln">
            </span><span class="tag">&lt;/b&gt;</span><span class="pln">
            </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"content"</span><span class="tag">&gt;</span><span class="pln">
                </span><span class="tag">&lt;p&gt;</span><span class="pln">{{ post.content }}</span><span class="tag">&lt;/p&gt;</span><span class="pln">
            </span><span class="tag">&lt;/div&gt;</span><span class="pln">

            {{ display_tags(post) }}

            </span><span class="tag">&lt;hr&gt;</span><span class="pln">
        </span><span class="tag">&lt;/div&gt;</span><span class="pln">
        {% endfor %}
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
{% endblock %}</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	استوردنا في الشيفرة السابقة الماكرو <code>()display_tags</code> من الملف "macros.html"، واستخدمنا القالب الرئيسي بالاعتماد على تعليمة <code>extend</code>. كما عيّنا ترويسةً ضمن كتلة المحتوى لتعمل مثل عنوان مُتضمنةً اسم الوسم، مرّرنا بعد ذلك على كافّة التدوينات المُتضمّنة للوسم المطلوب والتي قد وصلنا إليها باستخدام التعليمة <code>tag.posts</code>، لنعرض معرّف التدوينة وعنوانها ومحتواها، ومن ثمّ استدعينا الماكرو <code>()display_tags</code> لعرض كافّة الوسوم التابعة للتدوينة.
</p>

<p>
	الآن ومع كون خادم التطوير قيد التشغيل، ننتقل إلى الرابط التالي باستخدام المُتصفّح:
</p>

<pre class="ipsCode">http://127.0.0.1:5000/tags/writing/
</pre>

<p>
	وهي الصفحة المُخصّصة للوسم <code>writing</code>، وكما هو موضّح في الصورة أدناه فإنّ كافّة الوسوم المُتضمّنة للوسم <code>writing</code> معروضةٌ في هذه الصفحة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="120677" href="https://academy.hsoub.com/uploads/monthly_2023_03/4th.png.123f4fc9d4cf6edd544fbf36dd15b2eb.png" rel=""><img alt="4th.png" class="ipsImage ipsImage_thumbnailed" data-fileid="120677" data-unique="yqboztihb" src="https://academy.hsoub.com/uploads/monthly_2023_03/4th.thumb.png.bd55556cb96ab2919d0b09f7c5449229.png"> </a>
</p>

<p>
	أمّا الآن فسنعدّل الماكرو <code>()display_tags</code> لجعل روابط الوسوم فعّالة، لذا سنفتح الملف "macros.html":
</p>

<pre class="ipsCode">(env)user@localhost:$ nano templates/macros.html
</pre>

<p>
	ونعدّل قيمة السمة <code>href</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1965_53" style=""><span class="pln">{% macro display_tags(post) %}
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"tags"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;p&gt;</span><span class="pln">
            </span><span class="tag">&lt;h4&gt;</span><span class="pln">Tags:</span><span class="tag">&lt;/h4&gt;</span><span class="pln">
            {% for tag in post.tags %}
            </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('tag', tag_name=tag.name) }}"</span><span class="pln">
               </span><span class="atn">style</span><span class="pun">=</span><span class="atv">"</span><span class="kwd">text-decoration</span><span class="pun">:</span><span class="pln"> none</span><span class="pun">;</span><span class="pln"> </span><span class="kwd">color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#dd5b5b</span><span class="atv">"</span><span class="tag">&gt;</span><span class="pln">
                    {{ tag.name }}
                </span><span class="tag">&lt;/a&gt;</span><span class="pln">
                |
            {% endfor %}
        </span><span class="tag">&lt;/p&gt;</span><span class="pln">
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
{% endmacro %}</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	الآن وبتحديث الصفحات التي استخدمنا فيها الماكرو <code>()display_tags</code>، نلاحظ أنّ روابط الوسوم أصبحت فعّالة:
</p>

<pre class="ipsCode">http://127.0.0.1:5000/
http://127.0.0.1:5000/2/
http://127.0.0.1:5000/tags/writing/
</pre>

<p>
	وبذلك نجد أنّ استخدام الماكرو المُقدّمة من محرّك القوالب جينجا سمح بإعادة استخدام الشيفرة وبالتالي تقليل التكرار، ناهيك عن حقيقة كون تعديل ملف الماكرو الرئيسي كفيل بتطبيق التغييرات على كافّة القوالب المُستخدِمة له.
</p>

<p>
	ومع نهاية هذه الخطوة أصبح لدينا صفحة خاصّة بكل وسم يتمكّن من خلالها المُستخدمون من رؤية كافّة التدوينات المُتضمّنة لوسم معيّن، إذ يمكن الوصول إلى صفحة الوسم هذه من خلال النقر على الوسوم أسفل كل تدوينة كونها تتضمّن روابط إلى الصفحة الخاصّة بالوسم.
</p>

<h1>
	الخاتمة
</h1>

<p>
	وضحنا في هذا المقال كيفية إدارة العلاقات من نوع مُتعدّد-إلى-مُتعدّد باستخدام الإضافة Flask-SQLAlchemy وذلك بإضافة ميزة الوسوم إلى تطبيق المدونة السابق، إذ تعرفنا على كيفية ربط جدولين باستخدام جدول الارتباط (والذي يُدعى أيضًا جدول الصلة)، وكيفية ربط المدخلات ببعضها وإضافتها إلى قاعدة البيانات والوصول إلى البيانات وفك ارتباطها عن أحد المُدخلات.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-use-many-to-many-database-relationships-with-flask-sqlalchemy" rel="external nofollow">How To Use Many-to-Many Database Relationships with Flask-SQLAlchemy</a> لصاحبه Abdelhadi Dyouri.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/sql/%D8%A7%D9%84%D8%B9%D9%84%D8%A7%D9%82%D8%A7%D8%AA-%D8%A8%D9%8A%D9%86-%D8%A7%D9%84%D8%AC%D8%AF%D8%A7%D9%88%D9%84-%D9%81%D9%8A-sql-r590/" rel="">العلاقات بين الجداول في SQL</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B9%D9%84%D8%A7%D9%82%D8%A9-many-to-many-%D9%85%D8%B9-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D9%88%D9%85%D8%AD%D8%B1%D9%83-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-sqlite-r1632/" rel="">استخدام علاقة many-to-many مع فلاسك Flask ومحرك قواعد البيانات SQLite</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D9%84%D8%B1%D9%91%D8%A8%D8%B7-%D8%A8%D9%8A%D9%86-%D8%AC%D8%AF%D9%88%D9%84%D9%8A-%D8%A7%D9%84%D9%85%D9%82%D8%A7%D9%84%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D9%85%D9%8F%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D9%8A%D9%86-%D8%A8%D8%B9%D9%84%D8%A7%D9%82%D8%A9-%D9%88%D8%A7%D8%AD%D8%AF-%D9%84%D9%84%D8%B9%D8%AF%D9%8A%D8%AF-one-to-many-relationship-r512/" rel="">الرّبط بين جدولي المقالات والمُستخدمين بعلاقة واحد للعديد One-to-Many Relationship</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1924</guid><pubDate>Mon, 13 Mar 2023 13:01:03 +0000</pubDate></item><item><title>&#x643;&#x64A;&#x641;&#x64A;&#x629; &#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; SQLAlchemy &#x641;&#x64A; &#x641;&#x644;&#x627;&#x633;&#x643;</title><link>https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-sqlalchemy-%D9%81%D9%8A-%D9%81%D9%84%D8%A7%D8%B3%D9%83-r1843/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_01/588948409_--------one-to-many---SQLAlchemy--.png.e4f9dbea629937749a4988d04b2d4ee1.png" /></p>
<p>
	يُعد <a href="https://academy.hsoub.com/programming/python/flask/" rel="">فلاسك</a> إطار عمل للويب مبني بلغة <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D9%85%D8%B1%D8%AC%D8%B9-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%AA%D8%B9%D9%84%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r735/" rel="">بايثون</a>، ويتميز بكونه صغير الحجم وسهل المعالجة، ويوفّر أيضًا العديد من الأدوات والميزات التي من شأنها إنشاء تطبيقات ويب في لغة بايثون.
</p>

<p>
	أمّا SQLAlchemy، فهي أداةٌ في محرك قواعد البيانات SQL تؤمن وصولًا فعالًا وعالي الأداء إلى قواعد البيانات العلاقية، كما توفّر طرقًا للتخاطب مع العديد من محركات قواعد البيانات مثل SQLite، و MySQL، و PostgreSQ، مانحةً إمكانية الوصول إلى آليات SQL الخاصة بقواعد البيانات، كما توفّر مُخطِّط الكائنات العلاقية Object Relational Mapper -أو اختصارًا ORM- ليتيح إمكانية إنشاء الاستعلامات والتعامل مع البيانات باستخدام توابع وكائنات بسيطة في بايثون.
</p>

<p>
	تُعدّ <a href="https://flask-sqlalchemy.palletsprojects.com/" rel="external nofollow">Flask-SQLAlchemy</a> إضافة لفلاسك تسهّل من استخدام SQLAlchemy ضمنه وتؤمن الأدوات والوسائل المناسبة للتعامل مع قاعدة البيانات في تطبيقات فلاسك من خلال SQLAlchemy.
</p>

<p>
	تُعرَّف علاقة قاعدة البيانات من النوع واحد-إلى-متعدد one-to-many بكونها علاقةً بين جدولين من قاعدة البيانات، بحيث يمكن لسجلٍ موجودٍ في أحد الجدولين الإشارة إلى عدّة سجلات من الجدول الثاني، ويشكّل تطبيق المدونة خير مثال على العلاقات في قواعد البيانات من النوع واحد-إلى-متعدد، نظرًا لارتباط الجدول الخاص بتخزين التدوينات posts مع الجدول الخاص بتخزين التعليقات comments بعلاقة من نوع واحد-إلى-متعدد؛ إذ يمكن أن يكون لكل تدوينة عدّة تعليقاتٍ مرتبطةٍ بها، وبالمقابل يرتبط كل تعليق بتدوينة واحدةٍ فقط، وبذلك فإنّ للتدوينة الواحدة علاقةٌ بالعديد من التعليقات. يُعد الجدول الخاص بالتدوينات في هذه الحالة الجدول الأب parent، في حين يُعد الجدول الخاص بالتعليقات الجدول الابن child؛ إذ يمكن للسجل الموجود في الجدول الأب الإشارة إلى عدّة سجلات موجودة في الجدول الابن، وهو أمرٌ أساسي لإتاحة إمكانية الوصول إلى البيانات المطلوبة في كل جدول.
</p>

<p>
	سنعمل في هذا المقال على بناء مدونة ويب صغيرة لبيان كيفيّة إنشاء علاقة من النوع واحد-إلى-مُتعدّد باستخدام الإضافة Flask-SQLAlchemy، إذ سننشئ علاقةً بين التدوينات والتعليقات، فمن الممكن أن يكون لكل تدوينة عددًا من التعليقات.
</p>

<h2>
	مستلزمات العمل
</h2>

<p>
	قبل المتابعة في هذا المقال لا بدّ من:
</p>

<ul>
	<li>
		توفُّر <a href="https://academy.hsoub.com/programming/python/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%AA%D9%87%D8%A7-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r714/" rel="">بيئة برمجة بايثون 3 محلية</a>، مثبّتة على حاسوبك، وسنفترض في مقالنا أن اسم مجلد المشروع هو <code>flask_app</code>.
	</li>
	<li>
		الفهم الجيد لأساسيات فلاسك، مثل مفهوم الوجهات ودوال العرض، وفي هذا الصدد يمكنك الاطلاع على المقالين <a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%B9%D9%84%D9%85-%D8%A8%D9%86%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9%D9%83-%D8%A7%D9%84%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1715/" rel="">كيفية بناء موقعك الإلكتروني الأول باستخدام إطار عمل فلاسك Flask من لغة بايثون</a> و<a href="https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1737/" rel="">كيفية استخدام القوالب في تطبيقات فلاسك Flask</a> لفهم مبادئ فلاسك.
	</li>
	<li>
		فهم أساسيات لغة <a href="https://academy.hsoub.com/programming/html/" rel="">HTML</a>.
	</li>
</ul>

<h2>
	الخطوة 1: تثبيت فلاسك والأداة Flask-SQLAlchemy
</h2>

<p>
	سنعمل في هذه الخطوة على تثبيت كل من فلاسك وكافّة الحزم اللازمة لعمل التطبيق.
</p>

<p>
	لذا، وبعد التأكّد من تفعيل البيئة الافتراضية، سنستخدم أمر تثبيت الحزم <code>pip</code> لتثبيت كل من فلاسك وأداة Flask-SQLAlchemy على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_6" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ pip install </span><span class="typ">Flask</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">-</span><span class="typ">SQLAlchemy</span></pre>

<p>
	وبمجرّد انتهاء التثبيت بنجاح، سيظهر في السطر الأخير من الخرج ما يشبه التالي:
</p>

<pre class="ipsCode" id="ips_uid_444_8">Successfully installed Flask-2.1.1 Flask-SQLAlchemy-2.5.1 Jinja2-3.1.1 MarkupSafe-2.1.1 SQLAlchemy-1.4.35 Werkzeug-2.1.1 click-8.1.2 greenlet-1.1.2 itsdangerous-2.1.2</pre>

<p>
	ومع نهاية هذه الخطوة نكون قد ثبتنا ما يلزم من حزم بايثون، وأصبح من الممكن الانتقال إلى الخطوة التالية المُتمثّلة بإعداد قاعدة البيانات.
</p>

<h2>
	الخطوة 2: إعداد النماذج Models وقاعدة البيانات
</h2>

<p>
	سنعمل في هذه الخطوة على إعداد الاتصال مع قاعدة البيانات وإنشاء نماذج قاعدة البيانات SQLAlchemy، وهي أصناف بايثون تمثّل جداول قاعدة البيانات، إذ سننشئ نموذجًا للتدوينات وآخرًا للتعليقات، وسنعمل على تهيئة قاعدة البيانات وإنشاء جدول للتدوينات وآخرٍ للتعليقات وفقًا للنماذج التي سنصرّح عنها، ونهايةً سنضيف بعض التدوينات والتعليقات إلى قاعدة البيانات.
</p>

<h3>
	إعداد الاتصال بقاعدة البيانات
</h3>

<p>
	سننشئ بدايةً ملفًا باسم <code>app.py</code> ضمن المجلد <code>flask.app</code>، والذي سيحتوي على الشيفرات الخاصة بإعداد قاعدة البيانات ووجهات فلاسك اللازمة لعمل التطبيق:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_10" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	سيتصل هذا الملف بقاعدة بيانات من نوع SQLite باسم <code>database.db</code>، وسيحتوي على صنفين، الأوّل يدعى <code>Post</code> ويمثّل جدول التدوينات في قاعدة البيانات, والثاني يُدعى <code>Comment</code> ويمثّل جدول التعليقات، كما سيتضمّن هذا الملف وجهات فلاسك. الآن، سنضيف الاستدعاءات التالية باستخدام التعليمة <code>import</code> إلى بداية الملف <code>app.py</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_12" style=""><span class="kwd">import</span><span class="pln"> os
</span><span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> render_template</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">,</span><span class="pln"> redirect</span><span class="pun">,</span><span class="pln"> url_for
</span><span class="kwd">from</span><span class="pln"> flask_sqlalchemy </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">SQLAlchemy</span></pre>

<p>
	استوردنا في الشيفرة السابقة الوحدة <a href="https://docs.python.org/3/library/os.html" rel="external nofollow"><code>os</code></a> التي تمكنّنا من الوصول إلى واجهات نظام التشغيل المختلفة، والتي سنستخدمها في بناء مسار ملف قاعدة البيانات <code>database.db</code>.
</p>

<p>
	كما استوردنا من حزمة فلاسك كافّة المُساعدات اللازمة للتطبيق، مثل الصنف <code>Flask</code> المُستخدم في إنشاء النسخة الفعلية من التطبيق، والدالة <code>()render_template</code> لتصيير قوالب HTML، والكائن <code>request</code> المسؤول عن التعامل مع الطلبيات، والدالة <code>()url_for</code> لبناء روابط الوجهات، والدالة <code>()redirect</code> لإعادة توجيه المُستخدمين من صفحة لأُخرى.
</p>

<p>
	وبعد الانتهاء من الاستدعاءات، سنعيّن مسارًا لملف قاعدة البيانات، ونستنسخ تطبيق فلاسك، كما سنضبط ونؤسس اتصالًا للتطبيق مع الإضافة SQLAlchemy، ثمّ سنضيف الشيفرة التالية إلى الملف <code>app.py</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_14" style=""><span class="pln">basedir </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">abspath</span><span class="pun">(</span><span class="pln">os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">dirname</span><span class="pun">(</span><span class="pln">__file__</span><span class="pun">))</span><span class="pln">

app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">config</span><span class="pun">[</span><span class="str">'SQLALCHEMY_DATABASE_URI'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln">\
           </span><span class="str">'sqlite:///'</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="pln">basedir</span><span class="pun">,</span><span class="pln"> </span><span class="str">'database.db'</span><span class="pun">)</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">config</span><span class="pun">[</span><span class="str">'SQLALCHEMY_TRACK_MODIFICATIONS'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">False</span><span class="pln">


db </span><span class="pun">=</span><span class="pln"> </span><span class="typ">SQLAlchemy</span><span class="pun">(</span><span class="pln">app</span><span class="pun">)</span></pre>

<p>
	أنشأنا في الشيفرة السابقة مسارًا لملف قاعدة البيانات SQLite، إذ حددنا المجلد الحالي ليكون المجلد الرئيسي، كما استخدمنا الدالة <code>()os.path.abspath</code> للحصول على المسار الكامل لمجلد الملف الحالي، إذ يتضمّن المتغير الخاص <code>__file__</code> اسم مسار الملف الحالي <code>app.py</code>، لنخزّن المسار الكلي للمجلد الأساسي ضمن المتغير <code>basedir</code>، ثمّ أنشأنا نسخةً فعلية من تطبيق فلاسك باسم <code>app</code>، والتي سنستخدمها لضبط مفتاحي إعدادات خاصّين بالإضافة Flask-SQLAlchemy وهما:
</p>

<ul>
	<li>
		<code>SQLALCHEMY_DATABASE_URI</code>: وهو معرّف الموارد الموحّد الخاص بقاعدة البيانات database URI المسؤول عن تحديد قاعدة البيانات المراد إنشاء اتصال معها، وفي هذه الحالة تكون صيغة هذا المعرّف بالشكل الآتي:
	</li>
</ul>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_8173_7" style=""><span class="pln">sqlite:///path/to/database.db</span></pre>

<p>
	إذ نستخدم الدالة <a href="https://docs.python.org/3.8/library/os.path.html#os.path.join" rel="external nofollow"><code>()op.path.join</code></a> لتحقيق الربط المدروس ما بين المجلد الأساسي الذي أنشاناه وخزنّا مساره في المتغير <code>basedir</code>، وبين اسم الملف <code>database.db</code>، الأمر الذي يسمح بالاتصال مع ملف قاعدة البيانات <code>database.db</code> الموجود في المجلد <code>flask_app</code>، إذ سيُنشأ هذا الملف فور تهيئة قاعدة البيانات.
</p>

<ul>
	<li>
		<code>SQLALCHEMY_TRACK_MODIFICATIONS</code>: وهو إعداد لتمكين أو إلغاء تمكين ميزة تتبع تغيرات الكائنات، وقد عيّناه إلى القيمة false لإلغاء التتبع وبالتالي تحقيق استخدام أقل لموارد الذاكرة.
	</li>
</ul>

<p>
	<strong>ملاحظة:</strong> من الممكن استخدام أي محرّك قواعد بيانات آخر مثل، PostgreSQL، أو MySQL، ويجب في هذه الحالة استخدام معرّف الموارد الموحد URI المناسب. ففي حال استخدام PostgreSQL سيتبع الصيغة:
</p>

<pre class="ipsCode" id="ips_uid_444_11">postgresql://username:password@host:port/database_name</pre>

<p>
	أمّا في حال استخدام MySQL:
</p>

<pre class="ipsCode" id="ips_uid_444_13">mysql://username:password@host:port/database_name</pre>

<p>
	وبعد الانتهاء من ضبط إعدادات الأداة SQLAIchemy من خلال تعيين معرّف الموارد الموحّد لقاعدة البيانات وإلغاء تمكين ميزة تتبّع التغييرات، سننشئ كائن قاعدة بيانات باستخدام الصنف <code>SQLAIchemy</code>، ممررين إليه نسخة التطبيق بغية ربط تطبيق فلاسك مع الأداة SQLAIchemy، ونخزّن كائن قاعدة البيانات هذا ضمن متغير باسم <code>db</code> لنستخدمه في التخاطب مع قاعدة البيانات.
</p>

<h3>
	التصريح عن الجداول
</h3>

<p>
	الآن وبعد تأسيس الاتصال مع قاعدة البيانات وإنشاء كائن قاعدة البيانات، سنستخدم هذا الكائن لإنشاء جدول للتدوينات وآخر للتعليقات، حيث تُمثّل الجداول بنماذج models، والنموذج هو صنف بايثون يرث من صنف رئيسي توفرّه الأداة Flask-SQLAlchemy من خلال نسخة قاعدة البيانات db التي أنشأناها مُسبقًا. لذا، وبغية تعريف جدولي التدوينات والتعليقات مثل نماذج، سنضيف الصنفين التاليين إلى الملف <code>app.py</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_20" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
    id </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Integer</span><span class="pun">,</span><span class="pln"> primary_key</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
    title </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">String</span><span class="pun">(</span><span class="lit">100</span><span class="pun">))</span><span class="pln">
    content </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Text</span><span class="pun">)</span><span class="pln">
    comments </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="pln">relationship</span><span class="pun">(</span><span class="str">'Comment'</span><span class="pun">,</span><span class="pln"> backref</span><span class="pun">=</span><span class="str">'post'</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> __repr__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> f</span><span class="str">'&lt;Post "{self.title}"&gt;'</span><span class="pln">


</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Comment</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
    id </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Integer</span><span class="pun">,</span><span class="pln"> primary_key</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
    content </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Text</span><span class="pun">)</span><span class="pln">
    post_id </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Integer</span><span class="pun">,</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">ForeignKey</span><span class="pun">(</span><span class="str">'post.id'</span><span class="pun">))</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> __repr__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> f</span><span class="str">'&lt;Comment "{self.content[:20]}..."&gt;'</span></pre>

<p>
	أنشأنا في الشيفرة السابقة كل من النموذج <code>Post</code> والنموذج <code>Comment</code> مُستخدمين الصنف <code>db.Column</code> لتعريف أعمدة الجداول، إذ يمثّل الوسيط الأوّل نوع العمود، أمّا باقي الوسطاء فتمثّل إعدادات العمود.
</p>

<p>
	وقد عرفنا الأعمدة التالية للنموذج <code>Post</code>:
</p>

<ul>
	<li>
		<code>id</code>: وهو معرّف التدوينة، ويحتوي على بياناتٍ من نوع رقم صحيح وذلك باستخدام <code>db.Integer</code> و <code>primary_key=True</code> التي تعرّفه مفتاحًا أساسيًا، وتُخصّص قيمةٌ فريدة في قاعدة البيانات من أجل كل سجل (والسجل هو التدوينة في حالتنا).
	</li>
	<li>
		<code>title</code>: عنوان التدوينة، وهو سلسلة نصية بعدد محارف أعظمي يساوي 100.
	</li>
	<li>
		<code>content</code>: محتوى التدوينة، وتشير <code>db.Text</code> إلى أنّ هذا العمود يضم نصوصًا طويلة.
	</li>
</ul>

<p>
	وتُعرِّف سمة الصنف المسماة <code>comments</code> علاقةً من النوع واحد-إلى-متعدد ما بين النموذج <code>Post</code> والنموذج <code>Comment</code>، إذ استخدمنا التابع <code>()db.relationship</code> ممررين إليه اسم نموذج التعليقات (المُسمّى <code>Comment</code> في حالتنا)، كما استخدمنا المعامل <code>backref</code> لإضافة مرجعٍ للعودة back reference، والذي يتمثّل بأحد أعمدة النموذج <code>Comment</code>، بحيث يمكننا الوصول إلى المنشور الذي يتبع له تعليق معيّن باستخدام السمة <code>post</code>. على سبيل المثال، إذا كان لدينا كائن تعليق مُخزّن ضمن متغير باسم <code>comment</code>، فمن الممكن الوصول إلى المنشور الذي وُضع هذا التعليق عليه باستخدام التعليمة <code>comment.post</code>، وسنبين فيما بعد مثال يوضح هذه الفكرة.
</p>

<p>
	تمكننا الدالة الخاصة <a href="https://docs.python.org/3/reference/datamodel.html#object.__repr__" rel="external nofollow"><code>__repr__</code></a> من تمثيل كل كائن بصيغة سلسلة نصية، الأمر الذي يساعد في التعرّف على هذا الكائن لأغراض التنقيح، إذ استخدمنا في حالتنا عنوان التدوينة لهذا الغرض.
</p>

<p>
	أمّا النموذج <code>Comment</code> فيمثّل جدول التعليقات، وقد عرفنا الأعمدة التالية له:
</p>

<ul>
	<li>
		<code>id</code>: وهو معرّف التعليق، ويحتوي على بياناتٍ من نوع رقم صحيح وذلك باستخدام <code>db.Integer</code> و <code>primary_key=True</code> التي تعرّفه مفتاحًا أساسيًا، وتُخصّص قيمةٌ فريدة في قاعدة البيانات من أجل كل سجل (والسجل هو التعليق في هذه الحالة).
	</li>
	<li>
		<code>content</code>: محتوى التعليق، وتشير <code>db.Text</code> إلى أنّ هذا العمود يضم نصوصًا طويلة.
	</li>
	<li>
		<code>post_id</code>: وهو عدد صحيح يمثّل مفتاحًا خارجيًا foreign مُنشأ باستخدام الصنف <code>()db.ForeignKey</code>؛ وهو مفتاح يربط جدولًا بآخر من خلال مفتاحه الأساسي، وفي حالتنا سيربط التعليق بالتدوينة التي يتبع لها باستخدام المفتاح الأساسي للتدوينة المتمثّل بمعرّفها، وبذلك يكون جدول التدوينات في هذه الحالة هو الجدول الأب، ما يعني أنّ كل تدوينة سترتبط بعدّة تعليقات، إذ يمثّل جدول التعليقات الجدول الابن، ويرتبط كل تعليق بتدوينة من الجدول الأب باستخدام معرّفها، وبالتالي سيكون لكل تعليق قيمة في العمود <code>post_id</code> والتي سنستخدمها في الوصول إلى التدوينة التي أُضيف التعليق عليها.
	</li>
</ul>

<p>
	تُظهر الدالة الخاصة <code>__repr__</code> في نموذج التعليقات أوّل 20 محرفًا من محتوى التعليق ما يمنح كائن التعليق تمثيلًا محرفيًا قصيرًا.
</p>

<p>
	وبذلك سيبدو الملف <code>app.py</code> حاليًا بالشّكل التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_22" style=""><span class="kwd">import</span><span class="pln"> os
</span><span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> render_template</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">,</span><span class="pln"> redirect</span><span class="pun">,</span><span class="pln"> url_for
</span><span class="kwd">from</span><span class="pln"> flask_sqlalchemy </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">SQLAlchemy</span><span class="pln">

basedir </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">abspath</span><span class="pun">(</span><span class="pln">os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">dirname</span><span class="pun">(</span><span class="pln">__file__</span><span class="pun">))</span><span class="pln">

app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">config</span><span class="pun">[</span><span class="str">'SQLALCHEMY_DATABASE_URI'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln">\
           </span><span class="str">'sqlite:///'</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="pln">basedir</span><span class="pun">,</span><span class="pln"> </span><span class="str">'database.db'</span><span class="pun">)</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">config</span><span class="pun">[</span><span class="str">'SQLALCHEMY_TRACK_MODIFICATIONS'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">False</span><span class="pln">


db </span><span class="pun">=</span><span class="pln"> </span><span class="typ">SQLAlchemy</span><span class="pun">(</span><span class="pln">app</span><span class="pun">)</span><span class="pln">


</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
    id </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Integer</span><span class="pun">,</span><span class="pln"> primary_key</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
    title </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">String</span><span class="pun">(</span><span class="lit">100</span><span class="pun">))</span><span class="pln">
    content </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Text</span><span class="pun">)</span><span class="pln">
    comments </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="pln">relationship</span><span class="pun">(</span><span class="str">'Comment'</span><span class="pun">,</span><span class="pln"> backref</span><span class="pun">=</span><span class="str">'post'</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> __repr__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> f</span><span class="str">'&lt;Post "{self.title}"&gt;'</span><span class="pln">


</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Comment</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
    id </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Integer</span><span class="pun">,</span><span class="pln"> primary_key</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
    content </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Text</span><span class="pun">)</span><span class="pln">
    post_id </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Integer</span><span class="pun">,</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">ForeignKey</span><span class="pun">(</span><span class="str">'post.id'</span><span class="pun">))</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> __repr__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> f</span><span class="str">'&lt;Comment "{self.content[:20]}..."&gt;'</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<h3>
	إنشاء قاعدة بيانات
</h3>

<p>
	الآن، وبعد الانتهاء من إعداد كل من الاتصال مع قاعدة البيانات ونماذج التدوينة والتعليق، سنستخدم صدفة فلاسك لإنشاء قاعدة البيانات وجداول التدوينات والتعليقات اعتمادًا على النماذج المُعرّفة مُسبقًا.
</p>

<p>
	بعد التأكّد من كون البيئة الافتراضية مُفعّلة، نستخدم متغير البيئة <code>FLASK_APP</code> لتحديد الملف <code>app.py</code> على أنه تطبيق فلاسك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_24" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_APP</span><span class="pun">=</span><span class="pln">app</span></pre>

<p>
	ومن ثمّ نفتح صَدفة فلاسك باستخدام الأمر التالي وذلك أثناء وجودنا ضمن المجلد <code>flask_app</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_26" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ flask shell</span></pre>

<p>
	فتُفتح صَدفة بايثون تفاعلية خاصّة، تعمل على تشغيل الأوامر ضمن تطبيق فلاسك، وبذلك نضمن اتصال دوال Flask-SQLAlchemy التي سنستدعيها مع تطبيق فلاسك.
</p>

<p>
	سنستورد الآن كائن قاعدة البيانات ونماذج التدوينات والتعليقات، ثمّ سنشغّل الدالة <code>()db.create_all</code> بغية إنشاء الجداول الموافقة لتلك النماذج، كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_28" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">from</span><span class="pln"> app </span><span class="kwd">import</span><span class="pln"> db</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Comment</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> db</span><span class="pun">.</span><span class="pln">create_all</span><span class="pun">()</span></pre>

<p>
	نبقي الصدفة في حالة تشغيل، ونفتح نافذة طرفية جديدة وننتقل إلى المجلد <code>flask_app</code>، فنجد ملفًا جديدًا ضمنه باسم <code>database.db</code>.
</p>

<p>
	<strong>ملاحظة:</strong>لا تنشئ الدالة <code>()db.create_all</code> أو تحدّث جدولًا موجودًا أصلًا، فمثلًا لو عدّلنا أحد النماذج التي أنشأناها بإضافة عمود جديد إليه، ثمّ شغلنا الدالة <code>()db.create_all</code> فلن يُطبّق التغيير الذي أجريته على النموذج على الجدول إذا كان الجدول موجود أصلًا في قاعدة البيانات، ويكون الحل لهذه المشكلة بحذف كافّة الجداول الموجودة في قاعدة البيانات باستخدام الدالة <code>()db.drop_all</code>، ثمّ إعادة إنشائها باستخدام الدالة <code>()db.create_all</code>، على النحو التالي:
</p>

<pre class="ipsCode" id="ips_uid_444_17">&gt;&gt;&gt; db.drop_all()
&gt;&gt;&gt; db.create_all()</pre>

<p>
	ستعمل هذه الشيفرة على تطبيق التعديلات على النموذج، كما ستحذف كافّة البيانات الموجودة في قاعدة البيانات، فلو أردت تحديث قاعدة البيانات مع الحفاظ على البيانات الموجودة فيها، لا بدّ من استخدام <a href="https://en.wikipedia.org/wiki/Schema_migration" rel="external nofollow">أداة ترحيل ملف تخطيط قاعدة البيانات schema migration</a>، لنتمكن من تعديل الجداول مع الحفاظ على البيانات، ولاستخدام هذه الأداة من الممكن الاستعانة بالإضافة <a href="https://flask-migrate.readthedocs.io/en/latest/index.html" rel="external nofollow"><code>Flask-Migrate</code></a> لتنفيذ ترحيل ملف تخطيط قاعدة البيانات باستخدام الأداة SQLAIchemy من خلال واجهة أسطر الأوامر في فلاسك.
</p>

<p>
	وفي حال ظهور رسالة خطأ، يجب التأكّد من كون التصريح صحيح عن كل من النماذج ومعرّف الموارد الموحّد لقاعدة البيانات.
</p>

<h3>
	ملء الجدول
</h3>

<p>
	الآن وبعد إنشاء قاعدة البيانات وجداول التدوينات والتعليقات، سننشئ ملفًا ضمن المجلد <code>flask_app</code> لإضافة بعض التدوينات والتعليقات إلى قاعدة البيانات.
</p>

<p>
	لذا، سننشئ ملفًا جديدًا باسم <code>init_db.py</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_32" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano init_db</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ونكتب ضمنه الشيفرات التالية، إذ سينشئ هذا الملف ثلاثة كائنات للتدوينات وأربعة كائنات للمنشورات، مُضيفًا كل منها إلى قاعدة البيانات:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_34" style=""><span class="kwd">from</span><span class="pln"> app </span><span class="kwd">import</span><span class="pln"> db</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Comment</span><span class="pln">

post1 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">(</span><span class="pln">title</span><span class="pun">=</span><span class="str">'Post The First'</span><span class="pun">,</span><span class="pln"> content</span><span class="pun">=</span><span class="str">'Content for the first post'</span><span class="pun">)</span><span class="pln">
post2 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">(</span><span class="pln">title</span><span class="pun">=</span><span class="str">'Post The Second'</span><span class="pun">,</span><span class="pln"> content</span><span class="pun">=</span><span class="str">'Content for the Second post'</span><span class="pun">)</span><span class="pln">
post3 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">(</span><span class="pln">title</span><span class="pun">=</span><span class="str">'Post The Third'</span><span class="pun">,</span><span class="pln"> content</span><span class="pun">=</span><span class="str">'Content for the third post'</span><span class="pun">)</span><span class="pln">

comment1 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Comment</span><span class="pun">(</span><span class="pln">content</span><span class="pun">=</span><span class="str">'Comment for the first post'</span><span class="pun">,</span><span class="pln"> post</span><span class="pun">=</span><span class="pln">post1</span><span class="pun">)</span><span class="pln">
comment2 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Comment</span><span class="pun">(</span><span class="pln">content</span><span class="pun">=</span><span class="str">'Comment for the second post'</span><span class="pun">,</span><span class="pln"> post</span><span class="pun">=</span><span class="pln">post2</span><span class="pun">)</span><span class="pln">
comment3 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Comment</span><span class="pun">(</span><span class="pln">content</span><span class="pun">=</span><span class="str">'Another comment for the second post'</span><span class="pun">,</span><span class="pln"> post_id</span><span class="pun">=</span><span class="lit">2</span><span class="pun">)</span><span class="pln">
comment4 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Comment</span><span class="pun">(</span><span class="pln">content</span><span class="pun">=</span><span class="str">'Another comment for the first post'</span><span class="pun">,</span><span class="pln"> post_id</span><span class="pun">=</span><span class="lit">1</span><span class="pun">)</span><span class="pln">


db</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">add_all</span><span class="pun">([</span><span class="pln">post1</span><span class="pun">,</span><span class="pln"> post2</span><span class="pun">,</span><span class="pln"> post3</span><span class="pun">])</span><span class="pln">
db</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">add_all</span><span class="pun">([</span><span class="pln">comment1</span><span class="pun">,</span><span class="pln"> comment2</span><span class="pun">,</span><span class="pln"> comment3</span><span class="pun">,</span><span class="pln"> comment4</span><span class="pun">])</span><span class="pln">

db</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	استوردنا في الشيفرة السابقة كلًا من كائن قاعدة البيانات والنموذج <code>Post</code> والنموذج <code>Comment</code> من الملف <code>app.py</code>، كما أنشأنا بضعة كائنات تدوينات باستخدام النموذج <code>Post</code>، ممررين لذلك عنوان التدوينة إلى المعامل <code>title</code> ومحتواها إلى المعامل <code>content</code>، ثمّ أنشأنا بعد ذلك بضع كائنات تعليقات، ممررين محتوى التعليق؛ وهنا من الممكن استخدام أحدى طريقتين لربط التعليق بالتدوينة التي يتبع لها، الأولى بتمرير كائن التدوينة إلى المعامل <code>post</code> كما هو مبيّن في كلا الكائنين <code>comment1</code> و <code>comment2</code>، أو من الممكن تمرير معرّف التدوينة إلى المعامل <code>post_id</code> كما هو مبيّن في الكائنين <code>comment3</code> و <code>comment4</code>، وبالتالي من الممكن تمرير العدد الصحيح المُمثّل لمعرّف التدوينة وحده في حال عدم وجود كائن التدوينة في الشيفرة.
</p>

<p>
	بعد الانتهاء من تعريف كل من كائني التدوينة والتعليق، استخدمنا التابع <code>()db.session.add_all</code> لإضافة كافة كائنات التدوينات والتعليقات إلى جلسة قاعدة البيانات التي تدير العمليات، ثمّ استخدمنا التابع <code>()db.session.commit</code> لتأكيد وتطبيق التغييرات على قاعدة البيانات. للمزيد حول جلسات قواعد البيانات في SQLAIchemy، ننصحك بقراءة الخطوة الثانية من المقال <a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-flask-sqlalchemy-%D9%84%D9%84%D8%AA%D8%AE%D8%A7%D8%B7%D8%A8-%D9%85%D8%B9-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-r1842/" rel="">كيفية استخدام الإضافة Flask-SQLAlchemy للتخاطب مع قواعد البيانات في تطبيقات فلاسك</a>.
</p>

<p>
	الآن، سنشغل الملف <code>init_db.py</code> لتنفيذ الشيفرة وإضافة البيانات إلى قاعدة البيانات:
</p>

<pre class="ipsCode" id="ips_uid_444_19">(env)user@localhost:$ python init_db.py</pre>

<p>
	وللاطلاع على البيانات المُضافة إلى قاعدة البيانات، نفتح صَدفة فلاسك بغية الاستعلام عن كافّة التدوينات وعرض عناوينها ومحتويات التعليقات على كل منها، على النحو التالي:
</p>

<pre class="ipsCode" id="ips_uid_444_21">(env)user@localhost:$ flask shell</pre>

<p>
	ثمّ نشغّل الشيفرة التالية المسؤولة عن الاستعلام عن كافّة التدوينات وعرض عنوان كل تدوينة والتعليقات عليها أسفلها:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_40" style=""><span class="kwd">from</span><span class="pln"> app </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Post</span><span class="pln">

posts </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">all</span><span class="pun">()</span><span class="pln">

</span><span class="kwd">for</span><span class="pln"> post </span><span class="kwd">in</span><span class="pln"> posts</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">f</span><span class="str">'## {post.title}'</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> comment </span><span class="kwd">in</span><span class="pln"> post</span><span class="pun">.</span><span class="pln">comments</span><span class="pun">:</span><span class="pln">
            </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">f</span><span class="str">'&gt; {comment.content}'</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'----'</span><span class="pun">)</span></pre>

<p>
	استوردنا في الشيفرة السابقة النموذج <code>Post</code> من الملف <code>app.py</code>، ثمّ استعلمنا عن كافّة التدوينات الموجودة في قاعدة البيانات باستخدام السمة <code>query</code> للتابع <code>()all</code>، وخزّنا نتائج الاستعلام ضمن متغير باسم <code>posts</code>، ثمّ استخدمنا حلقة <code>for</code> تكرارية بغية المرور على كل عنصر من المتغير <code>posts</code>، لتُعرض العناوين، ثمّ استخدمنا بعد ذلك حلقة <code>for</code> تكرارية ثانية للمرور على كافّة تعليقات كل تدوينة، إذ استخدمنا التعليمة <code>post.comments</code> للوصول إلى التعليقات، ليُعرض بعدها محتوى التعليقات ويُطبَع فاصل بالشكل <code>'----'</code> بغية الفصل بين التدوينات في الخرج.
</p>

<p>
	فنحصل على الخرج التالي:
</p>

<pre class="ipsCode" id="ips_uid_444_23">## Post The First
&gt; Comment for the first post
&gt; Another comment for the first post
----
## Post The Second
&gt; Comment for the second post
&gt; Another comment for the second post
----
## Post The Third
----</pre>

<p>
	فكما هو واضح، من الممكن الوصول إلى بيانات كل تدوينة وتعليقاتها باستخدام شيفرة قصيرة جدًا.
</p>

<p>
	والآن نخرج من الصدفة:
</p>

<pre class="ipsCode" id="ips_uid_444_25">&gt;&gt;&gt; exit()</pre>

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

<h2>
	الخطوة 3: عرض جميع السجلات
</h2>

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

<p>
	نفتح الملف <code>app.py</code> لنضيف إليه وجهةً للصفحة الرئيسية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_46" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ونضيف الوجهة التالية إلى نهاية الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_48" style=""><span class="com"># ...</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> index</span><span class="pun">():</span><span class="pln">
    posts </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'index.html'</span><span class="pun">,</span><span class="pln"> posts</span><span class="pun">=</span><span class="pln">posts</span><span class="pun">)</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	استخدمنا في الشيفرة السابقة المُزخرف <code>()app.route</code> لإنشاء دالة عرض باسم <code>()index</code>، التي نستعلم فيها ضمن قاعدة البيانات لجلب كافّة التدوينات كما فعلنا في الخطوة السابقة، لنخزّن نتائج هذا الاستعلام ضمن متغير باسم <code>posts</code> ممررين إياه إلى ملف قالب باسم <code>index.html</code>، الذي ستصيّره باستخدام الدالة المساعدة <code>()render_template</code>.
</p>

<p>
	الآن وقبل إنشاء ملف القالب <code>index.html</code> المسؤول عن عرض التدوينات الموجودة ضمن قاعدة البيانات في الصفحة الرئيسية للتطبيق، سننشئ بدايةً ملف قالب رئيسي ليتضمّن كافة شيفرات HTML الأساسية اللازمة لترثها لاحقًا القوالب الأُخرى، وهذا ما يجنبنا تكرار الشيفرات؛ ثمّ سننشئ ملف قالب الصفحة الرئيسية <code>index.html</code> المُصيّر أصلًا باستخدام الدالة <code>()index</code>.
</p>

<p>
	لذلك، سننشئ مجلدًا للقوالب باسم <code>templates</code>، وننشئ ضمنه ملف قالب باسم <code>base.html</code>، والذي سيمثّل القالب الأساسي لبقية القوالب، كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_50" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ mkdir templates
</span><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">base</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	وسنكتب فيه الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_6370_52" style=""><span class="dec">&lt;!DOCTYPE html&gt;</span><span class="pln">
</span><span class="tag">&lt;html</span><span class="pln"> </span><span class="atn">lang</span><span class="pun">=</span><span class="atv">"en"</span><span class="tag">&gt;</span><span class="pln">
</span><span class="tag">&lt;head&gt;</span><span class="pln">
    </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">"UTF-8"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;title&gt;</span><span class="pln">{% block title %} {% endblock %} - FlaskApp</span><span class="tag">&lt;/title&gt;</span><span class="pln">
    </span><span class="tag">&lt;style&gt;</span><span class="pln">
        </span><span class="pun">.</span><span class="pln">title </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">margin</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5px</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        </span><span class="pun">.</span><span class="pln">content </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">margin</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">width</span><span class="pun">:</span><span class="pln"> </span><span class="lit">100%</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">display</span><span class="pun">:</span><span class="pln"> flex</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">flex-direction</span><span class="pun">:</span><span class="pln"> row</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">flex-wrap</span><span class="pun">:</span><span class="pln"> wrap</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        </span><span class="pun">.</span><span class="pln">comment </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">padding</span><span class="pun">:</span><span class="pln"> </span><span class="lit">10px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">margin</span><span class="pun">:</span><span class="pln"> </span><span class="lit">10px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">background-color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#fff</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        </span><span class="pun">.</span><span class="pln">post </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">flex</span><span class="pun">:</span><span class="pln"> </span><span class="lit">20%</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">padding</span><span class="pun">:</span><span class="pln"> </span><span class="lit">10px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">margin</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">background-color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#f3f3f3</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">inline-size</span><span class="pun">:</span><span class="pln"> </span><span class="lit">100%</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        </span><span class="pun">.</span><span class="pln">title a </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#00a36f</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">text-decoration</span><span class="pun">:</span><span class="pln"> none</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        nav a </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#d64161</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">font-size</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3em</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">margin-left</span><span class="pun">:</span><span class="pln"> </span><span class="lit">50px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">text-decoration</span><span class="pun">:</span><span class="pln"> none</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

    </span><span class="tag">&lt;/style&gt;</span><span class="pln">
</span><span class="tag">&lt;/head&gt;</span><span class="pln">
</span><span class="tag">&lt;body&gt;</span><span class="pln">
    </span><span class="tag">&lt;nav&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('index') }}"</span><span class="tag">&gt;</span><span class="pln">FlaskApp</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"#"</span><span class="tag">&gt;</span><span class="pln">Comments</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"#"</span><span class="tag">&gt;</span><span class="pln">About</span><span class="tag">&lt;/a&gt;</span><span class="pln">
    </span><span class="tag">&lt;/nav&gt;</span><span class="pln">
    </span><span class="tag">&lt;hr&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"content"</span><span class="tag">&gt;</span><span class="pln">
        {% block content %} {% endblock %}
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
</span><span class="tag">&lt;/body&gt;</span><span class="pln">
</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	يتضمّن القالب الأساسي كافّة الشيفرات المتداولة التي سنحتاجها في القوالب الأُخرى.
</p>

<p>
	ستُستبدل لاحقًا كتلة العنوان <code>title</code> بعنوان كل صفحة وكتلة المحتوى <code>content</code> بمحتواها، أمّا عن شريط التصفح فسيتضمّن ثلاثة روابط؛ إذ ينقل الأوّل المُستخدم إلى الصفحة الرئيسية للتطبيق باستخدام الدالة المساعدة <code>()url_for</code>؛ والثاني إلى صفحة التعليقات <strong>Comments</strong>، أمّا الأخير لصفحة المعلومات حول التطبيق <strong>About</strong> في حال قررت تضمينها في تطبيقك، مع ملاحظة أنّنا سنعدّل هذا الملف لاحقًا بعد إضافة صفحة لعرض التعليقات الأحدث بغية جعل الرابط إلى هذه الصفحة فعّالًا.
</p>

<p>
	الآن، سننشئ ملف قالب باسم <code>index.html</code> وهو الاسم الذي حددناه في الملف <code>app.py</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_54" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">index</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	ونضيف ضمنه الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_56" style=""><span class="pun">{%</span><span class="pln"> extends </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">

</span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">span </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"title"</span><span class="pun">&gt;&lt;</span><span class="pln">h1</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="typ">Posts</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}&lt;/</span><span class="pln">h1</span><span class="pun">&gt;&lt;/</span><span class="pln">span</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"content"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> post </span><span class="kwd">in</span><span class="pln"> posts </span><span class="pun">%}</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"post"</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;&lt;</span><span class="pln">b</span><span class="pun">&gt;#{{</span><span class="pln"> post</span><span class="pun">.</span><span class="pln">id </span><span class="pun">}}&lt;/</span><span class="pln">b</span><span class="pun">&gt;&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">b</span><span class="pun">&gt;</span><span class="pln">
                    </span><span class="pun">&lt;</span><span class="pln">p </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"title"</span><span class="pun">&gt;</span><span class="pln">
                        </span><span class="pun">&lt;</span><span class="pln">a href</span><span class="pun">=</span><span class="str">"#"</span><span class="pun">&gt;</span><span class="pln">
                            </span><span class="pun">{{</span><span class="pln"> post</span><span class="pun">.</span><span class="pln">title </span><span class="pun">}}</span><span class="pln">
                        </span><span class="pun">&lt;/</span><span class="pln">a</span><span class="pun">&gt;</span><span class="pln">
                    </span><span class="pun">&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;/</span><span class="pln">b</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"content"</span><span class="pun">&gt;</span><span class="pln">
                    </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;{{</span><span class="pln"> post</span><span class="pun">.</span><span class="pln">content </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">hr</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">{%</span><span class="pln"> endfor </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	اعتمدنا في الشيفرة السابقة على الوراثة من ملف القالب <code>base.html</code> باستخدام تعليمة <code>extends</code>، واستبدلنا محتوى كتلة المحتوى <code>content</code> مُستخدمين تنسيق العنوان من المستوى الأوّل <code><a href="https://wiki.hsoub.com/HTML/h1-h6" rel="external">&lt;h1&gt;</a></code> الذي يفي أيضًا بالغرض لعنوان الصفحة.
</p>

<p>
	استخدمنا في السطر البرمجي <code>{% for post in posts %}</code> حلقة <code>for</code> من تعليمات محرّك القوالب <a href="https://academy.hsoub.com/tags/jinja2" rel="">جينجا jinja</a>، والهدف من استخدام هذه الحلقة هو المرور على كل تدوينة ضمن المتغير <code>posts</code> المُمرّر من الدالة <code>()index</code> إلى هذا القالب، حيث سنعرض معرّف التدوينة وعنوانها ومحتواها، إذ سيعمل لاحقًا عنوان التدوينة مثل رابط إلى صفحة مُستقلّة تعرض هذه التدوينة مع تعليقاتها.
</p>

<p>
	الآن ومع وجودنا ضمن المجلد <code>flask_app</code> ومع كون البيئة الافتراضية مُفعّلة، سنُعلم فلاسك بالتطبيق المراد تشغيله (وهو في حالتنا الملف <code>app.py</code>) باستخدام متغير البيئة <code>FLASK_APP</code>، في حين يحدّد متغير البيئة <code>FLASK_ENV</code> وضع التشغيل وهنا قد اخترنا الوضع <code>development</code> ما يعني أنّ التطبيق سيعمل في وضع التطوير مع تشغيل مُنقّح الأخطاء. لمزيدٍ من المعلومات حول مُنقّح الأخطاء في فلاسك ننصحك بقراءة المقال <a href="https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A3%D8%AE%D8%B7%D8%A7%D8%A1-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1738/" rel="">كيفية التعامل مع الأخطاء في تطبيقات فلاسك</a>، ولتنفيذ ما سبق سنشغّل الأوامر التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_58" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_APP</span><span class="pun">=</span><span class="pln">app
</span><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_ENV</span><span class="pun">=</span><span class="pln">development</span></pre>

<p>
	والآن سنشغّل التطبيق باستخدام الأمر <code>flask run</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_60" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ flask run</span></pre>

<p>
	وبعد التأكد من تشغيل خادم التطوير، نذهب إلى الرابط التالي باستخدام المتصفح:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_63" style=""><span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span></pre>

<p>
	فتظهر لنا التدوينات المُضافة إلى قاعدة البيانات في صفحة مشابهة للصورة التالية:
</p>

<p style="text-align: center;">
	<img alt="ظهور التدوينات المُضافة إلى قاعدة البيانات بفلاسك" class="ipsImage ipsImage_thumbnailed" data-fileid="115940" data-ratio="35.33" data-unique="ib6egunhb" style="width: 750px; height: auto;" width="750" src="https://academy.hsoub.com/uploads/monthly_2023_01/1st.thumb.png.f997b43cc66dcdf8af7d4f911ed3391a.png">
</p>

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

<h2>
	الخطوة 4: عرض تدوينة مفردة مع التعليقات عليها
</h2>

<p>
	سننشئ في هذه الخطوة وجهة وقالب لعرض التفاصيل الخاصة بكل تدوينة في صفحة مُخصّصة لذلك، مع عرض كافّة تعليقات كل منها أدناها.
</p>

<p>
	في نهاية هذه الخطوة سيعرض الرابط http://127.0.0.1:5000/1 صفحةً تعرض التدوينة الأولى مع تعليقاتها (لأن الرابط يحوي على المعرّف رقم 1)، بينما يعرض الرابط http://127.0.0.1:5000/ID المنشور ذو المعرّف ID الموافق له إن وُجد.
</p>

<p>
	نبقي خادم التطوير قيد التشغيل، ونفتح نافذة طرفية جديدة.
</p>

<p>
	نفتح الملف <code>app.py</code> لتعديله كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_66" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ونضيف الوجهة التالية إلى نهاية الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_68" style=""><span class="com"># ...</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/&lt;int:post_id&gt;/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> post</span><span class="pun">(</span><span class="pln">post_id</span><span class="pun">):</span><span class="pln">
    post </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">get_or_404</span><span class="pun">(</span><span class="pln">post_id</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'post.html'</span><span class="pun">,</span><span class="pln"> post</span><span class="pun">=</span><span class="pln">post</span><span class="pun">)</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	استخدمنا في الشيفرة السابقة الوجهة <code>/&lt;int:post_id&gt;/</code> إذ يمثّل المعامل <code>:int</code> محوّلًا لتحويل السلسلة النصية الافتراضية في الرابط إلى نوع عدد صحيح، ويمثل <code>post_id</code> متغير الرابط الذي سيُحدّد التدوينة المطلوب عرضها في الصفحة.
</p>

<p>
	مررنا المعرّف من الرابط إلى دالة العرض <code>()post</code> من خلال المعامل <code>post_id</code>، والتي نستعلم فيها عن جدول التدوينات لنجلب منه التدوينة من خلال معرّفها باستخدام التابع <code>()get_or_404</code>، الأمر الذي يؤدي إلى حفظ بيانات التدوينة (في حال وجودها) ضمن المتغير <code>post</code>، وإلّا ستُعرض استجابة خطأ HTTP من النوع ‎404 Not Found في حال عدم وجود تدوينة موافقة للمعرّف المطلوب في قاعدة البيانات.
</p>

<p>
	ومن ثمّ صيّرنا قالبًا باسم <code>post.html</code> ممرّرين إليه التدوينة المُستخرجة.
</p>

<p>
	لذا، سنفتح ملف قالب جديد باسم <code>post.html</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_70" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">post</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	وسنكتب فيه الشيفرة التالية، والأمر مشابه للقالب <code>index.html</code> باستثناء أنّ هذا الملف سيعرض تدوينةً واحدةً كل مرة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_72" style=""><span class="pun">{%</span><span class="pln"> extends </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">

</span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">span </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"title"</span><span class="pun">&gt;&lt;</span><span class="pln">h1</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="pun">{{</span><span class="pln"> post</span><span class="pun">.</span><span class="pln">title </span><span class="pun">}}</span><span class="pln">  </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}&lt;/</span><span class="pln">h1</span><span class="pun">&gt;&lt;/</span><span class="pln">span</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"content"</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"post"</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;&lt;</span><span class="pln">b</span><span class="pun">&gt;#{{</span><span class="pln"> post</span><span class="pun">.</span><span class="pln">id </span><span class="pun">}}&lt;/</span><span class="pln">b</span><span class="pun">&gt;&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">b</span><span class="pun">&gt;</span><span class="pln">
                    </span><span class="pun">&lt;</span><span class="pln">p </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"title"</span><span class="pun">&gt;{{</span><span class="pln"> post</span><span class="pun">.</span><span class="pln">title </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;/</span><span class="pln">b</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"content"</span><span class="pun">&gt;</span><span class="pln">
                    </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;{{</span><span class="pln"> post</span><span class="pun">.</span><span class="pln">content </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">hr</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">h3</span><span class="pun">&gt;</span><span class="typ">Comments</span><span class="pun">&lt;/</span><span class="pln">h3</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> comment </span><span class="kwd">in</span><span class="pln"> post</span><span class="pun">.</span><span class="pln">comments </span><span class="pun">%}</span><span class="pln">
                    </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"comment"</span><span class="pun">&gt;</span><span class="pln">
                        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;#{{</span><span class="pln"> comment</span><span class="pun">.</span><span class="pln">id </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;{{</span><span class="pln"> comment</span><span class="pun">.</span><span class="pln">content </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                    </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">{%</span><span class="pln"> endfor </span><span class="pun">%}</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	اعتمدنا في هذه الشيفرة على الوراثة من القالب <code>base.html</code> باستخدام تعليمة <code>extends</code>، وجعلنا عنوان التدوينة عنوانًا للصفحة، كما عرضنا كلًا من معرّف التدوينة ومحتواها، ومن ثمّ مررنا على كافّة التعليقات المتوفرّة على هذه التدوينة باستخدام سمة الكائن <code>post.comments</code>، لنعرض معرّف التعليق ومحتواه.
</p>

<p>
	ننتقل باستخدام متصفح الويب إلى رابط التدوينة الثانية كما يلي:
</p>

<pre class="ipsCode" id="ips_uid_444_27">http://127.0.0.1:5000/2/</pre>

<p>
	فتظهر صفحة مشابهة لما يلي:
</p>

<p style="text-align: center;">
	<img alt="الانتقال إلى رابط التدوينة الثانية بمتصفح الويب" class="ipsImage ipsImage_thumbnailed" data-fileid="115941" data-ratio="48.53" data-unique="xar2p73v5" style="width: 750px; height: auto;" width="750" src="https://academy.hsoub.com/uploads/monthly_2023_01/2nd.thumb.png.4c38379dc15c8a9c016f0032a7527436.png">
</p>

<p>
	ثمّ سنحرّر ملف القالب <code>index.html</code> لنربط عنوان كل تدوينة مع الصفحة الخاصة بها:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_79" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">index</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	نعدّل قيمة سمة الرابط <code>href</code> لعنوان التدوينة ضمن حلقة <code>for</code> التكرارية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_81" style=""><span class="pun">...</span><span class="pln">

</span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> post </span><span class="kwd">in</span><span class="pln"> posts </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"post"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;&lt;</span><span class="pln">b</span><span class="pun">&gt;#{{</span><span class="pln"> post</span><span class="pun">.</span><span class="pln">id </span><span class="pun">}}&lt;/</span><span class="pln">b</span><span class="pun">&gt;&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">b</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">p </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"title"</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">a href</span><span class="pun">=</span><span class="str">"{{ url_for('post', post_id=post.id)}}"</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">{{</span><span class="pln"> post</span><span class="pun">.</span><span class="pln">title </span><span class="pun">}}</span><span class="pln">
                </span><span class="pun">&lt;/</span><span class="pln">a</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">b</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"content"</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;{{</span><span class="pln"> post</span><span class="pun">.</span><span class="pln">content </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">hr</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endfor </span><span class="pun">%}</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	ننتقل إلى الصفحة الرئيسية للتطبيق ونحدثّها:
</p>

<pre class="ipsCode" id="ips_uid_444_30">http://127.0.0.1:5000/</pre>

<p>
	وبالنقر على عنوان كل تدوينة في الصفحة الرئيسية للتطبيق نجد أنّه مرتبط بالصفحة الموافقة.
</p>

<p>
	الآن وبعد الانتهاء من إنشاء صفحة خاصّة بكل تدوينة، سنعمل في الخطوة التالية على إضافة نموذج ويب إلى صفحة التدوينة بغية السماح للمستخدمين بإضافة تعليقات جديدة.
</p>

<h2>
	الخطوة 5: إضافة تعليقات جديدة
</h2>

<p>
	سنعمل في هذه الخطوة على تحرير الوجهة <code>/&lt;int:post_id&gt;/</code> ودالة العرض <code>()post</code> الخاصة بها والمسؤولة عن عرض التدوينات المنفردة، إذ سنضيف نموذج ويب أسفل كل تدوينة من شأنه السماح للمستخدمين بإضافة تعليقات جديدة على التدوينة، ومن ثمّ سنجهّز الآليات اللازمة للتعامل مع إرسال التعليق وإضافته إلى قاعدة البيانات.
</p>

<p>
	لذا، سنفتح بدايةً ملف القالب <code>post.html</code> لإضافة نموذج ويب مكوّن من حقل نصي لمحتوى التعليق بالإضافة إلى زر أوامر <strong>Add Comment</strong> لتأكيد وإرسال التعليق:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_85" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">post</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	نعدّل الملف بإضافة نموذج بعد العنوان من المستوى الثالث <code>Comments</code> وقبل حلقة <code>for</code> التكرارية، كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_87" style=""><span class="pun">&lt;</span><span class="pln">hr</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">h3</span><span class="pun">&gt;</span><span class="typ">Comments</span><span class="pun">&lt;/</span><span class="pln">h3</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">form method</span><span class="pun">=</span><span class="str">"post"</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">textarea name</span><span class="pun">=</span><span class="str">"content"</span><span class="pln">
                    placeholder</span><span class="pun">=</span><span class="str">"Comment"</span><span class="pln">
                    cols</span><span class="pun">=</span><span class="str">"60"</span><span class="pln">
                    rows</span><span class="pun">=</span><span class="str">"5"</span><span class="pun">&gt;&lt;/</span><span class="pln">textarea</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">

    </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">button type</span><span class="pun">=</span><span class="str">"submit"</span><span class="pun">&gt;</span><span class="typ">Add</span><span class="pln"> comment</span><span class="pun">&lt;/</span><span class="pln">button</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">form</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> comment </span><span class="kwd">in</span><span class="pln"> post</span><span class="pun">.</span><span class="pln">comments </span><span class="pun">%}</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	استخدمنا في الشيفرة السابقة <a href="https://wiki.hsoub.com/HTML/form" rel="external">الوسم &lt;form&gt;</a> وضبطنا فيه السمة <code>method</code> التي تُحدّد نوع طلبية HTTP لتكون من النوع <code>POST</code>، ما يعني أنّ نموذج الويب هذا سيرسل طلبًا من النوع <code>POST</code>.
</p>

<p>
	وبذلك أصبح لدينا حقلًا نصيًا لمحتوى التعليق بالإضافة إلى زر تأكيد وإرسال له.
</p>

<p>
	الآن -وأثناء عمل خادم التطوير- نستخدم المتصفح للانتقال إلى تدوينة ما (ولتكن التدوينة الثانية مثلًا):
</p>

<pre class="ipsCode" id="ips_uid_444_32">http://127.0.0.1:5000/2/</pre>

<p>
	فستظهر صفحة مشابهة للصورة التالية:
</p>

<p style="text-align: center;">
	<img alt="استخدام المتصفح للانتقال إلى تدوينة ما عبر فلاسك" class="ipsImage ipsImage_thumbnailed" data-fileid="115942" data-ratio="45.73" data-unique="om2zdtbx5" style="width: 750px; height: auto;" width="750" src="https://academy.hsoub.com/uploads/monthly_2023_01/3rd.thumb.png.53b353cc1aa8f28b7b693f49ccb4297f.png">
</p>

<p>
	يرسل نموذج الإدخال هذا طلبًا من النوع <code>POST</code> إلى دالة العرض <code>()post</code>، ولكن حتى هذه اللحظة لا توجد شيفرة مسؤولة عن معالجة هذا الطلب، وبالتالي لن يحدث شيء في حال ملء النموذج الآن وإرساله بمعنى أنّ النموذج لا يعمل حتى اللحظة.
</p>

<p>
	الآن، سنضيف الشيفرة اللازمة لدالة العرض <code>()post</code> لمعالجة النماذج المُرسلة وإضافة التعليق الجديد إلى قاعدة البيانات. لذا، سنفتح الملف <code>app.py</code> بهدف تعديله ليتعامل مع الطلبات من النوع <code>post</code> المُرسلة من قبل المستخدم:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_92" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ونعدّل الوجهة <code>/&lt;int:post_id&gt;/</code> ودالة العرض <code>()post</code> الخاصة بها لتصبح كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_95" style=""><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/&lt;int:post_id&gt;/'</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=(</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">))</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> post</span><span class="pun">(</span><span class="pln">post_id</span><span class="pun">):</span><span class="pln">
    post </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">get_or_404</span><span class="pun">(</span><span class="pln">post_id</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">method </span><span class="pun">==</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">:</span><span class="pln">
        comment </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Comment</span><span class="pun">(</span><span class="pln">content</span><span class="pun">=</span><span class="pln">request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">],</span><span class="pln"> post</span><span class="pun">=</span><span class="pln">post</span><span class="pun">)</span><span class="pln">
        db</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="pln">comment</span><span class="pun">)</span><span class="pln">
        db</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> redirect</span><span class="pun">(</span><span class="pln">url_for</span><span class="pun">(</span><span class="str">'post'</span><span class="pun">,</span><span class="pln"> post_id</span><span class="pun">=</span><span class="pln">post</span><span class="pun">.</span><span class="pln">id</span><span class="pun">))</span><span class="pln">

    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'post.html'</span><span class="pun">,</span><span class="pln"> post</span><span class="pun">=</span><span class="pln">post</span><span class="pun">)</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	سمحنا في الشيفرة السابقة بكلا نوعي طلبيات HTTP وهما <code>GET</code> و <code>POST</code> من خلال المعامل <code>method</code>؛ إذ تتخصّص الطلبيات من النوع <code>GET</code> بجلب البيانات من الخادم، أمّا الطلبيات من النوع <code>POST</code> فهي مُتخصّصة بإرسال البيانات إلى وجهة مُحدّدة، مع ملاحظة أنّ الطلبيات من النوع <code>GET</code> هي الوحيدة المسموحة افتراضيًا.
</p>

<p>
	تُعالج الطلبية من النوع <code>POST</code> المُرسلة من قبل المُستخدم عبر النموذج ضمن العبارة الشرطية <code>'if request.method == 'POST</code>، إذ ننشئ كائن تعليقات باستخدام النموذج <code>Comment</code>، ممررين إليه محتوى التعليق المُرسل والمُستخرج من الكائن <code>request.form</code>، ثمّ نحدّد التدوينة التي يتبع لها التعليق باستخدام المعامل <code>post</code> ممررين إليه الكائن <code>post</code> المُستخرج اعتمادًا على مُعرّف التدوينة الموافقة باستخدام التابع <code>()get_or_404</code>.
</p>

<p>
	نهايةً، نضيف كائن التعليقات الذي أنشأناه للتو إلى جلسة قاعدة البيانات، ونحفظ التغييرات، ونعيد توجيه المستخدم إلى صفحة التدوينة.
</p>

<p>
	الآن، نحدّث صفحة التدوينة ونضيف تعليقًا ونرسله، فسيظهر هذا التعليق الجديد أسفل التدوينة.
</p>

<p>
	أصبح لدينا مع نهاية هذه الخطوة نموذج ويب يمكّن المُستخدمين من إضافة تعليقات على التدوينة، وللمزيد حول نماذج الويب ننصحك بقراءة المقال <a href="https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%86%D9%85%D8%A7%D8%B0%D8%AC-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1739/" rel="">كيفية استخدام نماذج الويب في تطبيقات فلاسك</a>، وللمزيد من المعلومات المتقدمة ولتتعرّف على كيفية إدارة نماذج الويب بأمان ننصحك بقراءة المقال <a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%86%D9%85%D8%A7%D8%B0%D8%AC-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D9%88%D8%A7%D9%84%D8%AA%D8%AD%D9%82%D9%82-%D9%85%D9%86%D9%87%D8%A7-%D9%81%D9%8A-%D9%81%D9%84%D8%A7%D8%B3%D9%83-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-flask-wtf-r1838/" rel="">استخدام والتحقق من نماذج الويب ذات واجهة المستخدم التفاعلية في فلاسك باستخدام الإضافة Flask-WTF</a>.
</p>

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

<h2>
	الخطوة 6: عرض جميع التعليقات
</h2>

<p>
	سنعمل في هذه الخطوة على إضافة صفحة للتعليقات <strong>Comments</strong> لعرض جميع التعليقات الموجودة ضمن قاعدة البيانات وترتيبها بحيث تظهر التعليقات الأحدث أولًا، حيث سيظهر مع كل تعليق عنوان ورابط التدوينة المرتبط بها.
</p>

<p>
	نفتح الملف <code>app.py</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_128" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ثم نضيف الوجهة التالية إلى نهاية هذا الملف، والتي تجلب جميع التعليقات من قاعدة البيانات، وترتّبها بحيث يكون الأحدث أولًا، لتمرّرها إلى ملف قالب باسم <code>comments.html</code>، الذي سننشئه لاحقًا:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_99" style=""><span class="com"># ...</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/comments/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> comments</span><span class="pun">():</span><span class="pln">
    comments </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Comment</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">order_by</span><span class="pun">(</span><span class="typ">Comment</span><span class="pun">.</span><span class="pln">id</span><span class="pun">.</span><span class="pln">desc</span><span class="pun">()).</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'comments.html'</span><span class="pun">,</span><span class="pln"> comments</span><span class="pun">=</span><span class="pln">comments</span><span class="pun">)</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	طبقنا في الشيفرة السابقة التابع <code>()order_by</code> على السمة <code>query</code> بغية جلب كافّة التعليقات ولكن وفق ترتيب معيّن، وقد استخدمنا في حالتنا التابع <code>()desc</code> على العمود <code>Comment.id</code> بهدف جلب التعليمات وفق ترتيب تنازلي، بحيث يكون التعليق الأحدث هو الأوّل، ومن ثمّ استخدمنا التابع <code>()all</code> للحصول على النتيجة وتخزينها ضمن متغير باسم <code>comments</code>.
</p>

<p>
	ثمّ صيّرنا ملف قالب باسم <code>comments.html</code>، ممررين إليه الكائن <code>comments</code> المُتضمّن كافّة التعليقات مُرتبّة بحيث يكون التعليق الأحدث أولًا.
</p>

<p>
	لذا، سنفتح الآن ملف القالب <code>comments.html</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_101" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">comments</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	ونكتب ضمنه الشيفرة التالية، والتي ستعمل على عرض التعليقات وربط كل منها بالتدوينة التي يتبع لها:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_103" style=""><span class="pun">{%</span><span class="pln"> extends </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">

</span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">span </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"title"</span><span class="pun">&gt;&lt;</span><span class="pln">h1</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="typ">Latest</span><span class="pln"> </span><span class="typ">Comments</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}&lt;/</span><span class="pln">h1</span><span class="pun">&gt;&lt;/</span><span class="pln">span</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"content"</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> comment </span><span class="kwd">in</span><span class="pln"> comments </span><span class="pun">%}</span><span class="pln">
                    </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"comment"</span><span class="pun">&gt;</span><span class="pln">
                        </span><span class="pun">&lt;</span><span class="pln">i</span><span class="pun">&gt;</span><span class="pln">
                            </span><span class="pun">(#{{</span><span class="pln"> comment</span><span class="pun">.</span><span class="pln">id </span><span class="pun">}})</span><span class="pln">
                            </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;{{</span><span class="pln"> comment</span><span class="pun">.</span><span class="pln">content </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                        </span><span class="pun">&lt;/</span><span class="pln">i</span><span class="pun">&gt;</span><span class="pln">
                        </span><span class="pun">&lt;</span><span class="pln">p </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"title"</span><span class="pun">&gt;</span><span class="pln">
                        </span><span class="typ">On</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln">a href</span><span class="pun">=</span><span class="str">"{{ url_for('post',
                                                post_id=comment.post.id) }}"</span><span class="pun">&gt;</span><span class="pln">
                                </span><span class="pun">{{</span><span class="pln"> comment</span><span class="pun">.</span><span class="pln">post</span><span class="pun">.</span><span class="pln">title </span><span class="pun">}}</span><span class="pln">
                              </span><span class="pun">&lt;/</span><span class="pln">a</span><span class="pun">&gt;</span><span class="pln">
                        </span><span class="pun">&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                    </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">{%</span><span class="pln"> endfor </span><span class="pun">%}</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	اعتمدنا في الشيفرة السابقة على الوراثة من ملف القالب الأساسي <code>base.html</code> باستخدام تعليمة <code>extends</code>، وعيّنا عنوانًا ومررنا على كافّة التعليقات باستخدام حلقة <code>for</code> تكرارية، لنعرض بعدها معرّف التعليق ومحتواه ورابط التدوينة التي يتبع لها، إذ تمكنا من الوصول إلى بيانات التدوينة باستخدام سمة الكائن <code>comment.post</code>.
</p>

<p>
	الآن وباستخدام المتصفّح ننتقل إلى صفحة التعليقات باستخدام الرابط التالي:
</p>

<pre class="ipsCode" id="ips_uid_444_35">http://127.0.0.1:5000/comments/</pre>

<p>
	فتظهر صفحة مشابهة للصورة التالية:
</p>

<p style="text-align: center;">
	<img alt="الانتقل إلى صفحة التعليقات من المتصفح" class="ipsImage ipsImage_thumbnailed" data-fileid="115943" data-ratio="23.07" data-unique="0aprodyy4" style="width: 750px; height: auto;" width="750" src="https://academy.hsoub.com/uploads/monthly_2023_01/4th.thumb.png.c10e773ac02c15c50369214186871599.png">
</p>

<p>
	والآن سنعمل على تمكين رابط صفحة التعليقات <strong>Comments</strong> في شريط التصفّح، لذا نفتح ملف القالب <code>base.html</code> لتحريره:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_108" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">base</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	ونعدل الشيفرة الخاصّة بشريط التنقل لتبدو كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_6370_110" style=""><span class="tag">&lt;nav&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('index') }}"</span><span class="tag">&gt;</span><span class="pln">FlaskApp</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('comments') }}"</span><span class="tag">&gt;</span><span class="pln">Comments</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"#"</span><span class="tag">&gt;</span><span class="pln">About</span><span class="tag">&lt;/a&gt;</span><span class="pln">
    </span><span class="tag">&lt;/nav&gt;</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	الآن وبتحديث صفحة التعليقات نجد أنّ الرابط <strong>Comments</strong> في شريط التصفّح بات يعمل.
</p>

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

<h2>
	الخطوة 7: حذف تعليقات
</h2>

<p>
	سنعمل في هذه الخطوة على إضافة زر حذف التعليق <strong>Delete Comment</strong> أسفل كل تعليق في صفحة المنشور لنمكّن المستخدمين من حذف التعليقات غير المرغوبة.
</p>

<p>
	بدايةً، سنضيف وجهةً جديدةً للحذف، وهي <code>‎/comments/ID/delete</code> تتعامل مع الطلبات من النوع <code>POST</code>، إذ ستستقبل دالة الحذف رقم معرّف التعليق ID المراد حذفه، لتجلبه من قاعدة البيانات وتحذفه، ليُعاد بعدها التوجيه إلى صفحة التدوينة التي حّذف منها التعليق.
</p>

<p>
	الآن، افتح الملف <code>app.py</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_112" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	أضِف الوجهة التالية إلى نهاية الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_114" style=""><span class="com"># ...</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/comments/&lt;int:comment_id&gt;/delete'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> delete_comment</span><span class="pun">(</span><span class="pln">comment_id</span><span class="pun">):</span><span class="pln">
    comment </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Comment</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">get_or_404</span><span class="pun">(</span><span class="pln">comment_id</span><span class="pun">)</span><span class="pln">
    post_id </span><span class="pun">=</span><span class="pln"> comment</span><span class="pun">.</span><span class="pln">post</span><span class="pun">.</span><span class="pln">id
    db</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">delete</span><span class="pun">(</span><span class="pln">comment</span><span class="pun">)</span><span class="pln">
    db</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> redirect</span><span class="pun">(</span><span class="pln">url_for</span><span class="pun">(</span><span class="str">'post'</span><span class="pun">,</span><span class="pln"> post_id</span><span class="pun">=</span><span class="pln">post_id</span><span class="pun">))</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	استخدمنا المزخرف <code>app.post</code> المُقدّم في الإصدار 2.0.0 من فلاسك بدلًا من استخدام المزخرف <code>app.route</code> المعتاد، والذي أضاف اختصارات للعديد من توابع HTTP الشائعة، فعلى سبيل المثال الأمر:
</p>

<pre class="ipsCode" id="ips_uid_444_39">@app.post("/login")</pre>

<p>
	هو اختصارٌ للأمر:
</p>

<pre class="ipsCode" id="ips_uid_444_41">@app.route("/login", methods=["POST"])</pre>

<p>
	ما يعني أن الدالة العاملة في فلاسك هذه تتعامل فقط مع طلبيات من النوع <code>POST</code>، وبزيارة الوجهة <code>comments/ID/delete/</code> في المتصفّح سيظهر الخطأ ‎405 Method Not Allowed لأن المتصفحات تستخدم طريقة <code>GET</code> افتراضيًا للطلبات، لذا بغية حذف تعليق ما، سنضيف زر أوامر عندما يضغط المستخدم عليه، تُرسَل إلى الوجهة طلبية من النوع <code>POST</code>.
</p>

<p>
	تستقبل دالة العرض <code>()delete_comment</code> في الشيفرة السابقة معرّف التعليق المراد حذفه من خلال متغير الرابط <code>comment_id</code>، إذ استخدمنا التابع <code>()get_or_404</code> لجلب التعليق وحفظه في متغير باسم <code>comment</code>، أو الاستجابة بخطأ من النوع ‎404 Not Found في حال عدم وجود تعليق موافق، كما خزّنا معرّف التدوينة التي يتبع لها التعليق المحذوف في المتغير <code>post_id</code> والذي سنستخدمه في إعادة التوجيه إلى صفحة هذه التدوينة بعد حذف التعليق.
</p>

<p>
	استخدمنا التابع <code>()delete</code> في الجلسة المفتوحة مع قاعدة البيانات في السطر البرمجي <code>(db.session.delete(comment</code> ممررين إليه كائن التدوينة، وهذا ما يجعل الجلسة بالنتيجة تحذف التعليق الموافق بمجرّد تأكيد الإجراءات، إذ ما من حاجة لإجراء أي تعديلات إضافية كون الإجراءات تُأكَّد مباشرةً باستخدام الأمر <code>()db.session.commit</code>، ونهايةً أعدنا توجيه المستخدم إلى صفحة التدوينة التي يتبع لها التعليق المحذوف.
</p>

<p>
	سنعدّل القالب "post.html" بإضافة زر لحذف التعليق <strong>Delete Comment</strong> أسفل كل تعليق:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_120" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">post</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	سنعدّل حلقة <code>for</code> التكرارية بإضافة <a href="https://wiki.hsoub.com/HTML/form" rel="external">وسم نموذج &lt;form&gt;</a> مباشرةً أسفل محتوى التعليق:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6370_122" style=""><span class="pun">{%</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> comment </span><span class="kwd">in</span><span class="pln"> post</span><span class="pun">.</span><span class="pln">comments </span><span class="pun">%}</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"comment"</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;#{{</span><span class="pln"> comment</span><span class="pun">.</span><span class="pln">id </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;{{</span><span class="pln"> comment</span><span class="pun">.</span><span class="pln">content </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">form method</span><span class="pun">=</span><span class="str">"POST"</span><span class="pln">
                action</span><span class="pun">=</span><span class="str">"{{ url_for('delete_comment',
                                    comment_id=comment.id) }}"</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"submit"</span><span class="pln"> value</span><span class="pun">=</span><span class="str">"Delete Comment"</span><span class="pln">
                    onclick</span><span class="pun">=</span><span class="str">"return confirm('Are you sure you want to delete this entry?')"</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">form</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">{%</span><span class="pln"> endfor </span><span class="pun">%}</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	أنشأنا في الملف السابق نموذج ويب يُرسل طلبًا من النوع <code>POST</code> إلى دالة العرض <code>()delete_comment</code> ممررين القيمة <code>comment.id</code> وسيطًا للمعامل <code>comment_id</code> لتحديد التعليق المراد حذفه، كما استخدمنا التابع <code>confirm()‎</code> المتوفّر في متصفحات الويب لعرض رسالة تأكيد قبل إرسال الطلب.
</p>

<p>
	الآن وبالانتقال إلى صفحة إحدى التدوينات (التدوينة الثانية مثلًا):
</p>

<pre class="ipsCode" id="ips_uid_444_43">http://127.0.0.1:5000/2/</pre>

<p>
	سيظهر زر أوامر لحذف التعليق Delete Comment أسفل كل تعليق، وبالنقر عليه ستظهر رسالة لتأكيد عملية الحذف، ومن ثمّ نجد أنّ التعليق قد حُذِف فعلًا.
</p>

<p>
	وبذلك أصبحت الآلية اللازمة لحذف التعليقات من قاعدة البيانات متوفّرة في التطبيق.
</p>

<h2>
	الخاتمة
</h2>

<p>
	عملنا في هذا المقال على بناء تطبيق مدونة بغية بيان كيفيّة إدارة علاقات قواعد البيانات من نوع واحد-إلى-مُتعدّد باستخدام الإضافة Flask-SQLAlchemy، إذ تعرفنا على كيفيّة ربط الجدول الأب بالجدول الابن، وكيفية إنشاء اقتران ما بين كائن ابن مع أبيه وإضافته إلى قاعدة البيانات، وكيفية الوصول إلى بيانات الابن انطلاقًا من سجلات الأب وبالعكس.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-use-one-to-many-database-relationships-with-flask-sqlalchemy" rel="external nofollow">How to Use One-to-Many Database Relationships with Flask-SQLAlchemy</a> لصاحبه Abdelhadi Dyouri.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/devops/servers/databases/%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-database/" rel="">دليلك الشامل إلى قواعد البيانات</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B9%D9%84%D8%A7%D9%82%D8%A9-one-to-many-%D9%85%D8%B9-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D9%88%D9%85%D8%AD%D8%B1%D9%83-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-sqlite-r1623/" rel="">استخدام علاقة one-to-many مع إطار العمل فلاسك Flask ومحرك قواعد البيانات SQLite</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%B9%D8%AF%D9%8A%D9%84-%D8%A7%D9%84%D8%B9%D9%86%D8%A7%D8%B5%D8%B1-%D9%81%D9%8A-%D8%B9%D9%84%D8%A7%D9%82%D8%A7%D8%AA-one-to-many-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D9%88%D9%85%D8%AD%D8%B1%D9%83-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-sqlite-r1631/" rel="">تعديل العناصر في علاقات one-to-many باستخدام فلاسك Flask ومحرك قاعدة البيانات SQLite</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D9%84%D8%B1%D9%91%D8%A8%D8%B7-%D8%A8%D9%8A%D9%86-%D8%AC%D8%AF%D9%88%D9%84%D9%8A-%D8%A7%D9%84%D9%85%D9%82%D8%A7%D9%84%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D9%85%D9%8F%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D9%8A%D9%86-%D8%A8%D8%B9%D9%84%D8%A7%D9%82%D8%A9-%D9%88%D8%A7%D8%AD%D8%AF-%D9%84%D9%84%D8%B9%D8%AF%D9%8A%D8%AF-one-to-many-relationship-r512/" rel="">الرّبط بين جدولي المقالات والمُستخدمين بعلاقة واحد للعديد One-to-Many Relationship</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1843</guid><pubDate>Thu, 09 Feb 2023 16:08:02 +0000</pubDate></item><item><title>&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x625;&#x636;&#x627;&#x641;&#x629; Flask-SQLAlchemy &#x644;&#x644;&#x62A;&#x62E;&#x627;&#x637;&#x628; &#x645;&#x639; &#x642;&#x648;&#x627;&#x639;&#x62F; &#x628;&#x64A;&#x627;&#x646;&#x627;&#x62A; &#x62A;&#x637;&#x628;&#x64A;&#x642;&#x627;&#x62A; &#x641;&#x644;&#x627;&#x633;&#x643;</title><link>https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-flask-sqlalchemy-%D9%84%D9%84%D8%AA%D8%AE%D8%A7%D8%B7%D8%A8-%D9%85%D8%B9-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-r1842/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_01/564879632_--Flask-SQLAlchemy-------.png.e06122a1a61325c237250374d019f7a8.png" /></p>
<p>
	نحتاج عادةً إلى <a href="https://academy.hsoub.com/devops/servers/databases/%D8%AE%D8%B5%D8%A7%D8%A6%D8%B5-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D9%85%D8%B2%D8%A7%D9%8A%D8%A7-%D8%A7%D9%84%D8%AA%D9%8A-%D8%AA%D9%82%D8%AF%D9%85%D9%87%D8%A7-r520/" rel="">قاعدة بيانات</a> في تطبيقات الويب، وهي مجموعةٌ مُنطمّةٌ من البيانات نستخدمها لتخزين وتنظيم البيانات الدائمة، موفّرةً لنا إمكانية استرجاعها ومعالجتها بفعالية، إذ سنحتاج مثلًا في تطبيق ما للتواصل الاجتماعي إلى قاعدة بيانات لتخزين بيانات المستخدم (من معلومات شخصية ومنشورات وتعليقات ومتابعين) لنتمكّن من معالجتها بفعالية، إذ توفّر قاعدة البيانات إمكانية إضافة بيانات جديدة إليها، أو استرجاع بيانات مُخزّنة أصلًا، أو التعديل عليها، أو حذفها بكل مرونة وذلك اعتمادًا على المتطلبات والشروط والظروف المُختلفة، ففي تطبيق ويب ما قد تكون هذه المتطلبات مثلًا هي حالة إضافة المُستخدم لمنشور جديد، أو حذف منشور سابق، أو حذف حسابه مع كافّة منشوراته أو مع الإبقاء عليها، بمعنى أنّ طريقة معالجة البيانات تعتمد أولًا وأخيرًا على الميزات التي يتيحها التطبيق، فقد لا ترغب مثلًا بالسماح لمُستخدمي تطبيقك بإضافة منشورات غير معنونة.
</p>

<p>
	يُعد <a href="https://academy.hsoub.com/programming/python/flask/" rel="">فلاسك</a> إطار عمل للويب مبني بلغة <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D9%85%D8%B1%D8%AC%D8%B9-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%AA%D8%B9%D9%84%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r735/" rel="">بايثون</a>، ويتميز بكونه صغير الحجم وسهل المعالجة، ويوفّر أيضًا عدة أدوات وميزات من شأنها إنشاء تطبيقات ويب في لغة بايثون.
</p>

<p>
	أمّا <a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%AC%D9%87%D9%8A%D8%B2-%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-flask-sqlalchemy-r508/" rel="">SQLAlchemy</a> فهي أداة في محرك قواعد البيانات SQL تؤمن وصولًا فعالًا وعالي الأداء إلى قواعد البيانات العلاقية، كما توفّر طرقًا للتخاطب مع العديد من محركات <a href="https://academy.hsoub.com/devops/servers/databases/%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-database/" rel="">قواعد البيانات</a>، مثل SQLite و MySQL و PostgreSQ، مانحةً إمكانية الوصول إلى آليات SQL الخاصة بقواعد البيانات، كما توفّر مُخطِّط الكائنات العلاقية Object Relational Mapper -أو اختصارًا ORM- الذي يتيح إمكانية إنشاء الاستعلامات والتعامل مع البيانات باستخدام توابع وكائنات بسيطة في بايثون.
</p>

<p>
	تُعدّ Flask-SQLAlchemy بمثابة إضافة لفلاسك، لتسهيل استخدام SQLAlchemy ضمن فلاسك، وتؤمن الأدوات والوسائل المناسبة للتعامل مع قاعدة البيانات في تطبيقات فلاسك من خلال SQLAlchemy.
</p>

<p>
	سنعمل في هذا المقال على إنشاء نظام إدارة صغير للطلاب يبين لنا كيفية استخدام إضافة Flask-SQLAlchemy، إذ سنستخدمها مع فلاسك لأداء مهام أساسية، مثل الاتصال بخادم قاعدة البيانات، أو إنشاء جدول وإضافة بيانات إليه واسترجاع هذه البيانات وتحديث وحذف العناصر من قاعدة البيانات.
</p>

<p>
	سنستخدم الإضافة SQLAlchemy مع محرّك قواعد البيانات <a href="https://sqlite.org/index.html" rel="external nofollow">SQLite</a> رغم أنه من الممكن استخدامها مع محركات قواعد بيانات أخرى، مثل PostgreSQL و MySQL، إلّا أنّ SQLite تعمل بكفاءة مع بايثون، لأن المكتبة القياسية لبايثون توفر الوحدة <code>sqlite3</code> المُستخدمة من قبل SQLAlchemy في الخلفية للتعامل مع قاعدة بيانات SQLite دون الحاجة إلى تثبيت أي شيء إضافي، إذ تُثبّت SQlite افتراضيًا على أنظمة لينكس Linux، كما تُثبّت على أنها جزءٌ من حزمة بايثون في أنظمة تشغيل ويندوز Windows.
</p>

<h2>
	مستلزمات العمل
</h2>

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

<ul>
	<li>
		توفُّر <a href="https://academy.hsoub.com/programming/python/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%AA%D9%87%D8%A7-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r714/" rel="">بيئة برمجة بايثون 3 محلية</a>، مثبّتة على حاسوبك، وسنفترض في مقالنا أن اسم مجلد المشروع هو <code>flask_app</code>.
	</li>
	<li>
		الفهم الجيد لأساسيات فلاسك، مثل مفهوم الوجهات ودوال العرض، ويمكنك في هذا الصدد الاطلاع على المقالين <a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%B9%D9%84%D9%85-%D8%A8%D9%86%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9%D9%83-%D8%A7%D9%84%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1715/" rel="">كيفية بناء موقعك الإلكتروني الأول باستخدام إطار عمل فلاسك Flask من لغة بايثون</a> و<a href="https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1737/" rel="">كيفية استخدام القوالب في تطبيقات فلاسك Flask</a> لفهم مبادئ فلاسك.
	</li>
	<li>
		فهم أساسيات لغة <a href="https://academy.hsoub.com/programming/html/" rel="">HTML</a>.
	</li>
</ul>

<h2>
	الخطوة 1 – تثبيت فلاسك والإضافة Flask-SQLAlchemy
</h2>

<p>
	سنعمل في هذه الخطوة على تثبيت كل من فلاسك وكافّة الحزم اللازمة لعمل التطبيق. لذلك، وبعد التأكّد من كون البيئة الافتراضية مُفعّلة، سنستخدم أمر تثبيت الحزم <code>pip</code> لتثبيت كل من فلاسك والإضافة Flask-SQLAlchemy على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_7" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ pip install </span><span class="typ">Flask</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">-</span><span class="typ">SQLAlchemy</span></pre>

<p>
	وبمجرّد انتهاء التثبيت بنجاح، سيظهر في السطر الأخير من الخرج ما يشبه التالي:
</p>

<pre class="ipsCode" id="ips_uid_3095_10">Successfully installed Flask-2.0.3 Flask-SQLAlchemy-2.5.1 Jinja2-3.0.3 MarkupSafe-2.1.0 SQLAlchemy-1.4.31 Werkzeug-2.0.3 click-8.0.4 greenlet-1.1.2 itsdangerous-2.1.0</pre>

<p>
	ومع نهاية هذه الخطوة نكون قد ثبتنا ما يلزم من حزم بايثون، وأصبح من الممكن الانتقال إلى الخطوة التالية المُتمثّلة بإعداد قاعدة البيانات.
</p>

<h2>
	الخطوة 2 – إعداد النموذج وقاعدة البيانات
</h2>

<p>
	سنعمل في هذه الخطوة على إعداد الاتصال مع قاعدة البيانات وإنشاء نموذج قاعدة البيانات SQLAlchemy، وهو صنف بايثون يمثّل الجدول الذي سيخزّن البيانات، إذ سنعمل على تهيئة قاعدة البيانات وإنشاء جدول للطلاب وفقًا للنموذج الذي سنصرّح عنه، ونهايةً سنضيف بعض الطلاب إلى جدول الطلاب هذا.
</p>

<h3>
	إعداد الاتصال بقاعدة البيانات
</h3>

<p>
	سننشئ بدايةً ملف باسم <code>app.py</code> ضمن المجلد <code>flask.app</code> والذي سيحتوي على الشيفرات الخاصة بإعداد قاعدة البيانات ووجهات فلاسك اللازمة لعمل التطبيق:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_11" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	سيتصل هذا الملف بقاعدة بيانات من نوع SQLite باسم <code>database.db</code>، كما يحتوي على صنف يدعى <code>Student</code> يمثّل جدول الطلاب في قاعدة البيانات المُستخدم لتخزين معلوماتهم، إضافةً إلى وجهات فلاسك. سنضيف الاستدعاءات التالية باستخدام التعليمة <code>import</code> إلى بداية الملف <code>app.py</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_13" style=""><span class="kwd">import</span><span class="pln"> os
</span><span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> render_template</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">,</span><span class="pln"> url_for</span><span class="pun">,</span><span class="pln"> redirect
</span><span class="kwd">from</span><span class="pln"> flask_sqlalchemy </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">SQLAlchemy</span><span class="pln">

</span><span class="kwd">from</span><span class="pln"> sqlalchemy</span><span class="pun">.</span><span class="pln">sql </span><span class="kwd">import</span><span class="pln"> func</span></pre>

<p>
	استوردنا في الشيفرة السابقة الوحدة <code>os</code> التي تمكنّنا من الوصول إلى واجهات نظام التشغيل المختلفة، والتي سنستخدمها في بناء مسار ملف قاعدة البيانات <code>database.db</code>، كما استوردنا من حزمة فلاسك كافّة المُساعدات اللازمة للتطبيق، مثل الصنف فلاسك المُستخدم في إنشاء النسخة الفعلية من التطبيق، والدالة <code>()render_template</code> لتصيير قوالب HTML، والكائن <code>request</code> المسؤول عن التعامل مع الطلبيات، والدالة <code>()url_for</code> لبناء روابط الوجهات، والدالة <code>()redirect</code> لإعادة توجيه المُستخدمين من صفحة لأُخرى.
</p>

<p>
	بعد ذلك، استوردنا الصنف <code>SQLAlchemy</code> من الإضافة Flask-SQLAlchemy، والذي يسمح لنا بالوصول إلى جميع الدوال والأصناف الخاصة بالإضافة SQLAlchemy، إضافةً إلى المُساعدات والآليات التي تدعم عمل فلاسك مع هذه الإضافة، والتي سنستخدمها لإنشاء كائن قاعدة بيانات يتصل مع تطبيق فلاسك سامحًا لنا بإنشاء وتعديل الجداول باستخدام أصناف وكائنات ودوال بايثون دون الحاجة لاستخدام لغة SQL.
</p>

<p>
	كما استوردنا المساعد <code>func</code> من الوحدة <code>sqlalchemy.sql</code> للوصول إلى دوال SQL، والتي سنستخدمها في نظام إدارة الطلاب لضبط الوقت والتاريخ افتراضيًا لدى إنشاء سجل طالب جديد.
</p>

<p>
	وبعد الانتهاء من الاستدعاءات، سنعيّن مسار ملف قاعدة البيانات، ونستنسخ تطبيق فلاسك، كما سنضبط ونؤسس اتصالًا للتطبيق مع الإضافة SQLAlchemy، ومن ثمّ سنضيف الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_15" style=""><span class="pln">basedir </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">abspath</span><span class="pun">(</span><span class="pln">os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">dirname</span><span class="pun">(</span><span class="pln">__file__</span><span class="pun">))</span><span class="pln">

app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">config</span><span class="pun">[</span><span class="str">'SQLALCHEMY_DATABASE_URI'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln">\
        </span><span class="str">'sqlite:///'</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="pln">basedir</span><span class="pun">,</span><span class="pln"> </span><span class="str">'database.db'</span><span class="pun">)</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">config</span><span class="pun">[</span><span class="str">'SQLALCHEMY_TRACK_MODIFICATIONS'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">False</span><span class="pln">

db </span><span class="pun">=</span><span class="pln"> </span><span class="typ">SQLAlchemy</span><span class="pun">(</span><span class="pln">app</span><span class="pun">)</span></pre>

<p>
	أنشأنا في الشيفرة السابقة مسارًا لملف قاعدة البيانات SQLite، إذ حددنا المجلد الحالي ليكون المجلد الرئيسي، كما استخدمنا الدالة <a href="https://docs.python.org/3.8/library/os.path.html#os.path.abspath" rel="external nofollow"><code>()os.path.abspath</code></a> للحصول على المسار الكامل لمجلد الملف الحالي، إذ يتضمّن المتغير الخاص <code>__file__</code> اسم مسار الملف الحالي <code>app.py</code>، لنخزّن المسار الكلي للمجلد الأساسي ضمن المتغير <code>basedir</code>.
</p>

<p>
	ثمّ أنشأنا نسخةً فعليةً من تطبيق فلاسك باسم app لنستخدمها في ضبط مفتاحي الضبط الخاصين بالإضافة Flask-SQLAlchemy، وهما:
</p>

<ul>
	<li>
		<p>
			<code>SQLALCHEMY_DATABASE_URI</code>: وهو معرّف الموارد الموحّد الخاص بقاعدة البيانات database URI المسؤول عن تحديد قاعدة البيانات المراد إنشاء اتصال معها، وفي هذه الحالة تكون صيغة هذا المعرّف بالشكل <code>sqlite:///path/to/database.db</code>، إذ نستخدم الدالة <a href="https://docs.python.org/3.8/library/os.path.html#os.path.join" rel="external nofollow"><code>()op.path.join</code></a> لتحقيق الربط المدروس بين المجلد الأساسي الذي أنشاناه وخزنّا مساره في المتغير <code>basedir</code>، وبين اسم الملف <code>database.db</code>، الأمر الذي يسمح بالاتصال مع ملف قاعدة البيانات <code>database.db</code> الموجود في المجلد <code>flask.app</code>، إذ سيُنشأ هذا الملف فور تهيئة قاعدة البيانات.
		</p>
	</li>
	<li>
		<p>
			<code>SQLALCHEMY_TRACK_MODIFICATIONS</code>: وهو إعداد لتمكين أو إلغاء تمكين ميزة تتبع تغيرات الكائنات، وقد عيّناه إلى القيمة false لإلغاء التتبع وبالتالي تحقيق استخدام أقل لموارد الذاكرة.
		</p>

		<p>
			<strong>ملاحظة:</strong> من الممكن استخدام أي محرّك قواعد بيانات آخر، مثل PostgreSQL، أو MySQL، وفي هذه الحالة يجب استخدام معرّف الموارد الموحد URI المناسب؛ ففي حال استخدام PostgreSQL، سيتّبع الصيغة:
		</p>
	</li>
</ul>

<pre class="ipsCode" id="ips_uid_3095_12">postgresql://username:password@host:port/database_name</pre>

<p>
	أمّا في حال استخدام MySQL:
</p>

<pre class="ipsCode" id="ips_uid_3095_15">mysql://username:password@host:port/database_name</pre>

<p>
	وبعد الانتهاء من ضبط الإضافة SQLAIchemy من خلال تعيين معرّف الموارد الموحّد لقاعدة البيانات وإلغاء تمكين ميزة تتبّع التغييرات، سننشئ كائن قاعدة بيانات باستخدام صنف الإضافة <code>SQLAIchemy</code>، ممررين إليه نسخة التطبيق بغية ربط تطبيق فلاسك مع الإضافة SQLAIchemy، وسنخزّن كائن قاعدة البيانات هذا ضمن متغير باسم <code>db</code>، والذي سنستخدمه في التخاطب مع قاعدة البيانات.
</p>

<h3>
	التصريح عن الجداول
</h3>

<p>
	بعد تأسيس الاتصال مع قاعدة البيانات وإنشاء كائن قاعدة البيانات، سنستخدم هذا الكائن لإنشاء جدول للطلاب في قاعدة البيانات، والمُمثّل بنموذج model، وهو صنف بايثون يرث من صنف رئيسي توفرّه الإضافة Flask-SQLAlchemy من خلال نسخة قاعدة البيانات <code>db</code> التي أنشأناها مُسبقًا، لذا وبغية تعريف جدول الطلاب مثل نموذج، سنضيف الصنف التالي إلى الملف <code>app.py</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_23" style=""><span class="com"># ...</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Student</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
    id </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Integer</span><span class="pun">,</span><span class="pln"> primary_key</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
    firstname </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">String</span><span class="pun">(</span><span class="lit">100</span><span class="pun">),</span><span class="pln"> nullable</span><span class="pun">=</span><span class="kwd">False</span><span class="pun">)</span><span class="pln">
    lastname </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">String</span><span class="pun">(</span><span class="lit">100</span><span class="pun">),</span><span class="pln"> nullable</span><span class="pun">=</span><span class="kwd">False</span><span class="pun">)</span><span class="pln">
    email </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">String</span><span class="pun">(</span><span class="lit">80</span><span class="pun">),</span><span class="pln"> unique</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">,</span><span class="pln"> nullable</span><span class="pun">=</span><span class="kwd">False</span><span class="pun">)</span><span class="pln">
    age </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Integer</span><span class="pun">)</span><span class="pln">
    created_at </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">DateTime</span><span class="pun">(</span><span class="pln">timezone</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">),</span><span class="pln">
                           server_default</span><span class="pun">=</span><span class="pln">func</span><span class="pun">.</span><span class="pln">now</span><span class="pun">())</span><span class="pln">
    bio </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Text</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> __repr__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> f</span><span class="str">'&lt;Student {self.firstname}&gt;'</span></pre>

<p>
	أنشأنا في الشيفرة السابقة النموذج <code>Student</code> الممثّل لجدول الطلاب والذي يرث من الصنف <code>db.Model</code>، كما استخدمنا الصنف <code>db.Column</code> لتعريف أعمدة الجدول، إذ يمثّل الوسيط الأوّل نوع العمود، أمّا باقي الوسطاء فتمثّل إعدادات العمود.
</p>

<p>
	وقد عرفنا الأعمدة التالية للنموذج <code>Student</code>:
</p>

<ul>
	<li>
		<code>id</code>: وهو معرّف الطالب، ويحتوي على بياناتٍ من نوع رقم صحيح وذلك باستخدام <code>db.Integer</code> و <code>primary_key=True</code> التي تعرّفه مفتاحًا أساسيًا، الذي يخصّص قيمةً فريدةً في قاعدة البيانات من أجل كل سجل (والسجل هو الطالب في حالتنا).
	</li>
	<li>
		<code>firstname</code>: الاسم الأول للطالب، وهو سلسلة نصية بعدد محارف أعظمي يساوي 100، وتشير التعليمة <code>nullable=False</code> إلى أن هذا العمود لا يجب أن يحتوي على قيمٍ فارغة.
	</li>
	<li>
		<code>lastname</code>: الاسم الأخير للطالب، وهو سلسلة نصية بعدد محارف أعظمي يساوي 100، وتشير التعليمة <code>nullable=False</code> إلى أن هذا العمود لا يجب أن يحتوي على قيمٍ فارغة.
	</li>
	<li>
		<code>email</code>: عنوان البريد الإلكتروني للطالب، وهو سلسلة نصية بعدد محارف أعظمي يساوي 80، وتشير التعليمة <code>unique=True</code> إلى أنّ البريد الإلكتروني يجب أن يكون فريدًا لكل طالب، كما تشير التعليمة <code>nullable=False</code> إلى أن هذا العمود لا يجب أن يحتوي على قيمٍ فارغة.
	</li>
	<li>
		<code>age</code>: عمر الطالب.
	</li>
	<li>
		<code>created_at</code>: يحتوي على تاريخ ووقت إنشاء سجل الطالب في قاعدة البيانات، إذ استخدمنا الوحدة <a href="https://docs.sqlalchemy.org/en/14/core/type_basics.html#sqlalchemy.types.DateTime" rel="external nofollow"><code>db.DateTime</code></a> لتعريفه مثل كائن وقت وتاريخ <code>datetime</code> في بايثون. تمكّن التعليمة <code>timezone=True</code> خاصية دعم المنطقة الزمنية، وتعيّن الوحدة <code>server_default</code> القيمة الزمنية الافتراضية في قاعدة البيانات لحظة إنشاء الجدول وبذلك تتعامل قاعدة البيانات مع القيم الافتراضية عوضًا عن النموذج، الذي نمرّر إليه الدالة <code>()func.now</code> التي تستدعي دالة <code>()now</code> المسؤولة عن تحديد الوقت والتاريخ بلغة SQL، لتُصيّر في SQLite بالشّكل <code>CURRENT_TIMESTAMP</code> عند إنشاء جدول الطلاب.
	</li>
	<li>
		<code>bio</code>: السيرة الذاتية للطالب، إذ تشير <code>()db.Text</code> إلى أنّ هذا العمود يضم نصوصًا طويلة.
	</li>
</ul>

<p>
	تمكننا الدالة الخاصة <a href="https://docs.python.org/3/reference/datamodel.html#object.__repr__" rel="external nofollow"><code>__repr__</code></a> من تمثيل كل كائن بصيغة سلسلة نصية، الأمر الذي يساعد في التعرّف على هذا الكائن لأغراض التنقيح، واستخدمنا في حالتنا الاسم الأوّل للطالب لهذا الغرض.
</p>

<p>
	وبذلك سيبدو الملف <code>app.py</code> حاليًا بالشّكل التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_25" style=""><span class="kwd">import</span><span class="pln"> os
</span><span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> render_template</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">,</span><span class="pln"> url_for</span><span class="pun">,</span><span class="pln"> redirect
</span><span class="kwd">from</span><span class="pln"> flask_sqlalchemy </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">SQLAlchemy</span><span class="pln">

</span><span class="kwd">from</span><span class="pln"> sqlalchemy</span><span class="pun">.</span><span class="pln">sql </span><span class="kwd">import</span><span class="pln"> func


basedir </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">abspath</span><span class="pun">(</span><span class="pln">os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">dirname</span><span class="pun">(</span><span class="pln">__file__</span><span class="pun">))</span><span class="pln">

app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">config</span><span class="pun">[</span><span class="str">'SQLALCHEMY_DATABASE_URI'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln">\
        </span><span class="str">'sqlite:///'</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="pln">basedir</span><span class="pun">,</span><span class="pln"> </span><span class="str">'database.db'</span><span class="pun">)</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">config</span><span class="pun">[</span><span class="str">'SQLALCHEMY_TRACK_MODIFICATIONS'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">False</span><span class="pln">

db </span><span class="pun">=</span><span class="pln"> </span><span class="typ">SQLAlchemy</span><span class="pun">(</span><span class="pln">app</span><span class="pun">)</span><span class="pln">


</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Student</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
    id </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Integer</span><span class="pun">,</span><span class="pln"> primary_key</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
    firstname </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">String</span><span class="pun">(</span><span class="lit">100</span><span class="pun">),</span><span class="pln"> nullable</span><span class="pun">=</span><span class="kwd">False</span><span class="pun">)</span><span class="pln">
    lastname </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">String</span><span class="pun">(</span><span class="lit">100</span><span class="pun">),</span><span class="pln"> nullable</span><span class="pun">=</span><span class="kwd">False</span><span class="pun">)</span><span class="pln">
    email </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">String</span><span class="pun">(</span><span class="lit">80</span><span class="pun">),</span><span class="pln"> unique</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">,</span><span class="pln"> nullable</span><span class="pun">=</span><span class="kwd">False</span><span class="pun">)</span><span class="pln">
    age </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Integer</span><span class="pun">)</span><span class="pln">
    created_at </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">DateTime</span><span class="pun">(</span><span class="pln">timezone</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">),</span><span class="pln">
                           server_default</span><span class="pun">=</span><span class="pln">func</span><span class="pun">.</span><span class="pln">now</span><span class="pun">())</span><span class="pln">
    bio </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="typ">Column</span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="typ">Text</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> __repr__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> f</span><span class="str">'&lt;Student {self.firstname}&gt;'</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<h3>
	إنشاء قاعدة بيانات
</h3>

<p>
	الآن وبعد الانتهاء من إعداد كل من الاتصال مع قاعدة البيانات ونموذج الطالب، سنستخدم صدفة فلاسك لإنشاء كل من قاعدة البيانات وجدول الطالب اعتمادًا على النموذج المسمّى <code>Student</code>.
</p>

<p>
	نستخدم -بعد التأكّد من تفعيل البيئة الافتراضية- متغير البيئة <code>FLASK_APP</code> لتحديد الملف <code>app.py</code> على أنه يمثّل تطبيق فلاسك، ومن ثمّ نفتح صَدفة فلاسك باستخدام الأمر التالي وذلك أثناء وجودنا ضمن المجلد <code>flask_app</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_27" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_APP</span><span class="pun">=</span><span class="pln">app
</span><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ flask shell</span></pre>

<p>
	فتُفتح صَدفة بايثون تفاعلية خاصّة، تعمل على تشغيل الأوامر ضمن تطبيق فلاسك، وبذلك نضمن انّ دوال Flask-SQLAlchemy التي سنستدعيها ستتصل مع تطبيق فلاسك.
</p>

<p>
	الآن، سنستورد كائن قاعدة البيانات ونماذج الطلاب، ومن ثمّ سنشغّل الدالة <code>()db.create_all</code> بغية إنشاء الجداول الموافقة لتلك النماذج، ولكن في حالتنا لا يوجد سوى نموذج واحد، ما يعني أنّ استدعاء الدالة سينتج عنه إنشاء جدول وحيد في قاعدة البيانات على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_30" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">from</span><span class="pln"> app </span><span class="kwd">import</span><span class="pln"> db</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Student</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> db</span><span class="pun">.</span><span class="pln">create_all</span><span class="pun">()</span></pre>

<p>
	نبقي الصدفة في حالة تشغيل، ونفتح نافذة طرفية جديدة وننتقل إلى المجلد <code>flask_app</code>، فنجد ملفًا جديدًا باسم <code>database.db</code> ضمنه.
</p>

<p>
	<strong>ملاحظة:</strong> لا تنشئ الدالة <code>()db.create_all</code> أو تحدّث جدولًا موجودًا أصلًا، فمثلًا لو عدلنا النموذج الذي أنشأناه بإضافة عمود جديد إليه، ومن ثمّ شغلنا الدالة <code>()db.create_all</code>، فلن تُطبّق التغييرات التي أجريناها على النموذج على الجدول في حال كان الجدول موجودًا أصلًا في قاعدة البيانات، ويكون الحل لهذه المشكلة بحذف كافّة الجداول الموجودة في قاعدة البيانات باستخدام الدالة <code>()db.drop_all</code> ومن ثمّ إعادة إنشائها باستخدام الدالة <code>()db.create_all</code>، كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_32" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> db</span><span class="pun">.</span><span class="pln">drop_all</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> db</span><span class="pun">.</span><span class="pln">create_all</span><span class="pun">()</span></pre>

<p>
	ستعمل هذه الشيفرة على تطبيق التعديلات على النماذج، كما ستحذف كافّة البيانات الموجودة في قاعدة البيانات، فلو أردت تحديث قاعدة البيانات مع الحفاظ على البيانات الموجودة فيها، لا بدّ من استخدام أداة ترحيل ملف تخطيط قاعدة البيانات schema migration، ويمكن باستخدامها تعديل الجداول مع الحفاظ على البيانات. لاستخدام هذه الأداة من الممكن الاستعانة بالإضافة<a href="https://flask-migrate.readthedocs.io/en/latest/index.html" rel="external nofollow">Flask-Migrate</a> لتنفيذ ترحيل ملف تخطيط قاعدة البيانات باستخدام الإضافة SQLAIchemy من خلال واجهة أسطر الأوامر في فلاسك.
</p>

<p>
	وفي حال ظهور رسالة خطأ، يجب التأكّد من كون التصريح عن كل من النموذج ومعرّف الموارد الموحّد لقاعدة البيانات صحيحًا.
</p>

<h3>
	ملء الجدول
</h3>

<p>
	الآن وبعد إنشاء كلٍ من قاعدة البيانات وجدول الطالب، سنستخدم صدفة فلاسك لإضافة بعض الطلاب إلى قاعدة البيانات من خلال النموذج <code>Student</code>.
</p>

<p>
	سنستخدم نفس صدفة فلاسك المُشغَّلة أصلًا، كما من الممكن فتح صَدفة جديدة بعد التأكد من تشغيل البيئة الافتراضية ضمن المجلد <code>flask_app</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_34" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ flask shell</span></pre>

<p>
	لإضافة طالب إلى قاعدة البيانات، لا بُدّ من استيراد كل من كائن قاعدة البيانات والنموذج <code>Student</code>، ثمّ بعد ذلك استنساخ النموذج لنمرر إليه بيانات الطالب عبر وسطائه الأساسية، على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_36" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">from</span><span class="pln"> app </span><span class="kwd">import</span><span class="pln"> db</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Student</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> student_john </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Student</span><span class="pun">(</span><span class="pln">firstname</span><span class="pun">=</span><span class="str">'john'</span><span class="pun">,</span><span class="pln"> lastname</span><span class="pun">=</span><span class="str">'doe'</span><span class="pun">,</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">                        email</span><span class="pun">=</span><span class="str">'jd@example.com'</span><span class="pun">,</span><span class="pln"> age</span><span class="pun">=</span><span class="lit">23</span><span class="pun">,</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">                        bio</span><span class="pun">=</span><span class="str">'Biology student'</span><span class="pun">)</span></pre>

<p>
	إذ يمثّل الكائن <code>student_john</code> الطالب المُراد إضافته إلى قاعدة البيانات، إلّا أنّ هذا الكائن لم يُنسخ إلى قاعدة البيانات بعد، لذا سنتحقّق من هذا الكائن في صدفة فلاسك للحصول على السلسلة النصية المُمثّلة له والتي أنشأناها باستخدام التابع <code>()__repr__</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_38" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> student_john</span></pre>

<p>
	فيظهر لنا خرج بالشّكل:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_7940_40" style=""><span class="tag">&lt;Student</span><span class="pln"> </span><span class="atn">john</span><span class="tag">&gt;</span></pre>

<p>
	ومن الممكن الحصول على قيم الأعمدة باستخدام سمات الصنف المُعرّفة أصلًا في النموذج <code>Student</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_42" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> student_john</span><span class="pun">.</span><span class="pln">firstname
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> student_john</span><span class="pun">.</span><span class="pln">bio</span></pre>

<p>
	ويكون الخرج:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_44" style=""><span class="str">'john'</span><span class="pln">
</span><span class="str">'Biology student'</span></pre>

<p>
	بما أن هذا الطالب لم يُضاف إلى قاعدة البيانات بعد، ستكون قيمة مُعرّفه <code>None</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_46" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">student_john</span><span class="pun">.</span><span class="pln">id</span><span class="pun">)</span></pre>

<p>
	ويكون الخرج:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_48" style=""><span class="kwd">None</span></pre>

<p>
	لإضافة هذا الطالب إلى قاعدة البيانات، يجب إضافته أولًا إلى جلسة قاعدة بيانات database session التي تدير إجراءات قاعدة البيانات، إذ توفّر الإضافة Flask-SQLAlchemy الكائن <code>db.session</code>، الذي ندير تغيرات قاعدة البيانات من خلاله.
</p>

<p>
	نضيف الكائن <code>student_john</code> إلى الجلسة باستخدام التابع <code>()db.session.add</code> الذي يجهّزه للنسخ على قاعدة البيانات:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_52" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> db</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="pln">student_john</span><span class="pun">)</span></pre>

<p>
	سيتطلّب إدخال الطالب في قاعدة البيانات استخدام التعليمة <code>INSERT</code>، ولكن لن يُخصَّص معرّف قبل تأكيد إجراءات قاعدة البيانات، ولتأكيد الإجراءات وحفظ تغيرات قاعدة البيانات، سنستخدم التابع <code>()db.session.commit</code> كما يلي:
</p>

<pre class="ipsCode">&gt;&gt;&gt; db.session.commit()
</pre>

<p>
	والآن وبعد إضافة الطالب John إلى قاعدة البيانات، أصبح من الممكن الحصول على معرّفه، بالشّكل:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3095_20" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">student_john</span><span class="pun">.</span><span class="pln">id</span><span class="pun">)</span></pre>

<p>
	ويكون الخرج:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_56" style=""><span class="lit">1</span></pre>

<p>
	كما من الممكن استخدام التابع <code>()db.session.add</code> لتحرير عنصر ما في قاعدة البيانات، فعلى سبيل المثال، من الممكن تعديل البريد الإلكتروني الخاص بالطالب على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_58" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> student_john</span><span class="pun">.</span><span class="pln">email </span><span class="pun">=</span><span class="pln"> </span><span class="str">'john_doe@example.com'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> db</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="pln">student_john</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> db</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span></pre>

<p>
	سنضيف الآن باستخدام صدفة فلاسك بضعة طلاب إلى قاعدة البيانات:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_60" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> sammy </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Student</span><span class="pun">(</span><span class="pln">firstname</span><span class="pun">=</span><span class="str">'Sammy'</span><span class="pun">,</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">                lastname</span><span class="pun">=</span><span class="str">'Shark'</span><span class="pun">,</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">                email</span><span class="pun">=</span><span class="str">'sammyshark@example.com'</span><span class="pun">,</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">                age</span><span class="pun">=</span><span class="lit">20</span><span class="pun">,</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">                bio</span><span class="pun">=</span><span class="str">'Marine biology student'</span><span class="pun">)</span><span class="pln">

</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> carl </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Student</span><span class="pun">(</span><span class="pln">firstname</span><span class="pun">=</span><span class="str">'Carl'</span><span class="pun">,</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">                lastname</span><span class="pun">=</span><span class="str">'White'</span><span class="pun">,</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">                email</span><span class="pun">=</span><span class="str">'carlwhite@example.com'</span><span class="pun">,</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">                age</span><span class="pun">=</span><span class="lit">22</span><span class="pun">,</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln">                bio</span><span class="pun">=</span><span class="str">'Marine geology student'</span><span class="pun">)</span><span class="pln">

</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> db</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="pln">sammy</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> db</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="pln">carl</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> db</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span></pre>

<p>
	من الممكن الآن الاستعلام عن كافّة السجلات في جدول الطلاب باستخدام السمة <code>query</code> مع التابع <code>()all</code>، كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7940_67" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="typ">Student</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">all</span><span class="pun">()</span></pre>

<p>
	فيظهر الخرج التالي:
</p>

<pre class="ipsCode" id="ips_uid_3095_22">[&lt;Student john&gt;, &lt;Student Sammy&gt;, &lt;Student Carl&gt;]</pre>

<p>
	ومع نهاية هذه الخطوة أصبح لدينا ثلاثة طلاب في قاعدة البيانات، وسنعمل في الخطوة التالية على إنشاء وجهة فلاسك للصفحة الرئيسية للتطبيق بغية عرض جميع الطلاب الموجودين ضمن قاعدة البيانات في هذه الصفحة.
</p>

<h2>
	الخطوة 3 – عرض جميع السجلات
</h2>

<p>
	سنعمل في هذه الخطوة على إنشاء وجهة وقالب لعرض كافّة الطلاب الموجودين في قاعدة البيانات ضمن الصفحة الرئيسية للتطبيق. لذلك، نبقي صَدفة فلاسك قيد التشغيل، ونفتح نافذة طرفية جديدة.
</p>

<p>
	نفتح ملف <code>app.py</code> لنضيف إليه وجهةً للصفحة الرئيسية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_71" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ونضيف الوجهة التالية إلى نهاية الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_73" style=""><span class="com"># ...</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> index</span><span class="pun">():</span><span class="pln">
    students </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Student</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'index.html'</span><span class="pun">,</span><span class="pln"> students</span><span class="pun">=</span><span class="pln">students</span><span class="pun">)</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	استخدمنا في الشيفرة السابقة المُزخرف <code>()app.route</code> لإنشاء دالة عرض باسم <code>()index</code>، والتي نستعلم فيها ضمن قاعدة البيانات لجلب كافّة الطلاب باستخدام السمة <code>query</code> من النموذج <code>Student</code>، التي تمكننا من جلب عنصر أو أكثر من قاعدة البيانات باستخدام توابع مُختلفة، واستخدمنا التابع <code>()all</code> لجلب كافّة مُدخلات الطلاب من قاعدة البيانات، لنخزّن نتائج الاستعلام ضمن متغير باسم <code>students</code> ممررين إياه إلى ملف قالب باسم <code>index.html</code> والذي سنصيّره باستخدام الدالة المساعدة <code>()render_template</code>.
</p>

<p>
	الآن، وقبل إنشاء ملف القالب <code>index.html</code>المسؤول عن عرض الطلاب الموجودين ضمن قاعدة البيانات في الصفحة الرئيسية للتطبيق، سننشئ ملف قالب رئيسي ليتضمّن كافة شيفرات HTML الأساسية اللازمة لترثها لاحقًا القوالب الأُخرى وهذا ما يجنبنا تكرار الشيفرات، ثمّ سننشئ ملف قالب الصفحة الرئيسية <code>index.html</code> المُصيّر أصلًا باستخدام الدالة <code>()index</code>.
</p>

<p>
	لذا سننشئ مجلدًا للقوالب باسم <code>templates</code>، وسننشئ ضمنه ملف قالب باسم <code>base.html</code>، الذي سيمثّل القالب الأساسي لبقية القوالب:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_75" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ mkdir templates
</span><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">base</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	وسنكتب فيه الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_7940_77" style=""><span class="dec">&lt;!DOCTYPE html&gt;</span><span class="pln">
</span><span class="tag">&lt;html</span><span class="pln"> </span><span class="atn">lang</span><span class="pun">=</span><span class="atv">"en"</span><span class="tag">&gt;</span><span class="pln">
</span><span class="tag">&lt;head&gt;</span><span class="pln">
    </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">"UTF-8"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;title&gt;</span><span class="pln">{% block title %} {% endblock %} - FlaskApp</span><span class="tag">&lt;/title&gt;</span><span class="pln">
    </span><span class="tag">&lt;style&gt;</span><span class="pln">
        </span><span class="pun">.</span><span class="pln">title </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">margin</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5px</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        </span><span class="pun">.</span><span class="pln">content </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">margin</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">width</span><span class="pun">:</span><span class="pln"> </span><span class="lit">100%</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">display</span><span class="pun">:</span><span class="pln"> flex</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">flex-direction</span><span class="pun">:</span><span class="pln"> row</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">flex-wrap</span><span class="pun">:</span><span class="pln"> wrap</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        </span><span class="pun">.</span><span class="pln">student </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">flex</span><span class="pun">:</span><span class="pln"> </span><span class="lit">20%</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">padding</span><span class="pun">:</span><span class="pln"> </span><span class="lit">10px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">margin</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">background-color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#f3f3f3</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">inline-size</span><span class="pun">:</span><span class="pln"> </span><span class="lit">100%</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        </span><span class="pun">.</span><span class="pln">bio </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">padding</span><span class="pun">:</span><span class="pln"> </span><span class="lit">10px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">margin</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">background-color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#ffffff</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#004835</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        </span><span class="pun">.</span><span class="pln">name a </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#00a36f</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">text-decoration</span><span class="pun">:</span><span class="pln"> none</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        nav a </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#d64161</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">font-size</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3em</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">margin-left</span><span class="pun">:</span><span class="pln"> </span><span class="lit">50px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">text-decoration</span><span class="pun">:</span><span class="pln"> none</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="tag">&lt;/style&gt;</span><span class="pln">
</span><span class="tag">&lt;/head&gt;</span><span class="pln">
</span><span class="tag">&lt;body&gt;</span><span class="pln">
    </span><span class="tag">&lt;nav&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('index') }}"</span><span class="tag">&gt;</span><span class="pln">FlaskApp</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"#"</span><span class="tag">&gt;</span><span class="pln">Create</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"#"</span><span class="tag">&gt;</span><span class="pln">About</span><span class="tag">&lt;/a&gt;</span><span class="pln">
    </span><span class="tag">&lt;/nav&gt;</span><span class="pln">
    </span><span class="tag">&lt;hr&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"content"</span><span class="tag">&gt;</span><span class="pln">
        {% block content %} {% endblock %}
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
</span><span class="tag">&lt;/body&gt;</span><span class="pln">
</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	يتضمّن القالب الأساسي هذا كافّة الشيفرات المتداولة التي سنحتاجها في القوالب الأُخرى.
</p>

<p>
	ستُستبدل كتلة العنوان <code>title</code> لاحقًا بعنوان كل صفحة وكتلة المحتوى <code>content</code> بمحتواها، أمّا عن شريط التصفح فسيتضمّن ثلاثة روابط، الأوّل ينقل المُستخدم إلى الصفحة الرئيسية للتطبيق باستخدام الدالة المساعدة <code>()url_for</code>، والثاني إلى صفحة إضافة طالب جديد <strong>Create</strong>، أمّا الأخير لصفحة المعلومات حول التطبيق <strong>About</strong> في حال قررت تضمينها في تطبيقك، مع ملاحظة أنّنا سنعدّل هذا الملف لاحقًا بعد إنشاء صفحة إضافة طالب جديد بغية جعل الرابط إلى هذه الصفحة فعّالًا.
</p>

<p>
	الآن، سننشئ ملف قالب باسم <code>index.html</code> وهو الاسم الذي حددناه في الملف <code>app.py</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_79" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">index</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	ونضيف ضمنه الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_81" style=""><span class="pun">{%</span><span class="pln"> extends </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">

</span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">h1 </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"title"</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="typ">Students</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"content"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> student </span><span class="kwd">in</span><span class="pln"> students </span><span class="pun">%}</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"student"</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;&lt;</span><span class="pln">b</span><span class="pun">&gt;#{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">id </span><span class="pun">}}&lt;/</span><span class="pln">b</span><span class="pun">&gt;&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">b</span><span class="pun">&gt;</span><span class="pln">
                    </span><span class="pun">&lt;</span><span class="pln">p </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"name"</span><span class="pun">&gt;{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">firstname </span><span class="pun">}}</span><span class="pln"> </span><span class="pun">{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">lastname </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;/</span><span class="pln">b</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">email </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">age </span><span class="pun">}}</span><span class="pln"> years old</span><span class="pun">.&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="typ">Joined</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">created_at </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"bio"</span><span class="pun">&gt;</span><span class="pln">
                    </span><span class="pun">&lt;</span><span class="pln">h4</span><span class="pun">&gt;</span><span class="typ">Bio</span><span class="pun">&lt;/</span><span class="pln">h4</span><span class="pun">&gt;</span><span class="pln">
                    </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">bio </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">{%</span><span class="pln"> endfor </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	اعتمدنا في الشيفرة السابقة على الوراثة من ملف القالب <code>base.html</code> من خلال تعليمة <code>extends</code>، واستبدلنا محتوى كتلة المحتوى <code>content</code> مُستخدمين تنسيق العنوان من المستوى الأوّل <code><a href="https://wiki.hsoub.com/HTML/h1-h6" rel="external">&lt;h1&gt;</a></code> الذي يفي أيضًا بالغرض عنوانًا للصفحة.
</p>

<p>
	استخدمنا في السطر البرمجي <code>{% for post in posts %}</code> حلقة <code>for</code> من تعليمات محرّك القوالب <a href="https://academy.hsoub.com/tags/jinja2" rel="">جينجا jinja</a>، والهدف من استخدام هذه الحلقة هو المرور على كل طالب ضمن المتغير <code>students</code> المُمرّر من الدالة <code>()index</code> إلى هذا القالب، إذ سنعرض معرّف الطالب واسمه الأوّل والأخير وتاريخ إضافته إلى قاعدة البيانات وسيرته الذاتية.
</p>

<p>
	الآن ومع وجودنا ضمن المجلد <code>flask_app</code> ومع كون البيئة الافتراضية مُفعّلة، سنُعلم فلاسك بالتطبيق المُراد تشغيله (وهو في حالتنا الملف app.py) باستخدام متغير البيئة <code>FLASK_APP</code>، في حين يحدّد متغير البيئة <code>FLASK_ENV</code> وضع التشغيل وهنا قد اخترنا الوضع <code>development</code> ما يعني أنّ التطبيق سيعمل في وضع التطوير مع تشغيل مُنقّح الأخطاء. لمزيدٍ من المعلومات حول مُنقّح الأخطاء في فلاسك ننصحك بقراءة المقال <a href="https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A3%D8%AE%D8%B7%D8%A7%D8%A1-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1738/" rel="">كيفية التعامل مع الأخطاء في تطبيقات فلاسك</a>، ولتنفيذ ما سبق سنشغّل الأوامر التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_83" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_APP</span><span class="pun">=</span><span class="pln">app
</span><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_ENV</span><span class="pun">=</span><span class="pln">development</span></pre>

<p>
	والآن سنشغّل التطبيق باستخدام الأمر <code>flask run</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_86" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ flask run</span></pre>

<p>
	وبعد التأكد من كون خادم التطوير ما يزال قيد التشغيل، نذهب إلى الرابط التالي باستخدام المتصفح:
</p>

<pre class="ipsCode" id="ips_uid_3095_25">http://127.0.0.1:5000/</pre>

<p>
	فيظهر لنا الطلاب المُضافين إلى قاعدة البيانات في صفحة مشابهة للصورة التالية:
</p>

<p style="text-align: center;">
	<img alt="ظهور الطلاب المُضافين إلى قاعدة البيانات" class="ipsImage ipsImage_thumbnailed" data-fileid="115936" data-ratio="44.40" data-unique="ogcqxvlh1" style="width: 750px; height: auto;" width="750" src="https://academy.hsoub.com/uploads/monthly_2023_01/1st.thumb.png.65a298e5e1f0f11f4776220896a7a142.png">
</p>

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

<h2>
	الخطوة 4 – عرض سجل مفرد
</h2>

<p>
	سنستخدم في هذه الخطوة صدفة فلاسك للاستعلام عن الطلاب من خلال معرّفاتهم، كما سننشئ وجهة وقالب لعرض التفاصيل الخاصة بكل طالب في صفحة مُخصّصة لذلك.
</p>

<p>
	سيعرض الرابط http://127.0.0.1:5000/1 في نهاية هذه الخطوة صفحةً تعرض الطالب الأول (لأن الرابط يحوي على المعرّف رقم 1)، بينما يعرض الرابط http://127.0.0.1:5000/ID الطالب ذو المعرّف ID الموافق له إن وُجد.
</p>

<p>
	نبقي خادم التطوير قيد التشغيل، ونفتح نافذة طرفية جديدة.
</p>

<p>
	الآن سنفتح صدفة فلاسك لنبيّن كيفيّة الاستعلام عن الطلاب، كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_91" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ flask shell</span></pre>

<p>
	توفّر الإضافة Flask-SQLAlchemy السمة <code>query</code> ضمن صنف النموذج للاستعلام عن السجلات وجلب البيانات الموافقة من قاعدة البيانات، ويمكن استخدام توابع هذه السمة لجلب السجلات وفقًا لمُرشّح مُعيّن. على سبيل المثال، من الممكن استخدام تابع الترشيح <code>()filter_by</code> مع معامل مثل <code>firstname</code> الذي يوافق عمودًا معينًا في الجدول مع استخدام وسيط لجلب طالب معيّن:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_93" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">from</span><span class="pln"> app </span><span class="kwd">import</span><span class="pln"> db</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Student</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="typ">Student</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">filter_by</span><span class="pun">(</span><span class="pln">firstname</span><span class="pun">=</span><span class="str">'Sammy'</span><span class="pun">).</span><span class="pln">all</span><span class="pun">()</span></pre>

<p>
	فيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode" id="ips_uid_6602_7">[&lt;Student Sammy&gt;]</pre>

<p>
	جلبنا في الشيفرة السابقة جميع الطلاب الذين يحملون القيمة <code>Sammy</code> في الاسم الأوّل، كما استخدمنا التابع <code>()all</code> للحصول على قائمة بجميع النتائج الموافقة، أمّا للحصول على النتيجة الأولى فقط (وهي النتيجة الوحيدة في حالتنا)، نستخدم التابع <code>()first</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_99" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="typ">Student</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">filter_by</span><span class="pun">(</span><span class="pln">firstname</span><span class="pun">=</span><span class="str">'Sammy'</span><span class="pun">).</span><span class="pln">first</span><span class="pun">()</span></pre>

<p>
	فيكون الخرج بالشّكل:
</p>

<pre class="ipsCode" id="ips_uid_3095_27">&lt;Student Sammy&gt;</pre>

<p>
	يمكننا استخدام التعليمة <code>(filter_by(id=ID</code> للحصول على الطالب من خلال معرّفه على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_103" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="typ">Student</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">filter_by</span><span class="pun">(</span><span class="pln">id</span><span class="pun">=</span><span class="lit">3</span><span class="pun">).</span><span class="pln">first</span><span class="pun">()</span></pre>

<p>
	أو يمكننا استخدام التابع الأقصر <code>()get</code> والذي يمكنّنا من جلب عنصر معين باستخدام مفتاحه الأساسي، كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_105" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="typ">Student</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="lit">3</span><span class="pun">)</span></pre>

<p>
	ستعطي كلا الطريقتين الخرج نفسه:
</p>

<pre class="ipsCode" id="ips_uid_3095_30">&lt;Student Carl&gt;</pre>

<p>
	أصبح من الممكن الآن الخروج من الصدفة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_111" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> exit</span><span class="pun">()</span></pre>

<p>
	لجلب بيانات طالب من خلال معرّفه، أنشأنا وجهةً جديدةً تُصيّر صفحةً لكل طالب، إذ استخدمنا التابع <code>()get_or_404</code> الذي توفرّه الإضافة Flask-SQLAlchemy بديلًا للتابع <code>()get</code> التقليدي؛ إذ يكمن الفرق بينهما في أنّ التابع <code>()get</code> يعيد القيمة <code>None</code> في حال عدم وجود نتيجة موافقة للمعرّف المطلوب، في حين يعيد التابع <code>()get_or_404</code> خطأ HTTP من النوع ‎404 Not Found، لذا سنفتح الملف <code>app.py</code> لتعديله، بالشّكل:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_113" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ونضيف الوجهة التالية إلى نهاية الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_115" style=""><span class="com"># ...</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/&lt;int:student_id&gt;/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> student</span><span class="pun">(</span><span class="pln">student_id</span><span class="pun">):</span><span class="pln">
    student </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Student</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">get_or_404</span><span class="pun">(</span><span class="pln">student_id</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'student.html'</span><span class="pun">,</span><span class="pln"> student</span><span class="pun">=</span><span class="pln">student</span><span class="pun">)</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	استخدمنا في الشيفرة السابقة الوجهة <code>/&lt;int:student_id&gt;/</code>، إذ يمثّل المعامل <code>:int</code> محوّلًا لتحويل السلسلة النصية الافتراضية في الرابط إلى نوع عدد صحيح، ويمثل <code>student_id</code> متغير الرابط الذي سيُحدّد الطالب المطلوب عرضه في الصفحة.
</p>

<p>
	مررنا المعرّف من الرابط إلى دالة العرض <code>()student</code> من خلال المعامل <code>student_id</code>، التي نستعلم فيها عن مجموعة الطلاب لنجلب منها الطالب من خلال المعرّف الخاص به باستخدام التابع <code>()get_or_404</code>، الأمر الذي يؤدي إلى حفظ بيانات الطالب (في حال وجوده) ضمن المتغير <code>student</code>، وإلّا ستُعرض استجابة خطأ HTTP من النوع ‎404 Not Found في حال عدم وجود طالب موافق للمعرّف المطلوب في قاعدة البيانات، ثمّ صيّرنا أخيرًا قالبًا باسم <code>student.html</code> ممررين إليه بيانات الطالب المُستخرجة.
</p>

<p>
	لذا، سنفتح ملف قالب جديد باسم <code>student.html</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_117" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">student</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	ونكتب فيه الشيفرة التالية، والأمر مشابه للقالب <code>index.html</code> باستثناء أنّ هذا الملف سيعرض طالبًا واحدًا كل مرة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_119" style=""><span class="pun">{%</span><span class="pln"> extends </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">

</span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">span </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"title"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">h1</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="pun">{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">firstname </span><span class="pun">}}</span><span class="pln"> </span><span class="pun">{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">lastname </span><span class="pun">}}{%</span><span class="pln"> endblock </span><span class="pun">%}&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">span</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"content"</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"student"</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;&lt;</span><span class="pln">b</span><span class="pun">&gt;#{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">id </span><span class="pun">}}&lt;/</span><span class="pln">b</span><span class="pun">&gt;&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">b</span><span class="pun">&gt;</span><span class="pln">
                    </span><span class="pun">&lt;</span><span class="pln">p </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"name"</span><span class="pun">&gt;{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">firstname </span><span class="pun">}}</span><span class="pln"> </span><span class="pun">{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">lastname </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;/</span><span class="pln">b</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">email </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">age </span><span class="pun">}}</span><span class="pln"> years old</span><span class="pun">.&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="typ">Joined</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">created_at </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"bio"</span><span class="pun">&gt;</span><span class="pln">
                    </span><span class="pun">&lt;</span><span class="pln">h4</span><span class="pun">&gt;</span><span class="typ">Bio</span><span class="pun">&lt;/</span><span class="pln">h4</span><span class="pun">&gt;</span><span class="pln">
                    </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">bio </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	اعتمدنا في الشيفرة السابقة على الوراثة من ملف القالب <code>base.html</code> من خلال تعليمة <code>extends</code>، وجعلنا اسم الطالب الكامل عنوانًا للصفحة، كما عرضنا كلًا من معرّف الطالب واسمه الأول والأخير وبريده الإلكتروني وتاريخ إنشاء السجل وسيرته الذاتية.
</p>

<p>
	ننتقل الآن باستخدام متصفح الويب إلى رابط الطالب الثاني كما يلي:
</p>

<pre class="ipsCode" id="ips_uid_3095_32">http://127.0.0.1:5000/2</pre>

<p>
	فتظهر صفحةٌ مشابهةٌ لما يلي:
</p>

<p style="text-align: center;">
	<img alt="ظهور الصفحة السجل المفرد حسب فلاسك" class="ipsImage ipsImage_thumbnailed" data-fileid="115937" data-ratio="46.13" data-unique="mgtjh89oc" style="width: 750px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2023_01/2nd.thumb.png.b1b58562f6ee7d6838823cbc5417b7ca.png">
</p>

<p>
	ومن ثمّ سنحرّر ملف القالب <code>index.html</code> لنربط اسم كل طالب مع الصفحة الخاصة به:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_125" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">index</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	ونعدّل حلقة <code>for</code> التكرارية على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_127" style=""><span class="pun">{%</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> student </span><span class="kwd">in</span><span class="pln"> students </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"student"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;&lt;</span><span class="pln">b</span><span class="pun">&gt;#{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">id </span><span class="pun">}}&lt;/</span><span class="pln">b</span><span class="pun">&gt;&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">b</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">p </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"name"</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">a href</span><span class="pun">=</span><span class="str">"{{ url_for('student', student_id=student.id)}}"</span><span class="pun">&gt;</span><span class="pln">
                    </span><span class="pun">{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">firstname </span><span class="pun">}}</span><span class="pln"> </span><span class="pun">{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">lastname </span><span class="pun">}}</span><span class="pln">
                </span><span class="pun">&lt;/</span><span class="pln">a</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">b</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">email </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">age </span><span class="pun">}}</span><span class="pln"> years old</span><span class="pun">.&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="typ">Joined</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">created_at </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"bio"</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">h4</span><span class="pun">&gt;</span><span class="typ">Bio</span><span class="pun">&lt;/</span><span class="pln">h4</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">bio </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endfor </span><span class="pun">%}</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	أضفنا في الشيفرة السابقة وسم الرابط <code><a href="https://wiki.hsoub.com/HTML/a" rel="external">&lt;a&gt;</a></code> إلى الاسم الكامل للطالب، بغية ربطه مع صفحة الطالب الموافقة باستخدام الدالة <code>()url_for</code>، ممررين معرّف الطالب ID المُخزّن في الكائن <code>student.id</code> إلى دالة العرض <code>()student</code>.
</p>

<p>
	ننتقل إلى الصفحة الرئيسية للتطبيق ونحدّثها:
</p>

<pre class="ipsCode" id="ips_uid_3095_34">http://127.0.0.1:5000/</pre>

<p>
	وبذلك نلاحظ أنّ اسم كل طالب أصبح مرتبطًا بصفحة الطالب الموافقة.
</p>

<p>
	الآن، وبعد الانتهاء من إنشاء صفحة لكل طالب، سنعمل في الخطوة التالية على إنشاء صفحة لإضافة طلاب جدد إلى قاعدة البيانات.
</p>

<h2>
	الخطوة 5 – إنشاء سجل جديد
</h2>

<p>
	سنعمل في هذه الخطوة على إضافة وجهة جديدة للتطبيق بغية إضافة طلاب جدد إلى قاعدة البيانات باستخدام نماذج ويب، إذ سنعمل على تصيير صفحة تتضمّن نموذج ويب يمكّن المستخدمين من إدخال بيانات الطالب، ثم سنوفّر الآليات اللازمة للتعامل مع النموذج المُرسل، وسننشئ كائنًا للطالب الجديد المضاف باستخدام النموذج <code>Student</code> لنضيفه إلى الجلسة، ونهايةً سنحفظ الإجراءات بما يشبه الآلية المُتبعة لدى إضافة مُدخلات الطالب في الخطوة 2 من هذا المقال.
</p>

<p>
	سنفتح نافذة طرفية جديدة مع بقاء خادم التطوير قيد التشغيل، وسنفتح منها بدايةً الملف <code>app.py</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_131" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	وسنضيف في نهايته الوجهة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_133" style=""><span class="com"># ...</span><span class="pln">


</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/create/'</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=(</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">))</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> create</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'create.html'</span><span class="pun">)</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	نمرّر في هذه الوجهة متغير صف tuple يحتوي على القيم <code>('GET', 'POST')</code> إلى المعامل <code>methods</code> بغية السماح بكلا نوعي طلبيات HTTP وهما <code>GET</code> و <code>POST</code>، إذ تتخصّص الطلبيات من النوع <code>GET</code> بجلب البيانات من الخادم، أمّا الطلبيات من النوع <code>POST</code> فهي مُتخصّصة بإرسال البيانات إلى وجهة مُحدّدة، مع ملاحظة أنّ الطلبيات من النوع <code>GET</code> هي الوحيدة المسموحة افتراضيًا، وحالما يطلب المستخدم الوجهة <code>create/</code> باستخدام طلبية من النوع <code>GET</code>، سيُصيّر ملف قالب باسم "create.html". سنعدّل هذه الوجهة لاحقًا لتتعامل أيضًا مع الطلبيات <code>POST</code> اللازمة لدى ملء المُستخدمين للنماذج وإرسالها بغية إضافة طلاب جدد.
</p>

<p>
	الآن، سننشئ ملفًا باسم "create.html" داخل مجلد القوالب "templates" على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_135" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">create</span><span class="pun">.</span><span class="pln">html</span></pre>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_137" style=""><span class="pun">{%</span><span class="pln"> extends </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">

</span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">h1 style</span><span class="pun">=</span><span class="str">"width: 100%"</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="typ">Add</span><span class="pln"> a </span><span class="typ">New</span><span class="pln"> </span><span class="typ">Student</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">form method</span><span class="pun">=</span><span class="str">"post"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">label </span><span class="kwd">for</span><span class="pun">=</span><span class="str">"firstname"</span><span class="pun">&gt;</span><span class="typ">First</span><span class="pln"> </span><span class="typ">Name</span><span class="pun">&lt;/</span><span class="pln">label</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"text"</span><span class="pln"> name</span><span class="pun">=</span><span class="str">"firstname"</span><span class="pln">
                   placeholder</span><span class="pun">=</span><span class="str">"First name"</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">input</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">

        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">label </span><span class="kwd">for</span><span class="pun">=</span><span class="str">"lastname"</span><span class="pun">&gt;</span><span class="typ">Last</span><span class="pln"> </span><span class="typ">Name</span><span class="pun">&lt;/</span><span class="pln">label</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"text"</span><span class="pln"> name</span><span class="pun">=</span><span class="str">"lastname"</span><span class="pln">
                   placeholder</span><span class="pun">=</span><span class="str">"Last name"</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">input</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">

        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">label </span><span class="kwd">for</span><span class="pun">=</span><span class="str">"email"</span><span class="pun">&gt;</span><span class="typ">Email</span><span class="pun">&lt;/</span><span class="pln">label</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"email"</span><span class="pln"> name</span><span class="pun">=</span><span class="str">"email"</span><span class="pln">
                   placeholder</span><span class="pun">=</span><span class="str">"Student email"</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">input</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">

        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">label </span><span class="kwd">for</span><span class="pun">=</span><span class="str">"age"</span><span class="pun">&gt;</span><span class="typ">Age</span><span class="pun">&lt;/</span><span class="pln">label</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"number"</span><span class="pln"> name</span><span class="pun">=</span><span class="str">"age"</span><span class="pln">
                   placeholder</span><span class="pun">=</span><span class="str">"Age"</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">input</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">

        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">label </span><span class="kwd">for</span><span class="pun">=</span><span class="str">"bio"</span><span class="pun">&gt;</span><span class="typ">Bio</span><span class="pun">&lt;/</span><span class="pln">label</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">br</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">textarea name</span><span class="pun">=</span><span class="str">"bio"</span><span class="pln">
                  placeholder</span><span class="pun">=</span><span class="str">"Bio"</span><span class="pln">
                  rows</span><span class="pun">=</span><span class="str">"15"</span><span class="pln">
                  cols</span><span class="pun">=</span><span class="str">"60"</span><span class="pln">
                  </span><span class="pun">&gt;&lt;/</span><span class="pln">textarea</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">button type</span><span class="pun">=</span><span class="str">"submit"</span><span class="pun">&gt;</span><span class="typ">Submit</span><span class="pun">&lt;/</span><span class="pln">button</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">form</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	اعتمدنا في الشيفرة السابقة على الوراثة من ملف القالب <code>base.html</code> من خلال تعليمة <code>extends</code>، كما عيّنا ترويسة لتكون عنوانًا، واستخدمنا <a href="https://wiki.hsoub.com/HTML/form" rel="external">الوسم &lt;form&gt; </a>وفيه ضبطنا السمة <code>method</code> التي تُحدّد نوع طلبية HTTP لتكون من النوع <code>post</code>، ما يعني أنّ نموذج الويب هذا سيرسل طلب من النوع "POST".
</p>

<p>
	كما أضفنا صندوقي حقل نصي باسم <code>firstname</code> و <code>lastname</code>، إذ سنستخدم هذه الأسماء لاحقًا للوصول إلى بيانات النموذج الذي أرسلها المستخدم في دالة العرض، كما أضفنا حقلًا نصيًا للبريد الالكتروني باسم <code>email</code>، وحقل بيانات عددية لإدخال عمر الطالب، وحقلاً نصياً مُتعدّد الأسطر لإضافة السيرة الذاتية للطالب، وأخيراً أضفنا زرًا لتأكيد النموذج وإرساله <strong>Submit</strong> إلى نهاية النموذج.
</p>

<p>
	الآن، وأثناء عمل خادم التطوير، نستخدم المتصفح للانتقال إلى الوجهة <code>‎/create</code>:
</p>

<pre class="ipsCode" id="ips_uid_3095_37">http://127.0.0.1:5000/create</pre>

<p>
	فستظهر صفحة إضافة طالب جديد <strong>Add a New Student</strong> تتضمّن نموذج ويب، وزرًا لتأكيد إرسال هذا النموذج <strong>Submit</strong>، كما في الشّكل:
</p>

<p style="text-align: center;">
	<img alt="ظهور صفحة إضافة طالب جديد تتضمّن نموذج ويب" class="ipsImage ipsImage_thumbnailed" data-fileid="115938" data-ratio="57.87" data-unique="f2rwtw9l6" style="width: 750px; height: auto;" width="750" src="https://academy.hsoub.com/uploads/monthly_2023_01/3rd.thumb.png.b883cf583623266b8a80acae5536413c.png">
</p>

<p>
	يرسل نموذج الإدخال هذا طلبًا من النوع POST إلى الخادم، ولكن حتى هذه اللحظة لا يوجد شيفرة مسؤولة عن معالجة هذا الطلب في الوجهة <code>create/</code>، وبالتالي لن يحدث شيء في حال ملء النموذج الآن وإرساله.
</p>

<p>
	الآن، سنفتح الملف <code>app.py</code> بهدف تعديله ليتعامل مع الطلبات من النوع POST المُرسلة من قبل المستخدم:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_142" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ونعدّل الوجهة <code>create/</code> لتصبح كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_144" style=""><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/create/'</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=(</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">))</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> create</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">method </span><span class="pun">==</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">:</span><span class="pln">
        firstname </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'firstname'</span><span class="pun">]</span><span class="pln">
        lastname </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'lastname'</span><span class="pun">]</span><span class="pln">
        email </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'email'</span><span class="pun">]</span><span class="pln">
        age </span><span class="pun">=</span><span class="pln"> int</span><span class="pun">(</span><span class="pln">request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'age'</span><span class="pun">])</span><span class="pln">
        bio </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'bio'</span><span class="pun">]</span><span class="pln">
        student </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Student</span><span class="pun">(</span><span class="pln">firstname</span><span class="pun">=</span><span class="pln">firstname</span><span class="pun">,</span><span class="pln">
                          lastname</span><span class="pun">=</span><span class="pln">lastname</span><span class="pun">,</span><span class="pln">
                          email</span><span class="pun">=</span><span class="pln">email</span><span class="pun">,</span><span class="pln">
                          age</span><span class="pun">=</span><span class="pln">age</span><span class="pun">,</span><span class="pln">
                          bio</span><span class="pun">=</span><span class="pln">bio</span><span class="pun">)</span><span class="pln">
        db</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="pln">student</span><span class="pun">)</span><span class="pln">
        db</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">

        </span><span class="kwd">return</span><span class="pln"> redirect</span><span class="pun">(</span><span class="pln">url_for</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">))</span><span class="pln">

    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'create.html'</span><span class="pun">)</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	تُعامل الطلبيات من النوع <code>POST</code> ضمن العبارة الشرطية <code>if request.method == 'POST'</code>، إذ يُستخرج كل من اسم الطالب الأوّل والأخير وبريده الإلكتروني وعمره وسيرته الذاتية التي أرسلها المُستخدم، من الكائن <code>request.form</code>، ليُحوّل العمر المُمرّر مثل سلسة نصيّة إلى نوع عدد صحيح باستخدام دالة بايثون<a href="https://docs.python.org/3.8/library/functions.html#int" rel="external nofollow"><code>()int</code></a>، ليُبنى بعدها كائن للطالب باسم <code>student</code> باستخدام النموذج <code>Student</code>، ومن ثم نضيف هذا الكائن إلى جلسة قاعدة البيانات، ونحفظ التغييرات.
</p>

<p>
	ونهايةً، نعيد توجيه المستخدم إلى الصفحة الرئيسية للتطبيق فيظهر الطالب المُضاف أسفل الطلاب الموجودين أصلًا.
</p>

<p>
	الآن وأثناء عمل خادم التطوير، نستخدم المتصفح للانتقال إلى الوجهة <code>‎/create</code>:
</p>

<pre class="ipsCode" id="ips_uid_3095_39">http://127.0.0.1:5000/create</pre>

<p>
	نملأ النموذج ببعض البيانات ونرسله، فيُعاد التوجيه إلى الصفحة الرئيسية، إذ سنجد الطالب المُضاف للتو، وبذلك أصبحت لدينا الآلية اللازمة لإضافة طلاب جدد، الآن، لا بدّ من إضافة رابط إلى صفحة الإضافة <strong>Create</strong> في شريط التصفح، لذا نفتح ملف القالب <code>base.html</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_148" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">base</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	سنحرّر جزء <a href="https://wiki.hsoub.com/HTML/body" rel="external">الوسم &lt;body&gt;</a> بتعديل قيمة سمة الرابط <code>href</code> لتصبح مساوية لرابط صفحة الإنشاء <strong>Create</strong>.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_7940_150" style=""><span class="tag">&lt;body&gt;</span><span class="pln">
    </span><span class="tag">&lt;nav&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('index') }}"</span><span class="tag">&gt;</span><span class="pln">FlaskApp</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('create') }}"</span><span class="tag">&gt;</span><span class="pln">Create</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"#"</span><span class="tag">&gt;</span><span class="pln">About</span><span class="tag">&lt;/a&gt;</span><span class="pln">
    </span><span class="tag">&lt;/nav&gt;</span><span class="pln">
    </span><span class="tag">&lt;hr&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"content"</span><span class="tag">&gt;</span><span class="pln">
        {% block content %} {% endblock %}
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
</span><span class="tag">&lt;/body&gt;</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	وبتحديث الصفحة الرئيسية للتطبيق نلاحظ أنّ الرابط <strong>Create</strong> في شريط التصفّح بات فعّالًا.
</p>

<p>
	ومع نهاية هذه الخطوة أصبح لدينا صفحة تتضمّن نموذج ويب لإضافة طلاب جدد، وللمزيد حول نماذج الويب ننصحك بقراءة المقال <a href="https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%86%D9%85%D8%A7%D8%B0%D8%AC-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1739/" rel="">كيفية استخدام نماذج الويب في تطبيقات فلاسك</a>، ولمزيدٍ من المعلومات المتقدمة ولتتعرّف على كيفية إدارة نماذج الويب بأمان ننصحك بقراءة المقال <a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%86%D9%85%D8%A7%D8%B0%D8%AC-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D9%88%D8%A7%D9%84%D8%AA%D8%AD%D9%82%D9%82-%D9%85%D9%86%D9%87%D8%A7-%D9%81%D9%8A-%D9%81%D9%84%D8%A7%D8%B3%D9%83-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-flask-wtf-r1838/" rel="">استخدام والتحقق من نماذج الويب ذات واجهة المستخدم التفاعلية في فلاسك باستخدام الإضافة Flask-WTF</a>.
</p>

<p>
	سنعمل في الخطوة التالية على إضافة صفحة لتعديل بيانات الطلاب الموجودين أصلًا في قاعدة البيانات.
</p>

<h2>
	الخطوة 6 – تعديل سجل
</h2>

<p>
	سنعمل في هذه الخطوة على إضافة صفحة جديدة إلى التطبيق بغية تعديل بيانات الطلاب الموجودة أصلًا في قاعدة البيانات. إذ سنضيف وجهة جديدة <code>/ID/edit/</code> لتعديل بيانات الطلاب بناءً على معرّف ID كل منهم.
</p>

<p>
	بدايةً، نفتح الملف <code>app.py</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_152" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ثمّ نضيف الوجهة التالية إلى نهاية الملف، والتي ستجلب المُدخل المراد تعديله من بيانات الطالب باستخدام المعرّف الموافق، لتستخرج البيانات الجديدة المُرسلة من نموذج ويب مُخصّص لهذا الغرض سننشئه لاحقًا، لتعدّل بيانات الطالب ومن ثمّ تعيد توجيه المستخدم إلى صفحة التطبيق الرئيسية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_154" style=""><span class="com"># ...</span><span class="pln">


</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/&lt;int:student_id&gt;/edit/'</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=(</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">))</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> edit</span><span class="pun">(</span><span class="pln">student_id</span><span class="pun">):</span><span class="pln">
    student </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Student</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">get_or_404</span><span class="pun">(</span><span class="pln">student_id</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">method </span><span class="pun">==</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">:</span><span class="pln">
        firstname </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'firstname'</span><span class="pun">]</span><span class="pln">
        lastname </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'lastname'</span><span class="pun">]</span><span class="pln">
        email </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'email'</span><span class="pun">]</span><span class="pln">
        age </span><span class="pun">=</span><span class="pln"> int</span><span class="pun">(</span><span class="pln">request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'age'</span><span class="pun">])</span><span class="pln">
        bio </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'bio'</span><span class="pun">]</span><span class="pln">

        student</span><span class="pun">.</span><span class="pln">firstname </span><span class="pun">=</span><span class="pln"> firstname
        student</span><span class="pun">.</span><span class="pln">lastname </span><span class="pun">=</span><span class="pln"> lastname
        student</span><span class="pun">.</span><span class="pln">email </span><span class="pun">=</span><span class="pln"> email
        student</span><span class="pun">.</span><span class="pln">age </span><span class="pun">=</span><span class="pln"> age
        student</span><span class="pun">.</span><span class="pln">bio </span><span class="pun">=</span><span class="pln"> bio

        db</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="pln">student</span><span class="pun">)</span><span class="pln">
        db</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">

        </span><span class="kwd">return</span><span class="pln"> redirect</span><span class="pun">(</span><span class="pln">url_for</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">))</span><span class="pln">

    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'edit.html'</span><span class="pun">,</span><span class="pln"> student</span><span class="pun">=</span><span class="pln">student</span><span class="pun">)</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	تتعامل الوجهة <code>/int:student_id&gt;/edit&gt;/</code> مع طلبيات HTTP من النوع <code>POST</code> و <code>GET</code>، إذ تستخدم <code>student_id</code> مثل متغير رابط مسؤول عن تمرير المعرّف ID إلى دالة العرض <code>()edit</code>.
</p>

<p>
	استخدمنا تابع الاستعلام <code>()get_or_404</code> على النموذج <code>Student</code> للحصول على الطالب الموافق للمعرّف المطلوب، والذي سيستجيب بخطأ من النوع "‎404 Not Found" في حال عدم تطابق أي طالب في قاعدة البيانات مع المعرّف المطلوب؛ أمّا في حال وجود طالب موافق للمعرّف المطلوب، فسيُستكمل تنفيذ الشيفرة وصولًا إلى العبارة الشرطية <code>'if request.method == 'POST</code>، فإذا كانت الطلبية من النوع <code>GET</code> فهذا يعني أنّ المُستخدم لم يملأ نموذج التعديل ويرسله، وبالتالي لن يتحقق الشرط السابق وسنتخطى التعليمات الواردة ضمنه وصولًا إلى السطر البرمجي:
</p>

<pre class="ipsCode" id="ips_uid_3095_41"> return render_template('edit.html', student=student)</pre>

<p>
	المسؤول عن تصيير القالب "edit.html" الذي سننشئة لاحقًا، مُمرّرًا إليه كائن الطالب المستخرج من قاعدة البيانات لتُعرض بيانات الطالب الحالية في نموذج الويب.
</p>

<p>
	في حال عدّل المستخدم بيانات الطالب في نموذج التعديل وأرسله، فعندها سيتحقق الشرط <code>if request.method == 'POST'</code> وبالتالي ستُنفّذ التعليمات الواردة ضمنه، إذ ستُستخرج بيانات الطالب المُرسلة من الكائن <code>request.form</code> إلى المتغير الموافق لكل منها، لتُضبط كل سمة من سمات كائن الطالب <code>student</code> لتوافق القيم الجديدة المُرسلة من النموذج عبر تعديل قيم الأعمدة بنفس الآلية المُتبعة في الخطوة 2 من هذا المقال؛ وفي حال عدم تعديل أي من حقول نموذج الويب، ستبقى قيم الأعمدة في قاعدة البيانات على حالها.
</p>

<p>
	بعد ضبط بيانات الطالب إلى تلك الجديدة المرسلة، سنضيف الكائن <code>student</code> إلى جلسة قاعدة البيانات ونحفظ التغييرات، ونهايةً سنعيد توجيه المستخدم إلى الصفحة الرئيسية للتطبيق.
</p>

<p>
	والآن سنعمل على إنشاء صفحة تسمح للمستخدمين بإجراء التعديلات، لذا سننشئ ملف القالب edit.html:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_158" style=""><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">edit</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	سيحتوي هذا الملف الجديد على نموذج ويب مشابه لذلك الموجود في ملف القالب <code>create.html</code>، بحيث يُملأ افتراضيًا بالبيانات الحالية للطالب، لذا سنكتب ضمنه الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_160" style=""><span class="pun">{%</span><span class="pln"> extends </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">

</span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">h1 style</span><span class="pun">=</span><span class="str">"width: 100%"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="typ">Edit</span><span class="pln"> </span><span class="pun">{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">firstname </span><span class="pun">}}</span><span class="pln">
                               </span><span class="pun">{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">lastname </span><span class="pun">}}</span><span class="str">'s Details
        {% endblock %}
    &lt;/h1&gt;
    &lt;form method="post"&gt;
        &lt;p&gt;
            &lt;label for="firstname"&gt;First Name&lt;/label&gt;
            &lt;input type="text" name="firstname"
                   value={{ student.firstname }}
                   placeholder="First name"&gt;
            &lt;/input&gt;
        &lt;/p&gt;

        &lt;p&gt;
            &lt;label for="lastname"&gt;Last Name&lt;/label&gt;
            &lt;input type="text" name="lastname"
                   value={{ student.lastname }}
                   placeholder="Last name"&gt;
            &lt;/input&gt;
        &lt;/p&gt;

        &lt;p&gt;
            &lt;label for="email"&gt;Email&lt;/label&gt;
            &lt;input type="email" name="email"
                   value={{ student.email }}
                   placeholder="Student email"&gt;
            &lt;/input&gt;
        &lt;/p&gt;

        &lt;p&gt;
            &lt;label for="age"&gt;Age&lt;/label&gt;
            &lt;input type="number" name="age"
                   value={{ student.age }}
                   placeholder="Age"&gt;
            &lt;/input&gt;
        &lt;/p&gt;

        &lt;p&gt;
        &lt;label for="bio"&gt;Bio&lt;/label&gt;
        &lt;br&gt;
        &lt;textarea name="bio"
                  placeholder="Bio"
                  rows="15"
                  cols="60"
                  &gt;{{ student.bio }}&lt;/textarea&gt;
        &lt;/p&gt;
        &lt;p&gt;
            &lt;button type="submit"&gt;Submit&lt;/button&gt;
        &lt;/p&gt;
    &lt;/form&gt;
{% endblock %}</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	يضم العنوان كلًا من الاسم الأوّل والأخير للطالب، وتُضبط خاصية القيمة <code>value</code> لكل حقل إدخال وللصندوق النصي المُتعدّد الأسطر المُخصّص للسيرة الذاتية لتساوي القيمة الموافقة من كائن الطالب <code>student</code> المُمرر من الدالة <code>()edit</code> إلى ملف القالب "edit.html".
</p>

<p>
	سننتقل الآن إلى الرابط التالي لنعدّل تفاصيل أوّل طالب:
</p>

<pre class="ipsCode" id="ips_uid_3095_43">http://127.0.0.1:5000/1/edit</pre>

<p>
	فتظهر صفحة مشابه للشكل التالي:
</p>

<p style="text-align: center;">
	<img alt="صفحة تعديل تفاصيل أول طالب" class="ipsImage ipsImage_thumbnailed" data-fileid="115939" data-ratio="61.47" data-unique="szsm7segt" style="width: 750px; height: auto;" width="750" src="https://academy.hsoub.com/uploads/monthly_2023_01/4th.thumb.png.c38e80d92f694d7bc7280de8d2995475.png">
</p>

<p>
	نعدّل بيانات الطالب ونرسل النموذج، فيُعاد توجيهنا إلى الصفحة الرئيسية للتطبيق فنلاحظ أنّ معلومات الطالب قد حُدثّت فعلًا، ثم سنضيف زر تعديل <strong>Edit</strong> أسفل كل طالب في الصفحة الرئيسية لنربط كل منهم بصفحة التعديل الخاصة به، لذا نفتح ملف القالب <code>index.html</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_165" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">index</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	نعدل حلقة <code>for</code> التكرارية في هذا الملف لتبدو تمامًا كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_167" style=""><span class="pun">{%</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> student </span><span class="kwd">in</span><span class="pln"> students </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"student"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;&lt;</span><span class="pln">b</span><span class="pun">&gt;#{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">id </span><span class="pun">}}&lt;/</span><span class="pln">b</span><span class="pun">&gt;&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">b</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">p </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"name"</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">a href</span><span class="pun">=</span><span class="str">"{{ url_for('student', student_id=student.id)}}"</span><span class="pun">&gt;</span><span class="pln">
                    </span><span class="pun">{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">firstname </span><span class="pun">}}</span><span class="pln"> </span><span class="pun">{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">lastname </span><span class="pun">}}</span><span class="pln">
                </span><span class="pun">&lt;/</span><span class="pln">a</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">b</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">email </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">age </span><span class="pun">}}</span><span class="pln"> years old</span><span class="pun">.&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="typ">Joined</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">created_at </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"bio"</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">h4</span><span class="pun">&gt;</span><span class="typ">Bio</span><span class="pun">&lt;/</span><span class="pln">h4</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">bio </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">a href</span><span class="pun">=</span><span class="str">"{{ url_for('edit', student_id=student.id) }}"</span><span class="pun">&gt;</span><span class="typ">Edit</span><span class="pun">&lt;/</span><span class="pln">a</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endfor </span><span class="pun">%}</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	أضفنا في الشيفرة السابقة <a href="https://wiki.hsoub.com/HTML/a" rel="external">وسم الرابط &lt;a&gt;</a> للربط مع دالة العرض <code>()edit</code> ممررين إليها القيمة <code>student.id</code> للربط مع صفحة التعديل لكل طالب باستخدام رابط تعديل صفحة <strong>Edit</strong>.
</p>

<p>
	ومع نهاية هذه الخطوة أصبح لدينا صفحة لتعديل بيانات الطلاب الموجودين في قاعدة البيانات، وسنعمل فيما يلي على إضافة زر حذف <strong>Delete</strong> لحذف طلاب من قاعدة البيانات.
</p>

<h2>
	الخطوة 7– حذف سجل
</h2>

<p>
	سنعمل في هذه الخطوة على إضافة وجهة جديدة وزر حذف <strong>Delete</strong> يسمح بحذف الطلاب الموجودين أصلًا في قاعدة البيانات.
</p>

<p>
	بدايةً، سنضيف وجهةً جديدةً للحذف، وهي <code>‎/id/delete</code> تتعامل مع الطلبات من النوع <code>POST</code>، إذ ستستقبل دالة الحذف الجديدة <code>delete()‎</code> رقم معرّف الطالب ID المراد حذفه ممرّرة إياه إلى تابع الاستعلام <code>()get_or_404</code> في النموذج <code>Student</code> لمعرفة ما إذا كان موجودًا، للاستجابة بصفحة خطأ من النوع "‎404 Not Found" في حال عدم وجود طالب موافق للمعرّف المُمرّر في قاعدة البيانات.
</p>

<p>
	الآن، افتح الملف <code>app.py</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_169" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	أضِف الوجهة التالية إلى نهاية الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_172" style=""><span class="com"># ...</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/&lt;int:student_id&gt;/delete/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> delete</span><span class="pun">(</span><span class="pln">student_id</span><span class="pun">):</span><span class="pln">
    student </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Student</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">get_or_404</span><span class="pun">(</span><span class="pln">student_id</span><span class="pun">)</span><span class="pln">
    db</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">delete</span><span class="pun">(</span><span class="pln">student</span><span class="pun">)</span><span class="pln">
    db</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> redirect</span><span class="pun">(</span><span class="pln">url_for</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">))</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	استخدمنا المزخرف <code>app.post</code> المُقدّم في الإصدار 2.0.0 من فلاسك بدلًا من استخدام المزخرف <code>app.route</code> المعتاد، لإضافة اختصارات للعديد من توابع HTTP الشائعة، فعلى سبيل المثال الأمر:
</p>

<pre class="ipsCode" id="ips_uid_3095_46">@app.post("/login")</pre>

<p>
	هو اختصار للأمر:
</p>

<pre class="ipsCode" id="ips_uid_3095_48">@app.route("/login", methods=["POST"])</pre>

<p>
	ما يعني أن دالة العرض هذه تتعامل فقط مع طلبيات من النوع <code>POST</code>، وبزيارة الوجهة <code>ID/delete/</code> في المتصفّح سيظهر الخطأ "‎405 Method Not Allowed" لأن المتصفحات تستخدم طريقة <code>GET</code> افتراضيًا للطلبات، لذا بغية حذف طالب ما، سنضيف زر أوامر عندما يضغط المستخدم عليه، تُرسَل إلى الوجهة طلبًا من النوع <code>POST</code>.
</p>

<p>
	تستقبل دالة العرض <code>()delete</code> في الشيفرة السابقة معرّف الطالب المراد حذفه من خلال متغير الرابط <code>student_id</code>، إذ يُستخدم التابع <code>()get_or_404</code> لجلب بيانات الطالب وحفظها في متغير باسم <code>student</code>، أو الاستجابة بخطأ من النوع ‎404 Not Found في حال عدم وجود طالب موافق.
</p>

<p>
	كما استخدمنا التابع <code>delete</code> في الجلسة المفتوحة مع قاعدة البيانات في السطر البرمجي <code>(db.session.delete(student</code> ممررين إليه الكائن الممثل للطالب، وهذا ما يجعل الجلسة بالنتيجة تحذف الطالب الموافق بمجرّد تأكيد الإجراءات، إذ ما من حاجة لإجراء أي تعديلات إضافية كون الإجراءات تُؤكَّد مباشرةً باستخدام الأمر <code>()db.session.commit</code>، ونهايةً أعدنا توجيه المستخدم إلى الصفحة الرئيسية للتطبيق.
</p>

<p>
	سنعدّل القالب <code>index.html</code> بإضافة زر لحذف طالب <strong>Delete Student</strong>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_178" style=""><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">index</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	وسنعدّل حلقة <code>for</code> التكرارية بإضافة وسم نموذج <code>&lt;form&gt;</code> مباشرةً أسفل رابط التعديل <strong>Edit</strong>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7940_180" style=""><span class="pun">{%</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> student </span><span class="kwd">in</span><span class="pln"> students </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"student"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;&lt;</span><span class="pln">b</span><span class="pun">&gt;#{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">id </span><span class="pun">}}&lt;/</span><span class="pln">b</span><span class="pun">&gt;&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">b</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">p </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"name"</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">a href</span><span class="pun">=</span><span class="str">"{{ url_for('student', student_id=student.id)}}"</span><span class="pun">&gt;</span><span class="pln">
                    </span><span class="pun">{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">firstname </span><span class="pun">}}</span><span class="pln"> </span><span class="pun">{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">lastname </span><span class="pun">}}</span><span class="pln">
                </span><span class="pun">&lt;/</span><span class="pln">a</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">b</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">email </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">age </span><span class="pun">}}</span><span class="pln"> years old</span><span class="pun">.&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="typ">Joined</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">created_at </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"bio"</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">h4</span><span class="pun">&gt;</span><span class="typ">Bio</span><span class="pun">&lt;/</span><span class="pln">h4</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;{{</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">bio </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">a href</span><span class="pun">=</span><span class="str">"{{ url_for('edit', student_id=student.id) }}"</span><span class="pun">&gt;</span><span class="typ">Edit</span><span class="pun">&lt;/</span><span class="pln">a</span><span class="pun">&gt;</span><span class="pln">

        </span><span class="pun">&lt;</span><span class="pln">hr</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">form method</span><span class="pun">=</span><span class="str">"POST"</span><span class="pln">
                action</span><span class="pun">=</span><span class="str">"{{ url_for('delete', student_id=student.id) }}"</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"submit"</span><span class="pln"> value</span><span class="pun">=</span><span class="str">"Delete Student"</span><span class="pln">
                onclick</span><span class="pun">=</span><span class="str">"return confirm('Are you sure you want to delete this entry?')"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">form</span><span class="pun">&gt;</span><span class="pln">

    </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endfor </span><span class="pun">%}</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	أنشأنا في الملف السابق نموذج ويب يُرسل طلبًا من النوع POST إلى دالة العرض <code>()delete</code> ممررين القيمة <code>student.id</code> وسيطًا للمعامل <code>student_id</code> لتحديد مُدخل الطالب المراد حذفه، كما استخدمنا التابع <code>confirm()‎</code> المتوفّر في متصفحات الويب لعرض رسالة تأكيد قبل إرسال الطلب.
</p>

<p>
	الآن وبتحديث الصفحة الرئيسية للتطبيق، سيظهر زر أوامر لحذف الطالب <strong>Delete Student</strong> أسفل كل مُدخل طالب، وبالنقر عليه ستظهر رسالة لتأكيد عملية الحذف، ومن ثمّ سيعيد التطبيق التوجيه إلى صفحته الرئيسية فنلاحظ حذف الطالب فعلًا.
</p>

<p>
	وبذلك أصبح لدينا آلية لحذف الطلاب من قاعدة البيانات في تطبيق إدارة الطلاب هذا.
</p>

<h1>
	الخاتمة
</h1>

<p>
	عملنا في هذا المقال على بناء تطبيق ويب مُصغّر في فلاسك لإدارة الطلاب باستخدام كل من إطار عمل فلاسك والإضافة Flask-SQLAlchemy مع قاعدة بيانات SQLite، كما تعلمنا كيفية الاتصال مع قاعدة البيانات وإعداد نماذج قاعدة البيانات التي تمثل الجداول، وكيفية إضافة العناصر إلى قاعدة البيانات وإنشاء الاستعلامات ضمن جدول معين، وكيفيّة تعديل البيانات في قاعدة البيانات.
</p>

<p>
	مكنّنا استخدام الإضافة SQLAlchemy في التطبيق من استخدام أصناف وكائنات بايثون لإدارة قاعدة بيانات SQL، وكان من الممكن استخدام أي محرّك قواعد بيانات بدلًا من SQLite، وفي هذه الحالة لن نضطر إلى تغيير أي شيء في شيفرة التطبيق الرئيسية باستثناء ضبط <code>SQLALCHEMY_DATABASE_URI</code> المسؤول عن عملية الاتصال، ما يسمح لنا بالانتقال من محرّك قواعد البيانات SQL إلى أي محرّك آخر بأقل تعديلات على الشيفرة البرمجية.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-use-flask-sqlalchemy-to-interact-with-databases-in-a-flask-application" rel="external nofollow">How to Use Flask-SQLAlchemy to Interact with Databases in a Flask Application</a> لصاحبه Abdelhadi Dyouri.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%AC%D9%87%D9%8A%D8%B2-%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-flask-sqlalchemy-r508/" rel="">تجهيز إضافة Flask-SQLAlchemy</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%AC%D9%87%D9%8A%D8%B2-%D8%AC%D8%AF%D9%88%D9%84%D9%8A-%D8%A7%D9%84%D9%85%D9%82%D8%A7%D9%84%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D9%8A%D9%86-%D8%A8%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84-%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-flask-sqlalchemy-r510/" rel="">تجهيز جدولي المقالات والمستخدمين باستعمال إضافة Flask-SQLAlchemy</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B9%D9%84%D8%A7%D9%82%D8%A9-many-to-many-%D9%85%D8%B9-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D9%88%D9%85%D8%AD%D8%B1%D9%83-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-sqlite-r1632/" rel="">استخدام علاقة many-to-many مع فلاسك Flask ومحرك قواعد البيانات SQLite</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1842</guid><pubDate>Sun, 05 Feb 2023 16:00:00 +0000</pubDate></item><item><title>&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x642;&#x627;&#x639;&#x62F;&#x629; &#x627;&#x644;&#x628;&#x64A;&#x627;&#x646;&#x627;&#x62A; MongoDB &#x641;&#x64A; &#x62A;&#x637;&#x628;&#x64A;&#x642; &#x641;&#x644;&#x627;&#x633;&#x643;</title><link>https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-mongodb-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%81%D9%84%D8%A7%D8%B3%D9%83-r1841/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_01/1686562439_---MongoDB---.png.ee7c55465b2eae46545cb38d70769efa.png" /></p>
<p>
	نحتاج عادةً إلى <a href="https://academy.hsoub.com/devops/servers/databases/%D8%AE%D8%B5%D8%A7%D8%A6%D8%B5-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D9%85%D8%B2%D8%A7%D9%8A%D8%A7-%D8%A7%D9%84%D8%AA%D9%8A-%D8%AA%D9%82%D8%AF%D9%85%D9%87%D8%A7-r520/" rel="">قاعدة بيانات</a> في تطبيقات الويب، وهي مجموعةٌ مُنطمّةٌ من البيانات نستخدمها لتخزين وتنظيم البيانات الدائمة، موفّرةً لنا إمكانية استرجاع هذه البيانات ومعالجتها بفعالية، إذ نحتاج مثلًا في تطبيق ما للتواصل الاجتماعي إلى قاعدة بيانات لتخزين بيانات المستخدم (من معلومات شخصية ومنشورات وتعليقات ومتابعين) لنتمكّن من معالجتها بفعالية، إذ توفّر قاعدة البيانات إمكانية إضافة بيانات جديدة إليها، أو استرجاع بيانات مُخزّنة أصلًا، أو التعديل عليها، أو حذفها بكل مرونة وذلك اعتمادًا على المتطلبات والشروط والظروف المُختلفة، ففي تطبيق ويب ما قد تكون هذه المتطلبات مثلًا هي حالة إضافة المُستخدم لمنشور جديد، أو حذف منشور سابق، أو حذف حسابه مع كافّة منشوراته أو مع الإبقاء عليها، بمعنى أنّ طريقة معالجة البيانات تعتمد أولًا وأخيرًا على الميزات التي يتيحها التطبيق، فمثلًا قد لا ترغب بالسماح لمُستخدمي تطبيقك بإضافة منشورات غير معنونة.
</p>

<p>
	يُعد <a href="https://academy.hsoub.com/programming/python/flask/" rel="">فلاسك</a> إطار عمل للويب مبني بلغة <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D9%85%D8%B1%D8%AC%D8%B9-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%AA%D8%B9%D9%84%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r735/" rel="">بايثون</a>، ويتميز بكونه صغير الحجم وسهل المعالجة، ويوفّر أيضًا عدد أدوات وميزات من شأنها إنشاء تطبيقات ويب في لغة بايثون.
</p>

<p>
	أمّا <a href="https://academy.hsoub.com/devops/servers/databases/mongodb/" rel="">MongoDB</a>، فهو برنامج قواعد بيانات عام الأغراض ومستندي التوجّه document-oreiented من نوع <a href="https://academy.hsoub.com/devops/servers/databases/%D8%B4%D8%B1%D8%AD-%D8%A7%D9%84%D9%81%D8%B1%D9%88%D9%82%D8%A7%D8%AA-%D8%A8%D9%8A%D9%86-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-sql-%D9%88%D9%86%D8%B8%D9%8A%D8%B1%D8%A7%D8%AA%D9%87%D8%A7-nosql-r71/" rel="">NoSQL</a>، الذي يستخدم المستندات المشابهة لصيغة <a href="https://academy.hsoub.com/programming/javascript/%D8%AA%D8%B9%D9%84%D9%85-json-r604/" rel="">JSON</a> لتخزين البيانات. تتيح قواعد البيانات المُعتمدة على المستندات المشابهة لصيغة JSON تخطيطًا ديناميكيًا مرنًا لقاعدة البيانات مع الحفاظ على بساطتها على عكس العلاقات المتعددة بين الجداول المُستخدمة في قواعد البيانات العلّاقية، فعمومًا تتميز <a href="https://academy.hsoub.com/devops/servers/databases/%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-database/" rel="">قواعد البيانات</a> من النوع NoSQL بالتوسّع الأفقي، ما يجعلها مناسبةً للبيانات الضخمة وتطبيقات الوقت الحقيقي.
</p>

<p>
	سنعمل في هذا المقال على بناء تطبيق ويب مصغر لإنشاء قوائم المهام ومن خلاله سنوضّح كيفية استخدام المكتبة <a href="https://pymongo.readthedocs.io/en/stable/index.html" rel="external nofollow">PyMongo</a>، وهي محرك قواعد بيانات يتيح إمكانية التخاطب مع قاعدة بيانات MongoDB باستخدام لغة بايثون، وسنستخدمها ضمن فلاسك Flask لتأدية مهام التطبيق الأساسية، مثل الاتصال بخادم قاعدة البيانات وإنشاء التجميعات Collections التي من شأنها تخزين مجموعة من المستندات في قاعدة البيانات MongoDB وإدخال البيانات ضمن تجميعة ما، إضافةً إلى جلب وحذف البيانات من التجميعات.
</p>

<h2>
	مستلزمات العمل
</h2>

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

<ul>
	<li>
		توفُّر <a href="https://academy.hsoub.com/programming/python/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%AA%D9%87%D8%A7-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r714/" rel="">بيئة برمجة بايثون 3 محلية</a>، مثبّتة على حاسوبك، وسنفترض في مقالنا أن اسم مجلد المشروع هو <code>flask_app</code>.
	</li>
	<li>
		توفّر محرّك قواعد بيانات MongoDB مثبّتٍ على حاسوبك، وفي هذا الصدد ننصحك بقراءة المقال <a href="https://academy.hsoub.com/devops/servers/databases/mongodb/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%88%D8%AA%D8%A3%D9%85%D9%8A%D9%86-mongodb-%D8%B9%D9%84%D9%89-%D8%A3%D9%88%D8%A8%D9%88%D9%86%D8%AA%D9%88-1804-r435/" rel="">كيفية تثبيت وتأمين MongoDB على أوبونتو 18.04</a> للمزيد حول كيفية إعداد قاعدة بيانات MongoDB.
	</li>
	<li>
		الفهم الجيد لأساسيات فلاسك، مثل مفهوم الوجهات ودوال العرض والقوالب، وفي هذا الصدد يمكنك الاطلاع على المقالين <a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%B9%D9%84%D9%85-%D8%A8%D9%86%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9%D9%83-%D8%A7%D9%84%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1715/" rel="">كيفية بناء موقعك الإلكتروني الأول باستخدام إطار عمل فلاسك Flask من لغة بايثون</a> و<a href="https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1737/" rel="">كيفية استخدام القوالب في تطبيقات فلاسك Flask</a> لفهم مبادئ فلاسك.
	</li>
	<li>
		فهم أساسيات لغة <a href="https://academy.hsoub.com/programming/html/" rel="">HTML</a>.
	</li>
</ul>

<h2>
	الخطوة 1 - إعداد مكتبة PyMongo وإطار فلاسك
</h2>

<p>
	سنعمل في هذه الخطوة على تثبيت كل من فلاسك والمكتبة PyMongo.
</p>

<p>
	لذا، وبعد التأكّد من تفعيل البيئة الافتراضية، نستخدم أمر تثبيت الحزم <code>pip</code> لتثبيت كل من فلاسك ومكتبة <code>PyMongo</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2912_7" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ pip install </span><span class="typ">Flask</span><span class="pln"> pymongo</span></pre>

<p>
	وبمجرّد انتهاء التثبيت بنجاح، سيظهر في نهاية الخرج سطر شبيه بما يلي مؤكدًا نجاح العملية:
</p>

<pre class="ipsCode" id="ips_uid_2325_7">Successfully installed Flask-2.0.2 Jinja2-3.0.3 MarkupSafe-2.0.1 Werkzeug-2.0.2 click-8.0.3 itsdangerous-2.0.1 pymongo-4.0.1</pre>

<p>
	وبذلك نكون قد ثبتنا حزم بايثون اللازمة، وفيما يلي سنعمل على الاتصال بخادم قاعدة البيانات MongoDB وإنشاء تجميعة.
</p>

<h2>
	الخطوة 2 - الاتصال بخادم MongoDB وإنشاء تجميعة
</h2>

<p>
	سنستخدم في هذه الخطوة مكتبة <code>PyMongo</code> لإنشاء عميل سنستخدمه في التخاطب مع خادم MongoDB، وإنشاء قاعدة بيانات، ومن ثم إنشاء تجميعة لتخزين المهام ضمنها.
</p>

<p>
	الآن وبعد التأكّد من تفعيل البيئة الافتراضية، ننشئ ملفًا جديدًا باسم <code>app.py</code> لتحريره ضمن المجلد <code>flask_app</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2912_11" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	سيستورد هذا الملف الصنف والمساعدات الضرورية من حزمة فلاسك ومن مكتبة <code>PyMongo</code>، إذ سنتخاطب مع خادم MongoDB بغية إنشاء قاعدة بيانات وتجميعة لتخزين المهام، لذلك سنكتب الشيفرات التالية ضمن الملف <code>app.py</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2912_13" style=""><span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pln">
</span><span class="kwd">from</span><span class="pln"> pymongo </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">MongoClient</span><span class="pln">

app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln">

client </span><span class="pun">=</span><span class="pln"> </span><span class="typ">MongoClient</span><span class="pun">(</span><span class="str">'localhost'</span><span class="pun">,</span><span class="pln"> </span><span class="lit">27017</span><span class="pun">)</span><span class="pln">

db </span><span class="pun">=</span><span class="pln"> client</span><span class="pun">.</span><span class="pln">flask_db
todos </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="pln">todos</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	استوردنا في الشيفرة السابقة الصنف <code>Flask</code> الذي سنستخدمه في إنشاء نسخة فعلية من التطبيق باسم <code>app</code>، كما استوردنا الوحدة <code>MongoClient</code> التي سنستخدمها لإنشاء كائن عميل باسم <code>client</code> لنسخة قاعدة البيانات MongoDB، والذي يسمح لنا بالاتصال والتخاطب مع خادم MongoDB. عند استنساخ الدالة <code>()MongoClient</code> فإننا نمرّر لها كل من اسم مضيف خادم MongoDB وهو في حالتنا الحاسوب نفسه <code>localhost</code>، ورقم المنفذ وهو هنا <code>27017</code>.
</p>

<p>
	<strong>ملاحظة:</strong> من الضروري اتباع إجراءات أمان صارمة لدى تثبيت MongoDB وذلك باتباع الخطوات الواردة في المقال <a href="https://academy.hsoub.com/devops/servers/databases/mongodb/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%88%D8%AA%D8%A3%D9%85%D9%8A%D9%86-mongodb-%D8%B9%D9%84%D9%89-%D8%A3%D9%88%D8%A8%D9%88%D9%86%D8%AA%D9%88-1804-r435/" rel="">كيفية تثبيت وتأمين MongoDB على أوبونتو 18.04</a>، وبمجرد تأمينها يمكنك البدء بضبط إعداداتها للتعامل مع الاتصالات عن بعد. بمجرّد تمكين خاصية الاستيثاق في MongoDB، سيتوجّب علينا لدى إنشاء نسخة من دالة <code>()MongoClient</code> بتمرير معاملين إضافيين لاسم المستخدم وكلمة المرور، وهما: <code>username</code> و <code>password</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2912_15" style=""><span class="pln">client </span><span class="pun">=</span><span class="pln"> </span><span class="typ">MongoClient</span><span class="pun">(</span><span class="str">'localhost'</span><span class="pun">,</span><span class="pln"> </span><span class="lit">27017</span><span class="pun">,</span><span class="pln"> username</span><span class="pun">=</span><span class="str">'username'</span><span class="pun">,</span><span class="pln"> password</span><span class="pun">=</span><span class="str">'password'</span><span class="pun">)</span></pre>

<p>
	ثمّ استخدمنا في الشيفرة نسخة كائن العميل <code>client</code> لإنشاء قاعدة بيانات MongoDB باسم <code>flask_db</code>، وحفظ مرجع لها ضمن متغير باسم <code>db</code>.
</p>

<p>
	بعدها أنشأنا تجميعة باسم <code>todos</code> ضمن قاعدة البيانات <code>flask_db</code> باستخدام المتغير <code>db</code>، إذ تُعد التجميعات مسؤولة عن تخزين مجموعة من المستندات في قاعدة البيانات MongoDB على غرار مبدأ الجداول في قواعد البيانات العلاقية.
</p>

<p>
	نلاحظ إنشاء كل من قواعد البيانات والتجميعات في MongoDB اعتماديًا، بمعنى أنّه حتى لو جرى تنفيذ الملف <code>app.py</code>، فلن تُنفّذ أي من الشيفرات الخاصة بقاعدة البيانات فعليًا إلى أن يُنشَأ المستند الأول.
</p>

<p>
	سننشئ في الخطوة التالية تطبيق فلاسك مُصغّر يحتوي على صفحة تمكّن المستخدمين من إدخال مستندات مهامهم ضمن التجميعة المسماة <code>todos</code>، حيث سيُنشأ ملف قاعدة البيانات <code>flask_db</code> والتجميعة <code>todos</code> ضمن خادم MongoDB بمجرّد إضافة أوّل مستند مهام.
</p>

<p>
	وللحصول على قائمة بقواعد البيانات الموجودة حاليًا، نفتح نافذة طرفية terminal جديدة ونشغّل صدفة <code>mongo</code> باستخدام الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2912_17" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ mongo</span></pre>

<p>
	فتظهر نافذة موجه أوامر، سنستخدم فيها الأمر التالي للتحقّق من قواعد البيانات الموجودة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2912_19" style=""><span class="pun">&gt;</span><span class="pln"> show dbs</span></pre>

<p>
	وفي حال كون MongoDB مُثبّتة حديثًا فلن يظهر ضمن القائمة في الخرج سوى قواعد البيانات <code>admin</code> و <code>config</code> و <code>local</code>، كما نلاحظ عدم ظهور قاعدة البيانات <code>flask_db</code> في هذه المرحلة.
</p>

<p>
	أمّا الآن فسنترك صدفة <code>mongo</code> قيد التشغيل ضمن نافذة الطرفية وننتقل إلى الخطوة التالية.
</p>

<h2>
	الخطوة 3 - إنشاء صفحة ويب لإضافة واستعراض المهام
</h2>

<p>
	سنعمل في هذه الخطوة على إنشاء صفحة ويب تسمح للمستخدمين بإضافة المهام واستعراضها ضمن نفس الصفحة.
</p>

<p>
	لذا، سننشئ ملفًا باسم <code>app.py</code> لتحريره وذلك أثناء كون البيئة البرمجية مُفعّلة، كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2912_21" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	وفيه سنضيف بدايةً الاستدعاءات التالية من حزمة فلاسك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2912_23" style=""><span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> render_template</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">,</span><span class="pln"> url_for</span><span class="pun">,</span><span class="pln"> redirect
</span><span class="kwd">from</span><span class="pln"> pymongo </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">MongoClient</span><span class="pln">

</span><span class="com"># ...</span></pre>

<p>
	استدعينا في الشيفرة السابقة الدالة المساعدة <code>()render_template</code> والتي سنستخدمها في تصيير قالب HTML، كما استدعينا الكائن <code>request</code> لاستخدامه في الوصول إلى البيانات التي سيرسلها المستخدم عبر التطبيق؛ أما الدالة <code>()url_for</code> فستنشئ عناوين URLs، وستعيد الدالة <code>()redirect</code> توجيه المستخدم إلى صفحة التطبيق الرئيسية بعد إضافته مهمّةً جديدة.
</p>

<p>
	والآن سنضيف الوجهة التالية إلى نهاية الملف <code>app.py</code>، على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2912_25" style=""><span class="com"># ...</span><span class="pln">


</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=(</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">))</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> index</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'index.html'</span><span class="pun">)</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	نمرّر في هذه الوجهة متغير صف tuple يحتوي على القيم <code>('GET', 'POST')</code> إلى المعامل <code>methods</code> بغية السماح بكلا نوعي طلبيات HTTP وهما <code>GET</code> و <code>POST</code>؛ إذ تتخصّص الطلبيات من النوع <code>GET</code> بجلب البيانات من الخادم؛ أمّا الطلبيات من النوع <code>POST</code> فهي مُتخصّصة بإرسال البيانات إلى وجهة مُحدّدة، مع ملاحظة أنّ الطلبيات من النوع <code>GET</code> هي الوحيدة المسموحة افتراضيًا، وحالما يطلب المستخدم الوجهة <code>/</code> باستخدام طلبية من النوع <code>GET</code>، سيُصيّر ملف قالب باسم <code>index.html</code>.
</p>

<p>
	سنعدّل هذه الوجهة لاحقًا لتتعامل أيضًا مع الطلبيات من نوع <code>POST</code> اللازمة لدى ملء المُستخدمين نموذج الويب وإرساله بغية إنشاء مهام جديدة، ثمّ سننشئ مجلدًا للقوالب ضمن المجلد <code>flask_app</code>، وسنفتح ضمنه ملف القالب <code>index.html</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2912_27" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ mkdir templates
</span><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">index</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	وسنكتب الشيفرة التالية ضمن ملف القالب <code>index.html</code>:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_2912_29" style=""><span class="dec">&lt;!DOCTYPE html&gt;</span><span class="pln">
</span><span class="tag">&lt;html</span><span class="pln"> </span><span class="atn">lang</span><span class="pun">=</span><span class="atv">"en"</span><span class="tag">&gt;</span><span class="pln">
</span><span class="tag">&lt;head&gt;</span><span class="pln">
    </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">"UTF-8"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;title&gt;</span><span class="pln">FlaskApp</span><span class="tag">&lt;/title&gt;</span><span class="pln">
    </span><span class="tag">&lt;style&gt;</span><span class="pln">
        </span><span class="pun">.</span><span class="pln">todo </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">padding</span><span class="pun">:</span><span class="pln"> </span><span class="lit">20px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">margin</span><span class="pun">:</span><span class="pln"> </span><span class="lit">10px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">background-color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#eee</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="tag">&lt;/style&gt;</span><span class="pln">
</span><span class="tag">&lt;/head&gt;</span><span class="pln">
</span><span class="tag">&lt;body&gt;</span><span class="pln">
    </span><span class="tag">&lt;h1&gt;</span><span class="pln">FlaskTODO</span><span class="tag">&lt;/h1&gt;</span><span class="pln">
    </span><span class="tag">&lt;hr&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"content"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;form</span><span class="pln"> </span><span class="atn">method</span><span class="pun">=</span><span class="atv">"post"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;p&gt;</span><span class="pln">
            </span><span class="tag">&lt;b&gt;&lt;label</span><span class="pln"> </span><span class="atn">for</span><span class="pun">=</span><span class="atv">"content"</span><span class="tag">&gt;</span><span class="pln">Todo content</span><span class="tag">&lt;/label&gt;&lt;/b&gt;</span><span class="pln">
        </span><span class="tag">&lt;/p&gt;</span><span class="pln">
        </span><span class="tag">&lt;p&gt;</span><span class="pln">
            </span><span class="tag">&lt;input</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"text"</span><span class="pln"> </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"content"</span><span class="pln">
                </span><span class="atn">placeholder</span><span class="pun">=</span><span class="atv">"Todo Content"</span><span class="tag">&gt;&lt;/input&gt;</span><span class="pln">
        </span><span class="tag">&lt;/p&gt;</span><span class="pln">

        </span><span class="tag">&lt;p&gt;</span><span class="pln">
            </span><span class="tag">&lt;b&gt;&lt;label</span><span class="pln"> </span><span class="atn">for</span><span class="pun">=</span><span class="atv">"degree"</span><span class="tag">&gt;</span><span class="pln">Degree</span><span class="tag">&lt;/label&gt;&lt;/b&gt;</span><span class="pln">
        </span><span class="tag">&lt;/p&gt;</span><span class="pln">
        </span><span class="tag">&lt;p&gt;</span><span class="pln">
            </span><span class="tag">&lt;input</span><span class="pln"> </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"degree-0"</span><span class="pln"> </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"degree"</span><span class="pln"> </span><span class="atn">required</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"radio"</span><span class="pln"> </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"Important"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;label</span><span class="pln"> </span><span class="atn">for</span><span class="pun">=</span><span class="atv">"degree-0"</span><span class="tag">&gt;</span><span class="pln">Important</span><span class="tag">&lt;/label&gt;</span><span class="pln">
        </span><span class="tag">&lt;/p&gt;</span><span class="pln">
        </span><span class="tag">&lt;p&gt;</span><span class="pln">
            </span><span class="tag">&lt;input</span><span class="pln"> </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"degree-1"</span><span class="pln"> </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"degree"</span><span class="pln"> </span><span class="atn">required</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"radio"</span><span class="pln"> </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"Unimportant"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;label</span><span class="pln"> </span><span class="atn">for</span><span class="pun">=</span><span class="atv">"degree-1"</span><span class="tag">&gt;</span><span class="pln">Unimportant</span><span class="tag">&lt;/label&gt;</span><span class="pln">
        </span><span class="tag">&lt;/p&gt;</span><span class="pln">
        </span><span class="tag">&lt;button</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"submit"</span><span class="tag">&gt;</span><span class="pln">Submit</span><span class="tag">&lt;/button&gt;</span><span class="pln">
    </span><span class="tag">&lt;/form&gt;</span><span class="pln">
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
</span><span class="tag">&lt;/body&gt;</span><span class="pln">
</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	وبذلك أصبح لدينا صفحة HTML أساسية تحتوي على عنوان وبعض التنسيقات وترويسة ونموذج ويب، إذ ضبطنا الخاصية <code>method</code> في نموذج الويب لتكون <code>post</code> للدلالة على كون نموذج الويب سيرسل إلى الخادم طلبيات من النوع <code>POST</code>، كما أضفنا إلى نموذج الويب هذا حقل إدخال نصي باسم <code>content</code>، وهو مُخصّص لإدخال محتوى المهمّة، والذي سنستخدمه لاحقًا للوصول إلى بيانات العنوان في الوجهة <code>/</code>، كما أضفنا للنموذج زري انتقاء باسم <code>degree</code> ليحدّد من خلالهما المستخدم درجة أهمية كل عنصر مهام، بحيث يمكّنه من انتقاء خيار أهمية المهمّة عند إنشائها (مهمة Important أو غير مهمة Unimportant)، وأضفنا في نهاية النموذج زر أوامر <strong>Submit</strong> لتأكيد النموذج وإرساله.
</p>

<p>
	سنعلم بعد ذلك فلاسك بموقع التطبيق (وهو الملف <code>app.py</code> في هذه الحالة) باستخدام متغير البيئة <code>FLASK_APP</code> وذلك أثناء وجودنا ضمن المجلد <code>flask_app</code> ومع تفعيل البيئة الافتراضية، كما سنضبط متغير البيئة <code>FLASK_ENV</code> المسؤول عن تحديد وضع التشغيل، وهنا قد اخترنا الوضع <code>development</code> ما يعني أنّ التطبيق سيعمل في وضع التطوير مع تشغيل مُنقّح الأخطاء. لمزيدٍ من المعلومات حول مُنقّح الأخطاء في فلاسك ننصحك بقراءة المقال <a href="https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A3%D8%AE%D8%B7%D8%A7%D8%A1-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1738/" rel="">كيفية التعامل مع الأخطاء في تطبيقات فلاسك</a>، ولتنفيذ ما سبق سنشغّل الأوامر التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2912_31" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_APP</span><span class="pun">=</span><span class="pln">app
</span><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_ENV</span><span class="pun">=</span><span class="pln">development</span></pre>

<p>
	والآن سنشغّل التطبيق باستخدام الأمر <code>flask run</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2912_33" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ flask run</span></pre>

<p>
	<strong>ملاحظة:</strong>لدى محاولة تشغيل التطبيق قد تظهر لك الرسالة <code>'ModuleNotFoundError: No module named 'pymongo</code> والتي تفيد بعدم وجود وحدة باسم <code>pymongo</code>، ولإصلاح هذا الخطأ ما عليك سوى إلغاء تفعيل البيئة الافتراضية وإعادة تفعيلها مجدّدًا، وبعدها أعد تشغيل الأمر <code>flask run</code> من جديد.
</p>

<p>
	وبعد التأكد من كون خادم التطوير ما يزال قيد التشغيل، نذهب إلى الرابط التالي باستخدام المتصفح:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2912_35" style=""><span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span></pre>

<p>
	فستظهر لك الصفحة الرئيسية للتطبيق متضمّنةً حقلًا لإدخال محتوى المهمّة وزريّ انتقاء لاختيار درجة الأهمية وزر أوامر لإرسال النموذج، كما في الشكل أدناه:
</p>

<p style="text-align: center;">
	<img alt="الصفحة الرئيسية للتطبيق متضمّنةً حقلًاباستخدام flask" class="ipsImage ipsImage_thumbnailed" data-fileid="115935" data-ratio="36.00" data-unique="xluvyauwg" style="width: 750px; height: auto;" width="750" src="https://academy.hsoub.com/uploads/monthly_2023_01/1st.thumb.png.bc38208033f276ba50c95f7edbd6339c.png">
</p>

<p>
	ولمزيدٍ من المعلومات المتقدمة ولتتعرّف على كيفية إدارة نماذج الويب بأمان ننصحك بقراءة مقال <a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%86%D9%85%D8%A7%D8%B0%D8%AC-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D9%88%D8%A7%D9%84%D8%AA%D8%AD%D9%82%D9%82-%D9%85%D9%86%D9%87%D8%A7-%D9%81%D9%8A-%D9%81%D9%84%D8%A7%D8%B3%D9%83-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-flask-wtf-r1838/" rel="">استخدام والتحقق من نماذج الويب ذات واجهة المستخدم التفاعلية في فلاسك باستخدام الإضافة Flask-WTF</a>.
</p>

<p>
	يرسل نموذج الإدخال هذا طلبًا من النوع <code>POST</code> إلى الخادم، ولكن حتى هذه اللحظة لا توجد شيفرة مسؤولة عن معالجة هذا الطلب في الوجهة <code>/</code>، وبالتالي لن يحدث شيء في حال ملء النموذج الآن وإرساله.
</p>

<p>
	نفتح نافذة طرفية جديدة أثناء تشغيل الخادم، ثمّ نفتح الملف <code>app.py</code> لتعديله ليتعامل مع الطلبات من النوع <code>POST</code> المُرسلة من قبل المستخدم، بحيث يرسلها إلى تجميعة تخزين المهام ليعرضها ضمن صفحة التطبيق الرئيسية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2912_38" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ونعدّل الوجهة <code>/</code> لتصبح كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2912_40" style=""><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=(</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">))</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> index</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">method</span><span class="pun">==</span><span class="str">'POST'</span><span class="pun">:</span><span class="pln">
        content </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">]</span><span class="pln">
        degree </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'degree'</span><span class="pun">]</span><span class="pln">
        todos</span><span class="pun">.</span><span class="pln">insert_one</span><span class="pun">({</span><span class="str">'content'</span><span class="pun">:</span><span class="pln"> content</span><span class="pun">,</span><span class="pln"> </span><span class="str">'degree'</span><span class="pun">:</span><span class="pln"> degree</span><span class="pun">})</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> redirect</span><span class="pun">(</span><span class="pln">url_for</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">))</span><span class="pln">

    all_todos </span><span class="pun">=</span><span class="pln"> todos</span><span class="pun">.</span><span class="pln">find</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'index.html'</span><span class="pun">,</span><span class="pln"> todos</span><span class="pun">=</span><span class="pln">all_todos</span><span class="pun">)</span></pre>

<p>
	احفظ الملف واغلقه.
</p>

<p>
	وبعد هذه التغييرات، سيجري التعامل مع الطلبيات من النوع <code>POST</code> باستخدام العبارة الشرطية <code>'if request.method == 'POST</code>، ففي حال تحقق الشرط سيُستخرج من الكائن <code>request.form</code> كلًا من محتوى المهمّة ودرجة الأهمية التي أرسلها المستخدم.
</p>

<p>
	استخدمنا التابع <a href="https://pymongo.readthedocs.io/en/stable/api/pymongo/collection.html#pymongo.collection.Collection.insert_one" rel="external nofollow"><code>()insert_one</code></a> على تجميعة المهام بغية إضافة مستند مهام إليها، كما وفرّنا بيانات المهمة ضمن <a href="https://academy.hsoub.com/programming/python/%D9%81%D9%87%D9%85-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%85%D9%8A%D8%B3-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-r743/" rel="">قاموس بايثون</a> عبر ضبط قيمة المفتاح المسمّى <code>'content'</code> لتساوي القيمة التي أدخلها المستخدم أصلًا ضمن الحقل النصي المُخصّص لمحتوى المهمّة في النموذج، كما ضبطنا قيمة المفتاح المسمّى <code>'degree'</code> لتساوي قيمة زر الانتقاء الذي اختاره المُستخدم. بعد ذلك، نعيد توجيه المُستخدم إلى الصفحة الرئيسية للتطبيق، إذ تُحدَّث هذه الصفحة لتعرض عنصر المهام الجديد المضاف.
</p>

<p>
	استخدمنا التابع <a href="https://pymongo.readthedocs.io/en/stable/api/pymongo/collection.html#pymongo.collection.Collection.find" rel="external nofollow"><code>()find</code></a> بغية عرض المهام المحفوظة خارج الجزء المسؤول عن التعامل مع الطلبيات من النوع POST من الشيفرة، والذي يعيد كافّة مستندات المهام المتوفرّة ضمن التجميعة <code>todos</code>، إذ تُحفظ المهام المُستخرجة من قاعدة البيانات ضمن متغير باسم <code>all_todos</code>، ثمّ عدلنا استدعاء التابع <code>()render_template</code> بحيث يمرر قائمة مستندات المهام إلى ملف القالب <code>index.html</code>، والتي ستتواجد ضمن هذا القالب في متغير باسم <code>todos</code>.
</p>

<p>
	إذا حدّثت الصفحة الرئيسية للتطبيق، سيعرض المتصفح رسالة يطلب فيها تأكيد إعادة إرسال النموذج، وفي حال التأكيد سيُضاف عنصر المهام الذي حاولنا إرساله قبل إضافة الشيفرة المسؤولة عن التعامل مع الطلبيات من النوع POST إلى قاعدة البيانات، وذلك نظرًا لكون الشيفرة اللازمة للتعامل مع الطلبيات POST أصبحت متوفرّةً في الوجهة، ولكن بما أنّنا لم نضف حتى الآن شيفرة خاصّة بعرض عناصر المهام، فلن يظهر العنصر الذي أضفناه، ومن الممكن رؤية العنصر الأخير المُضاف عبر فتح صدفة mongo والاتصال مع قاعدة البيانات <code>flask_db</code> باستخدام الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2912_42" style=""><span class="pun">&gt;</span><span class="pln"> use flask_db</span></pre>

<p>
	سنستخدم بعد ذلك الدالة <code>()find</code> لجلب كافّة عناصر المهام الموجودة في قاعدة البيانات:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2912_44" style=""><span class="pun">&gt;</span><span class="pln"> db</span><span class="pun">.</span><span class="pln">todos</span><span class="pun">.</span><span class="pln">find</span><span class="pun">()</span></pre>

<p>
	ففي حال إرسال أي بيانات ستظهر في الخرج هنا.
</p>

<p>
	الآن، سنفتح ملف القالب <code>index.html</code> لتعديله بغية عرض محتويات قائمة المهام <code>todos</code> التي مررناها مُسبقًا إليه، على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2912_46" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">index</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	نعدّل الملف بإضافة وسم فاصل أفقي <code><a href="https://wiki.hsoub.com/HTML/hr" rel="external">&lt;hr&gt;</a></code> وحلقة تكرارية <code>for</code> من تعليمات <a href="https://academy.hsoub.com/tags/jinja2" rel="">جينجا jinja</a> وذلك بعد الجزء الخاص بالنموذج، بحيث يصبح الملف كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_2912_48" style=""><span class="dec">&lt;!DOCTYPE html&gt;</span><span class="pln">
</span><span class="tag">&lt;html</span><span class="pln"> </span><span class="atn">lang</span><span class="pun">=</span><span class="atv">"en"</span><span class="tag">&gt;</span><span class="pln">
</span><span class="tag">&lt;head&gt;</span><span class="pln">
    </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">"UTF-8"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;title&gt;</span><span class="pln">FlaskApp</span><span class="tag">&lt;/title&gt;</span><span class="pln">
    </span><span class="tag">&lt;style&gt;</span><span class="pln">
        </span><span class="pun">.</span><span class="pln">todo </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">padding</span><span class="pun">:</span><span class="pln"> </span><span class="lit">20px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">margin</span><span class="pun">:</span><span class="pln"> </span><span class="lit">10px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">background-color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#eee</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="tag">&lt;/style&gt;</span><span class="pln">
</span><span class="tag">&lt;/head&gt;</span><span class="pln">
</span><span class="tag">&lt;body&gt;</span><span class="pln">
    </span><span class="tag">&lt;h1&gt;</span><span class="pln">FlaskTODO</span><span class="tag">&lt;/h1&gt;</span><span class="pln">
    </span><span class="tag">&lt;hr&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"content"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;form</span><span class="pln"> </span><span class="atn">method</span><span class="pun">=</span><span class="atv">"post"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;p&gt;</span><span class="pln">
            </span><span class="tag">&lt;b&gt;&lt;label</span><span class="pln"> </span><span class="atn">for</span><span class="pun">=</span><span class="atv">"content"</span><span class="tag">&gt;</span><span class="pln">Todo content</span><span class="tag">&lt;/label&gt;&lt;/b&gt;</span><span class="pln">
        </span><span class="tag">&lt;/p&gt;</span><span class="pln">
        </span><span class="tag">&lt;p&gt;</span><span class="pln">
            </span><span class="tag">&lt;input</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"text"</span><span class="pln"> </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"content"</span><span class="pln">
                </span><span class="atn">placeholder</span><span class="pun">=</span><span class="atv">"Todo Content"</span><span class="tag">&gt;&lt;/input&gt;</span><span class="pln">
        </span><span class="tag">&lt;/p&gt;</span><span class="pln">

        </span><span class="tag">&lt;p&gt;</span><span class="pln">
            </span><span class="tag">&lt;b&gt;&lt;label</span><span class="pln"> </span><span class="atn">for</span><span class="pun">=</span><span class="atv">"degree"</span><span class="tag">&gt;</span><span class="pln">Degree</span><span class="tag">&lt;/label&gt;&lt;/b&gt;</span><span class="pln">
        </span><span class="tag">&lt;/p&gt;</span><span class="pln">
        </span><span class="tag">&lt;p&gt;</span><span class="pln">
            </span><span class="tag">&lt;input</span><span class="pln"> </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"degree-0"</span><span class="pln"> </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"degree"</span><span class="pln"> </span><span class="atn">required</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"radio"</span><span class="pln"> </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"Important"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;label</span><span class="pln"> </span><span class="atn">for</span><span class="pun">=</span><span class="atv">"degree-0"</span><span class="tag">&gt;</span><span class="pln">Important</span><span class="tag">&lt;/label&gt;</span><span class="pln">
        </span><span class="tag">&lt;/p&gt;</span><span class="pln">
        </span><span class="tag">&lt;p&gt;</span><span class="pln">
            </span><span class="tag">&lt;input</span><span class="pln"> </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"degree-1"</span><span class="pln"> </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"degree"</span><span class="pln"> </span><span class="atn">required</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"radio"</span><span class="pln"> </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"Unimportant"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;label</span><span class="pln"> </span><span class="atn">for</span><span class="pun">=</span><span class="atv">"degree-1"</span><span class="tag">&gt;</span><span class="pln">Unimportant</span><span class="tag">&lt;/label&gt;</span><span class="pln">
        </span><span class="tag">&lt;/p&gt;</span><span class="pln">
        </span><span class="tag">&lt;button</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"submit"</span><span class="tag">&gt;</span><span class="pln">Submit</span><span class="tag">&lt;/button&gt;</span><span class="pln">
    </span><span class="tag">&lt;/form&gt;</span><span class="pln">
    </span><span class="tag">&lt;hr&gt;</span><span class="pln">
    {% for todo in todos %}
        </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"todo"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;p&gt;</span><span class="pln">{{ todo['content'] }} </span><span class="tag">&lt;i&gt;</span><span class="pln">({{ todo['degree']}})</span><span class="tag">&lt;/i&gt;&lt;/p&gt;</span><span class="pln">
        </span><span class="tag">&lt;/div&gt;</span><span class="pln">
    {% endfor %}

    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
</span><span class="tag">&lt;/body&gt;</span><span class="pln">
</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	أضفنا في الملف السابق <a href="https://wiki.hsoub.com/HTML/hr" rel="external">وسم الفاصل الأفقي &lt;hr&gt;</a> بغية فضل نموذج الويب عن قائمة المهام المعروضة، كما استخدمنا حلقة <code>for</code> التكرارية ضمن السطر البرمجي الآتي:
</p>

<pre class="ipsCode" id="ips_uid_2325_11">{% for todo in todos %}</pre>

<p>
	بغية المرور على كافة عناصر المهام في قائمة <code>todos</code>، لتُعرض محتويات المهمّة ودرجة أهميتها ضمن <a href="https://wiki.hsoub.com/HTML/p" rel="external">وسم فقرة &lt;p&gt;</a>.
</p>

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

<h2>
	الخطوة 4 - حذف مهام
</h2>

<p>
	سنعمل في هذه الخطوة على إضافة وجهة تمكّن المستخدمين من حذف المهام باستخدام زر أوامر مُخصّص لهذا الغرض.
</p>

<p>
	بدايةً، سنضيف وجهةً جديدةً للحذف، وهي <code>‎/id/delete</code> تتعامل مع الطلبات من النوع POST، إذ ستستقبل دالة الحذف الجديدة <code>delete()‎</code> رقم معرّف التدوينة ID المراد حذفها من خلال الرابط URL لتستخدم هذا المعرّف لحذف المهمّة الموافقة.
</p>

<p>
	لحذف مهمة ما، سنجلب بدايةً معرّفها مثل سلسلة نصية والتي لا بُدّ من تحويلها إلى النوع ObjectId (نمط البيانات BSON وهو التمثيل الثنائي للنمط JSON) قبل تمريره إلى تابع الحذف من التجميعة، لذا يجب استيراد الصنف <code>()ObjectId</code> من الوحدة <a href="https://pymongo.readthedocs.io/en/stable/api/bson/index.html" rel="external nofollow"><code>bson</code></a>، وهي المسؤولة عن التعامل مع ترميز وفك ترميز نمط البيانات BSON (أي <a href="https://academy.hsoub.com/programming/javascript/%D8%AA%D8%B9%D9%84%D9%85-json-r604/" rel="">JSON</a> الثنائي).
</p>

<p>
	الآن، سنفتح الملف <code>app.py</code> لتحريره:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2912_51" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	وبدايةً سنضيف الاستدعاء التالي إلى بداية الملف، على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2912_53" style=""><span class="kwd">from</span><span class="pln"> bson</span><span class="pun">.</span><span class="pln">objectid </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">ObjectId</span><span class="pln">

</span><span class="com"># ...</span></pre>

<p>
	استُدعي الصنف <code>()ObjectId</code> لاستخدامه في تحويل المعرفات IDs إلى كائنات من النوع ObjectId.
</p>

<p>
	سنضيف الآن الوجهة التالية إلى نهاية الملف <code>app.py</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2912_55" style=""><span class="com"># ...</span><span class="pln">


</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'/&lt;id&gt;/delete/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> delete</span><span class="pun">(</span><span class="pln">id</span><span class="pun">):</span><span class="pln">
    todos</span><span class="pun">.</span><span class="pln">delete_one</span><span class="pun">({</span><span class="str">"_id"</span><span class="pun">:</span><span class="pln"> </span><span class="typ">ObjectId</span><span class="pun">(</span><span class="pln">id</span><span class="pun">)})</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> redirect</span><span class="pun">(</span><span class="pln">url_for</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">))</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	استخدمنا في الشيفرة السابقة المزخرف <a href="https://flask.palletsprojects.com/en/2.0.x/api/#flask.Flask.post" rel="external nofollow"><code>app.post</code></a> المُقدّم في الإصدار 2.0.0 من فلاسك بدلًا من استخدام المزخرف <code>app.route</code> المعتاد، والذي أضاف اختصارات للعديد من توابع HTTP الشائعة، فعلى سبيل المثال الأمر <code>("app.post("/login@</code> هو اختصار للأمر:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2337_12" style=""><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">"/login"</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=[</span><span class="str">"POST"</span><span class="pun">])</span></pre>

<p>
	ما يعني أن دالة العرض هذه تتعامل فقط مع طلبيات من النوع <code>POST</code>، وبزيارة الوجهة <code>‎/ID/delete</code> في المتصفّح سيظهر الخطأ <code>‎405 Method Not Allowed</code> لأن المتصفحات تستخدم طريقة <code>GET</code> افتراضيًا للطلبات. لذلك، بغية حذف مهمة ما، سنضيف زر أوامر عندما يضغط المستخدم عليه، تُرسَل إلى الوجهة طلبية من النوع POST.
</p>

<p>
	تستقبل الدالة معرّف مستند المهمّة المراد حذفها، ليُمرّر إلى التابع <a href="https://pymongo.readthedocs.io/en/stable/api/pymongo/collection.html#pymongo.collection.Collection.delete_one" rel="external nofollow"><code>()delete_one</code></a> المُطبّق على التجميعة <code>todos</code>، ليُحوّل المعرّف المُستقبل من نوع سلسلة نصية إلى كائن من النوع ObjectId باستخدام الصنف <code>()ObjectId</code> المستورد سابقًا.
</p>

<p>
	وبعد حذف مستند المهمة، نعيد توجيه المستخدم إلى الصفحة الرئيسية للتطبيق، أمّا الآن فسنعمل على تحرير ملف القالب <code>index.html</code> لإضافة زر الأوامر الخاص بحذف المهام، على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2912_57" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">index</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	سنعدّل حلقة <code>for</code> التكرارية بإضافة <a href="https://wiki.hsoub.com/HTML/form" rel="external">وسم نموذج &lt;form&gt;</a> جديد، كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2912_59" style=""><span class="pun">{%</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> todo </span><span class="kwd">in</span><span class="pln"> todos </span><span class="pun">%}</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"todo"</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;{{</span><span class="pln"> todo</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">}}</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln">i</span><span class="pun">&gt;({{</span><span class="pln"> todo</span><span class="pun">[</span><span class="str">'degree'</span><span class="pun">]}})&lt;/</span><span class="pln">i</span><span class="pun">&gt;&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">form method</span><span class="pun">=</span><span class="str">"POST"</span><span class="pln"> action</span><span class="pun">=</span><span class="str">"{{ url_for('delete', id=todo['_id']) }}"</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"submit"</span><span class="pln"> value</span><span class="pun">=</span><span class="str">"Delete Todo"</span><span class="pln">
                       onclick</span><span class="pun">=</span><span class="str">"return confirm('Are you sure you want to delete this entry?')"</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">form</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">{%</span><span class="pln"> endfor </span><span class="pun">%}</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	وبذلك يكون لدينا نموذج ويب يُرسل طلبيةً من النوع <code>POST</code> إلى دالة العرض <code>()delete</code>، إذ مررنا المعامل <code>['todo['_id</code> لتحديد المهمّة المطلوب حذفها، كما استخدمنا التابع <code>()confirm</code> المتوفّر في متصفّح الويب لعرض رسالة تأكيد قبل إرسال الطلب.
</p>

<p>
	الآن، بعد تحديث الصفحة الرئيسية للتطبيق، سيظهر زر أوامر لحذف المهمة أسفل كل عنصر مهام، وعند النقر عليه ستظهر رسالة لتأكيد عملية الحذف، ومن ثمّ سيعيد التطبيق التوجيه إلى صفحته الرئيسية فنلاحظ حذف المهمة فعلًا.
</p>

<p>
	وبذلك أصبح تطبيق فلاسك يوفّر طريقةً لحذف المهام غير المرغوبة من قاعدة البيانات mongoDB.
</p>

<p>
	ولتأكيد الحذف، نفتح صدفة mongo ونستخدم الدالة <code>()find</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2912_61" style=""><span class="pun">&gt;</span><span class="pln"> db</span><span class="pun">.</span><span class="pln">todos</span><span class="pun">.</span><span class="pln">find</span><span class="pun">()</span></pre>

<p>
	وعندها سنلاحظ أنّ العناصر المحذوفة لم تعد موجودةً في التجميعة <code>todos</code>.
</p>

<h1>
	الخاتمة
</h1>

<p>
	أنشأنا في هذا المقال تطبيق ويب في فلاسك لإدارة المهام، إذ يتخاطب هذا التطبيق مع قاعدة بيانات MongoDB، وتعرفنا على كيفية الاتصال مع خادم قاعدة البيانات MongoDB، وأنشأنا تجميعات تُخزّن مجموعة من المستندات، واستعرضنا كيفية إدخال البيانات إلى تجميعة وجلبها أو حذفها منها.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-use-mongodb-in-a-flask-application" rel="external nofollow">How To Use MongoDB in a Flask Application</a> لصاحبه Abdelhadi Dyouri.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1737/" rel="">كيفية استخدام القوالب في تطبيقات فلاسك Flask</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/php/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-mongodb-%D9%88-redis-%D9%81%D9%8A-php-r1173/" rel="">استخدام MongoDB و Redis في PHP</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%AF%D9%85%D8%AC-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-mongodb-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D9%83-node-r810/" rel="">دمج قاعدة البيانات MongoDB في تطبيقك Node</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1841</guid><pubDate>Sat, 28 Jan 2023 16:00:00 +0000</pubDate></item><item><title>&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x642;&#x627;&#x639;&#x62F;&#x629; &#x628;&#x64A;&#x627;&#x646;&#x627;&#x62A; PostgreSQL &#x641;&#x64A; &#x62A;&#x637;&#x628;&#x64A;&#x642; &#x641;&#x644;&#x627;&#x633;&#x643;</title><link>https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-postgresql-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%81%D9%84%D8%A7%D8%B3%D9%83-r1840/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_01/1202046686_---PostgreSQL---.png.9d1a0fad1532a1f14c1eb0c8813a78ee.png" /></p>
<p>
	نحتاج عادةً إلى <a href="https://academy.hsoub.com/devops/servers/databases/%D8%AE%D8%B5%D8%A7%D8%A6%D8%B5-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D9%85%D8%B2%D8%A7%D9%8A%D8%A7-%D8%A7%D9%84%D8%AA%D9%8A-%D8%AA%D9%82%D8%AF%D9%85%D9%87%D8%A7-r520/" rel="">قاعدة بيانات</a> في تطبيقات الويب، وهي مجموعة مُنطمّة من البيانات، نستخدمها لتخزين وتنظيم البيانات الدائمة، موفّرةً لنا إمكانية استرجاع هذه البيانات ومعالجتها بفعالية، إذ نحتاج مثلًا في تطبيق ما للتواصل الاجتماعي إلى قاعدة بيانات لتخزين بيانات المستخدم (من معلومات شخصية ومنشورات وتعليقات ومتابعين) بحيث نتمكّن من معالجة البيانات هذه بفعالية، إذ توفّر قاعدة البيانات إمكانية إضافة بيانات جديدة إليها، أو استرجاع بيانات مُخزّنة أصلًا، أو التعديل عليها، أو حذفها بكل مرونة وذلك اعتمادًا على المتطلبات والشروط والظروف المُختلفة، ففي تطبيق ويب ما قد تكون هذه المتطلبات مثلًا هي حالة إضافة المُستخدم لمنشور جديد، أو حذف منشور سابق، أو حذف حسابه مع كافّة منشوراته أو مع الإبقاء عليها، بمعنى أنّ طريقة معالجة البيانات تعتمد أولًا وأخيرًا على الميزات التي يتيحها التطبيق، فمثلًا قد لا ترغب بالسماح لمُستخدمي تطبيقك بإضافة منشورات غير معنونة.
</p>

<p>
	يُعد <a href="https://academy.hsoub.com/programming/python/flask/" rel="">فلاسك</a> إطار عمل للويب مبني بلغة <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D9%85%D8%B1%D8%AC%D8%B9-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%AA%D8%B9%D9%84%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r735/" rel="">بايثون</a>، ويتميز بكونه صغير الحجم وسهل المعالجة، ويوفّر أيضًا عدة أدوات وميزات من شأنها إنشاء تطبيقات ويب في لغة بايثون.
</p>

<p>
	أمّا <a href="https://academy.hsoub.com/devops/servers/databases/postgresql/" rel="">PostgreSQL</a> أو Postgres فهو نظام لإدارة <a href="https://academy.hsoub.com/devops/servers/databases/%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-database/" rel="">قواعد البيانات</a> العلاقية، ويوفّر تطبيقًا للغة الاستعلامات SQL، ومتوافق مع معاييرها، ناهيك عن امتلاكه العديد من الميزات المتطورة من عمليات موثوقة وتحكّم متزامن مُتعدّد النسخ دون قيود على قراءة البيانات من القاعدة.
</p>

<p>
	سنعمل في هذا المقال على بناء تطبيق ويب مُصغّر لتقييم الكتب والذي سنوضّح من خلاله كيفية استخدام المكتبة <code>psycopg2</code> ومحوّل قاعدة بيانات PostgreSQL، الذي يسمح لنا بالتخاطب والتفاعل مع قاعدة بيانات PostgreSQL باستخدام لغة بايثون، إذ سنبني التطبيق باستخدام إطار العمل فلاسك ليؤدّي بعض المهام الأساسية من اتصال بخادم قاعدة البيانات وإنشاء الجداول وإدخال بيانات في جدول، إضافةً إلى جلب بيانات من جدول.
</p>

<h2>
	مستلزمات العمل
</h2>

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

<ul>
	<li>
		توفُّر <a href="https://academy.hsoub.com/programming/python/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%AA%D9%87%D8%A7-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r714/" rel="">بيئة برمجة بايثون 3 محلية</a>، مثبّتة على حاسوبك، وسنفترض في مقالنا أن اسم مجلد المشروع هو "flask_app".
	</li>
	<li>
		الفهم الجيد لأساسيات فلاسك، مثل مفهوم الوجهات ودوال العرض والقوالب، وفي هذا الصدد يمكنك الاطلاع على المقالين <a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%B9%D9%84%D9%85-%D8%A8%D9%86%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9%D9%83-%D8%A7%D9%84%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1715/" rel="">كيفية بناء موقعك الإلكتروني الأول باستخدام إطار عمل فلاسك Flask من لغة بايثون</a> و<a href="https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1737/" rel="">كيفية استخدام القوالب في تطبيقات فلاسك Flask</a> لفهم مبادئ فلاسك.
	</li>
	<li>
		فهم أساسيات لغة <a href="https://academy.hsoub.com/programming/html/" rel="">HTML</a>.
	</li>
	<li>
		توفّر <a href="https://academy.hsoub.com/devops/servers/databases/postgresql/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-postgres-%D9%88%D8%A7%D9%84%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%A5%D8%AF%D8%A7%D8%B1%D8%AA%D9%87%D8%A7-%D9%84%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-r472/" rel="">PostgreSQL</a> مثبت على حاسوبك مع وصول إلى نافذة أسطر الأوامر الخاصة به.
	</li>
</ul>

<h2>
	الخطوة 1 - إنشاء كل من قاعدة بيانات ومستخدم PostgreSQL
</h2>

<p>
	سنعمل في هذه الخطوة على إنشاء قاعدة بيانات باسم "flask_db" ومستخدم لها باسم <code>user</code> وذلك لاستخدامهما لاحقًا في تطبيق فلاسك المراد إنشاؤه.
</p>

<p>
	يُنشأ خلال تثبيت <code>postgres</code> مستخدم بصلاحيات على مستوى نظام التشغيل باسم <code>postgres</code> ليتوافق مع حساب المستخدم المسؤول كامل الصلاحيات <code>postgres</code> الخاص بنظام PostgreSQL، إذ سنستخدم هذا الحساب في المهام التي تتطلّب صلاحيات مسؤول، فحينها سنستخدم الأمر <code>sudo</code> ممرّرين بعده اسم المستخدم مع استخدام الخيار <code>iu-</code>.
</p>

<p>
	لذا، سجّل دخولك الآن إلى جلسة عمل تفاعلية في Postgres مُستخدمًا الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_16" style=""><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ sudo </span><span class="pun">-</span><span class="pln">iu postgres psql</span></pre>

<p>
	فستظهر لك طرفية PostgreSQL والتي من الممكن إعداد المتطلبات من خلالها.
</p>

<p>
	بدايةً، سننشئ قاعدة بيانات لمشروعنا:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_12" style=""><span class="pln">postgres</span><span class="pun">=#</span><span class="pln"> CREATE DATABASE flask_db</span><span class="pun">;</span></pre>

<p>
	<strong>ملاحظة:</strong> يجب أن تنتهي كل تعليمة من تعليمات Postgres بفاصلة منقوطة، ففي حال ظهور أي أخطاء، تأكّد من انتهاء كل سطر برمجي بفاصلة منقوطة.
</p>

<p>
	ومن ثمّ سننشئ حساب مستخدم لقاعدة البيانات، وهنا لا بدّ من اختيار كلمة مرور آمنة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_14" style=""><span class="pln">postgres</span><span class="pun">=#</span><span class="pln"> CREATE USER user WITH PASSWORD </span><span class="str">'password'</span><span class="pun">;</span></pre>

<p>
	والآن سنعطي هذا المستخدم صلاحيات المسؤول على قاعدة البيانات:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_20" style=""><span class="pln">postgres</span><span class="pun">=#</span><span class="pln"> GRANT ALL PRIVILEGES ON DATABASE flask_db TO sammy</span><span class="pun">;</span></pre>

<p>
	وللتأكّد من إتمام إنشاء قاعدة البيانات، نكتب الأمر التالي المسؤول عن إظهار قائمة بقواعد البيانات المُنشأة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_22" style=""><span class="pln">postgres</span><span class="pun">=#</span><span class="pln"> \l</span></pre>

<p>
	فستظهر قاعدة البيانات المسماة "flask_db" ضمن القائمة السابقة.
</p>

<p>
	وعند الانتهاء نغلق نافذة طرفية PstgreSQL باستخدام الأمر:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_24" style=""><span class="pln">postgres</span><span class="pun">=#</span><span class="pln"> \q</span></pre>

<p>
	ومع نهاية هذه الخطوة نكون قد أعددنا sPostgre وأصبح من الممكن الاتصال وإدارة المعلومات المخزّنة في قواعد بياناته باستخدام لغة بايثون معتمدين على المكتبة <code>psycopg2</code>، أمّا في الخطوة التالية فسنعمل على تثبيت هذه المكتبة جنبًا إلى جنب مع حزمة فلاسك.
</p>

<h2>
	الخطوة 2 - تثبيت فلاسك ومكتبة psycopg2
</h2>

<p>
	سنعمل في هذه الخطوة على تثبيت كل من فلاسك ومكتبة <code>psycopg2</code> مما يمكننا من التخاطب مع قاعدة البيانات باستخدام لغة بايثون.
</p>

<p>
	لذا، وبعد التأكّد من كون البيئة الافتراضية مُفعّلة، نستخدم أمر تثبيت الحزم <code>pip</code> لتثبيت كل من فلاسك ومكتبة <code>psycopg2</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_26" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ pip install </span><span class="typ">Flask</span><span class="pln"> psycopg2</span><span class="pun">-</span><span class="pln">binary</span></pre>

<p>
	وبمجرّد انتهاء التثبيت بنجاح، سيظهر في نهاية الخرج سطر شبيه بما يلي مؤكدًا نجاح العملية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_28" style=""><span class="typ">Successfully</span><span class="pln"> installed </span><span class="typ">Flask</span><span class="pun">-</span><span class="lit">2.0</span><span class="pun">.</span><span class="lit">2</span><span class="pln"> </span><span class="typ">Jinja2</span><span class="pun">-</span><span class="lit">3.0</span><span class="pun">.</span><span class="lit">3</span><span class="pln"> </span><span class="typ">MarkupSafe</span><span class="pun">-</span><span class="lit">2.0</span><span class="pun">.</span><span class="lit">1</span><span class="pln"> </span><span class="typ">Werkzeug</span><span class="pun">-</span><span class="lit">2.0</span><span class="pun">.</span><span class="lit">2</span><span class="pln"> click</span><span class="pun">-</span><span class="lit">8.0</span><span class="pun">.</span><span class="lit">3</span><span class="pln"> itsdangerous</span><span class="pun">-</span><span class="lit">2.0</span><span class="pun">.</span><span class="lit">1</span><span class="pln"> psycopg2</span><span class="pun">-</span><span class="pln">binary</span><span class="pun">-</span><span class="lit">2.9</span><span class="pun">.</span><span class="lit">2</span></pre>

<p>
	مع انتهاء هذه الخطوة، نكون قد ثبتنا كافّة الحزم اللازمة في البيئة الافتراضية، وسنعمل في الخطوة التالية على إعداد قاعدة البيانات والاتصال بها.
</p>

<h2>
	الخطوة 3 - إعداد قاعدة بيانات
</h2>

<p>
	سننشئ في هذه الخطوة -ضمن مجلد المشروع المسمّى "flask_app"- ملف بايثون وظيفته الاتصال مع قاعدة البيانات "flask_db"، كما سننشئ ضمن قاعدة البيانات هذه جدولًا لتخزين الكتب، وسندخل ضمنه يدويًا بعض الكتب مع نبذة عن كل منها.
</p>

<p>
	الآن وبعد التأكّد من كون البيئة الافتراضية مُفعّلة، نفتح ملفًا جديدًا باسم "init_db.py" ضمن المجلد "flask_app" على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_30" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano init_db</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	سيفتح هذا الملف الاتصال مع قاعدة البيانات "flask_db"، مُنشئًا فيها جدولًا للكتب باسم "books"، مالئًا إياه ببيانات تجريبية، ولإنجاز ذلك نكتب الشيفرة التالية ضمن الملف "init_db.py":
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_32" style=""><span class="kwd">import</span><span class="pln"> os
</span><span class="kwd">import</span><span class="pln"> psycopg2

conn </span><span class="pun">=</span><span class="pln"> psycopg2</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="pln">
        host</span><span class="pun">=</span><span class="str">"localhost"</span><span class="pun">,</span><span class="pln">
        database</span><span class="pun">=</span><span class="str">"flask_db"</span><span class="pun">,</span><span class="pln">
        user</span><span class="pun">=</span><span class="pln">os</span><span class="pun">.</span><span class="pln">environ</span><span class="pun">[</span><span class="str">'DB_USERNAME'</span><span class="pun">],</span><span class="pln">
        password</span><span class="pun">=</span><span class="pln">os</span><span class="pun">.</span><span class="pln">environ</span><span class="pun">[</span><span class="str">'DB_PASSWORD'</span><span class="pun">])</span><span class="pln">

</span><span class="com"># Open a cursor to perform database operations</span><span class="pln">
cur </span><span class="pun">=</span><span class="pln"> conn</span><span class="pun">.</span><span class="pln">cursor</span><span class="pun">()</span><span class="pln">

</span><span class="com"># Execute a command: this creates a new table</span><span class="pln">
cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'DROP TABLE IF EXISTS books;'</span><span class="pun">)</span><span class="pln">
cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'CREATE TABLE books (id serial PRIMARY KEY,'</span><span class="pln">
                                 </span><span class="str">'title varchar (150) NOT NULL,'</span><span class="pln">
                                 </span><span class="str">'author varchar (50) NOT NULL,'</span><span class="pln">
                                 </span><span class="str">'pages_num integer NOT NULL,'</span><span class="pln">
                                 </span><span class="str">'review text,'</span><span class="pln">
                                 </span><span class="str">'date_added date DEFAULT CURRENT_TIMESTAMP);'</span><span class="pln">
                                 </span><span class="pun">)</span><span class="pln">

</span><span class="com"># Insert data into the table</span><span class="pln">

cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'INSERT INTO books (title, author, pages_num, review)'</span><span class="pln">
            </span><span class="str">'VALUES (%s, %s, %s, %s)'</span><span class="pun">,</span><span class="pln">
            </span><span class="pun">(</span><span class="str">'A Tale of Two Cities'</span><span class="pun">,</span><span class="pln">
             </span><span class="str">'Charles Dickens'</span><span class="pun">,</span><span class="pln">
             </span><span class="lit">489</span><span class="pun">,</span><span class="pln">
             </span><span class="str">'A great classic!'</span><span class="pun">)</span><span class="pln">
            </span><span class="pun">)</span><span class="pln">


cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'INSERT INTO books (title, author, pages_num, review)'</span><span class="pln">
            </span><span class="str">'VALUES (%s, %s, %s, %s)'</span><span class="pun">,</span><span class="pln">
            </span><span class="pun">(</span><span class="str">'Anna Karenina'</span><span class="pun">,</span><span class="pln">
             </span><span class="str">'Leo Tolstoy'</span><span class="pun">,</span><span class="pln">
             </span><span class="lit">864</span><span class="pun">,</span><span class="pln">
             </span><span class="str">'Another great classic!'</span><span class="pun">)</span><span class="pln">
            </span><span class="pun">)</span><span class="pln">

conn</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">

cur</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	استوردنا في الشيفرة السابقة الوحدة <code>os</code> المُستخدمة في الوصول إلى متغيرات البيئة التي خزّنا فيها سابقًا كل من اسم المستخدم وكلمة المرور الخاصين بقاعدة البيانات، وهذا يضمن عدم ظهورهما ضمن الشيفرة المصدرية.
</p>

<p>
	كما استوردنا المكتبة <code>psycopg2</code>، ومن ثمّ فتحنا اتصالًا مع قاعدة البيانات flask_db باستخدام الدالة <code>()psycopg2.connect</code>، وفيها حددنا المضيف وهو الحاسوب المحلي في حالتنا، كما مرّرنا اسم قاعدة البيانات إلى المُعامل <code>database</code>.
</p>

<p>
	وقد وفرّنا كل من اسم المستخدم وكلمة المرور اللازمين للدخول إلى قاعدة البيانات باستخدام الكائن <code>os.environ</code> الذي يُمكنّنا من الوصول إلى متغيرات البيئة التي أعددناها سابقًا ضمن البيئة البرمجية، ليُخزّن اسم المستخدم ضمن متغير بيئة باسم <code>DB_USERNAME</code> وكلمة المرور ضمن متغير بيئة باسم <code>DB_PASSWORD</code>، وبهذا نكون قد خزّنا كل من اسم المستخدم وكلمة المرور خارج الشيفرة المصدرية، ما يضمن أمان هذه المعلومات الحساسة ويمنع كشفها لدى حفظ نسخة من الشيفرة المصدرية في المتحكّم المركزي، أو لدى رفعها على خادم عامل على <a href="https://academy.hsoub.com/devops/networking/%D8%A2%D9%84%D9%8A%D8%A9-%D8%B9%D9%85%D9%84-%D8%B4%D8%A8%D9%83%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%B1%D9%86%D8%AA-r571/" rel="">شبكة الإنترنت</a>.
</p>

<p>
	كما أنشأنا مؤشر <code>cur</code> باستخدام التابع <code>()connection.cursor</code>، الذي يمكّن شيفرات بايثون من تنفيذ أوامر PostgreSQL خلال جلسات عمل قاعدة البيانات.
</p>

<p>
	استخدمنا تابع المؤشر <code>()execute</code> لحذف أي جداول موجودةٍ مسبقًا باسم "books" في حال وجودها، وذلك لتجنُّب أي تضاربٍ، أو نتائج عشوائية ناتجةٍ عن تشابه أسماء الجداول (مثل حالة وجود جدولين بنفس الاسم وبأعمدة مُختلفة في قاعدة البيانات)، ولكن وفي حالتنا الآن وكوننا لم ننشئ أي جداول بعد، فلن يُنفَّذ هذا السطر في الوقت الراهن، ومن الجدير بالملاحظة أنّ هذا الأمر سيحذِف كل المحتويات في قاعدة البيانات في كل مرة تشغِّل فيها الملف "init_db.py"، ولكن في حالة مثالنا سننفذ هذا الملف لمرّة واحدة فقط لتهيئة قاعدة البيانات، إلّا أنّك قد ترغب مُستقبلًا بتنفيذه مُجدّدًا بغية تفريغ قاعدة البيانات من كافّة محتوياتها والبدء من جديد بقاعدة بيانات فارغة.
</p>

<p>
	ثمّ استخدمنا الأمر <code>CREATE TABLE books</code> لإنشاء جدول للكتب ضمن قاعدة البيانات باسم <code>books</code> يحتوي على الأعمدة التالية:
</p>

<ul>
	<li>
		'id': يحتوي على بياناتٍ من نمط رقم تسلسلي <code>serial</code>، أي أعداد صحيحة متزايدة تلقائيًا، ويمثّل هذا العمود مفتاحًا أساسيًا والذي قد حدّدناه باستخدام الكلمات المفتاحية <code>PRIMARY KEY</code>. وستُخصّص قاعدة البيانات قيمة فريدة لهذا المفتاح من أجل كل سجل مُدخل (والسجل هو الكتاب في حالتنا).
	</li>
	<li>
		"title": يمثّل عنوان الكتاب، ويحتوي على بيانات من النوع <code>varchar</code>، وهو نوع بيانات يحتوي على محارف بطول معين، وفي حالتنا <code>(varchar (150</code> تعني أنّ عدد المحارف المتاحة للعنوان هي حتى 150 محرف، أمّا التعليمة <code>NOT NULL</code> فتعني أنّه من غير المسموح ترك هذا العمود فارغًا.
	</li>
	<li>
		<code>author</code>: يحتوي على اسم مؤلف الكتاب، على ألّا يتجاوز 50 محرف، أمّا التعليمة <code>NOT NULL</code> فتعني أنّه من غير المسموح ترك هذا العمود فارغًا.
	</li>
	<li>
		<code>pages_num</code>: يحتوي على عدد صحيح يمثّل عدد صفحات الكتاب، وتعني التعليمة <code>NOT NULL</code> أنّه من غير المسموح ترك هذا العمود فارغًا.
	</li>
	<li>
		<code>review</code>: يحتوي على نبذة عن الكتاب، وبياناته من النوع <code>text</code>، ما يعني أنّ محتويات هذا العمود هي سلاسل نصية بأي طول.
	</li>
	<li>
		'date_added': يحتوي على تاريخ إضافة الكتاب إلى الجدول، والقيمة <code>DEFAULT</code> تعني ضبط القيمة الافتراضية إلى <code>CURRENT_TIMESTAMP</code>، التي تمثِّل وقت إضافة الكتاب إلى قاعدة البيانات، وكما هو الحال في عمود <code>id</code>، لا يتوجب عليك تحديد قيم لهذا العمود، إذ أنها تُملأ تلقائيًا.
	</li>
</ul>

<p>
	بعد الانتهاء من إنشاء الجدول، استخدمنا تابع المؤشّر <code>()execute</code> لإدخال كتابين في الجدول، الأول بعنوان "A Tale of Two Cities" للكاتب "Charles Dickens"، والثاني بعنوان "Anna Karenina" للمؤلف "Leo Tolstoy"، وقد استخدمنا الموضع المؤقت <code>s%</code> لتمرير القيم إلى تعليمات SQL، إذ تتعامل مكتبة <code>psycopg2</code> مع عمليات الإدخال في الخلفية بطريقة تضمن الحماية من هجمات <a href="https://academy.hsoub.com/tags/sql%20injection/" rel="">حقن تعليمات SQL</a>.
</p>

<p>
	بعد الانتهاء من إدخال بيانات الكتب إلى الجدول، استخدمنا التابع <a href="https://www.psycopg.org/docs/connection.html#connection.commit" rel="external nofollow"><code>()connection.commit</code></a> لتأكيد العملية وتطبيق التغييرات على قاعدة البيانات، ونهايةً أغلقنا كل من المؤشر والاتصال باستخدام التابعين <code>()cur.close</code> و <code>()conn.close</code> على التوالي.
</p>

<p>
	الآن، سنشغّل الأوامر التالية لتأسيس الاتصال مع قاعدة البيانات، وإعداد كل من متغيري البيئة <code>DB_USERNAME</code> و <code>DB_PASSWORD</code>، ولكن استخدم اسم المستخدم وكلمة المرور الخاصين بك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_35" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export DB_USERNAME</span><span class="pun">=</span><span class="str">"user"</span><span class="pln">
</span><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export DB_PASSWORD</span><span class="pun">=</span><span class="str">"password"</span></pre>

<p>
	والآن، سنشغّل الملف "init_db.py" ضمن نافذة الطرفية باستخدام الأمر <code>python</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_37" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ python init_db</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	وفور انتهاء تنفيذ الملف بنجاح، سيُضاف جدول جديد باسم "books" إلى قاعدة البيانات "flask_db".
</p>

<p>
	والآن سجّل الدخول إلى جلسة Postgres تفاعلية للتحقّق من الجدول الجديد المُسمّى "books"، على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_40" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ sudo </span><span class="pun">-</span><span class="pln">iu postgres psql</span></pre>

<p>
	نتصل مع قاعدة البيانات "flask_db" باستخدام الأمر <code>c\</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_44" style=""><span class="pln">postgres</span><span class="pun">=#</span><span class="pln"> \c flask_db</span></pre>

<p>
	سنستخدم الآن التعليمة <code>SELECT</code> للحصول على عناوين الكتب وأسماء مؤلفيها من جدول الكتب <code>books</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_46" style=""><span class="pln">postgres</span><span class="pun">=#</span><span class="pln"> SELECT title</span><span class="pun">,</span><span class="pln"> author FROM books</span><span class="pun">;</span></pre>

<p>
	فيظهر الخرج كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_48" style=""><span class="pln">       title         </span><span class="pun">|</span><span class="pln">      author
</span><span class="pun">----------------------+------------------</span><span class="pln">
 A </span><span class="typ">Tale</span><span class="pln"> of </span><span class="typ">Two</span><span class="pln"> </span><span class="typ">Cities</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> </span><span class="typ">Charles</span><span class="pln"> </span><span class="typ">Dickens</span><span class="pln">
 </span><span class="typ">Anna</span><span class="pln"> </span><span class="typ">Karenina</span><span class="pln">        </span><span class="pun">|</span><span class="pln"> </span><span class="typ">Leo</span><span class="pln"> </span><span class="typ">Tolstoy</span></pre>

<p>
	نُغلق الجلسة التفاعلية باستخدام الأمر <code>q\</code>.
</p>

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

<h2>
	الخطوة 4 - عرض الكتب
</h2>

<p>
	سنعمل في هذه الخطوة على إنشاء تطبيق فلاسك ذو صفحة رئيسية مسؤولة عن عرض الكتب الموجودة في قاعدة البيانات بعد جلبها منها.
</p>

<p>
	لذا وأثناء كون البيئة البرمجية مُفعّلة وبعد تثبيت فلاسك، سنفتح ملف "app.py" ضمن المجلد "flask_app" لتحريره:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_50" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	سيعمل هذا الملف على إعداد الاتصال مع قاعدة البيانات وإنشاء وجهة فلاسك وحيدة قادرة على استخدام هذا الاتصال، لذا سنكتب ضمنه الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_53" style=""><span class="kwd">import</span><span class="pln"> os
</span><span class="kwd">import</span><span class="pln"> psycopg2
</span><span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> render_template

app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> get_db_connection</span><span class="pun">():</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> psycopg2</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="pln">host</span><span class="pun">=</span><span class="str">'localhost'</span><span class="pun">,</span><span class="pln">
                            database</span><span class="pun">=</span><span class="str">'flask_db'</span><span class="pun">,</span><span class="pln">
                            user</span><span class="pun">=</span><span class="pln">os</span><span class="pun">.</span><span class="pln">environ</span><span class="pun">[</span><span class="str">'DB_USERNAME'</span><span class="pun">],</span><span class="pln">
                            password</span><span class="pun">=</span><span class="pln">os</span><span class="pun">.</span><span class="pln">environ</span><span class="pun">[</span><span class="str">'DB_PASSWORD'</span><span class="pun">])</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> conn


</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> index</span><span class="pun">():</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">
    cur </span><span class="pun">=</span><span class="pln"> conn</span><span class="pun">.</span><span class="pln">cursor</span><span class="pun">()</span><span class="pln">
    cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'SELECT * FROM books;'</span><span class="pun">)</span><span class="pln">
    books </span><span class="pun">=</span><span class="pln"> cur</span><span class="pun">.</span><span class="pln">fetchall</span><span class="pun">()</span><span class="pln">
    cur</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'index.html'</span><span class="pun">,</span><span class="pln"> books</span><span class="pun">=</span><span class="pln">books</span><span class="pun">)</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	استوردنا بدايةً في الشيفرة السابقة كل من الوحدة <code>os</code> والمكتبة <code>psycopg2</code> والصنف <code>Flask</code> ودالة تصيير القوالب <code>()render_template</code> من حزمة فلاسك، كما أنشأنا نسخة من التطبيق باسم "app"، ثمّ عرفنا دالةً باسم <code>()get_db_connection</code> لإنشاء اتصال مع قاعدة البيانات "flask_db" باستخدام اسم المستخدم وكلمة المرور المخزنيْن في متغيرات البيئة <code>DB_USERNAME</code> و <code>DB_PASSWORD</code> على التوالي، ويعيد التابع كائن الاتصال <code>conn</code>، الذي سنستخدمه للوصول إلى قاعدة البيانات.
</p>

<p>
	استخدمنا بعد ذلك المُزخرف <code>()app.route</code> لإنشاء وجهة رئيسية <code>/</code> ودالة عاملة في فلاسك باسم <code>()index</code>، وفتحنا اتصالًا مع قاعدة البيانات باستخدام الدالة <code>get_db_connection()‎</code>، ثم أنشأنا مؤشرًا ونفذنا التعليمة <code>;SELECT * FROM books</code> من تعليمات SQL بغية الحصول على كل الكتب الموجودة في قاعدة البيانات، إذ أنّنا نستدعي التابع <code>fetchall()‎</code> لحفظ البيانات ضمن متغير باسم <code>books</code>، ومن ثم نغلق كل من المؤشر والاتصال مع قاعدة البيانات. ونهايةً نجعل القيمة المعادة استدعاءً للدالة <code>()render_template</code> لإخراج ملف قالب باسم "index.html" ممررين له قائمة الكتب التي جلبناها من قاعدة البيانات إلى المتغير <code>books</code>.
</p>

<p>
	الآن، وبغية عرض الكتب المُخزنة ضمن قاعدة البيانات في الصفحة الرئيسية للتطبيق، سننشئ بدايةً ملف قالب ليتضمّن كافة شيفرات HTML الأساسية اللازمة لتستخدمها لاحقًا القوالب الأُخرى، وهذا ما يجنبنا تكرار الشيفرات، ثمّ سننشئ ملف قالب الصفحة الرئيسية "index.html" المُصيّر أصلًا باستخدام الدالة <code>()index</code>.
</p>

<p>
	سننشئ مجلدًا للقوالب باسم "templates" وننشئ ضمنه ملف قالب باسم "base.html"، والذي سيمثّل القالب الأساسي لبقية القوالب على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_55" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ mkdir templates
</span><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">base</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	وسنكتب ضمنه الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4546_57" style=""><span class="dec">&lt;!DOCTYPE html&gt;</span><span class="pln">
</span><span class="tag">&lt;html</span><span class="pln"> </span><span class="atn">lang</span><span class="pun">=</span><span class="atv">"en"</span><span class="tag">&gt;</span><span class="pln">
</span><span class="tag">&lt;head&gt;</span><span class="pln">
    </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">"UTF-8"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;title&gt;</span><span class="pln">{% block title %} {% endblock %}- FlaskApp</span><span class="tag">&lt;/title&gt;</span><span class="pln">
    </span><span class="tag">&lt;style&gt;</span><span class="pln">
        nav a </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#d64161</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">font-size</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3em</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">margin-left</span><span class="pun">:</span><span class="pln"> </span><span class="lit">50px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">text-decoration</span><span class="pun">:</span><span class="pln"> none</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        </span><span class="pun">.</span><span class="pln">book </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">padding</span><span class="pun">:</span><span class="pln"> </span><span class="lit">20px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">margin</span><span class="pun">:</span><span class="pln"> </span><span class="lit">10px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">background-color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#f7f4f4</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        </span><span class="pun">.</span><span class="pln">review </span><span class="pun">{</span><span class="pln">
                </span><span class="kwd">margin-left</span><span class="pun">:</span><span class="pln"> </span><span class="lit">50px</span><span class="pun">;</span><span class="pln">
                </span><span class="kwd">font-size</span><span class="pun">:</span><span class="pln"> </span><span class="lit">20px</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

    </span><span class="tag">&lt;/style&gt;</span><span class="pln">
</span><span class="tag">&lt;/head&gt;</span><span class="pln">
</span><span class="tag">&lt;body&gt;</span><span class="pln">
    </span><span class="tag">&lt;nav&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('index') }}"</span><span class="tag">&gt;</span><span class="pln">FlaskApp</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"#"</span><span class="tag">&gt;</span><span class="pln">About</span><span class="tag">&lt;/a&gt;</span><span class="pln">
    </span><span class="tag">&lt;/nav&gt;</span><span class="pln">
    </span><span class="tag">&lt;hr&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"content"</span><span class="tag">&gt;</span><span class="pln">
        {% block content %} {% endblock %}
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
</span><span class="tag">&lt;/body&gt;</span><span class="pln">
</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	احفظ الملف واغلقه.
</p>

<p>
	يتضمّن القالب الأساسي كافّة الشيفرات المتداولة التي سنحتاجها في القوالب الأُخرى.
</p>

<p>
	ستُستبدل لاحقًا كتلة العنوان <code>title</code> بعنوان كل صفحة وكتلة المحتوى <code>content</code> بمحتواها، أمّا عن شريط التصفح فسيتضمّن رابطين، الأوّل ينقل المُستخدم إلى الصفحة الرئيسية للتطبيق باستخدام الدالة المساعدة <code>()url_for</code> لتحقيق الربط مع دالة العرض <code>()index</code>، والآخر لصفحة المعلومات حول التطبيق <strong>About</strong> في حال قررت تضمينها في تطبيقك.
</p>

<p>
	الآن، سنفتح ملف القالب باسم "index.html" وهو الاسم الذي حددناه في الملف "app.py":
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_59" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">index</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	ونضيف ضمنه الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_61" style=""><span class="pun">{%</span><span class="pln"> extends </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">

</span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">h1</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="typ">Books</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> book </span><span class="kwd">in</span><span class="pln"> books </span><span class="pun">%}</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">'book'</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">h3</span><span class="pun">&gt;#{{</span><span class="pln"> book</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln"> </span><span class="pun">}}</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">{{</span><span class="pln"> book</span><span class="pun">[</span><span class="lit">1</span><span class="pun">]</span><span class="pln"> </span><span class="pun">}}</span><span class="pln"> BY </span><span class="pun">{{</span><span class="pln"> book</span><span class="pun">[</span><span class="lit">2</span><span class="pun">]</span><span class="pln"> </span><span class="pun">}}&lt;/</span><span class="pln">h3</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">i</span><span class="pun">&gt;&lt;</span><span class="pln">p</span><span class="pun">&gt;({{</span><span class="pln"> book</span><span class="pun">[</span><span class="lit">3</span><span class="pun">]</span><span class="pln"> </span><span class="pun">}}</span><span class="pln"> pages</span><span class="pun">)&lt;/</span><span class="pln">p</span><span class="pun">&gt;&lt;/</span><span class="pln">i</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">p </span><span class="kwd">class</span><span class="pun">=</span><span class="str">'review'</span><span class="pun">&gt;{{</span><span class="pln"> book</span><span class="pun">[</span><span class="lit">4</span><span class="pun">]</span><span class="pln"> </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">i</span><span class="pun">&gt;&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="typ">Added</span><span class="pln"> </span><span class="pun">{{</span><span class="pln"> book</span><span class="pun">[</span><span class="lit">5</span><span class="pun">]</span><span class="pln"> </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;&lt;/</span><span class="pln">i</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">{%</span><span class="pln"> endfor </span><span class="pun">%}</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	اعتمدنا في الشيفرة السابقة على الوراثة من ملف القالب "base.html" من خلال تعليمة <code>extends</code>، واستبدلنا محتوى كتلة المحتوى <code>content</code> مُستخدمين تنسيق العنوان من المستوى الأوّل <code>&lt;h1&gt;</code> الذي يفي أيضًا بالغرض مثل عنوان للصفحة.
</p>

<p>
	استخدمنا في السطر البرمجي <code>{% for book in books %}</code> حلقة <code>for</code> من تعليمات محرّك القوالب <a href="https://academy.hsoub.com/tags/jinja2" rel="">جينجا jinja</a>، والهدف من استخدام هذه الحلقة هو المرور على كل عنصر في القائمة <code>books</code>، إذ نظهر بدايةً رقم معرّف الكتاب ID والذي يمثّل العنصر الأوّل باستخدام الأمر <code>[book[0</code>، ثمّ نظهر كلًا من عنوان الكتاب واسم مؤلفه وعدد صفحاته والنبذة التعريفية عنه وتاريخ إضافته.
</p>

<p>
	الآن ومع وجودنا ضمن المجلد flask_app ومع كون البيئة الافتراضية مُفعّلة، نُعلم فلاسك بالتطبيق المراد تشغيله (وهو في حالتنا الملف "app.py") باستخدام متغير البيئة <code>FLASK_APP</code>، ومن ثمّ نعيّن وضع التشغيل من خلال متغير البيئة <code>FLASK_ENV</code> واخترنا هنا العمل في وضع التطوير <code>development</code>، مع تشغيل مُنقّح الأخطاء. للمزيد من المعلومات حول مُنقّح الأخطاء في فلاسك ننصحك بقراءة المقال <a href="https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A3%D8%AE%D8%B7%D8%A7%D8%A1-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1738/" rel="">كيفية التعامل مع الأخطاء في تطبيقات فلاسك</a>. لتنفيذ ما سبق سنشغّل الأوامر التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_63" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_APP</span><span class="pun">=</span><span class="pln">app
</span><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_ENV</span><span class="pun">=</span><span class="pln">development</span></pre>

<p>
	وهنا لا بُدّ من التأكد من تعيين متغيرات البيئة <code>DB_PASSWORD</code> و <code>DB_USERNAME</code> في حال عدم إعدادها مُسبقًا:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_65" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export DB_USERNAME</span><span class="pun">=</span><span class="str">"user"</span><span class="pln">
</span><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export DB_PASSWORD</span><span class="pun">=</span><span class="str">"password"</span></pre>

<p>
	والآن سنشغّل التطبيق باستخدام الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_67" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ flask run</span></pre>

<p>
	وبعد التأكد من كون خادم التطوير ما يزال قيد التشغيل، نذهب إلى الرابط التالي باستخدام المتصفح:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_69" style=""><span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span></pre>

<p>
	وعندها ستظهر لك الكتب التي أضفتها بدايةً إلى قاعدة البيانات بالشّكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="115934" href="https://academy.hsoub.com/uploads/monthly_2023_01/V5uBvuu.png.ecef3c8019b2f3ea380602538bfa0aa9.png" rel=""><img alt="ظهور الكتب المضافة لقاعدة البيانات في فلاسك" class="ipsImage ipsImage_thumbnailed" data-fileid="115934" data-ratio="50.11" data-unique="k2egyga0i" style="width: 750px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2023_01/V5uBvuu.thumb.png.c37e96abcc2e3a40a2022b50d4657e5a.png"></a>
</p>

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

<h2>
	الخطوة 5 - إضافة كتب جديدة
</h2>

<p>
	سنعمل في هذه الخطوة على إضافة وجهة جديدة للسماح للمُستخدمين بإضافة كتب جديدة ونبذات عنها إلى قاعدة البيانات. لذلك، سنضيف للتطبيق صفحة جديدة تتضمّن نموذج ويب لإدخال كل من عنوان الكتاب واسم مؤلفه وعدد صفحاته ونبذة عنه من قبل المستخدم.
</p>

<p>
	سنفتح نافذة طرفية جديدة مع بقاء خادم التطوير قيد التشغيل، ثمّ سنفتح الملف "app.py":
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_73" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	للتعامل مع نموذج الويب، لا بُد أولًا من استيراد التالي من حزمة فلاسك:
</p>

<ul>
	<li>
		الكائن <a href="https://flask.palletsprojects.com/en/2.0.x/api/#flask.request" rel="external nofollow"><code>request</code></a> العام المسؤول عن الوصول إلى بيانات الطلب المُرسَل من قبل المُستخدم.
	</li>
	<li>
		الدالة <a href="https://flask.palletsprojects.com/en/2.0.x/api/#flask.url_for" rel="external nofollow"><code>url_for()‎</code></a> لتوليد عناوين الروابط.
	</li>
	<li>
		الدالة <a href="https://flask.palletsprojects.com/en/2.0.x/api/#flask.redirect" rel="external nofollow"><code>redirect()‎</code></a> لإعادة توجيه المستخدم إلى صفحة التطبيق الرئيسية بعد إضافة كتاب إلى قاعدة البيانات.
	</li>
</ul>

<p>
	أضِف هذه الاستيرادات إلى السطر الأوّل من الملف "app.py" كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_75" style=""><span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> render_template</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">,</span><span class="pln"> url_for</span><span class="pun">,</span><span class="pln"> redirect

</span><span class="com"># ...</span></pre>

<p>
	ثمّ أضِف الوجهة التالية إلى نهايته:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_77" style=""><span class="com"># ...</span><span class="pln">


</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/create/'</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=(</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">))</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> create</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'create.html'</span><span class="pun">)</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	مرّرنا في هذه الوجهة صفًا tuple، يحتوي على القيم <code>('GET', 'POST')</code> إلى المعامل <code>methods</code> بغية السماح بكلا نوعي طلبات HTTP وهما <code>GET</code> و <code>POST</code>، إذ تتخصّص الطلبات من النوع <code>GET</code> بجلب البيانات من الخادم، أمّا الطلبات من النوع <code>POST</code> فهي مُتخصّصة بإرسال البيانات إلى وجهة مُحدّدة، مع ملاحظة أنّ الطلبات من النوع <code>GET</code> هي الوحيدة المسموحة افتراضيًا.
</p>

<p>
	وحالما يطلب المستخدم الوجهة <code>create/</code> باستخدام طلب من النوع <code>GET</code>، سيُصيّر ملف قالب باسم "create.html"، وسنعدّل هذه الوجهة لاحقًا لتتعامل أيضًا مع الطلبات <code>POST</code> اللازمة لدى ملء المُستخدمين للنماذج وإرسالها بغية إضافة كتب جديدة.
</p>

<p>
	الآن، افتح ملف "create.html" الجديد داخل مجلد القوالب "templates" على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_79" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">create</span><span class="pun">.</span><span class="pln">html</span></pre>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_82" style=""><span class="pun">{%</span><span class="pln"> extends </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">

</span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">h1</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="typ">Add</span><span class="pln"> a </span><span class="typ">New</span><span class="pln"> </span><span class="typ">Book</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">form method</span><span class="pun">=</span><span class="str">"post"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">label </span><span class="kwd">for</span><span class="pun">=</span><span class="str">"title"</span><span class="pun">&gt;</span><span class="typ">Title</span><span class="pun">&lt;/</span><span class="pln">label</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"text"</span><span class="pln"> name</span><span class="pun">=</span><span class="str">"title"</span><span class="pln">
                   placeholder</span><span class="pun">=</span><span class="str">"Book title"</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">input</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">

        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">label </span><span class="kwd">for</span><span class="pun">=</span><span class="str">"author"</span><span class="pun">&gt;</span><span class="typ">Author</span><span class="pun">&lt;/</span><span class="pln">label</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"text"</span><span class="pln"> name</span><span class="pun">=</span><span class="str">"author"</span><span class="pln">
                   placeholder</span><span class="pun">=</span><span class="str">"Book author"</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">input</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">

        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">label </span><span class="kwd">for</span><span class="pun">=</span><span class="str">"pages_num"</span><span class="pun">&gt;</span><span class="typ">Number</span><span class="pln"> of pages</span><span class="pun">&lt;/</span><span class="pln">label</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"number"</span><span class="pln"> name</span><span class="pun">=</span><span class="str">"pages_num"</span><span class="pln">
                   placeholder</span><span class="pun">=</span><span class="str">"Number of pages"</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">input</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">label </span><span class="kwd">for</span><span class="pun">=</span><span class="str">"review"</span><span class="pun">&gt;</span><span class="typ">Review</span><span class="pun">&lt;/</span><span class="pln">label</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">br</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">textarea name</span><span class="pun">=</span><span class="str">"review"</span><span class="pln">
                  placeholder</span><span class="pun">=</span><span class="str">"Review"</span><span class="pln">
                  rows</span><span class="pun">=</span><span class="str">"15"</span><span class="pln">
                  cols</span><span class="pun">=</span><span class="str">"60"</span><span class="pln">
                  </span><span class="pun">&gt;&lt;/</span><span class="pln">textarea</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">button type</span><span class="pun">=</span><span class="str">"submit"</span><span class="pun">&gt;</span><span class="typ">Submit</span><span class="pun">&lt;/</span><span class="pln">button</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">form</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	احفظ الملف واغلقه.
</p>

<p>
	اعتمدنا في الشيفرة السابقة على الوراثة من ملف القالب "base.html" من خلال تعليمة <code>extends</code>، وعيّنا وسم ترويسة ليكون عنوانًا؛ واستخدمنا الوسم <code>&lt;form&gt;</code> وضبطنا فيه السمة <code>method</code> التي تحدّد نوع طلب HTTP لتكون من النوع <code>POST</code>، ما يعني أنّ نموذج الويب هذا سيرسل طلبًا من النوع <code>POST</code>؛ كما أضفنا صندوقًا نصيًا باسم <code>title</code> لنستخدمه للوصول إلى بيانات عنوان الكتاب في الوجهة <code>create/</code>؛ وأضفنا صندوقًا نصيًا مخصصًا لاسم مؤلف الكتاب، وصندوق عددي مخصص لعدد صفحات الكتاب؛ كما أضفنا حقلًا نصيًا مُتعدّد الأسطر مُخصّص للنبذة حول الكتاب، ونهايةً أضفنا إلى نهاية النموذج زر <strong>Submit</strong> لتأكيد إرساله.
</p>

<p>
	الآن، وأثناء خادم التطوير، استخدم المتصفح للانتقال إلى الوجهة <code>‎/create</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_84" style=""><span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln">create</span></pre>

<p>
	فستظهر لك صفحة إضافة كتاب جديد مع صندوق نصي لإدخال عنوان الكتاب، وآخر لإدخال اسم مؤلفه، ومربع لإدخال عدد صفحاته وحقل نصي مُتعدّد الأسطر لإدخال نبذة حول الكتاب، وزر لتأكيد إرسال النموذج، كما في الشّكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="115933" href="https://academy.hsoub.com/uploads/monthly_2023_01/1st.png.e581e851ff2b873f2bfd0222e00335d0.png" rel=""><img alt="إضافة الكتب في قاعدة البيانات" class="ipsImage ipsImage_thumbnailed" data-fileid="115933" data-ratio="48.00" data-unique="xwew5hin8" style="width: 750px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2023_01/1st.thumb.png.67dce4589e08a967a5ab411ca1b0d0bf.png"></a>
</p>

<p>
	يرسل نموذج الإدخال هذا طلبًا من النوع "POST" إلى الخادم، ولكن حتى هذه اللحظة لا يوجد شيفرة مسؤولة عن معالجة هذا الطلب في الوجهة <code>create/</code>، وبالتالي لن يحدث شيء في حال ملء النموذج الآن وإرساله.
</p>

<p>
	الآن، افتح الملف "app.py" لتعديله بحيث يتعامل مع الطلبات من النوع "POST" المُرسلة من قبل المستخدم:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_86" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ونعدّل الوجهة <code>create/</code> لتصبح كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_91" style=""><span class="com"># ...</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/create/'</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=(</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">))</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> create</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">method </span><span class="pun">==</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">:</span><span class="pln">
        title </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'title'</span><span class="pun">]</span><span class="pln">
        author </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'author'</span><span class="pun">]</span><span class="pln">
        pages_num </span><span class="pun">=</span><span class="pln"> int</span><span class="pun">(</span><span class="pln">request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'pages_num'</span><span class="pun">])</span><span class="pln">
        review </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'review'</span><span class="pun">]</span><span class="pln">

        conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">
        cur </span><span class="pun">=</span><span class="pln"> conn</span><span class="pun">.</span><span class="pln">cursor</span><span class="pun">()</span><span class="pln">
        cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'INSERT INTO books (title, author, pages_num, review)'</span><span class="pln">
                    </span><span class="str">'VALUES (%s, %s, %s, %s)'</span><span class="pun">,</span><span class="pln">
                    </span><span class="pun">(</span><span class="pln">title</span><span class="pun">,</span><span class="pln"> author</span><span class="pun">,</span><span class="pln"> pages_num</span><span class="pun">,</span><span class="pln"> review</span><span class="pun">))</span><span class="pln">
        conn</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">
        cur</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
        conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> redirect</span><span class="pun">(</span><span class="pln">url_for</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">))</span><span class="pln">

    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'create.html'</span><span class="pun">)</span></pre>

<p>
	احفظ الملف واغلقه.
</p>

<p>
	تَمكَّنا باستخدام العبارة الشرطية <code>if request.method == 'POST'</code> التي تقارن قيمة <code>request.method</code> مع القيمة <code>POST</code> من التحقُّق بأنّ التعليمات التالية لها لن تُنفّذ إلّا إذا كان الطلب الحالي هو فعلًا بطريقة <code>POST</code>، ومن ثمّ قرأنا قيم كل من عنوان الكتاب واسم مؤلفه وعدد صفحاته والنبذة المرسلة عنه من الكائن <code>request.form</code> الذي يمكِّننا من الوصول إلى بيانات نموذج الإدخال المُضمّنة في الطلب.
</p>

<p>
	فتحنا بعد ذلك اتصالًا مع قاعدة البيانات باستخدام الدالة <code>get_db_connection()‎</code>، وأنشأنا مؤشرًا، ونفّذنا الأمر <code>INSERT INTO</code> من أوامر SQL باستخدام التابع <code>()execute</code> لإدخال كل من عنوان الكتاب واسم مؤلفه وعدد صفحاته ونبذة عنه إلى جدول الكتب <code>books</code> في قاعدة البيانات وفق ما أُدخل أصلًا من قبل المُستخدم في النموذج.
</p>

<p>
	نهايةً، حفظنا التغييرات باستخدام الدالة <code>()connection.commit</code>، وأُغلق الاتصال مع قاعدة البيانات باستخدام الدالة <code>()connection.close</code>، ثم أعدنا توجيه المًستخدم إلى الصفحة الرئيسية من التطبيق ليرى الكتاب الجديد المُضاف أسفل الكتب السابقة الموجودة أصلًا.
</p>

<p>
	الآن وأثناء عمل خادم التطوير، استخدم المتصفح للانتقال إلى الوجهة <code>‎/create</code> باستخدام الرابط:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_93" style=""><span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln">create</span></pre>

<p>
	املأ النموذج بالبيانات التي تريد، وحال إرسال النموذج سيُعاد توجيهك إلى الصفحة الرئيسية للتطبيق لترى الكتاب الجديد فيها.
</p>

<p>
	سنفتح الآن ملف القالب الأساسي base.html لإضافة رابط في شريط التصفّح للوصول إلى صفحة إضافة كتاب جديد:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4546_95" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">base</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	ونعدّل الملف ليصبح كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4546_97" style=""><span class="dec">&lt;!DOCTYPE html&gt;</span><span class="pln">
</span><span class="tag">&lt;html</span><span class="pln"> </span><span class="atn">lang</span><span class="pun">=</span><span class="atv">"en"</span><span class="tag">&gt;</span><span class="pln">
</span><span class="tag">&lt;head&gt;</span><span class="pln">
    </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">"UTF-8"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;title&gt;</span><span class="pln">{% block title %} {% endblock %} - FlaskApp</span><span class="tag">&lt;/title&gt;</span><span class="pln">
    </span><span class="tag">&lt;style&gt;</span><span class="pln">
        nav a </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#d64161</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">font-size</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3em</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">margin-left</span><span class="pun">:</span><span class="pln"> </span><span class="lit">50px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">text-decoration</span><span class="pun">:</span><span class="pln"> none</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        </span><span class="pun">.</span><span class="pln">book </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">padding</span><span class="pun">:</span><span class="pln"> </span><span class="lit">20px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">margin</span><span class="pun">:</span><span class="pln"> </span><span class="lit">10px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">background-color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#f7f4f4</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        </span><span class="pun">.</span><span class="pln">review </span><span class="pun">{</span><span class="pln">
                </span><span class="kwd">margin-left</span><span class="pun">:</span><span class="pln"> </span><span class="lit">50px</span><span class="pun">;</span><span class="pln">
                </span><span class="kwd">font-size</span><span class="pun">:</span><span class="pln"> </span><span class="lit">20px</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

    </span><span class="tag">&lt;/style&gt;</span><span class="pln">
</span><span class="tag">&lt;/head&gt;</span><span class="pln">
</span><span class="tag">&lt;body&gt;</span><span class="pln">
    </span><span class="tag">&lt;nav&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('index') }}"</span><span class="tag">&gt;</span><span class="pln">FlaskApp</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('create') }}"</span><span class="tag">&gt;</span><span class="pln">Create</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"#"</span><span class="tag">&gt;</span><span class="pln">About</span><span class="tag">&lt;/a&gt;</span><span class="pln">
    </span><span class="tag">&lt;/nav&gt;</span><span class="pln">
    </span><span class="tag">&lt;hr&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"content"</span><span class="tag">&gt;</span><span class="pln">
        {% block content %} {% endblock %}
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
</span><span class="tag">&lt;/body&gt;</span><span class="pln">
</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	أضفنا في الشيفرة السابقة وسم رابط <code>&lt;a&gt;</code> جديد إلى شريط التصفُّح والذي يشير إلى صفحة إضافة كتاب جديد، إذ سيظهر الرابط الجديد بتحديث الصفحة الرئيسية للتطبيق ضمن شريط التصفُّح.
</p>

<p>
	ومع نهاية هذه الخطوة أصبح لدينا صفحة ذات نموذج إدخال لإضافة نبذات عن كتب جديدة، ولمزيدٍ من المعلومات حول النماذج وكيفية جعلها آمنة، ننصحك بقراءة المقال <a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%86%D9%85%D8%A7%D8%B0%D8%AC-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D9%88%D8%A7%D9%84%D8%AA%D8%AD%D9%82%D9%82-%D9%85%D9%86%D9%87%D8%A7-%D9%81%D9%8A-%D9%81%D9%84%D8%A7%D8%B3%D9%83-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-flask-wtf-r1838/" rel="">استخدام والتحقق من نماذج الويب ذات واجهة المستخدم التفاعلية في فلاسك باستخدام الإضافة Flask-WTF</a> والمقال <a href="https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%86%D9%85%D8%A7%D8%B0%D8%AC-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1739/" rel="">كيفية استخدام نماذج الويب في تطبيقات فلاسك</a>.
</p>

<h1>
	الخاتمة
</h1>

<p>
	أنشأنا في هذا المقال تطبيق ويب مصغّر لتقييمات الكتب والذي يتخاطب مع قاعدة بيانات PostgreSQL، مع كامل الوظائف الأساسية من إضافة بيانات جديدة إلى قاعدة البيانات، وجلب بيانات منها لعرضها في صفحات التطبيق.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-use-a-postgresql-database-in-a-flask-application" rel="external nofollow">How To Use a PostgreSQL Database in a Flask Application</a> لصاحبه Abdelhadi Dyouri.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/devops/servers/databases/postgresql/%D8%A7%D9%84%D9%85%D8%B1%D8%AC%D8%B9-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%AA%D8%B9%D9%84%D9%85-postgresql-r481/" rel="">المرجع الشامل إلى تعلم PostgreSQL</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%AC%D9%87%D9%8A%D8%B2-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-postgresql-%D9%88%D8%A7%D9%84%D8%AA%D9%91%D8%B9%D8%B1%D9%8A%D9%81-%D8%A8%D9%85%D9%81%D9%87%D9%88%D9%85%D9%8A-orm-%D9%88%D8%A5%D8%B6%D8%A7%D9%81%D8%A7%D8%AA-flask-r506/" rel="">تجهيز قاعدة البيانات PostgreSQL والتّعريف بمفهومي ORM وإضافات Flask</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1840</guid><pubDate>Tue, 24 Jan 2023 16:02:00 +0000</pubDate></item><item><title>&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x642;&#x627;&#x639;&#x62F;&#x629; &#x628;&#x64A;&#x627;&#x646;&#x627;&#x62A; SQLite &#x641;&#x64A; &#x62A;&#x637;&#x628;&#x64A;&#x642; &#x641;&#x644;&#x627;&#x633;&#x643;</title><link>https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-sqlite-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%81%D9%84%D8%A7%D8%B3%D9%83-r1839/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_01/2069517650_---SQLite---.png.f49e9a3e6e3f225dcecbf4c8d2ce39f6.png" /></p>
<p>
	نحتاج عادةً إلى <a href="https://academy.hsoub.com/devops/servers/databases/%D8%AE%D8%B5%D8%A7%D8%A6%D8%B5-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D9%85%D8%B2%D8%A7%D9%8A%D8%A7-%D8%A7%D9%84%D8%AA%D9%8A-%D8%AA%D9%82%D8%AF%D9%85%D9%87%D8%A7-r520/" rel="">قاعدة بيانات</a> في تطبيقات الويب، وهي مجموعةٌ مُنظمّةٌ من البيانات نستخدمها لتخزين وتنظيم البيانات الدائمة، موفّرةً لنا إمكانية استرجاع هذه البيانات وإدارتها بفعالية، إذ نحتاج مثلًا في تطبيق ما للتواصل الاجتماعي إلى قاعدة بيانات لتخزين بيانات المستخدم (من معلومات شخصية ومنشورات وتعليقات ومتابعين) بحيث نتمكّن من معالجة هذه البيانات بفعالية، إذ توفّر قاعدة البيانات إمكانية إضافة بيانات جديدة إليها، أو استرجاع بيانات مُخزّنة أصلًا، أو التعديل عليها، أو حذفها بكل مرونة وذلك اعتمادًا على المتطلبات والشروط والظروف المُختلفة؛ ففي تطبيق ويب ما قد تكون هذه المتطلبات مثلًا هي حالة مُستخدم يضيف منشورًا جديدًا، أو يحذف منشورًا سابقًا، أو يحذف حسابه مع كافّة منشوراته أو مع الإبقاء عليها، بمعنى أنّ طريقة معالجة البيانات تعتمد أولًا وأخيرًا على الميزات التي يتيحها التطبيق، فمثلًا قد لا ترغب بالسماح لمُستخدمي تطبيقك بإضافة منشورات غير معنونة.
</p>

<p>
	يُعد <a href="https://academy.hsoub.com/programming/python/flask/" rel="">فلاسك</a> إطار عمل للويب مبني بلغة <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D9%85%D8%B1%D8%AC%D8%B9-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%AA%D8%B9%D9%84%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r735/" rel="">بايثون</a>، ويتميز بكونه صغير الحجم وسهل المعالجة، ويوفّر أيضًا عدة أدوات وميزات من شأنها إنشاء تطبيقات ويب في لغة بايثون.
</p>

<p>
	أمّا <a href="https://academy.hsoub.com/devops/servers/databases/%D9%83%D9%8A%D9%81-%D9%88%D9%85%D8%AA%D9%89-%D9%86%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-sqlite-r111/" rel="">SQLite</a> فهو محّرك قواعد بيانات مفتوح المصدر، يمتاز ببساطته وسرعته ويُستخدم مع بايثون لتخزين بيانات التطبيق ومعالجتها، إذ يعمل مع بايثون بكفاءة، نظرًا لكون مكتبة بايثون المعيارية توفّر الوحدة <code>sqlite3</code> المُختصّة بالتخاطب مع أي قاعدة بيانات SQLite دون الحاجة لتثبيت أي أدوات إضافية، فاستخدام قواعد بيانات SQLite تحديدًا مع بايثون يتطلّب كمًّا قليلًا من التثبيتات مقارنةً بمحركات <a href="https://academy.hsoub.com/devops/servers/databases/%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-database/" rel="">قواعد البيانات</a> الأُخرى.
</p>

<p>
	سنعمل في هذا المقال على إنشاء تطبيق ويب مُصغّر لنبيّن من خلاله كيفية استخدام قواعد بيانات SQLite في فلاسك لإجراء عمليات المعالجة الأساسية للبيانات من إنشاء Create وقراءة Read وتحديث Update وحذف Delete، أو ما يسمّى اختصارًا CRUD، إذ سيكون تطبيق الويب مدونة تعرض التدوينات ضمن صفحتها الرئيسية متيحةً لمستخدميها إمكانية إنشاء وتعديل وحذف أي تدوينة.
</p>

<h2>
	مستلزمات العمل
</h2>

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

<ul>
	<li>
		توفُّر <a href="https://academy.hsoub.com/programming/python/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%AA%D9%87%D8%A7-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r714/" rel="">بيئة برمجة بايثون 3 محلية</a>، مثبّتة على حاسوبك، وسنفترض في مقالنا أن اسم مجلد المشروع هو "flask_app".
	</li>
	<li>
		الفهم الجيد لأساسيات فلاسك، مثل مفهوم الوجهات ودوال العرض والقوالب، ويمكنك في هذا الصدد الاطلاع على المقالين <a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%B9%D9%84%D9%85-%D8%A8%D9%86%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9%D9%83-%D8%A7%D9%84%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1715/" rel="">كيفية بناء موقعك الإلكتروني الأول باستخدام إطار عمل فلاسك Flask من لغة بايثون</a> و<a href="https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1737/" rel="">كيفية استخدام القوالب في تطبيقات فلاسك Flask</a> لفهم مبادئ فلاسك.
	</li>
	<li>
		فهم أساسيات لغة <a href="https://academy.hsoub.com/programming/html/" rel="">HTML</a>.
	</li>
	<li>
		فهم أساسيات استخدام <a href="https://academy.hsoub.com/devops/servers/databases/%D9%83%D9%8A%D9%81-%D9%88%D9%85%D8%AA%D9%89-%D9%86%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-sqlite-r111/" rel="">SQLite</a>.
	</li>
</ul>

<h2>
	الخطوة الأولى - إعداد قاعدة البيانات
</h2>

<p>
	سنعمل في هذه الخطوة على إعداد قاعدة بيانات من النوع SQLite والتي سنخزّن فيها بيانات التطبيق (وهي التدوينات في حالة تطبيقنا)، ثم سنملؤها ببعض المُدخلات التجريبية، وسنستخدم الوحدة <code>sqlite3</code> المتوفرة بسهولة في مكتبة بايثون المعيارية بغية التخاطب والتفاعل مع قاعدة البيانات.
</p>

<p>
	بدايةً، وبما أنّ تخزين البيانات في SQLite يكون ضمن جداول وأعمدة، وكون بيانات تطبيقنا هي تدوينات، سننشئ جدولًا باسم "posts" يحتوي على كافّة الأعمدة اللازمة، إذ سننشئ ملفًا بلاحقة <code>sql.</code> يحتوي على أوامر <a href="https://academy.hsoub.com/programming/sql/" rel="">SQL</a> اللازمة لإنشاء الجدول "posts" وأعمدته اللازمة، ثم سنستخدم ملف تخطيط قاعدة البيانات هذا لإنشاء قاعدة البيانات.
</p>

<p>
	لذا، افتح ملف تخطيط قاعدة البيانات المُسمى "schema.sql" الموجود في المجلد "flask_app":
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_8" style=""><span class="pln">$ nano schema</span><span class="pun">.</span><span class="pln">sql</span></pre>

<p>
	واكتب تعليمات SQL التالية داخل هذا الملف:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_5221_10" style=""><span class="pln">DROP TABLE IF EXISTS posts</span><span class="pun">;</span><span class="pln">

CREATE TABLE posts </span><span class="pun">(</span><span class="pln">
    id INTEGER PRIMARY KEY AUTOINCREMENT</span><span class="pun">,</span><span class="pln">
    created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP</span><span class="pun">,</span><span class="pln">
    title TEXT NOT NULL</span><span class="pun">,</span><span class="pln">
    content TEXT NOT NULL
</span><span class="pun">);</span></pre>

<p>
	احفظ الملف واغلقه.
</p>

<p>
	يعمل أوّل أمر من أوامر SQL -في ملف تخطيط قاعدة البيانات السابق- على حذف أي جداول موجودةٍ مسبقًا باسم "posts" لتجنُّب أي تضارب، أو نتائج عشوائية ناتجةٍ عن تشابه أسماء الجداول (مثل حالة وجود جدولين بنفس الاسم وبأعمدة مُختلفة في قاعدة البيانات)، ولكن وفي حالتنا الآن وكوننا لم ننشئ أي جداول بعد، فلن يُنفَّذ هذا السطر في الوقت الراهن، ومن الجدير بالملاحظة أنّ هذا الأمر سيحذِف كل المحتويات في قاعدة البيانات في كل مرة تُشغِّل فيها أوامر SQL هذه، ولكن في حالة مثالنا سننفذ هذا الملف لمرّة واحدة فقط، إلّا أنّك قد ترغب مُستقبلًا بتنفيذه مُجدّدًا بغية تفريغ قاعدة البيانات من كافّة محتوياتها والبدء من جديد بقاعدة بيانات فارغة.
</p>

<p>
	بينما ينشئ الأمر الثاني من أوامر SQL وهو:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_5221_13" style=""><span class="pln">CREATE TABLE posts</span></pre>

<p>
	جدولًا باسم "posts" له الأعمدة التالية:
</p>

<ul>
	<li>
		'id': ويحتوي على بياناتٍ من نوع رقم صحيح ويمثّل مفتاحًا أساسيًا يحتوي على قيمةٍ فريدة في قاعدة البيانات من أجل كل سجل (والسجل هو التدوينة في حالتنا). أمّا الأمر <code>AUTOINCREMENT</code> فيعمل على زيادة قيمة المُعرّف "ID" للتدوينات تلقائيًا، بمعنى أنّ قيمة المعرّف ID للتدوينة الأولى ستكون "1"، وستكون للتدوينة المُضافة بعدها تلقائيًا "2"، وهكذا، وستحافظ كل تدوينة على رقم معرّفها حتى في حال حذف تدوينات سابقة أو لاحقة لها.
	</li>
	<li>
		'created': يحتوي على تاريخ ووقت إنشاء التدوينة، وتشير <code>NOT NULL</code> إلى أن هذا العمود يجب ألا يحتوي على قيمٍ فارغة، أما القيمة الافتراضية فهي <code>CURRENT_TIMESTAMP</code>، والتي تمثِّل تاريخ ووقت إضافة التدوينة إلى قاعدة البيانات، وكما هو الحال في عمود <code>id</code>، لا يتوجب عليك تحديد قيم لهذا العمود، إذ أنها تُملأ تلقائيًا.
	</li>
	<li>
		<code>title</code>: عنوان التدوينة.
	</li>
	<li>
		<code>content</code>: محتوى التدوينة.
	</li>
</ul>

<p>
	والآن سنستخدم ملف تخطيط قاعدة البيانات "schema.sql" لإنشاء قاعدة البيانات، لذا سنُنشئ ملف بايثون سيبني بدوره ملف قاعدة بيانات SQLite بلاحقة <code>db.</code> اعتمادًا على الملف schema.sql.
</p>

<p>
	افتح الملف "init_db.py" ضمن المجلد "flask_app" لتحريره (باستخدام محرِّر النصوص المفضل لديك، وهو محرر نانو nano هنا):
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_17" style=""><span class="pln">$ nano init_db</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ثم اكتب ضمنه الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_5221_21" style=""><span class="kwd">import</span><span class="pln"> sqlite3

connection </span><span class="pun">=</span><span class="pln"> sqlite3</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="str">'database.db'</span><span class="pun">)</span><span class="pln">


</span><span class="kwd">with</span><span class="pln"> open</span><span class="pun">(</span><span class="str">'schema.sql'</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> f</span><span class="pun">:</span><span class="pln">
    connection</span><span class="pun">.</span><span class="pln">executescript</span><span class="pun">(</span><span class="pln">f</span><span class="pun">.</span><span class="pln">read</span><span class="pun">())</span><span class="pln">

cur </span><span class="pun">=</span><span class="pln"> connection</span><span class="pun">.</span><span class="pln">cursor</span><span class="pun">()</span><span class="pln">

cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO posts (title, content) VALUES (?, ?)"</span><span class="pun">,</span><span class="pln">
            </span><span class="pun">(</span><span class="str">'First Post'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Content for the first post'</span><span class="pun">)</span><span class="pln">
            </span><span class="pun">)</span><span class="pln">

cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO posts (title, content) VALUES (?, ?)"</span><span class="pun">,</span><span class="pln">
            </span><span class="pun">(</span><span class="str">'Second Post'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Content for the second post'</span><span class="pun">)</span><span class="pln">
            </span><span class="pun">)</span><span class="pln">

connection</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">
connection</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span></pre>

<p>
	استوردنا في الشيفرة السابقة وحدة <code>sqlite3</code>، ثم أنشأنا اتصالًا مع ملف قاعدة بيانات باسم "database.db"، والذي يُنشأ تلقائيًا فور تشغيل ملف بايثون هذا، ومن ثم استخدمنا الدالة <code>()open</code> لفتح الملف schema.sql، ثم نفّذنا باستخدام التابع <code>()executescript</code> محتويات هذا الملف، الذي ينفِّذ عدة عبارات SQL معًا، وهكذا يُنشأ الجدول "posts"، كما استخدمنا كائن <a href="https://wiki.hsoub.com/Python/Cursor" rel="external">المؤشر Cursor</a>، الذي يمكِّننا من معالجة السجلات، وفي حالتنا استخدمنا تابعه <code>()execute</code> لتنفيذ تعليمتي إدخال <code>INSERT</code> في SQL لإضافة تدوينتين معًا إلى جدول التدوينات "posts"، وفي النهاية أُرسلت هذه الأوامر إلى قاعدة البيانات وأُغلِق الاتصال المفتوح معها.
</p>

<p>
	اِحفظ الملف وأغلقه، ثم شغِّله من موجّه الأوامر باستخدام أمر بايثون التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_23" style=""><span class="pln">$ python init_db</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	وحالما ينتهي التنفيذ، سيكون لديك ملفٌ جديدٌ باسم "database.db" ضمن مجلد "flask_app"، وهذا يدل على نجاح عملية إعداد قاعدة البيانات. سنعمل في الخطوة التالية على إنشاء تطبيق فلاسك مُصغّر لقراءة التدوينات المُضافة إلى قاعدة البيانات وعرضها في صفحته الرئيسية.
</p>

<h2>
	الخطوة 2 – عرض كل التدوينات
</h2>

<p>
	سنعمل في هذه الخطوة على إنشاء تطبيق فلاسك ذو صفحة رئيسية مسؤولة عن عرض التدوينات الموجودة في قاعدة البيانات. بعد تفعيل البيئة البرمجية وتثبيت فلاسك، سنفتح ملفًا باسم "app.py" ضمن المجلد "flask_app" لتحريره:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_25" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	سيعمل هذا الملف على إعداد الاتصال مع قاعدة البيانات وإنشاء وجهة فلاسك وحيدة قادرة على استخدام هذا الاتصال، لذا سنكتب ضمنه الشيفرات التالية:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_5221_29" style=""><span class="kwd">import</span><span class="pln"> sqlite3
</span><span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> render_template

app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> get_db_connection</span><span class="pun">():</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> sqlite3</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="str">'database.db'</span><span class="pun">)</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">row_factory </span><span class="pun">=</span><span class="pln"> sqlite3</span><span class="pun">.</span><span class="typ">Row</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> conn


</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> index</span><span class="pun">():</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">
    posts </span><span class="pun">=</span><span class="pln"> conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'SELECT * FROM posts'</span><span class="pun">).</span><span class="pln">fetchall</span><span class="pun">()</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'index.html'</span><span class="pun">,</span><span class="pln"> posts</span><span class="pun">=</span><span class="pln">posts</span><span class="pun">)</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	استوردنا بدايةً في الشيفرة السابقة وحدة <code>sqlite3</code> لاستخدامها في إنشاء الاتصال مع قاعدة البيانات، ومن ثمّ استوردنا كل من الصنف <code>Flask</code> ودالة تصيير القوالب <code>()render_template</code> من حزمة فلاسك، كما أنشأنا نسخةً فعليةً من التطبيق باسم <code>app</code>، ومن ثمّ عرفنا دالةً باسم <code>()get_db_connection</code> لإنشاء اتصال مع ملف قاعدة البيانات "database.db" المُنشأ مُسبقًا، وتحدّد بعد ذلك قيمة السمة <code>row_factory</code> لتكون <code>sqlite3.Row</code> لنتمكّن من الوصول إلى الأعمدة باستخدام أسمائها، ما يعني أنّ اتصال قاعدة البيانات سيعيد سجلات يمكننا التعامل معها كما هو الحال مع <a href="https://academy.hsoub.com/programming/python/%D9%81%D9%87%D9%85-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%85%D9%8A%D8%B3-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-r743/" rel="">قواميس بايثون</a> النمطية، ونهايةً تعيد الدالة كائن الاتصال <code>conn</code>، الذي سنستخدمه للوصول إلى قاعدة البيانات.
</p>

<p>
	استخدمنا بعد ذلك المُزخرف <code>()app.route</code> لإنشاء دالة عرض view في فلاسك باسم <code>()index</code>، وبعدها نفتح اتصالًا مع قاعدة البيانات باستخدام الدالة <code>get_db_connection()‎</code> التي عرفناها للتو. إذ سننفذ استعلام SQL للحصول على كل المدخلات الموجودة في الجدول "posts"، وسنستدعي التابع <code>fetchall()‎</code> لجلب كل الأسطر الناتجة عن الاستعلام، وهذا سيعيد بالنتيجة قائمةً بالتدوينات المُدخلة إلى قاعدة البيانات في الخطوة السابقة.
</p>

<p>
	يمكنك إغلاق الاتصال بقاعدة البيانات باستخدام التابع <code>close()‎</code> وإعادة نتيجة تصيير القالب index.html، كما يمكنك تمرير الكائن <code>posts</code> وسيطًا، فهو الكائن الحاوي على النتائج المُستخلصة من قاعدة البيانات، ويساعدنا هذا التمرير على الوصول إلى التدوينات برمجيًا داخل قالب "index.html".
</p>

<p>
	الآن وبغية عرض التدوينات المُخزنة ضمن قاعدة البيانات في الصفحة الرئيسية للتطبيق، سننشئ بدايةً ملف قالب ليتضمّن كافة شيفرات HTML الأساسية اللازمة لترثها لاحقًا القوالب الأُخرى ما يجنبنا تكرار الشيفرات، ومن ثمّ سننشئ ملف قالب الصفحة الرئيسية index.html المُصيّر أصلًا باستخدام الدالة <code>()index</code>.
</p>

<p>
	لذا سننشئ مجلدًا للقوالب باسم "templates"، وسننشئ ضمنه ملف قالب باسم "base.html"، والذي سيمثّل القالب الأساسي لبقية القوالب، كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_31" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ mkdir templates
</span><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">base</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	وسنكتب فيه الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_5221_33" style=""><span class="dec">&lt;!DOCTYPE html&gt;</span><span class="pln">
</span><span class="tag">&lt;html</span><span class="pln"> </span><span class="atn">lang</span><span class="pun">=</span><span class="atv">"en"</span><span class="tag">&gt;</span><span class="pln">
</span><span class="tag">&lt;head&gt;</span><span class="pln">
    </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">"UTF-8"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;title&gt;</span><span class="pln">{% block title %} {% endblock %}- FlaskApp</span><span class="tag">&lt;/title&gt;</span><span class="pln">
    </span><span class="tag">&lt;style&gt;</span><span class="pln">
        </span><span class="pun">.</span><span class="pln">post </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">padding</span><span class="pun">:</span><span class="pln"> </span><span class="lit">10px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">margin</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">background-color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#f3f3f3</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        nav a </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#d64161</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">font-size</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3em</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">margin-left</span><span class="pun">:</span><span class="pln"> </span><span class="lit">50px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">text-decoration</span><span class="pun">:</span><span class="pln"> none</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="tag">&lt;/style&gt;</span><span class="pln">
</span><span class="tag">&lt;/head&gt;</span><span class="pln">
</span><span class="tag">&lt;body&gt;</span><span class="pln">
    </span><span class="tag">&lt;nav&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('index') }}"</span><span class="tag">&gt;</span><span class="pln">FlaskApp</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"#"</span><span class="tag">&gt;</span><span class="pln">About</span><span class="tag">&lt;/a&gt;</span><span class="pln">
    </span><span class="tag">&lt;/nav&gt;</span><span class="pln">
    </span><span class="tag">&lt;hr&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"content"</span><span class="tag">&gt;</span><span class="pln">
        {% block content %} {% endblock %}
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
</span><span class="tag">&lt;/body&gt;</span><span class="pln">
</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	احفظ الملف واغلقه.
</p>

<p>
	يتضمّن القالب الأساسي كافّة الشيفرات المتداولة التي سنحتاجها في القوالب الأُخرى.
</p>

<p>
	ستُستبدل لاحقًا كتلة العنوان <code>title</code> بعنوان كل صفحة وكتلة المحتوى <code>content</code> بمحتواها، أمّا عن شريط التصفح فسيتضمّن رابطين، الأوّل ينقل المُستخدم إلى الصفحة الرئيسية للتطبيق باستخدام الدالة المساعدة <code>()url_for</code> لتحقيق الربط مع دالة العرض <code>()index</code>، والآخر لصفحة المعلومات حول التطبيق في حال قررت تضمينها في تطبيقك.
</p>

<p>
	الآن، سننشئ ملف قالب باسم "index.html" وهو الاسم الذي حددناه في الملف "app.py":
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_35" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">index</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	ونضيف ضمنه الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_37" style=""><span class="pun">{%</span><span class="pln"> extends </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">

</span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">h1</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="typ">Posts</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> post </span><span class="kwd">in</span><span class="pln"> posts </span><span class="pun">%}</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">'post'</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;{{</span><span class="pln"> post</span><span class="pun">[</span><span class="str">'created'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">h2</span><span class="pun">&gt;{{</span><span class="pln"> post</span><span class="pun">[</span><span class="str">'title'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">}}&lt;/</span><span class="pln">h2</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;{{</span><span class="pln"> post</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">{%</span><span class="pln"> endfor </span><span class="pun">%}</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	اعتمدنا في الشيفرة السابقة على الوراثة من ملف القالب "base.html" من خلال تعليمة <code>extends</code>، واستبدلنا محتوى كتلة المحتوى <code>content</code> مُستخدمين تنسيق عنوان من المستوى الأوّل <code>&lt;h1&gt;</code> الذي يفي أيضًا بالغرض ويكون عنوانًا للصفحة.
</p>

<p>
	استخدمنا في السطر البرمجي <code>{% for post in posts %}</code> حلقة <code>for</code> من تعليمات محرّك القوالب <a href="https://academy.hsoub.com/tags/jinja2" rel="">جينجا jinja</a>، والهدف من استخدام هذه الحلقة هو المرور على كل عنصر في القائمة <code>posts</code>، وبما أنّ كل عنصر (تدوينة post) في القائمة سيكون مشابهًا لما هو عليه في قاموس بايثون، فمن الممكن الوصول إلى تاريخ إنشاء التدوينة باستخدام الأمر <code>{{ post['created']‎ }}</code>، وإلى عنوانها من خلال <code>post['title'] }}‎ }}</code>، وإلى محتواها من خلال <code>{{ post['content']‎ }}</code>.
</p>

<p>
	الآن ومع وجودنا ضمن المجلد "flask_app" ومع كون البيئة الافتراضية مُفعّلة، سنُعلم فلاسك بالتطبيق المراد تشغيله (وهو في حالتنا الملف app.py) باستخدام متغير البيئة <code>FLASK_APP</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_41" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_APP</span><span class="pun">=</span><span class="pln">app</span></pre>

<p>
	نضبط متغير البيئة <code>FLASK_ENV</code> على القيمة <code>development</code> لتشغيل التطبيق في وضع التطوير مع تشغيل مُنقّح الأخطاء، ولمزيدٍ من المعلومات حول مُنقّح الأخطاء في فلاسك ننصحك بقراءة المقال <a href="https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A3%D8%AE%D8%B7%D8%A7%D8%A1-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1738/" rel="">كيفية التعامل مع الأخطاء في تطبيقات فلاسك</a>، ولتنفيذ ما سبق سنشغّل الأوامر التالية (مع ملاحظة أنّنا نستخدم الأمر <code>set</code> في بيئة ويندوز عوضًا عن الأمر <code>export</code><span class="ipsEmoji">?</span>
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_43" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_ENV</span><span class="pun">=</span><span class="pln">development</span></pre>

<p>
	والآن سنشغّل التطبيق باستخدام الأمر <code>flask run</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_45" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ flask run</span></pre>

<p>
	وبعد التأكد من كون خادم التطوير ما يزال قيد التشغيل، نذهب إلى الرابط التالي باستخدام المتصفح:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_68" style=""><span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span></pre>

<p>
	وعندها ستظهر لك التدوينتان المُضافتان سابقًا إلى قاعدة البيانات بالشّكل التالي:
</p>

<p style="text-align: center;">
	<img alt="ظهور التدوينات المضافة إلى قاعدة البيانات" class="ipsImage ipsImage_thumbnailed" data-fileid="115930" data-ratio="41.87" data-unique="d1wiw2ffh" style="width: 750px; height: auto;" width="750" src="https://academy.hsoub.com/uploads/monthly_2023_01/1st.thumb.png.39c2b1dc98fcb44cb3d3d391539d9d80.png">
</p>

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

<h1>
	الخطوة 3 - إنشاء تدوينات
</h1>

<p>
	سنضيف في هذه الخطوة وجهة جديدة للسماح للمُستخدمين بإضافة تدوينات جديدة إلى قاعدة البيانات، لتظهر لاحقًا ضمن الصفحة الرئيسية للتطبيق، إذ سنضيف للتطبيق صفحةً جديدةً تتضمّن نموذج ويب لإدخال كل من عنوان ومحتوى التدوينة من قبل المستخدم، وسيجري التحقّق من هذه المُدخلات للتأكد من عدم إرسال المُستخدم نموذجًا فارغًا، وفي حال وجود خطأ ما في البيانات المُدخلة، سيُعلم المُستخدم بالخطأ الحاصل من خلال <a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D9%84%D8%B1%D8%B3%D8%A7%D8%A6%D9%84-%D8%A7%D9%84%D8%AE%D8%A7%D8%B7%D9%81%D8%A9-flash-messages-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-flask-r422/" rel="">الرسائل الخاطفة</a> التي تُعرض لمرة واحدة وتختفي بمجرّد إنشاء أي طلب جديد (مثل الانتقال إلى صفحة أُخرى من التطبيق).
</p>

<p>
	لذلك، سنفتح نافذة أسطر أوامر جديدة مع بقاء خادم التطوير قيد التشغيل، ثمّ سنفتح الملف "app.py":
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_52" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	للتعامل مع نموذج الويب، لا بُد أولًا من استيراد التالي من حزمة فلاسك:
</p>

<ul>
	<li>
		الكائن <code>request</code> العام المسؤول عن الوصول إلى بيانات الطلب والتي ستُرسل من خلال نموذج HTML.
	</li>
	<li>
		الدالة <code>url_for()‎</code> لتوليد عناوين الروابط.
	</li>
	<li>
		الدالة <code>flash()‎</code> لعرض رسالةٍ خاطفة في حال ورود طلب غير صحيح.
	</li>
	<li>
		الدالة <code>redirect()‎</code> لإعادة توجيه المستخدم إلى صفحة التطبيق الرئيسية بعد إضافة التدوينات الجديدة إلى قاعدة البيانات.
	</li>
</ul>

<p>
	أضِف هذه الاستيرادات إلى السطر الأوّل من ملف "app.py" كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_54" style=""><span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> render_template</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">,</span><span class="pln"> url_for</span><span class="pun">,</span><span class="pln"> flash</span><span class="pun">,</span><span class="pln"> redirect

</span><span class="com"># ...</span></pre>

<p>
	تخزّن الدالة <code>flash()‎</code> الرسائل في جلسة المتصفح لدى المستخدم، وهذا ما يتطلّب إعداد مفتاح أمان Secret Key، إذ سيُستخدم هذا المفتاح لجعل الجلسات آمنة، وبذلك يتمكّن فلاسك من الحفاظ على المعلومات عند الانتقال من طلبٍ إلى آخر، ولا يجب أن تسمح لأي أحدٍ بالوصول إلى مفتاح الأمان الخاص بك.
</p>

<p>
	لإعداد مفتاح أمان، سنضيف ضبط <code>SECRET_KEY</code> إلى التطبيق من خلال الكائن <code>app.config</code>، الذي سنضيفه مباشرةً بعد تعريف الكائن <code>app</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_56" style=""><span class="com"># ...</span><span class="pln">
app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">config</span><span class="pun">[</span><span class="str">'SECRET_KEY'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'your secret key'</span></pre>

<p>
	تذكّر أنّ مفتاح الأمان يجب أن يكون سلسلةً نصيةً عشوائية بطولٍ مناسب، وللمزيد حول نماذج الويب وضبط مفتاح الأمان الخاص به، ننصحك بقراءة المقال <a href="https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%86%D9%85%D8%A7%D8%B0%D8%AC-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1739/" rel="">كيفية استخدام نماذج الويب في تطبيقات فلاسك</a>.
</p>

<p>
	بعد ذلك، سنضيف الوجهة التالية إلى نهاية الملف app.py :
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_58" style=""><span class="com"># ...</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/create/'</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=(</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">))</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> create</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'create.html'</span><span class="pun">)</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	نمرّر في هذه الوجهة صفًا tuple يحتوي على القيم <code>('GET', 'POST')</code> إلى المعامل <code>methods</code> بغية السماح بكلا نوعي طلبيات HTTP وهما <code>GET</code> و <code>POST</code>؛ إذ تتخصّص الطلبيات من النوع <code>GET</code> بجلب البيانات من الخادم؛ أمّا الطلبيات من النوع <code>POST</code> فهي مُتخصّصة بإرسال البيانات إلى وجهة مُحدّدة، مع ملاحظة أنّ الطلبيات من النوع <code>GET</code> هي الوحيدة المسموحة افتراضيًا، وحالما يطلب المستخدم الوجهة <code>create/</code> باستخدام طلبية من النوع <code>GET</code>، سيُصيّر ملف قالب باسم "create.html".
</p>

<p>
	سنعدّل هذه الوجهة لاحقًا لتتعامل أيضًا مع الطلبيات من نوع <code>POST</code> اللازمة لدى ملء المُستخدمين للنماذج وإرسالها بغية إنشاء تدوينات جديدة.
</p>

<p>
	والآن، افتح ملف "create.html" الجديد داخل مجلد القوالب "templates" على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_61" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">create</span><span class="pun">.</span><span class="pln">html</span></pre>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_63" style=""><span class="pun">{%</span><span class="pln"> extends </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">

</span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">h1</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="typ">Add</span><span class="pln"> a </span><span class="typ">New</span><span class="pln"> </span><span class="typ">Post</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">form method</span><span class="pun">=</span><span class="str">"post"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">label </span><span class="kwd">for</span><span class="pun">=</span><span class="str">"title"</span><span class="pun">&gt;</span><span class="typ">Title</span><span class="pun">&lt;/</span><span class="pln">label</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">br</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"text"</span><span class="pln"> name</span><span class="pun">=</span><span class="str">"title"</span><span class="pln">
               placeholder</span><span class="pun">=</span><span class="str">"Post title"</span><span class="pln">
               value</span><span class="pun">=</span><span class="str">"{{ request.form['title'] }}"</span><span class="pun">&gt;&lt;/</span><span class="pln">input</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">br</span><span class="pun">&gt;</span><span class="pln">

        </span><span class="pun">&lt;</span><span class="pln">label </span><span class="kwd">for</span><span class="pun">=</span><span class="str">"content"</span><span class="pun">&gt;</span><span class="typ">Post</span><span class="pln"> </span><span class="typ">Content</span><span class="pun">&lt;/</span><span class="pln">label</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">br</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">textarea name</span><span class="pun">=</span><span class="str">"content"</span><span class="pln">
                  placeholder</span><span class="pun">=</span><span class="str">"Post content"</span><span class="pln">
                  rows</span><span class="pun">=</span><span class="str">"15"</span><span class="pln">
                  cols</span><span class="pun">=</span><span class="str">"60"</span><span class="pln">
                  </span><span class="pun">&gt;{{</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">}}&lt;/</span><span class="pln">textarea</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">br</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">button type</span><span class="pun">=</span><span class="str">"submit"</span><span class="pun">&gt;</span><span class="typ">Submit</span><span class="pun">&lt;/</span><span class="pln">button</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">form</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	احفظ الملف واغلقه.
</p>

<p>
	وبذلك نكون قد ورثنا خصائص القالب "base.html"، واستخدمنا الوسم <code>&lt;form&gt;</code> وفيه ضبطنا سمة نوع طلبية HTTP لتكون من النوع <code>POST</code> ما يعني أنّ نموذج الويب هذا سيرسل طلب من النوع POST، كما أضفنا صندوق نصي باسم <code>title</code> لنستخدمه في الوصول إلى بيانات عنوان التدوينة في الوجهة <code>create/</code>، وضبطنا القيمة داخل الصندوق النصي هذا الخاص بعنوان التدوينة إلى <code>{{ request.form['title']‎ }}</code> والتي قد تكون فارغةً، أو نسخةً محفوظةً مؤقتًا من العنوان في حال كون النموذج المرسل خاطئًا، وهذا ما يحافظ على البيانات المدخلة في الأداة عند حدوث خطأ ما.
</p>

<p>
	أضفنا بعد الصندوق النصي المُخصّص للعنوان حقلًا نصيًا مُتعدّد الأسطر مُخصّص لمحتوى التدوينة باسم <code>content</code> والقيمة داخله هي <code>{{ request.form['content']‎ }}</code> للحفاظ على البيانات المدخلة فيه في حال كون النموذج المرسل خاطئًا، لأنّ المعلومات المدخلة سيُحتفظ بها في الكائن العام <code>request</code>، ونهايةً أضفنا إلى نهاية النموذج زرًا لتأكيد إرساله.
</p>

<p>
	الآن وأثناء عمل خادم التطوير، استخدم المتصفح للانتقال إلى الوجهة <code>‎/create</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_65" style=""><span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln">create</span></pre>

<p>
	فستظهر لك صفحة إنشاء تدوينة جديدة مع صندوق نصي لإدخال العنوان، وحقل نصي مُتعدّد الأسطر لإدخال المحتوى، وزر لتأكيد إرسال النموذج Submit، كما في الشّكل التالي:
</p>

<p style="text-align: center;">
	<img alt="صفحة إنشاء تدوينة جديدة" class="ipsImage ipsImage_thumbnailed" data-fileid="115931" data-ratio="43.73" data-unique="wgcknzsuj" style="width: 750px; height: auto;" width="780" src="https://academy.hsoub.com/uploads/monthly_2023_01/2nd.thumb.png.dd998d601629e39c0e599bca0e5f12f5.png">
</p>

<p>
	يرسل نموذج الإدخال هذا طلبًا من النوع <code>POST</code> إلى الخادم، ولكن حتى هذه اللحظة لا يوجد شيفرة مسؤولة عن معالجة هذا الطلب في الوجهة <code>create/</code>، وبالتالي لن يحدث شيء في حال ملء النموذج الآن وإرساله.
</p>

<p>
	لذا، فيما يلي ستعالج الدالة <code>create()‎</code> الطلبات الواردة بطريقة <code>POST</code> عند إرسال محتويات نموذج الإدخال وذلك بعد التحقق من قيمة تابع الطلب <code>request.method</code>؛ فإذا كانت قيمته 'POST'، تستمر بمتابعة قراءة البيانات المرسلة والتحقق منها وإدخالها في قاعدة البيانات.
</p>

<p>
	الآن، افتح الملف "app.py" بهدف تعديله ليتعامل مع الطلبات من النوع <code>POST</code> المُرسلة من قبل المستخدم:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_72" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ونعدّل الوجهة <code>create/</code> لتصبح كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_74" style=""><span class="com"># ...</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/create/'</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=(</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">))</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> create</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">method </span><span class="pun">==</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">:</span><span class="pln">
        title </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'title'</span><span class="pun">]</span><span class="pln">
        content </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">]</span><span class="pln">

        </span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> title</span><span class="pun">:</span><span class="pln">
            flash</span><span class="pun">(</span><span class="str">'Title is required!'</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">elif</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> content</span><span class="pun">:</span><span class="pln">
            flash</span><span class="pun">(</span><span class="str">'Content is required!'</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
            conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">
            conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'INSERT INTO posts (title, content) VALUES (?, ?)'</span><span class="pun">,</span><span class="pln">
                         </span><span class="pun">(</span><span class="pln">title</span><span class="pun">,</span><span class="pln"> content</span><span class="pun">))</span><span class="pln">
            conn</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">
            conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> redirect</span><span class="pun">(</span><span class="pln">url_for</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">))</span><span class="pln">

    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'create.html'</span><span class="pun">)</span></pre>

<p>
	احفظ الملف واغلقه.
</p>

<p>
	تَمكَّنا باستخدام العبارة الشرطية <code>if request.method == 'POST'</code> التي تقارن قيمة <code>request.method</code> مع القيمة <code>POST</code> من التحقُّق بأنّ التعليمات التالية لها لن تُنفّذ إلّا إذا كان الطلب الحالي هو فعلًا بطريقة <code>POST</code>، ومن ثمّ قرأنا قيم العنوان والمحتوى المرسلين من الكائن <code>request.form</code>، الذي يمكِّننا من الوصول إلى بيانات نموذج الإدخال المُضمّنة في الطلب.
</p>

<p>
	في حال عدم إدخال قيمةٍ للعنوان ستظهر رسالة خاطفة للمستخدم نعلمه من خلالها باستخدام الدالة <code>()flash</code> بأن العنوان مطلوب <code>!Title is required</code>، وسيحدث الأمر ذاته في حال عدم ملء حقل المحتوى؛ أمّا في حال وجود كل من العنوان والمحتوى، فسيُفتَح اتصال مع قاعدة البيانات باستخدام الدالة <code>get_db_connection()‎</code>، وسيُنفّذ الأمر <code>INSERT INTO</code> من أوامر SQL باستخدام التابع <code>()execute</code> وذلك لإضافة التدوينة الجديدة إلى جدول التدوينات "posts" في قاعدة البيانات وفق العنوان والمحتوى المُدخلان أصلًا من قِبل المُستخدم في النموذج.
</p>

<p>
	استخدمنا الموضع المؤقت <code>?</code> بما يضمن إدخال البيانات في الجدول بأمان، ثمّ حفظنا التغييرات باستخدام الدالة <code>()connection.commit</code>، وأُغلق الاتصال مع قاعدة البيانات باستخدام الدالة <code>()connection.close</code>، ونهايةً أعدنا توجيه المًستخدم إلى الصفحة الرئيسية من التطبيق ليرى تدوينته الجديدة أسفل التدوينات السابقة الموجودة أصلًا.
</p>

<p>
	<strong>تنبيه:</strong> لا تستخدم عمليات بايثون المحرفية بغية إنشاء سلسلة تعليمات SQL ديناميكيًا، بل استخدم دائمًا الموضع المؤقت <code>?</code> في تعليمات SQL لتعويض القيم ديناميكيًا، فمن الممكن تمرير صف tuple من القيم مثل وسيط ثانٍ ضمن التابع <code>()execute</code> لربط هذه القيم مع تعليمة SQL، وهذا ما يمنع هجمات <a href="https://academy.hsoub.com/tags/sql%20injection/" rel="">حقن استعلامات SQL</a> (إحدى أكثر طرق الاختراق خطرًا على كل من المواقع والأنظمة، وتتضمن هذه الطريقة إدخال استعلامات SQL في حقول الإدخال).
</p>

<p>
	الآن وأثناء عمل خادم التطوير، استخدم المتصفح للانتقال إلى الوجهة <code>‎/create</code> باستخدام الرابط:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_76" style=""><span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln">create</span></pre>

<p>
	املأ النموذج بالعنوان والمحتوى الذي تريد، وحال إرسال النموذج سيُعاد توجيهك إلى الصفحة الرئيسية للتطبيق، حيث سترى التدوينة الجديدة فيها؛ أمّا في حال إرسال النموذج مع حقل عنوان أو محتوى فارغ، فلن تُضاف التدوينة إلى قاعدة البيانات، ولن يُعاد التوجيه إلى الصفحة الرئيسية للتطبيق، وكل ذلك دون الحصول على أي معلومات حول ماهية ما حصل وسببه، وذلك لأننا لم نُعدّ الرسائل الخاطفة للعرض حتى الآن.
</p>

<p>
	لذلك، سنفتح الآن ملف القالب الأساسي base.html لإضافة رابط في شريط التصفح للوصول إلى صفحة إنشاء تدوينة جديدة، ولإظهار الرسائل الخاطفة أسفله:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_78" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">base</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	ونعدل الملف ليصبح كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_5221_80" style=""><span class="dec">&lt;!DOCTYPE html&gt;</span><span class="pln">
</span><span class="tag">&lt;html</span><span class="pln"> </span><span class="atn">lang</span><span class="pun">=</span><span class="atv">"en"</span><span class="tag">&gt;</span><span class="pln">
</span><span class="tag">&lt;head&gt;</span><span class="pln">
    </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">"UTF-8"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;title&gt;</span><span class="pln">{% block title %} {% endblock %} - FlaskApp</span><span class="tag">&lt;/title&gt;</span><span class="pln">
    </span><span class="tag">&lt;style&gt;</span><span class="pln">
        </span><span class="pun">.</span><span class="pln">post </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">padding</span><span class="pun">:</span><span class="pln"> </span><span class="lit">10px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">margin</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">background-color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#f3f3f3</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        nav a </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#d64161</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">font-size</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3em</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">margin-left</span><span class="pun">:</span><span class="pln"> </span><span class="lit">50px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">text-decoration</span><span class="pun">:</span><span class="pln"> none</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        </span><span class="pun">.</span><span class="pln">alert </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">padding</span><span class="pun">:</span><span class="pln"> </span><span class="lit">20px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">margin</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#970020</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">background-color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#ffd5de</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="tag">&lt;/style&gt;</span><span class="pln">
</span><span class="tag">&lt;/head&gt;</span><span class="pln">
</span><span class="tag">&lt;body&gt;</span><span class="pln">
    </span><span class="tag">&lt;nav&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('index') }}"</span><span class="tag">&gt;</span><span class="pln">FlaskApp</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('create') }}"</span><span class="tag">&gt;</span><span class="pln">Create</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"#"</span><span class="tag">&gt;</span><span class="pln">About</span><span class="tag">&lt;/a&gt;</span><span class="pln">
    </span><span class="tag">&lt;/nav&gt;</span><span class="pln">
    </span><span class="tag">&lt;hr&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"content"</span><span class="tag">&gt;</span><span class="pln">
        {% for message in get_flashed_messages() %}
            </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"alert"</span><span class="tag">&gt;</span><span class="pln">{{ message }}</span><span class="tag">&lt;/div&gt;</span><span class="pln">
        {% endfor %}

        {% block content %} {% endblock %}
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
</span><span class="tag">&lt;/body&gt;</span><span class="pln">
</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	أضفنا في الشيفرة السابقة وسم رابط <code>&lt;a&gt;</code> جديد إلى شريط التصفُّح والذي يشير إلى صفحة إنشاء تدوينة جديدة.
</p>

<p>
	استخدمنا حلقة <code>for</code> التكرارية من تعليمات جينجا للمرور على الرسائل الخاطفة، إذ يوفّر فلاسك هذه الرسائل من خلال دالة فلاسك الخاصة <code>get_flashed_messages()‎</code> لتُعرض ضمن وسم <code>&lt;div&gt;</code> ذو صنف <a href="https://academy.hsoub.com/programming/css/" rel="">CSS</a> يدعى <code>alert</code>، ونسقنا مظهر الوسم <code>&lt;div&gt;</code> من خلال الوسم <code>&lt;style&gt;</code> المتوضّع في قسم الترويسة <code>&lt;head&gt;</code>.
</p>

<p>
	سيظهر الرابط الجديد ضمن شريط التصفُّح عند تحديث الصفحة الرئيسية للتطبيق، وسينقلنا النقر على هذا الرابط الجديد إلى صفحة إنشاء تدوينة جديدة، فلو أرسلنا في هذه المرحلة نموذجًا فارغًا، ستظهر رسالة خاطفة تعلمنا بأنّ حقل العنوان مطلوب "!Title is required".
</p>

<p>
	ولو أرسلنا بعد ذلك نموذجًا بعنوان ما وحقل محتوى فارغ، فستظهر رسالة خاطفة لتعلمنا بكون حقل المحتوى مطلوب "!Content is required"، وعندها تختفي الرسالة "!Title is required" السابقة، لأنها رسالة خاطفة وليست دائمة.
</p>

<p>
	ومع نهاية هذه الخطوة أصبح لدينا آلية لإضافة التدوينات الجديدة، وسنعمل في الخطوة التالية على إضافة وجهة جديدة لتعديل التدوينات الموجودة أصلًا.
</p>

<h2>
	الخطوة 4 - تعديل تدوينات
</h2>

<p>
	سنعمل في هذه الخطوة على إضافة وجهة جديدة للتطبيق لتُمكّن المُستخدمين من تعديل التدوينات المنشورة أصلًا.
</p>

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

<p>
	لذا، افتح الملف "app.py":
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_82" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ستعيد الدالة الجديدة التي سنستخدمها في جلب التدوينات المطلوبة من قاعدة البيانات خطأ HTTP من النوع "404" أي "404 Not Found" في حال كون المعرّف المطلوب لا يوافق أي من معرفات التدوينات الموجودة في قاعدة البيانات، ولإنجاز ذلك سنستعين بالدالة <code>()abort</code> التي تُلغي الطلب الخاطئ مُستجيبةً برسالة خطأ.
</p>

<p>
	لذا سنضيف الدالة <code>()abort</code> إلى مجموعة الاستيرادات في بداية الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_84" style=""><span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> render_template</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">,</span><span class="pln"> url_for</span><span class="pun">,</span><span class="pln"> flash</span><span class="pun">,</span><span class="pln"> redirect</span><span class="pun">,</span><span class="pln"> abort</span></pre>

<p>
	سنضيف بعد الدالة <code>()get_db_connection</code> دالة جديدة باسم <code>()get_post</code>، كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_86" style=""><span class="com"># ...</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> get_db_connection</span><span class="pun">():</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> sqlite3</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="str">'database.db'</span><span class="pun">)</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">row_factory </span><span class="pun">=</span><span class="pln"> sqlite3</span><span class="pun">.</span><span class="typ">Row</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> conn

</span><span class="kwd">def</span><span class="pln"> get_post</span><span class="pun">(</span><span class="pln">post_id</span><span class="pun">):</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">
    post </span><span class="pun">=</span><span class="pln"> conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'SELECT * FROM posts WHERE id = ?'</span><span class="pun">,</span><span class="pln">
                        </span><span class="pun">(</span><span class="pln">post_id</span><span class="pun">,)).</span><span class="pln">fetchone</span><span class="pun">()</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> post </span><span class="kwd">is</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">:</span><span class="pln">
        abort</span><span class="pun">(</span><span class="lit">404</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> post

</span><span class="com"># ...</span></pre>

<p>
	تمتلك هذه الدالة الجديدة وسيطًا <code>post_id</code>، والذي نحدّد من خلاله معّرف ID التدوينة المراد جلبها مثل قيمة معادة من قاعدة البيانات، لذا تعمل الدالة <code>()get_post</code> على فتح اتصال مع قاعدة البيانات باستخدام الدالة <code>()get_db_connection</code> لتنفّذ فيها استعلامًا للوصول إلى التدوينة الموافقة لقيمة المُعرّف المُمرّر أصلًا إلى الوسيط <code>post_id</code>، إذ تُجلب التدوينة باستخدام التابع <code>()fetchone</code> لتُخزّن في المتغير <code>post</code>، ومن ثمّ يُغلق الاتصال مع قاعدة البيانات.
</p>

<p>
	إذا كانت قيمة المتغير <code>post</code> فارغة أي تساوي "None"، فهذا يعني أنّه لم يوجد أي نتيجة موافقة في قاعدة البيانات، وعندها تُستخدم الدالة <code>()abort</code> التي استوردناها سابقًا للاستجابة برسالة خطأ HTTP من النوع "404"، ثمّ إيقاف التنفيذ؛ أمّا في حال إيجاد معرّف التدوينة المطلوبة ضمن قاعدة البيانات فتُعاد قيمة المتغير <code>post</code>.
</p>

<p>
	والآن سنضيف وجهة جديدة مُخصّصة لتعديل التدوينات إلى نهاية الملف "app.py"، كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_88" style=""><span class="com"># ...</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/&lt;int:id&gt;/edit/'</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=(</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">))</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> edit</span><span class="pun">(</span><span class="pln">id</span><span class="pun">):</span><span class="pln">
    post </span><span class="pun">=</span><span class="pln"> get_post</span><span class="pun">(</span><span class="pln">id</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">method </span><span class="pun">==</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">:</span><span class="pln">
        title </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'title'</span><span class="pun">]</span><span class="pln">
        content </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">]</span><span class="pln">

        </span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> title</span><span class="pun">:</span><span class="pln">
            flash</span><span class="pun">(</span><span class="str">'Title is required!'</span><span class="pun">)</span><span class="pln">

        </span><span class="kwd">elif</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> content</span><span class="pun">:</span><span class="pln">
            flash</span><span class="pun">(</span><span class="str">'Content is required!'</span><span class="pun">)</span><span class="pln">

        </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
            conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">
            conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'UPDATE posts SET title = ?, content = ?'</span><span class="pln">
                         </span><span class="str">' WHERE id = ?'</span><span class="pun">,</span><span class="pln">
                         </span><span class="pun">(</span><span class="pln">title</span><span class="pun">,</span><span class="pln"> content</span><span class="pun">,</span><span class="pln"> id</span><span class="pun">))</span><span class="pln">
            conn</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">
            conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> redirect</span><span class="pun">(</span><span class="pln">url_for</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">))</span><span class="pln">

    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'edit.html'</span><span class="pun">,</span><span class="pln"> post</span><span class="pun">=</span><span class="pln">post</span><span class="pun">)</span></pre>

<p>
	ثمّ نحفظ الملف ونغلقه.
</p>

<p>
	سنستخدم الوجهة الجديدة بالشكل <code>/int:id&gt;/edit&gt;/</code>، إذ يمثّل <code>:int</code> محوّلًا لنمط البيانات، ويقبل قيمًا عددية صحيحة موجبة فقط، أما <code>id</code> فيمثّل الجزء المتغير من الرابط المطلوب، والذي يدل على التدوينة المراد تعديلها، فمثلًا استخدام الوجهة (ضمن الرابط) بالشكل <code>/‎2/edit/</code> يعني أنّنا نود تعديل التدوينة ذات معرّف ID المساوي "2"، إذ يُمرّر المعرّف هذا من الرابط إلى دالة العرض <code>()edit</code>، لتُمرر فيها قيمة الوسيط <code>id</code> إلى الدالة <code>()get_post</code> لجلب التدوينة الموافقة لهذا المعرّف من قاعدة البيانات، مع ملاحظة أنّه في حال عدم وجود تدوينة موافقة للمعرّف المطلوب، ستكون الاستجابة برسالة خطأ "‎404 Not Found".
</p>

<p>
	أمّا السطر الأخير من الشيفرة السابقة فيصيّر ملف قالب باسم edit.html مُمرّرًا إليه المتغير <code>post</code> المُتضمّن بيانات التدوينة المراد تعديلها، وهذا القالب مسؤول عن عرض عنوان ومحتوى التدوينة الحاليين (قبل التعديل) ضمن صفحة التعديل.
</p>

<p>
	وعلى نحوٍ مشابه لحالة إنشاء تدوينة جديدة فإن الجملة الشرطية <code>'if request.method == 'POST</code> مسؤولة عن التعامل مع البيانات الجديدة التي يرسلها المُستخدم، فبعد تحقّق الشرط نستخلص من النموذج العنوان والمحتوى الجديدين مع إظهار رسالة خاطفة في حال كون أحدهما فارغ.
</p>

<p>
	في حال كون النموذج المرسل صحيحًا، نفتح اتصالًا مع قاعدة البيانات ونحدّث الجدول "posts" بالعنوان والمحتوى الجديدين للتدوينة ذات المعرّف المساوي لذلك المحدَّد في الرابط المطلوب باستخدام التعليمة <code>UPDATE</code> من تعليمات SQL، ومن ثمّ نؤكّد التغييرات ونغلق الاتصال مع قاعدة البيانات ونعيد توجيه المستخدم إلى الصفحة الرئيسية للتطبيق.
</p>

<p>
	أمّا الآن فعلينا إنشاء الصفحة التي سيعدّل المستخدمون التدوينات ضمنها، لذا سنفتح ملف قالب جديد باسم "edit.html":
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_90" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">edit</span><span class="pun">.</span><span class="pln">html</span></pre>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_92" style=""><span class="pun">{%</span><span class="pln"> extends </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">

</span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">h1</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="typ">Edit</span><span class="pln"> </span><span class="str">"{{ post['title'] }}"</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">form method</span><span class="pun">=</span><span class="str">"post"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">label </span><span class="kwd">for</span><span class="pun">=</span><span class="str">"title"</span><span class="pun">&gt;</span><span class="typ">Title</span><span class="pun">&lt;/</span><span class="pln">label</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">br</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"text"</span><span class="pln"> name</span><span class="pun">=</span><span class="str">"title"</span><span class="pln">
               placeholder</span><span class="pun">=</span><span class="str">"Post title"</span><span class="pln">
               value</span><span class="pun">=</span><span class="str">"{{ request.form['title'] or post['title'] }}"</span><span class="pun">&gt;&lt;/</span><span class="pln">input</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">br</span><span class="pun">&gt;</span><span class="pln">

        </span><span class="pun">&lt;</span><span class="pln">label </span><span class="kwd">for</span><span class="pun">=</span><span class="str">"content"</span><span class="pun">&gt;</span><span class="typ">Post</span><span class="pln"> </span><span class="typ">Content</span><span class="pun">&lt;/</span><span class="pln">label</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">br</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">textarea name</span><span class="pun">=</span><span class="str">"content"</span><span class="pln">
                  placeholder</span><span class="pun">=</span><span class="str">"Post content"</span><span class="pln">
                  rows</span><span class="pun">=</span><span class="str">"15"</span><span class="pln">
                  cols</span><span class="pun">=</span><span class="str">"60"</span><span class="pln">
                  </span><span class="pun">&gt;{{</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">]</span><span class="pln"> </span><span class="kwd">or</span><span class="pln"> post</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">}}&lt;/</span><span class="pln">textarea</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">br</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">button type</span><span class="pun">=</span><span class="str">"submit"</span><span class="pun">&gt;</span><span class="typ">Submit</span><span class="pun">&lt;/</span><span class="pln">button</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">form</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	الشيفرة السابقة مُشابهة لتلك في القالب "create.html" ما عدا أنها تعمل على إظهار عنوان التدوينة مثل عنوان للصفحة من خلال التعليمة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_94" style=""><span class="pun">{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="typ">Edit</span><span class="pln"> </span><span class="str">"{{ post['title'] }}"</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	وتجعل القيمة داخل الصندوق النصي بالشّكل:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_96" style=""><span class="pun">{{</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'title'</span><span class="pun">]</span><span class="pln"> </span><span class="kwd">or</span><span class="pln"> post</span><span class="pun">[</span><span class="str">'title'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">}}</span></pre>

<p>
	والقيمة داخل الحقل النصي مُتعدّد الأسطر على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_98" style=""><span class="pun">{{</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">]</span><span class="pln"> </span><span class="kwd">or</span><span class="pln"> post</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">}}</span></pre>

<p>
	وبذلك ستظهر البيانات المًخزّنة في الطلب في حال وجودها، وإلّا ستظهر البيانات الموجودة في المتغير <code>post</code> المُمرّر إلى القالب الذي يحتوي على البيانات الحالية لقاعدة البيانات.
</p>

<p>
	الآن وفي حين خادم التطوير يعمل، استخدم المتصفح للانتقال إلى الرابط التالي بغية تعديل التدوينة الأولى`:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_100" style=""><span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="lit">1</span><span class="pun">/</span><span class="pln">edit</span></pre>

<p>
	فتظهر صفحة بالشّكل:
</p>

<p style="text-align: center;">
	<img alt="كيفية تعديل التدوينات في فلاسك flask" class="ipsImage ipsImage_thumbnailed" data-fileid="115932" data-ratio="44.13" data-unique="on8pg3oxg" style="width: 750px; height: auto;" width="750" src="https://academy.hsoub.com/uploads/monthly_2023_01/3rd.thumb.png.378f1ce49fefc59f96d5a710fd2533ce.png">
</p>

<p>
	وبتعديل التدوينة وإرسال النموذج ستظهر التغييرات في الصفحة الرئيسية للتطبيق، وفي حال إرسال النموذج بعنوان أو محتوى فارغ، ستظهر رسالة خاطفة لإعلامنا بالخطأ الحاصل.
</p>

<p>
	أمّا الآن فسنعمل على إضافة رابط للوصول إلى صفحة التعديل لكل تدوينة من الصفحة الرئيسية للتطبيق، لذا نفتح ملف القالب index.html:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_105" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">index</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	ونعدله ليصبح تمامًا على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_107" style=""><span class="pun">{%</span><span class="pln"> extends </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">

</span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">h1</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="typ">Posts</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> post </span><span class="kwd">in</span><span class="pln"> posts </span><span class="pun">%}</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">'post'</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;{{</span><span class="pln"> post</span><span class="pun">[</span><span class="str">'created'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">h2</span><span class="pun">&gt;{{</span><span class="pln"> post</span><span class="pun">[</span><span class="str">'title'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">}}&lt;/</span><span class="pln">h2</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;{{</span><span class="pln"> post</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">a href</span><span class="pun">=</span><span class="str">"{{ url_for('edit', id=post['id']) }}"</span><span class="pun">&gt;</span><span class="typ">Edit</span><span class="pun">&lt;/</span><span class="pln">a</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">{%</span><span class="pln"> endfor </span><span class="pun">%}</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	أضفنا الوسم <code>&lt;a&gt;</code> للربط مع دالة العرض <code>()edit</code>، مُمرّرين معّرف التدوينة ID الموجود في <code>post['id'])‎</code> إلى الدالة <code>()url_for</code> لتُنشئ رابط تعديل التدوينة الموافقة، وهذا ما سيضيف بالنتيجة رابطًا إلى صفحة التعديل من أجل كل تدوينة.
</p>

<p>
	الآن، وبعد تحديث الصفحة الرئيسية للتطبيق يصبح من الممكن تعديل أي تدوينة بالنقر على رابط التعديل <strong>Edit</strong> الخاص بها.
</p>

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

<h2>
	الخطوة 5 - حذف تدوينات
</h2>

<p>
	سنعمل في هذه الخطوة على إضافة زر حذف إلى صفحة التعديل بحيث يسمح للمستخدمين بحذف تدوينة ما.
</p>

<p>
	بدايةً، سنضيف وجهةً جديدةً للحذف، وهي <code>‎/id/delete</code> تتعامل مع الطلبات من النوع POST بآلية مشابهة لتلك في دالة العرض <code>edit()‎</code>، إذ ستستقبل دالة الحذف الجديدة <code>delete()‎</code> رقم معرّف التدوينة ID المُراد حذفها من خلال الرابط URL ليجري جلبها باستخدام الدالة <code>()get_post</code> ومن ثمّ حذفها من قاعدة البيانات في حال وجودها.
</p>

<p>
	الآن، افتح الملف "app.py":
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_109" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	أضِف الوجهة التالية إلى نهاية الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_111" style=""><span class="com"># ...</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/&lt;int:id&gt;/delete/'</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=(</span><span class="str">'POST'</span><span class="pun">,))</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> delete</span><span class="pun">(</span><span class="pln">id</span><span class="pun">):</span><span class="pln">
    post </span><span class="pun">=</span><span class="pln"> get_post</span><span class="pun">(</span><span class="pln">id</span><span class="pun">)</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'DELETE FROM posts WHERE id = ?'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">id</span><span class="pun">,))</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
    flash</span><span class="pun">(</span><span class="str">'"{}" was successfully deleted!'</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="pln">post</span><span class="pun">[</span><span class="str">'title'</span><span class="pun">]))</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> redirect</span><span class="pun">(</span><span class="pln">url_for</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">))</span></pre>

<p>
	احفظ الملف واغلقه.
</p>

<p>
	تتعامل هذه الدالة فقط مع الطلبات الواردة بطريقة <code>POST</code> وفق ما جرى تحديده في المعامل <code>methods</code> المسؤول عن تحديد أنواع طلبيات HTTP المسموحة؛ وهذا يعني أنك إذا انتقلت إلى الوجهة <code>‎/ID/delete</code> في المتصفح، ستحصل على خطأ "‎405 Method Not Allowed"، لأن المتصفحات تستخدم طريقة <code>GET</code> افتراضيًا للطلبات؛ لذا أضفنا زرًا لحذف التدوينات كونه يُرسل إلى الوجهة طلبًا من النوع <code>POST</code>.
</p>

<p>
	تستقبل الدالة قيمة المعرّف ID للتدوينة المراد حذفها وتستخدمها لجلب التدوينة من قاعدة البيانات باستخدام دالة <code>get_post()‎</code>، لتستجيب بخطأ من النوع 404 بالرسالة "‎404 Not Found" في حال عدم وجود تدوينة موافقة، وإلّا يُفتح اتصال مع قاعدة البيانات وتُنفّذ تعليمة <code>DELETE FROM</code> من تعليمات SQL لحذف التدوينة، إذ استخدمنا التعبير <code>WHERE id = ?</code> للدلالة على التدوينة المراد حذفها.
</p>

<p>
	نهايةً، يُؤكَّد على التعديلات وتُرسل إلى قاعدة البيانات ويُغلق الاتصال لتظهر رسالةٌ خاطفةٌ تُعلم المستخدم بانتهاء عملية حذف التدوينة بنجاح، ثم يُعاد توجيه المستخدم إلى الصفحة الرئيسية.
</p>

<p>
	لاحظ أننا هنا لا نصيّر ملف قالب، وإنمّا نضيف فقط زر أوامر "Delete" إلى صفحة تعديل التدوينة.
</p>

<p>
	الآن افتح ملف القالب edit.html:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_113" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">edit</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	ثم أضف وسم النموذج <code>&lt;form&gt;</code> بعد وسم إظهار الفاصل الأفقي <code>&lt;hr&gt;</code> تماماً بعد السطر البرمجي <code>{% endblock %}</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_119" style=""><span class="pun">&lt;</span><span class="pln">button type</span><span class="pun">=</span><span class="str">"submit"</span><span class="pun">&gt;</span><span class="typ">Submit</span><span class="pun">&lt;/</span><span class="pln">button</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">form</span><span class="pun">&gt;</span><span class="pln">


    </span><span class="pun">&lt;</span><span class="pln">hr</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">form action</span><span class="pun">=</span><span class="str">"{{ url_for('delete', id=post['id']) }}"</span><span class="pln"> method</span><span class="pun">=</span><span class="str">"POST"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"submit"</span><span class="pln"> value</span><span class="pun">=</span><span class="str">"Delete Post"</span><span class="pln">
                onclick</span><span class="pun">=</span><span class="str">"return confirm('Are you sure you want to delete this post?')"</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">form</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	احفظ الملف واغلقه.
</p>

<p>
	أنشأنا في الملف السابق نموذج ويب يُرسل طلبًا من النوع <code>POST</code> إلى دالة العرض <code>()delete</code> ممررين القيمة <code>['post['id</code> لتحديد التدوينة المراد حذفها، كما استخدمنا <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%AF%D9%88%D8%A7%D9%84-%D8%A7%D9%84%D8%AA%D9%81%D8%A7%D8%B9%D9%84%D9%8A%D8%A9-confirm-%D8%8Cprompt-%D8%8Calert-r701/" rel="">التابع <code>confirm()‎</code></a> المتوفّر في متصفحات الويب لعرض رسالة تأكيد قبل إرسال الطلب.
</p>

<p>
	الآن انتقل إلى صفحة تعديل تدوينة في المتصفح مجددًا وجرّب حذفها:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5221_121" style=""><span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="lit">1</span><span class="pun">/</span><span class="pln">edit</span></pre>

<p>
	بعد تأكيد الحذف سيُعاد توجيهك إلى الصفحة الرئيسية للتطبيق، حيث ستختفي التدوينة المحذوفة، وستظهر رسالة خاطفة أسفل شريط التصفّح لإعلامك بنجاح عملية حذف التدوينة، وبذلك يكون أصبح لدينا آليةً في تطبيق فلاسك هذا لحذف التدوينات غير المرغوب بها.
</p>

<h2>
	الخاتمة
</h2>

<p>
	أنشأنا في هذا المقال مدونة ويب مصغّرة تتخاطب مع قاعدة بيانات SQLite، مع كامل الوظائف الأساسية من إضافة بيانات جديدة إلى قاعدة البيانات، وجلب بيانات منها لعرضها في صفحات التطبيق، وحذف وتعديل بيانات موجودة أصلًا.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-use-an-sqlite-database-in-a-flask-application" rel="external nofollow">How To Use an SQLite Database in a Flask Application</a> لصاحبه Abdelhadi Dyouri.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/devops/servers/databases/%d9%83%d9%8a%d9%81-%d9%88%d9%85%d8%aa%d9%89-%d9%86%d8%b3%d8%aa%d8%ae%d8%af%d9%85-sqlite-r111/" rel="">كيف ومتى نستخدم SQLite</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-sqlite-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-flask-r346/" rel="">التعامل مع قواعد البيانات SQLite في تطبيقات Flask</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B9%D9%84%D8%A7%D9%82%D8%A9-%D9%86%D9%88%D8%B9-%D9%88%D8%A7%D8%AD%D8%AF-%D8%A5%D9%84%D9%89-%D9%85%D8%AA%D8%B9%D8%AF%D8%AF-one-to-many-%D9%85%D8%B9-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D9%88%D9%85%D8%AD%D8%B1%D9%83-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-sqlite-r1623/" rel="">كيفية استخدام علاقة نوع واحد-إلى-متعدد one-to-many مع إطار العمل فلاسك Flask ومحرك قواعد البيانات SQLite</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%B9%D8%AF%D9%8A%D9%84-%D8%A7%D9%84%D8%B9%D9%86%D8%A7%D8%B5%D8%B1-%D9%81%D9%8A-%D8%B9%D9%84%D8%A7%D9%82%D8%A7%D8%AA-one-to-many-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D9%88%D9%85%D8%AD%D8%B1%D9%83-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-sqlite-r1631/" rel="">تعديل العناصر في علاقات من نوع واحد-إلى-متعدد one-to-many باستخدام إطار العمل فلاسك Flask ومحرك قاعدة البيانات SQLite</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B9%D9%84%D8%A7%D9%82%D8%A9-many-to-many-%D9%85%D8%B9-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D9%88%D9%85%D8%AD%D8%B1%D9%83-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-sqlite-r1632/" rel="">استخدام علاقة متعدد-إلى-متعدد many-to-many مع إطار العمل فلاسك Flask ومحرك قواعد البيانات SQLite</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1839</guid><pubDate>Fri, 20 Jan 2023 16:02:00 +0000</pubDate></item><item><title>&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x646;&#x645;&#x627;&#x630;&#x62C; &#x627;&#x644;&#x648;&#x64A;&#x628; &#x648;&#x627;&#x644;&#x62A;&#x62D;&#x642;&#x642; &#x645;&#x646;&#x647;&#x627; &#x641;&#x64A; &#x641;&#x644;&#x627;&#x633;&#x643; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x627;&#x644;&#x625;&#x636;&#x627;&#x641;&#x629; Flask-WTF</title><link>https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%86%D9%85%D8%A7%D8%B0%D8%AC-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D9%88%D8%A7%D9%84%D8%AA%D8%AD%D9%82%D9%82-%D9%85%D9%86%D9%87%D8%A7-%D9%81%D9%8A-%D9%81%D9%84%D8%A7%D8%B3%D9%83-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-flask-wtf-r1838/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_01/1709566845_---------Flask-WTF.png.47e7a42fdc9cbe811fdf145fc1961104.png" /></p>
<p>
	تمنح نماذج الويب web forms مثل الحقول النصيّة السطرية text fields وصناديق إدخال النصوص text areas المستخدمين القدرة على إرسال البيانات إلى التطبيق، سواءً احتوت هذه النماذج على قوائم منسدلة، أو أزرار انتقاء radio button، فسيستخدمها التطبيق في تنفيذ أمر ما، أو حتّى لإرسال محتوًى نصي كبير، إذ يُمنح المستخدم مثلًا في تطبيقات التواصل الاجتماعي حيزًا حيث يمكنه إضافة محتوى جديد إلى صفحته الشخصية.
</p>

<p>
	يُعد <a href="https://academy.hsoub.com/programming/python/flask/" rel="">فلاسك flask</a> إطار عمل للويب مبني بلغة <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D9%85%D8%B1%D8%AC%D8%B9-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%AA%D8%B9%D9%84%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r735/" rel="">بايثون</a>، ويتميز بكونه صغير الحجم وسهل المعالجة، ويوفّر أيضًا العديد من الأدوات والميزات التي من شأنها جعل إنشاء تطبيقات الويب في لغة بايثون أسهل، وبغية تصييّر نماذج الويب والتحقق من صحة مدخلاتها بأمانٍ ومرونة، سنستخدم إضافة فلاسك <a href="https://academy.hsoub.com/programming/python/flask/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-wtforms-%D9%84%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D9%86%D9%85%D8%A7%D8%B0%D8%AC-html-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-flask-r411/" rel="">Flask-WTF</a> التي تتيح لنا استخدام المكتبة <a href="https://academy.hsoub.com/tags/wtforms/" rel="">WTForms</a> المُتضمّنة نماذج الويب ذات واجهة المستخدم التفاعلية لاستخدامها في تطبيقات فلاسك.
</p>

<p>
	إذًا، إضافة WTForms هي مكتبة من مكاتب بايثون، توفّر تصييرًا مرنًا لنماذج الويب، فمن الممكن استخدامها في تصيير الحقول النصيّة السطرية وصناديق إدخال النصوص متعددة الأسطر وصناديق كلمات المرور وأزرار الانتقاء وغيرها، كما أنّها توفّر آلية فعّالة للتحقّق من صحة البيانات التي يدخلها المستخدم بالاعتماد على ميّزة المُصادِقين المختلفين الهادفة للتأكّد من كون هذه البيانات تُطابق صيغًا وقواعد ومعايير محدّدة، فمثلًا من الممكن التحقق من كون المُستخدم قد أدخل فعلًا البيانات اللازمة في أحد الحقول المطلوب ملؤها من قبله، أو أنّ المدخلات بطول معيّن.
</p>

<p>
	تستخدم مكتبة WTForms -إضافةً إلى مُساعدتنا على تأكيد البيانات وتقييدها بشروطٍ خاصّة- مفتاحًا مساعدًا لتحمينا من هجمات تزوير الطلب عبر المواقع Cross-site request forgery -أو اختصارًا CSRF- وهي نوع من الهجمات التي تُمكّن المخترق من تنفيذ أمور خطيرة غير مرغوبة في تطبيق الويب، متنكّرا على هيئة مُستخدم قد سجّل دخوله في التطبيق فعلًا، فقد تجبر الهجمات الناجحة المُستخدم الضحية على إرسال طلبات تغيير أساسية state-changing بغية تحويل أموال مثلًا إلى حساب المخترِق البنكي في أحد تطبيقات الصيرفة، أو تغيير عنوان البريد الإلكتروني الخاص بالمستخدم وهكذا دواليك، وفي حال كون الحساب الضحية حساب بصلاحيات مدير، فعندها ستكون هجمة تزوير الطلب CSRF قادرةً على اختراق كامل التطبيق.
</p>

<p>
	سننشئ في هذا المقال تطبيق ويب مُصغّر بغية توضيح كيفية تصيير نماذج الويب والتحقّق من صحة مدخلاتها باستخدام الإضافة Flask-WTF، إذ سيتضمّن التطبيق صفحةً لعرض الدورات التدريبيّة المُخزّنة أصلًا في قائمة بايثون، أما الصفحة الرئيسية فستتضمّن نموذجًا لإدخال عنوان الدورة التدريبية ووصفها وتكلفتها وكونها متاحة أم لا إضافةً إلى مستواها (مبتدئ، أو متوسط، أو متقدم).
</p>

<h2>
	مستلزمات العمل
</h2>

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

<ul>
	<li>
		توفُّر <a href="https://academy.hsoub.com/programming/python/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%AA%D9%87%D8%A7-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r714/" rel="">بيئة برمجة بايثون 3 محلية</a>، مثبّتة على حاسوبك، وسنفترض في مقالنا أن اسم مجلد المشروع هو "flask_app".
	</li>
	<li>
		الفهم الجيد لأساسيات فلاسك، مثل مفهوم الوجهات ودوال العرض، وفي هذا الصدد يمكنك الاطلاع على المقالين <a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%B9%D9%84%D9%85-%D8%A8%D9%86%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9%D9%83-%D8%A7%D9%84%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1715/" rel="">كيفية بناء موقعك الإلكتروني الأول باستخدام إطار عمل فلاسك من لغة بايثون</a> و<a href="https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1737/" rel="">كيفية استخدام القوالب في تطبيقات فلاسك Flask</a> لفهم مبادئ فلاسك.
	</li>
	<li>
		فهم أساسيات لغة <a href="https://academy.hsoub.com/programming/html/" rel="">HTML</a>.
	</li>
	<li>
		من المحبّذ أيضًا فهم أساسيات استخدام نماذج الويب في فلاسك، وهنا ننصحك بقراءة المقال <a href="https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%86%D9%85%D8%A7%D8%B0%D8%AC-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1739/" rel="">كيفية استخدام نماذج الويب في تطبيقات فلاسك</a> للاطلاع على هذه النقطة.
	</li>
</ul>

<h2>
	الخطوة الأولى - تثبيت فلاسك والإضافة Flask-WTF
</h2>

<p>
	سنعمل في هذه الخطوة على تثبيت كل من فلاسك والإضافة Flask-WTF والتي بدورها ستثبّت المكتبة WTForms تلقائيًا. لذلك، وبعد التأكّد من تفعيل البيئة الافتراضية، سنستخدم أمر تثبيت الحزم <code>pip</code> لتثبيت كل من فلاسك والإضافة Flask-WTF كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8796_6" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$  pip install </span><span class="typ">Flask</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">-</span><span class="pln">WTF</span></pre>

<p>
	وبمجرّد انتهاء التثبيت بنجاح، سيظهر في السطر الأخير من الخرج ما يشبه التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8796_8" style=""><span class="typ">Successfully</span><span class="pln"> installed </span><span class="typ">Flask</span><span class="pun">-</span><span class="lit">2.0</span><span class="pun">.</span><span class="lit">2</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">-</span><span class="pln">WTF</span><span class="pun">-</span><span class="lit">1.0</span><span class="pun">.</span><span class="lit">0</span><span class="pln"> </span><span class="typ">Jinja2</span><span class="pun">-</span><span class="lit">3.0</span><span class="pun">.</span><span class="lit">3</span><span class="pln"> </span><span class="typ">MarkupSafe</span><span class="pun">-</span><span class="lit">2.0</span><span class="pun">.</span><span class="lit">1</span><span class="pln"> </span><span class="typ">WTForms</span><span class="pun">-</span><span class="lit">3.0</span><span class="pun">.</span><span class="lit">0</span><span class="pln"> </span><span class="typ">Werkzeug</span><span class="pun">-</span><span class="lit">2.0</span><span class="pun">.</span><span class="lit">2</span><span class="pln"> click</span><span class="pun">-</span><span class="lit">8.0</span><span class="pun">.</span><span class="lit">3</span><span class="pln"> itsdangerous</span><span class="pun">-</span><span class="lit">2.0</span><span class="pun">.</span><span class="lit">1</span></pre>

<p>
	ومنه نلاحظ أنّ المكتبة WTForms قد ثُبتّت أيضًا كونها تتبع للحزمة Flask-WTF، أما باقي الحزم المُثبّتة فهي من تلك التابعة لفلاسك نفسه، ومع نهاية هذه الخطوة نكون قد ثبتنا ما يلزم من حزم بايثون، وأصبح من الممكن الانتقال إلى الخطوة التالية المُتمثّلة بإعداد نموذج ويب.
</p>

<h2>
	الخطوة الثانية - إعداد النماذج
</h2>

<p>
	سنعدّ في هذه الخطوة نموذج ويب باستخدام مُدقّقين validators وحقول مستوردة من مكتبة WTForms.
</p>

<p>
	إذ سنعدّ الحقول التالية:
</p>

<ul>
	<li>
		"Title": وهو صندوق إدخال نصي لإدخال عنوان الدورة التدريبية.
	</li>
	<li>
		"Description": وهو حقل نصي مُتعدّد الأسطر لإدخال توصيف الدورة التدريبية.
	</li>
	<li>
		"Price": وهو حقل مُخصّص لإدخال رقم صحيح يُمثّل تكلفة الدورة التدريبية.
	</li>
	<li>
		"Level": وهو حقل انتقاء بثلاث خيارات لتحديد مستوى الدورة التدريبية وهي: مبتدئ، متوسّط، متقدّم.
	</li>
	<li>
		"Available": وهو مربع اختيار يشير لكون الدورة التدريبية متوفرّة ومتاحة حاليًا أم لا.
	</li>
</ul>

<p>
	لذا، سننشئ بدايةً ضمن مجلد المشروع "flask_app" سننشئ ملفًا للنماذج باسم "forms.py"، ليتضمّن كافّة النماذج اللازمة للتطبيق:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8796_10" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano forms</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	إذ سيتضمّن هذا الملف صنفًا يمثّل نموذج الويب الذي نُعدّه، ولذلك سنضيف الاستيرادات التالية <code>import</code> إلى بداية الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8796_12" style=""><span class="kwd">from</span><span class="pln"> flask_wtf </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">FlaskForm</span><span class="pln">
</span><span class="kwd">from</span><span class="pln"> wtforms </span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="typ">StringField</span><span class="pun">,</span><span class="pln"> </span><span class="typ">TextAreaField</span><span class="pun">,</span><span class="pln"> </span><span class="typ">IntegerField</span><span class="pun">,</span><span class="pln"> </span><span class="typ">BooleanField</span><span class="pun">,</span><span class="pln">
                     </span><span class="typ">RadioField</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">from</span><span class="pln"> wtforms</span><span class="pun">.</span><span class="pln">validators </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">InputRequired</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Length</span></pre>

<p>
	الآن، ولبناء نموذج ويب، سننشئ صنفًا فرعيًا من الصنف الأساسي <code>FlaskForm</code> الذي استوردناه أصلًا من الحزمة <code>flask_wtf</code>، كما لا بُدّ من تحديد الحقول المراد استخدامها في النموذج والتي سنستوردها من المكتبة <code>wtforms</code>، وهي:
</p>

<ul>
	<li>
		<a href="https://wtforms.readthedocs.io/en/3.0.x/fields/#wtforms.fields.StringField" rel="external nofollow"><code>StringField</code></a>: وهو صندوق إدخال نصّي.
	</li>
	<li>
		<a href="https://wtforms.readthedocs.io/en/3.0.x/fields/#wtforms.fields.TextAreaField" rel="external nofollow"><code>TextAreaField</code></a>: وهو حقل إدخال نصّي مُتعدّد الأسطر.
	</li>
	<li>
		<a href="https://wtforms.readthedocs.io/en/3.0.x/fields/#wtforms.fields.IntegerField" rel="external nofollow"><code>IntegerField</code></a>: وهو حقل مُخصّص لإدخال أعداد صحيحة.
	</li>
	<li>
		<a href="https://wtforms.readthedocs.io/en/3.0.x/fields/#wtforms.fields.BooleanField" rel="external nofollow"><code>BooleanField</code></a>: وهو مربع اختيار.
	</li>
	<li>
		<a href="https://wtforms.readthedocs.io/en/3.0.x/fields/#wtforms.fields.RadioField" rel="external nofollow"><code>RadioField</code></a>: وهو حقل لإظهار أزرار الانتقاء ليتيح للمُستخدم اختيار إحداها.
	</li>
</ul>

<p>
	هذه الحقول هي المسؤولة عن تصيّير البيانات وتحويلها إلى النمط المطلوب، إضافةً إلى تفويض المُدققين بغية التحقق من صحة البيانات المُدخلة.
</p>

<p>
	استوردنا في السطر التالي من الشيفرة السابقة اثنين من المُدقّقين لنطبّقهما على الحقول الآنفة الذكر بما يضمن صحة مُدخلات المُستخدم:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8796_14" style=""><span class="kwd">from</span><span class="pln"> wtforms</span><span class="pun">.</span><span class="pln">validators </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">InputRequired</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Length</span></pre>

<p>
	إذ يُستخدم المُدقق <code>InputRequired</code> للتأكّد من كون المُستخدم قد أدخل فعلًا قيمًا في الحقول المطلوب ملؤها (الإجبارية)، والمُدقق <code>Length</code> يُستخدم للتأكّد من أن طول السلسلة النصية المُدخلة في حقل ما يُحقّق الحد الأدنى المطلوب من عدد المحارف، أو أنّه لم يتجاوز العدد الأعظمي المُتاح.
</p>

<p>
	أمّا الآن، فسنضيف <a href="https://academy.hsoub.com/programming/python/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%A7%D9%84%D8%A3%D8%B5%D9%86%D8%A7%D9%81-%D9%88%D8%AA%D8%B9%D8%B1%D9%8A%D9%81-%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-r754/" rel="">الصنف</a> التالي بعد تعليمة الاستيراد <code>import</code> مُباشرةً على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8796_16" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">CourseForm</span><span class="pun">(</span><span class="typ">FlaskForm</span><span class="pun">):</span><span class="pln">
    title </span><span class="pun">=</span><span class="pln"> </span><span class="typ">StringField</span><span class="pun">(</span><span class="str">'Title'</span><span class="pun">,</span><span class="pln"> validators</span><span class="pun">=[</span><span class="typ">InputRequired</span><span class="pun">(),</span><span class="pln">
                                             </span><span class="typ">Length</span><span class="pun">(</span><span class="pln">min</span><span class="pun">=</span><span class="lit">10</span><span class="pun">,</span><span class="pln"> max</span><span class="pun">=</span><span class="lit">100</span><span class="pun">)])</span><span class="pln">
    description </span><span class="pun">=</span><span class="pln"> </span><span class="typ">TextAreaField</span><span class="pun">(</span><span class="str">'Course Description'</span><span class="pun">,</span><span class="pln">
                                validators</span><span class="pun">=[</span><span class="typ">InputRequired</span><span class="pun">(),</span><span class="pln">
                                            </span><span class="typ">Length</span><span class="pun">(</span><span class="pln">max</span><span class="pun">=</span><span class="lit">200</span><span class="pun">)])</span><span class="pln">
    price </span><span class="pun">=</span><span class="pln"> </span><span class="typ">IntegerField</span><span class="pun">(</span><span class="str">'Price'</span><span class="pun">,</span><span class="pln"> validators</span><span class="pun">=[</span><span class="typ">InputRequired</span><span class="pun">()])</span><span class="pln">
    level </span><span class="pun">=</span><span class="pln"> </span><span class="typ">RadioField</span><span class="pun">(</span><span class="str">'Level'</span><span class="pun">,</span><span class="pln">
                       choices</span><span class="pun">=[</span><span class="str">'Beginner'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Intermediate'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Advanced'</span><span class="pun">],</span><span class="pln">
                       validators</span><span class="pun">=[</span><span class="typ">InputRequired</span><span class="pun">()])</span><span class="pln">
    available </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BooleanField</span><span class="pun">(</span><span class="str">'Available'</span><span class="pun">,</span><span class="pln"> default</span><span class="pun">=</span><span class="str">'checked'</span><span class="pun">)</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	ورثنا في الصنف <code>CourseForm</code> أعلاه الشيفرات من الصنف الأساسي <code>FlaskForm</code> المُستورد أصلًا من حزمة فلاسك، كما عرّفنا مجموعةً من حقول النموذج مثل متغيرات لهذا الصنف باستخدام حقول النموذج نفسها المستوردة سابقًا من المكتبة WTForms، بحيث يكون عنوان الحقل هو الوسيط الأوّل لدى استنساخه.
</p>

<p>
	نعرّف المُدققين اللازمين لكل حقل بتمرير قائمة بأسماء المُدققين المُستوردين أصلًا من الوحدة <code>wtforms.validators</code> مثل وسيط لمتغير الصنف، فمثًلا بالنسبة للصندوق النصّي المُخصّص لعنوان الدورة التدريبية "title" في مثالنا، يكون وسيط العنوان فيه هو السلسلة النصية <code>Title</code>، مع تمرير مُدققين، هما:
</p>

<ul>
	<li>
		<code>InputRequired</code>: للدلالة على كون هذا الحقل مطلوب ملؤه من قبل المُستخدم ولا يجوز تركه فارغًا.
	</li>
	<li>
		<code>Length</code>: ويُمرّر له وسيطين، الأوّل هو <code>min</code> الذي يدل على الحد الأدنى المطلوب من عدد المحارف للسلسلة وهو في مثالنا "10"، أي لا ينبغي لطول سلسلة العنوان أن يقل عن عشرة محارف، أمّا الوسيط الثاني <code>max</code> فيشير إلى الحد الأعظمي المسموح فيه لعدد محارف السلسلة، وهو في حالتنا "100"، وبالتالي لا يجوز لطول العنوان المُدخل في الصندوق النصي أن يتجاوز مئة محرف.
	</li>
</ul>

<p>
	يتضمن الحقل النصي مُتعدّد الأسطر المُخصّص لوصف description الدورة التدريبية مُصادقين أيضًا، هما: <code>InputRequired</code> الذي يعني أنّ هذا الحقل مطلوب، والآخر <code>Length</code> جاعلًا قيمة الوسيط <code>max</code> تساوي "200"، دون وجود قيمة لوسيط الحد الأدنى <code>min</code>، ما يعني أنّ المطلوب فقط عدم تجاوز طول السلسلة المُدخلة لمئتي محرف.
</p>

<p>
	نعرّف بنفس الطريقة حقلًا مطلوبًا لإدخال قيمة عددية صحيحة تُمثّل تكلفة الدورة التدريبية باسم <code>price</code>؛ أما الحقل المُخصّص لمستوى الدورة <code>level</code> فهو زر انتقاء ذو عدّة خيارات، بحيث نعرّف هذه الخيارات ضمن قائمة بايثون ومن ثمّ نمررها قيمةً للوسيط <code>choices</code> من زر الانتقاء، ويُعرَّف هذا الحقل أيضًا على أنه مطلوب ملؤه باستخدام المُدقق <code>InputRequired</code>.
</p>

<p>
	أمّا عن الحقل <code>available</code> (وهو حقل مربع اختيار check box field) فهو يدل على أن الدورة مُتاحةٌ للتسجيل حاليًا، وقد عيّنا القيمة الافتراضية له ليكون مُفعلًا <code>checked</code> أي جرى اختياره عبر تمرير هذه القيمة إلى معامل الحالة الافتراضية <code>default</code>، ما يعني أنّ مُربّع الاختيار هذا سيكون مُفعّلًا افتراضيًا بمجرّد إضافة أي دورة جديدة للدلالة على أنها مُتاحة، إلّا في حال ألغى المُستخدم تفعيله يدويًا.
</p>

<p>
	ومع نهاية هذه الخطوة نكون قد أعددنا نموذج الويب المطلوب ضمن الملف المُسمّى "forms.py"، وسنعمل في الخطوة التالية على إنشاء تطبيق فلاسك، وسنستورد فيه هذا النموذج ونعرض حقوله ضمن صفحة التطبيق الرئيسية، كما سننشئ صفحةً أُخرى لعرض قائمة بالدورات التدريبية.
</p>

<p>
	يمكنك الاطلاع على <a href="https://wtforms.readthedocs.io/en/3.0.x/crash_course/" rel="external nofollow">صفحة دورة Crash</a> في توثيق WTForms لمزيدٍ من المعلومات حول كيفية استخدام مكتبة WTForms، ومراجعة صفحة الحقول و<a href="https://wtforms.readthedocs.io/en/3.0.x/validators/" rel="external nofollow">المدققين</a> للتأكد من صحة بيانات النموذج.
</p>

<h2>
	الخطوة الثالثة - عرض نموذج الويب وقائمة الدورات التدريبية
</h2>

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

<p>
	لذا، وبعد التأكّد من تفعيل البيئة البرمجية وتثبيت فلاسك، سننشئ ملفًا باسم "app.py" ضمن المجلد flask_app لنحرّره:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8796_18" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	سنستورد في هذا الملف الصنف والمُساعِدات اللازمة من فلاسك، كما سنستورد النموذج <code>CourseForm</code> من الملف "forms.py"، كما سنبني قائمةً بالدورات التدريبية، ومن ثمّ سنستنسخ instantiate النموذج ونمرّره إلى ملف ليكون قالبًا، ولإنجاز ذلك نكتب الشيفرات التالية ضمن الملف app.py:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8796_20" style=""><span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> render_template</span><span class="pun">,</span><span class="pln"> redirect</span><span class="pun">,</span><span class="pln"> url_for
</span><span class="kwd">from</span><span class="pln"> forms </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">CourseForm</span><span class="pln">

app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">config</span><span class="pun">[</span><span class="str">'SECRET_KEY'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'your secret key'</span><span class="pln">


courses_list </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[{</span><span class="pln">
    </span><span class="str">'title'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Python 101'</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'description'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Learn Python basics'</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'price'</span><span class="pun">:</span><span class="pln"> </span><span class="lit">34</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'available'</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">True</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'level'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Beginner'</span><span class="pln">
    </span><span class="pun">}]</span><span class="pln">


</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=(</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">))</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> index</span><span class="pun">():</span><span class="pln">
    form </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CourseForm</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'index.html'</span><span class="pun">,</span><span class="pln"> form</span><span class="pun">=</span><span class="pln">form</span><span class="pun">)</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	استوردنا في الشيفرة السابقة من فلاسك ما يلي:
</p>

<ul>
	<li>
		الصنف <code>Flask</code> اللازم لإنشاء نسخة من تطبيق فلاسك.
	</li>
	<li>
		الدالة <code>()render_template</code> اللازمة لتصيّير قالب الصفحة الرئيسية.
	</li>
	<li>
		الدالة <code>()redirect</code> اللازمة لإعادة توجيه المُستخدم إلى الصفحة المُخصّصة بعرض الدورات التدريبية بمجرّد إضافة دورة جديدة.
	</li>
	<li>
		الدالة <code>()url_for</code> اللازمة لبناء الروابط.
	</li>
</ul>

<p>
	استوردنا بدايةً الصنف <code>()CourseForm</code> من الملف "forms.py"، ثمّ انشأنا النسخة الفعلية من تطبيق فلاسك باسم "app"؛ كما أعددنا الضبط اللازم لمفتاح الأمان الخاص بنماذج WTForms لاستخدامه لدى توليد المفتاح المساعد للحماية من هجمات CSRF، وهذا سيساعد بالنتيجة على تأمين نماذج الويب، مع الأخذ بالحسبان أنّ مفتاح الأمان يجب أن يكون سلسلةً نصيةً عشوائية بطول مناسب.
</p>

<p>
	أنشأنا بعد ذلك قائمةً من <a href="https://academy.hsoub.com/programming/python/%D9%81%D9%87%D9%85-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%85%D9%8A%D8%B3-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-r743/" rel="">قواميس بايثون</a> باسم <code>courses_list</code>، ولا تحتوي هذه المرحلة سوى على قاموس واحد يتضمّن دورة تجريبية بعنوان <code>Python 101</code>، بمعنى أنّنا نستخدم قائمة بايثون لتخزين البيانات وذلك كون مقالنا تعليمي ولسنا بصدد شرح طرق التخزين فيه، ولكن في التطبيقات العملية الواقعية نستخدم قاعدة بيانات لتُخزّن هذه البيانات بصورةٍ دائمة سامحةً لنا بتعديلها واسترجاعها بسهولة وفعالية، وللتعرّف على كيفية استخدام قاعدة بيانات لتخزين بيانات الدورات ننصحك بقراءة المقال [How To Use an SQLite Database in a Flask Application](أحد مقالات المجموعة الحالية 531039).
</p>

<p>
	أنشأنا الوجهة الرئيسية <code>/</code> باستخدام المزخرف <code>()app.route</code> ضمن دالة العرض <code>()index</code>، إذ تتعامل هذه الوجهة مع كلا نوعي طلبات HTTP وهما <code>GET</code> و <code>POST</code> من خلال المعامل <code>methods</code>، إذ تتخصّص الطلبات من النوع <code>GET</code> بجلب البيانات، أما الطلبات من النوع <code>POST</code> فهي مُتخصّصة بإرسال البيانات إلى الخادم عبر نموذج ويب مثلًا.
</p>

<p>
	بعد ذلك، استنسخنا الصنف <code>()CourseForm</code> المُمثّل لنموذج الويب، وحفظنا هذه النسخة ضمن متغير باسم <code>form</code>، لتكون القيمة المعادة هي استدعاء للدالة <code>()render_template</code> ممرين لها ملف قالب باسم "index.html" مع نسخة النموذج.
</p>

<p>
	بغية عرض نموذج الويب ضمن الصفحة الرئيسية للتطبيق، سننشئ بدايةً ملف قالب ليتضمّن كافة شيفرات HTML الأساسية اللازمة لترثها لاحقًا القوالب الأُخرى، وسيجنّبنا هذا تكرار الشيفرات، ومن ثمّ سننشئ ملف قالب الصفحة الرئيسية "index.html" المُصيّر أصلًا باستخدام الدالة <code>()index</code>.
</p>

<p>
	الآن، سننشئ مجلدًا للقوالب باسم "templates"، إذ سيبحث فلاسك عن القوالب ضمن المجلد "flask_app"، وسننشئ ضمن مجلّد القوالب هذا ملف قالب باسم "base.html"، الذي سيمثّل القالب الأساسي لبقية القوالب على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8796_22" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ mkdir templates
</span><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">base</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	وسنكتب فيه الشيفرات التالية لإنشاء القالب الرئيسي بحيث يتضمّن شريط تصفُّح وكتلة محتوى:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_8796_24" style=""><span class="dec">&lt;!DOCTYPE html&gt;</span><span class="pln">
</span><span class="tag">&lt;html</span><span class="pln"> </span><span class="atn">lang</span><span class="pun">=</span><span class="atv">"en"</span><span class="tag">&gt;</span><span class="pln">
</span><span class="tag">&lt;head&gt;</span><span class="pln">
    </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">"UTF-8"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;title&gt;</span><span class="pln">{% block title %} {% endblock %} - FlaskApp</span><span class="tag">&lt;/title&gt;</span><span class="pln">
    </span><span class="tag">&lt;style&gt;</span><span class="pln">
        nav a </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#d64161</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">font-size</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3em</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">margin-left</span><span class="pun">:</span><span class="pln"> </span><span class="lit">50px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">text-decoration</span><span class="pun">:</span><span class="pln"> none</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="tag">&lt;/style&gt;</span><span class="pln">
</span><span class="tag">&lt;/head&gt;</span><span class="pln">
</span><span class="tag">&lt;body&gt;</span><span class="pln">
    </span><span class="tag">&lt;nav&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('index') }}"</span><span class="tag">&gt;</span><span class="pln">FlaskApp</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"#"</span><span class="tag">&gt;</span><span class="pln">About</span><span class="tag">&lt;/a&gt;</span><span class="pln">
    </span><span class="tag">&lt;/nav&gt;</span><span class="pln">
    </span><span class="tag">&lt;hr&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"content"</span><span class="tag">&gt;</span><span class="pln">
        {% block content %} {% endblock %}
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
</span><span class="tag">&lt;/body&gt;</span><span class="pln">
</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	إذ يتضمّن القالب الأساسي كافّة الشيفرات المتداولة التي ستحتاجها في القوالب الأُخرى.
</p>

<p>
	ستُستبدل لاحقًا كتلة العنوان <code>title</code> بعنوان كل صفحة، وكتلة المحتوى <code>content</code> بمحتواها، أمّا عن شريط التصفح فسيتضمّن رابطين، الأوّل ينقل المُستخدم إلى الصفحة الرئيسية للتطبيق باستخدام الدالة المساعدة <code>()url_for</code> لتحقيق الربط مع دالة العرض <code>()index</code>، والآخر لصفحة المعلومات حول التطبيق في حال قررت تضمينها في تطبيقك.
</p>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	الآن، سننشئ ملف قالب باسم "index.html" وهو الاسم الذي حددناه في الملف "app.py":
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_8796_28" style=""><span class="pln">(env)user@localhost:$ nano templates/index.html</span></pre>

<p>
	سيتضمّن هذا الملف نموذج الويب المُمرر إلى القالب "index.html" عن طريق المتغير <code>form</code>، وسنضيف ضمنه الشيفرات التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8796_51" style=""><span class="pun">{%</span><span class="pln"> extends </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">

</span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">h1</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="typ">Add</span><span class="pln"> a </span><span class="typ">New</span><span class="pln"> </span><span class="typ">Course</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">

    </span><span class="pun">&lt;</span><span class="pln">form method</span><span class="pun">=</span><span class="str">"POST"</span><span class="pln"> action</span><span class="pun">=</span><span class="str">"/"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">{{</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">csrf_token </span><span class="pun">}}</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">{{</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">title</span><span class="pun">.</span><span class="pln">label </span><span class="pun">}}</span><span class="pln">
            </span><span class="pun">{{</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">title</span><span class="pun">(</span><span class="pln">size</span><span class="pun">=</span><span class="lit">20</span><span class="pun">)</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">

        </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">title</span><span class="pun">.</span><span class="pln">errors </span><span class="pun">%}</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">ul </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"errors"</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> error </span><span class="kwd">in</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">title</span><span class="pun">.</span><span class="pln">errors </span><span class="pun">%}</span><span class="pln">
                    </span><span class="pun">&lt;</span><span class="pln">li</span><span class="pun">&gt;{{</span><span class="pln"> error </span><span class="pun">}}&lt;/</span><span class="pln">li</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">{%</span><span class="pln"> endfor </span><span class="pun">%}</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">ul</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">{%</span><span class="pln"> endif </span><span class="pun">%}</span><span class="pln">

        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">{{</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">description</span><span class="pun">.</span><span class="pln">label </span><span class="pun">}}</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">{{</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">description</span><span class="pun">(</span><span class="pln">rows</span><span class="pun">=</span><span class="lit">10</span><span class="pun">,</span><span class="pln"> cols</span><span class="pun">=</span><span class="lit">50</span><span class="pun">)</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">

        </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">description</span><span class="pun">.</span><span class="pln">errors </span><span class="pun">%}</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">ul </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"errors"</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> error </span><span class="kwd">in</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">description</span><span class="pun">.</span><span class="pln">errors </span><span class="pun">%}</span><span class="pln">
                    </span><span class="pun">&lt;</span><span class="pln">li</span><span class="pun">&gt;{{</span><span class="pln"> error </span><span class="pun">}}&lt;/</span><span class="pln">li</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">{%</span><span class="pln"> endfor </span><span class="pun">%}</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">ul</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">{%</span><span class="pln"> endif </span><span class="pun">%}</span><span class="pln">

        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">{{</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">price</span><span class="pun">.</span><span class="pln">label </span><span class="pun">}}</span><span class="pln">
            </span><span class="pun">{{</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">price</span><span class="pun">()</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">

        </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">price</span><span class="pun">.</span><span class="pln">errors </span><span class="pun">%}</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">ul </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"errors"</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> error </span><span class="kwd">in</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">price</span><span class="pun">.</span><span class="pln">errors </span><span class="pun">%}</span><span class="pln">
                    </span><span class="pun">&lt;</span><span class="pln">li</span><span class="pun">&gt;{{</span><span class="pln"> error </span><span class="pun">}}&lt;/</span><span class="pln">li</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">{%</span><span class="pln"> endfor </span><span class="pun">%}</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">ul</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">{%</span><span class="pln"> endif </span><span class="pun">%}</span><span class="pln">

        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">{{</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">available</span><span class="pun">()</span><span class="pln"> </span><span class="pun">}}</span><span class="pln"> </span><span class="pun">{{</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">available</span><span class="pun">.</span><span class="pln">label </span><span class="pun">}}</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">

        </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">available</span><span class="pun">.</span><span class="pln">errors </span><span class="pun">%}</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">ul </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"errors"</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> error </span><span class="kwd">in</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">available</span><span class="pun">.</span><span class="pln">errors </span><span class="pun">%}</span><span class="pln">
                    </span><span class="pun">&lt;</span><span class="pln">li</span><span class="pun">&gt;{{</span><span class="pln"> error </span><span class="pun">}}&lt;/</span><span class="pln">li</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">{%</span><span class="pln"> endfor </span><span class="pun">%}</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">ul</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">{%</span><span class="pln"> endif </span><span class="pun">%}</span><span class="pln">

        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">{{</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">level</span><span class="pun">.</span><span class="pln">label </span><span class="pun">}}</span><span class="pln">
            </span><span class="pun">{{</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">level</span><span class="pun">()</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">

        </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">level</span><span class="pun">.</span><span class="pln">errors </span><span class="pun">%}</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">ul </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"errors"</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> error </span><span class="kwd">in</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">level</span><span class="pun">.</span><span class="pln">errors </span><span class="pun">%}</span><span class="pln">
                    </span><span class="pun">&lt;</span><span class="pln">li</span><span class="pun">&gt;{{</span><span class="pln"> error </span><span class="pun">}}&lt;/</span><span class="pln">li</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">{%</span><span class="pln"> endfor </span><span class="pun">%}</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">ul</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">{%</span><span class="pln"> endif </span><span class="pun">%}</span><span class="pln">

        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"submit"</span><span class="pln"> value</span><span class="pun">=</span><span class="str">"Add"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">form</span><span class="pun">&gt;</span><span class="pln">

</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	امتد القالب الرئيسي في بداية الشيفرة، وعيّنا عنوانًا للصفحة ضمن تنسيق عنوان من المستوى الأوّل باستخدام الوسم <code>&lt;h1&gt;</code>، ثمّ صيّرنا حقول نموذج الويب ضمن الوسم <code>&lt;form&gt;</code>، وضبطنا نوع طلبات HTTP الخاصّة به لتكون من النوع <code>POST</code>، والحدث المرتبط به ليكون الانتقال إلى الوجهة الرئيسية <code>/</code> المُتمثّلة بالصفحة الرئيسية للتطبيق. صيّرنا بدايةً المفتاح المساعد token الذي تستخدمه نماذج WTForms لحماية النموذج من هجمات CSRF باستخدام التعليمة <code>{{ form.csrf_token }}</code>، إذ يُرسل هذا المفتاح إلى الخادم مع بقية بيانات النموذج، ومن المهم جدًا تصيّير المفتاح المساعد لجعل النماذج آمنة.
</p>

<p>
	صيّرنا كل حقل من النموذج باستخدام الصيغة <code>()form.field</code>، كما صيّرنا عنوان كل منها باستخدام الصيغة <code>form.field.label</code>، وهنا من الممكن تمرير وسطاء إلى كل حقل والتي من شأنها التحكّم بطريقة عرضه، فمثلًا عيّنا حجم الصندوق النصي الخاص بالعنوان بالشّكل <code>{{ form.title(size=20) }}</code>، كما عيّنا عدد الأسطر والأعمدة ضمن الحقل النصي مُتعدّد الأسطر والخاص بوصف الدورة بالشّكل من خلال المعاملين <code>rows</code> و <code>cols</code> وبنفس الطريقة التقليدية المُتبعة في لغة HTML، وبذلك من الممكن إضافة ما نشاء من سمات HTML إلى الحقول، مثل سمة <code>class</code> مثلًا بغية تعيّين صنفٍ لشيفرات <a href="https://academy.hsoub.com/programming/css/" rel="">CSS</a>.
</p>

<p>
	استخدمنا الصيغة <code>if form.field.errors</code> للتحقّق من وجود أخطاء في المُصادقة، ففي حال وجود أخطاء في حقلٍ ما، سيجري المرور عليها باستخدام حلقة <code>for</code> تكرارية لتُعرض ضمن قائمة أسفل الحقل.
</p>

<p>
	الآن، ومع وجودنا ضمن المجلد "flask_app" ومع كون البيئة الافتراضية مُفعّلة، سنُعلم فلاسك بالتطبيق المراد تشغيله (وهو في حالتنا الملف app.py) باستخدام متغير البيئة <code>FLASK_APP</code>، ثم نضبط متغير البيئة <code>FLASK_ENV</code> لتشغيل التطبيق في وضع التطوير <code>development</code>، مع إمكانية الوصول إلى مُنقّح الأخطاء؛ ولمزيدٍ من المعلومات حول مُنقّح الأخطاء في فلاسك ننصحك بقراءة المقال <a href="https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A3%D8%AE%D8%B7%D8%A7%D8%A1-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1738/" rel="">كيفية التعامل مع الأخطاء في تطبيقات فلاسك</a>، ولتنفيذ ما سبق سنشغّل الأوامر التالية (مع ملاحظة أنّنا نستخدم الأمر <code>set</code> في بيئة ويندوز عوضًا عن الأمر <code>export</code><span class="ipsEmoji">?</span>
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8796_32" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_APP</span><span class="pun">=</span><span class="pln">app
</span><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_ENV</span><span class="pun">=</span><span class="pln">development</span></pre>

<p>
	والآن سنشغّل التطبيق باستخدام الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8796_34" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ flask run</span></pre>

<p>
	وبعد التأكد من كون خادم التطوير ما يزال قيد التشغيل، نذهب إلى الرابط التالي باستخدام المتصفح:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8796_36" style=""><span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span></pre>

<p>
	فيظهر نموذج الويب ضمن الصفحة الرئيسية للتطبيق كما هو موضح في الشكل التالي:
</p>

<p style="text-align: center;">
	<img alt="شكل نموذج الويب ضمن الصفحة الرئيسية للتطبيق" class="ipsImage ipsImage_thumbnailed" data-fileid="115908" data-ratio="45.43" data-unique="9frbe7gud" style="width: 700px; height: 318px;" width="600" src="https://academy.hsoub.com/uploads/monthly_2023_01/1st.thumb.png.69d1c634d4f8a869eb87f902640f77ac.png">
</p>

<p>
	ولو جرّبنا إرسال النموذج مع ترك حقل العنوان فارغًا، ستظهر رسالة خطأ تعلمنا بأن العنوان مطلوب، وهنا من الجيد تجربة إرسال نماذج ببيانات لا تحقق الشروط بغية التعرّف على رسائل الخطأ المُختلفة، مثل إدخال عنوان بأقل من 10 محارف، أو وصف يتجاوز 200 محرف، أمّا إذا ملأنا النموذج ببيانات صحيحة وأرسلناه فلن يحدث شيء حاليًا، والسبب أنّنا لم نضف حتى الآن الشيفرة اللازمة للتعامل مع النماذج المُرسلة، الأمر الذي سنفعله لاحقًا.
</p>

<p>
	سنعمل الآن على إضافة صفحة لعرض الدورات الموجودة في القائمة، وسنضيف فيما بعد ما يلزم للتعامل مع البيانات الآتية من نموذج الويب، والتي ستضيف بالنتيجة دورة جديدة إلى القائمة، ليُعاد بعدها توجيه المُستخدم إلى صفحة عرض الدورات ليرى أنّ الدورة الجديدة قد أُضيفت فعلًا.
</p>

<p>
	لذلك، سنفتح نافذة أسطر أوامر جديدة مع بقاء خادم التطوير قيد التشغيل، ثمّ سنفتح الملف app.py لإضافة الوجهة الخاصّة بصفحة عرض الصفحات إليه:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8796_41" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	وسنضيف الوجهة التالية إلى نهاية الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8796_43" style=""><span class="com"># ...</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/courses/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> courses</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'courses.html'</span><span class="pun">,</span><span class="pln"> courses_list</span><span class="pun">=</span><span class="pln">courses_list</span><span class="pun">)</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	تُخرج الوجهة السابقة قالبًا باسم "courses.html" ممرّرة له قائمة الدورات <code>courses_list</code>.
</p>

<p>
	أمّا الآن فسننشئ القالب "courses.html" المسؤول عن عرض الدورات:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_8796_45" style=""><span class="pln">(env)user@localhost:$ nano templates/courses.html</span></pre>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8796_49" style=""><span class="pun">{%</span><span class="pln"> extends </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">

</span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">h1</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="typ">Courses</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">hr</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> course </span><span class="kwd">in</span><span class="pln"> courses_list </span><span class="pun">%}</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">h2</span><span class="pun">&gt;</span><span class="pln"> </span><span class="pun">{{</span><span class="pln"> course</span><span class="pun">[</span><span class="str">'title'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">}}</span><span class="pln"> </span><span class="pun">&lt;/</span><span class="pln">h2</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">h4</span><span class="pun">&gt;</span><span class="pln"> </span><span class="pun">{{</span><span class="pln"> course</span><span class="pun">[</span><span class="str">'description'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">}}</span><span class="pln"> </span><span class="pun">&lt;/</span><span class="pln">h4</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln"> </span><span class="pun">{{</span><span class="pln"> course</span><span class="pun">[</span><span class="str">'price'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">$ </span><span class="pun">&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;&lt;</span><span class="pln">i</span><span class="pun">&gt;({{</span><span class="pln"> course</span><span class="pun">[</span><span class="str">'level'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">}})&lt;/</span><span class="pln">i</span><span class="pun">&gt;&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="typ">Availability</span><span class="pun">:</span><span class="pln">
            </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> course</span><span class="pun">[</span><span class="str">'available'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">
                </span><span class="typ">Available</span><span class="pln">
            </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">
                </span><span class="typ">Not</span><span class="pln"> </span><span class="typ">Available</span><span class="pln">
            </span><span class="pun">{%</span><span class="pln"> endif </span><span class="pun">%}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">hr</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">{%</span><span class="pln"> endfor </span><span class="pun">%}</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	عيّنا في الشيفرة السابقة عنوانًا، ثمّ مررنا على كافّة عناصر القائمة <code>courses_list</code>، إذ سيُعرض العنوان ضمن تنسيق عنوان من المستوى الثاني باستخدام الوسم <code>&lt;h2&gt;</code>، والوصف ضمن تنسيق عنوان من المستوى الرابع <code>&lt;h4&gt;</code>، أمّا كل من السعر والمستوى فسيُعرضان ضمن تنسيق فقرة باستخدام الوسم <code>&lt;p&gt;</code>، ويجري التحقّق من كون الدورة مُتاحة باستخدام العبارة الشرطية <code>['if course['available</code>، لتُعرض العبارة "Available"، والتي تعني أن الدورة مُتاحة في حال توفرها، والعبارة "Not Available" في حال عدم توفرها.
</p>

<p>
	وبالانتقال إلى صفحة الدورات باستخدام الرابط التالي في المتصفح:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8796_53" style=""><span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln">courses</span><span class="pun">/</span></pre>

<p>
	ستظهر الصفحة مُتضمّنةً دورةً تدريبيةً واحدةً وهي تلك التي أضفناها يدويًا إلى قائمة بايثون سابقًا، كما هو موضح في الشّكل التالي:
</p>

<p style="text-align: center;">
	<img alt="ظهور الصفحة متضمنة دورة تدريبية واحدة" class="ipsImage ipsImage_thumbnailed" data-fileid="115909" data-ratio="48.43" data-unique="s9l0f9qtr" style="width: 700px; height: auto;" width="700" src="https://academy.hsoub.com/uploads/monthly_2023_01/2nd.thumb.png.4498be31eeb4771426304ece5ffa2ee7.png">
</p>

<p>
	أمّا الآن، سنفتح ملف القالب الأساسي "base.html" لتعديله بغية إضافة رابط مباشر ينقلنا إلى صفحة عرض الدورات وذلك ضمن شريط التصفّح:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8796_56" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">base</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	ونعدله ليبدو على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_8796_58" style=""><span class="dec">&lt;!DOCTYPE html&gt;</span><span class="pln">
</span><span class="tag">&lt;html</span><span class="pln"> </span><span class="atn">lang</span><span class="pun">=</span><span class="atv">"en"</span><span class="tag">&gt;</span><span class="pln">
</span><span class="tag">&lt;head&gt;</span><span class="pln">
    </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">"UTF-8"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;title&gt;</span><span class="pln">{% block title %} {% endblock %} - FlaskApp</span><span class="tag">&lt;/title&gt;</span><span class="pln">
    </span><span class="tag">&lt;style&gt;</span><span class="pln">
        nav a </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">color</span><span class="pun">:</span><span class="pln"> </span><span class="lit">#d64161</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">font-size</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3em</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">margin-left</span><span class="pun">:</span><span class="pln"> </span><span class="lit">50px</span><span class="pun">;</span><span class="pln">
            </span><span class="kwd">text-decoration</span><span class="pun">:</span><span class="pln"> none</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="tag">&lt;/style&gt;</span><span class="pln">
</span><span class="tag">&lt;/head&gt;</span><span class="pln">
</span><span class="tag">&lt;body&gt;</span><span class="pln">
    </span><span class="tag">&lt;nav&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('index') }}"</span><span class="tag">&gt;</span><span class="pln">FlaskApp</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('courses') }}"</span><span class="tag">&gt;</span><span class="pln">Courses</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"#"</span><span class="tag">&gt;</span><span class="pln">About</span><span class="tag">&lt;/a&gt;</span><span class="pln">
    </span><span class="tag">&lt;/nav&gt;</span><span class="pln">
    </span><span class="tag">&lt;hr&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"content"</span><span class="tag">&gt;</span><span class="pln">
        {% block content %} {% endblock %}
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
</span><span class="tag">&lt;/body&gt;</span><span class="pln">
</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	الآن سيظهر رابط الانتقال إلى صفحة عرض الدورات ضمن شريط التصفّح عند تحديث الصفحة الرئيسية للتطبيق في المتصفّح.
</p>

<p>
	ومع نهاية هذه الخطوة نكون قد أنشأنا الصفحات اللازمة للتطبيق، وهي: الصفحة الرئيسية "index.html" المُتضمّنة نموذج ويب لإضافة دورات جديدة، وصفحة لعرض الدورات المُخزّنة ضمن قائمة الدورات "courses.html".
</p>

<p>
	لجعل التطبيق يعمل فعليًا، سنعالج في الخطوة التالية البيانات التي يرسلها المُستخدم عبر نموذج الويب من حيث التحقّق من صحتها وإضافتها إلى قائمة الدورات.
</p>

<h2>
	الخطوة الرابعة - الوصول إلى بيانات النموذج
</h2>

<p>
	سنعمل في هذه الخطوة على الوصول إلى البيانات التي أرسلها المُستخدم عبر النموذج للتأكّد من صحتها ومن ثمّ إضافتها إلى قائمة الدورات. لذلك، سنفتح الملف "app.py" لتعديله بإضافة الشيفرات اللازمة للتعامل مع بيانات نموذج الويب وذلك ضمن الدالة <code>()index</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8796_60" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	وسنعدّل الدالة <code>()index</code> لتصبح كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8796_62" style=""><span class="com"># ...</span><span class="pln">
</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=(</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">))</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> index</span><span class="pun">():</span><span class="pln">
    form </span><span class="pun">=</span><span class="pln"> </span><span class="typ">CourseForm</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">validate_on_submit</span><span class="pun">():</span><span class="pln">
        courses_list</span><span class="pun">.</span><span class="pln">append</span><span class="pun">({</span><span class="str">'title'</span><span class="pun">:</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">title</span><span class="pun">.</span><span class="pln">data</span><span class="pun">,</span><span class="pln">
                             </span><span class="str">'description'</span><span class="pun">:</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">description</span><span class="pun">.</span><span class="pln">data</span><span class="pun">,</span><span class="pln">
                             </span><span class="str">'price'</span><span class="pun">:</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">price</span><span class="pun">.</span><span class="pln">data</span><span class="pun">,</span><span class="pln">
                             </span><span class="str">'available'</span><span class="pun">:</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">available</span><span class="pun">.</span><span class="pln">data</span><span class="pun">,</span><span class="pln">
                             </span><span class="str">'level'</span><span class="pun">:</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">level</span><span class="pun">.</span><span class="pln">data
                             </span><span class="pun">})</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> redirect</span><span class="pun">(</span><span class="pln">url_for</span><span class="pun">(</span><span class="str">'courses'</span><span class="pun">))</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'index.html'</span><span class="pun">,</span><span class="pln"> form</span><span class="pun">=</span><span class="pln">form</span><span class="pun">)</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	استدعينا في الشيفرة السابقة التابع <code>()validate_on_submit</code> من الكائن <code>form</code>، إذ يتحقّق هذا التابع من كون طلب HTTP من النوع POST، ثمّ شغلنا المدققات التي أعددناها سابقًا لكل حقل من الحقول، فإذا أعاد أي منها خطأ ما لن يتحقق الشرط أي ستكون قيمته <code>False</code>، وسيظهر كل خطأ أسفل الحقل الذي تسبب بحدوثه.
</p>

<p>
	أمّا في حال كانت كافّة البيانات المُدخلة صحيحة، سيتحقق الشرط أي ستكون قيمته <code>True</code> وبالتالي سيُنفّذ جزء الشيفرة التالي لتعليمة <code>if</code>، وفيها نبني قاموس بايثون للدورة الجديدة ومن ثمّ نستخدم التابع <code>append</code> لإضافة هذه الدورة إلى قائمة الدورات <code>courses_list</code>، إذ استخدمنا الصيغة <code>form.field.data</code> للوصول إلى قيمة كل حقل في النموذج، ونهايةً وبعد إضافة الدورة الجديدة إلى قائمة الدورات، نعيد توجيه المُستخدم إلى صفحة عرض الدورات.
</p>

<p>
	الآن، سننتقل إلى الصفحة الرئيسية للتطبيق مع كون خادم التطوير قيد التشغيل، باستخدام الرابط:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8796_64" style=""><span class="pln">http</span><span class="pun">:</span><span class="com">//127.0.0.1:5000/</span></pre>

<p>
	وبملء النموذج ببيانات صحيحة وإرساله، نلاحظ إضافة الدورة الجديدة فعلًا، وسنُوجّه تلقائيًا إلى صفحة عرض الدورات من التطبيق والتي ستظهر فيها الدورة الجديدة المُضافة.
</p>

<h2>
	الخلاصة
</h2>

<p>
	أنشأنا في هذه المقال تطبيق فلاسك ذا نموذج ويب مبني باستخدام الإضافة Flask-WTF والمكتبة WTForms، إذ ضمّنا في نموذج الويب هذا العديد من أنواع الحقول التي من شأنها استقبال البيانات من المُستخدم وتحقّقنا من صحتها بالاعتماد على المُدققين الخاصين بمكتبة WTForms، لنضيف هذه البيانات بالنتيجة إلى مخزن البيانات وهو قائمة بايثون في مثالنا.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-use-and-validate-web-forms-with-flask-wtf" rel="external nofollow">How To Use and Validate Web Forms with Flask-WTF</a> لصاحبه Abdelhadi Dyouri.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%86%D9%85%D8%A7%D8%B0%D8%AC-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1739/" rel="">كيفية استخدام نماذج الويب في تطبيقات فلاسك Flask</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-wtforms-%D9%84%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D9%86%D9%85%D8%A7%D8%B0%D8%AC-html-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-flask-r411/" rel="">مدخل إلى استخدام مكتبة WTForms للتعامل مع نماذج HTML في تطبيقات Flask</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%88%D9%8A%D8%A8-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D9%85%D9%86-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1622/" rel="">إنشاء تطبيق ويب باستخدام إطار عمل فلاسك Flask من لغة بايثون</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1838</guid><pubDate>Fri, 13 Jan 2023 16:00:00 +0000</pubDate></item><item><title>&#x643;&#x64A;&#x641;&#x64A;&#x629; &#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x646;&#x645;&#x627;&#x630;&#x62C; &#x627;&#x644;&#x648;&#x64A;&#x628; &#x641;&#x64A; &#x62A;&#x637;&#x628;&#x64A;&#x642;&#x627;&#x62A; &#x641;&#x644;&#x627;&#x633;&#x643; Flask</title><link>https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%86%D9%85%D8%A7%D8%B0%D8%AC-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1739/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_10/633d2d7b7426b_------.jpg.3bee4e411d273ee54a4572ab297aa342.jpg" /></p>

<p>
	تمنح نماذج الويب web forms من صناديقٍ نصيّة وحقول نصوص مُتعدّدة الأسطر المستخدمين القدرة على إرسال البيانات إلى التطبيق لاستخدامها في تنفيذ أمرٍ ما، أو حتّى لإرسال محتوًى نصي كبير، فمثلًا يُمنح المستخدم في تطبيقات التواصل الاجتماعي حيزًا يمكِّنه من إضافة محتوًى جديد إلى صفحته الشخصية؛ كما يوجد أيضًا في صفحة تسجيل الدخول حقلٌ لإدراج اسم المستخدم وحقلٌ لإدراج كلمة المرور، إذ يستخدم الخادم (تطبيق فلاسك في حالتنا) هذه البيانات التي أدخلها المستخدم ليسجّل دخوله إذا كانت البيانات صحيحة أو يستجيب برسالة خطأ مثل "!Invalid credentials" لإعلام المستخدم بأن البيانات التي أدخلها خاطئة.
</p>

<p>
	يُعد <a href="https://academy.hsoub.com/programming/python/flask/" rel="">فلاسك</a> إطار عمل للويب مبني بلغة <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D9%85%D8%B1%D8%AC%D8%B9-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%AA%D8%B9%D9%84%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r735/" rel="">بايثون</a>، ويتميز بكونه صغير الحجم وسهل المعالجة، ويوفّر أيضًا عدّة أدوات وميزات من شأنها جعل إنشاء تطبيقات الويب في <a href="https://academy.hsoub.com/programming/python/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r211/" rel="">لغة بايثون</a> أسهل.
</p>

<p>
	سننشئ في هذا المقال تطبيق ويب مُصغّر لبيان كيفيّة استخدام نماذج الويب، إذ سيتضمّن صفحةً لإظهار رسائل مُخزّنةٍ مُسبقًا ضمن قائمة باستخدام بايثون، وصفحةً أُخرى لإضافة رسائل جديدة، كما سنستخدم <a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D9%84%D8%B1%D8%B3%D8%A7%D8%A6%D9%84-%D8%A7%D9%84%D8%AE%D8%A7%D8%B7%D9%81%D8%A9-flash-messages-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-flask-r422/" rel="">الرسائل الخاطفة message flashing</a> لإبلاغ المستخدمين بوجود خطأ لدى إدخالهم لبيانات غير صحيحة.
</p>

<p>
	#مستلزمات العمل
</p>

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

<ul>
<li>
		توفُّر <a href="https://academy.hsoub.com/programming/python/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%AA%D9%87%D8%A7-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r714/" rel="">بيئة برمجة بايثون 3 محلية</a>، مثبّتة على حاسوبك، وسنفترض في مقالنا أن اسم مجلد المشروع هو "flask_app".
	</li>
	<li>
		الفهم الجيد لأساسيات فلاسك، مثل مفهوم الوجهات routes ودوال العرض view في فلاسك، وفي هذا الصدد يمكنك الاطلاع على المقال <a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%B9%D9%84%D9%85-%D8%A8%D9%86%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9%D9%83-%D8%A7%D9%84%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1715/" rel="">كيفية بناء موقعك الإلكتروني الأول باستخدام إطار عمل فلاسك Flask من لغة بايثون</a> لفهم مبادئ فلاسك.
	</li>
	<li>
		فهم أساسيات لغة <a href="https://academy.hsoub.com/programming/html/" rel="">HTML</a>.
	</li>
</ul>
<h2>
	الخطوة الأولى - عرض الرسائل
</h2>

<p>
	سننشئ في هذه الخطوة تطبيق فلاسك بصفحة رئيسية لعرض رسائل مُخزنة أصلًا في قائمة من <a href="https://academy.hsoub.com/programming/python/%D9%81%D9%87%D9%85-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%85%D9%8A%D8%B3-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-r743/" rel="">قواميس بايثون</a>.
</p>

<p>
	لذا سنفتح ملف جديد باسم "app.py" للتعديل عليه:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_782_8" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	وسنضيف الشيفرة التالية ضمنه، بغية إنشاء خادم فلاسك ذو وجهةٍ واحدة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_782_10" style="">
<span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> render_template

app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln">

messages </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[{</span><span class="str">'title'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Message One'</span><span class="pun">,</span><span class="pln">
             </span><span class="str">'content'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Message One Content'</span><span class="pun">},</span><span class="pln">
            </span><span class="pun">{</span><span class="str">'title'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Message Two'</span><span class="pun">,</span><span class="pln">
             </span><span class="str">'content'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Message Two Content'</span><span class="pun">}</span><span class="pln">
            </span><span class="pun">]</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> index</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'index.html'</span><span class="pun">,</span><span class="pln"> messages</span><span class="pun">=</span><span class="pln">messages</span><span class="pun">)</span></pre>

<p>
	ثمّ نحفظ الملف ونغلقه.
</p>

<p>
	استوردنا في الشيفرة السابقة كلًا من الصنف <code>Flask</code> الذي سنستخدمه لإنشاء نسخة من التطبيق باسم <code>app</code>، ودالة تصيّير القوالب <code>()render_template</code> من حزمة فلاسك <code>flask</code>، مُمررّين المتغير الخاص <code>__name__</code> اللازم لتمكين فلاسك من إعداد بعض المسارات العاملة في الخلفية؛ أمّا عن تصيير القوالب، فننصحك بقراءة المقال <a href="https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1737/" rel="">كيفية استخدام القوالب في تطبيقات فلاسك Flask</a>.
</p>

<p>
	ثمّ أنشأنا قائمةً عامّة في بايثون باسم <code>messages</code>، والتي تحتوي على قواميس بايثون، وفي كل قاموس أضفنا مفتاحين أساسيين، هما: <code>title</code> لتخزين عنوان الرسالة، و <code>content</code> لتخزين مضمونها، وما هذا إلّا مثال مُبسّط لآلية تخزين البيانات، إذ نستخدم في التطبيقات العملية الواقعية قاعدة بيانات لتُخزّن هذه البيانات بصورةٍ دائم سامحةً لنا بتعديلها واسترجاعها بسهولة وفعالية.
</p>

<p>
	وبعد إنشاء قائمة بايثون، أنشأنا دالة العرض <code>()index</code> باستخدام المُزخرف <code>()app.route@</code> الذي يحوّل دوال بايثون الاعتيادية إلى دوال عاملة في فلاسك، وفيها تكون القيمة المعادة استدعاءً للدالة <code>()render_template</code> التي تُعلم فلاسك بأنّ الوجهة الحالية يجب أن تعرض في النتيحة قالب HTML باسم index.html (سننشئ ملف القالب هذا في الخطوات اللاحقة) ممررين له قيمة المتغير المُسمّى <code>messages</code> والذي يحتوي على قائمة الرسائل <code>messages</code> التي صرحنا عنها للتو مُسندين لها القيم اللازمة، وجعلناها متاحةً للاستخدام من قبل قالب HTML.
</p>

<p>
	ننصحك في هذا الصدد بقراءة المقال <a href="%D8%B1%D8%A7%D8%A8%D8%B7" rel="">How To Create Your First Web Application Using Flask and Python 3</a> لفهم أعمق لدوال العرض في فلاسك.
</p>

<p>
	الآن، سننشئ مجلدًا للقوالب باسم "templates" ضمن المجلد "flask_app"، إذ سيبحث فلاسك ضمنه عن القوالب، وبعدها سننشئ ملف القالب الأساسي باسم "base.html" وفيه سنكتب الشيفرة الأساسية التي سترثها بقية القوالب لتجنّب تكرار الشيفرات:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_782_13" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ mkdir templates
</span><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">base</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	ومن ثمّ نكتب الشيفرة التالية ضمن الملف base.html لإنشاء قالب HTML الأساسي مع شريط تنقل وجزء مُخصّص للمحتوى:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_782_15" style="">
<span class="pun">&lt;!</span><span class="pln">DOCTYPE html</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">html lang</span><span class="pun">=</span><span class="str">"en"</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">head</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">meta charset</span><span class="pun">=</span><span class="str">"UTF-8"</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">title</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="typ">FlaskApp</span><span class="pun">&lt;/</span><span class="pln">title</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">style</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">.</span><span class="pln">message </span><span class="pun">{</span><span class="pln">
            padding</span><span class="pun">:</span><span class="pln"> </span><span class="lit">10px</span><span class="pun">;</span><span class="pln">
            margin</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5px</span><span class="pun">;</span><span class="pln">
            background</span><span class="pun">-</span><span class="pln">color</span><span class="pun">:</span><span class="pln"> </span><span class="com">#f3f3f3</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        nav a </span><span class="pun">{</span><span class="pln">
            color</span><span class="pun">:</span><span class="pln"> </span><span class="com">#d64161;</span><span class="pln">
            font</span><span class="pun">-</span><span class="pln">size</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3em</span><span class="pun">;</span><span class="pln">
            margin</span><span class="pun">-</span><span class="pln">left</span><span class="pun">:</span><span class="pln"> </span><span class="lit">50px</span><span class="pun">;</span><span class="pln">
            text</span><span class="pun">-</span><span class="pln">decoration</span><span class="pun">:</span><span class="pln"> none</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

    </span><span class="pun">&lt;/</span><span class="pln">style</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">head</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">body</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">nav</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">a href</span><span class="pun">=</span><span class="str">"{{ url_for('index') }}"</span><span class="pun">&gt;</span><span class="typ">FlaskApp</span><span class="pun">&lt;/</span><span class="pln">a</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">a href</span><span class="pun">=</span><span class="str">"#"</span><span class="pun">&gt;</span><span class="typ">About</span><span class="pun">&lt;/</span><span class="pln">a</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">nav</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">hr</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"content"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">body</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">html</span><span class="pun">&gt;</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	يتضمّن القالب الأساسي كافّة الشيفرات المتداولة التي سنحتاجها في القوالب الأُخرى.
</p>

<p>
	ستُستبدل لاحقًا كتلة العنوان <code>title</code> بعنوان كل صفحة وكتلة المحتوى <code>content</code> بمحتواها، أمّا عن شريط التصفح فسيتضمّن رابطين، الأوّل ينقل المُستخدم إلى الصفحة الرئيسية للتطبيق باستخدام الدالة المساعدة <code>()url_for</code> لتحقيق الربط مع دالة العرض <code>()index</code>، والآخر لصفحة المعلومات حول التطبيق في حال قررت تضمينها في تطبيقك.
</p>

<p>
	لذا سنفتح ملف القالب المُسمى "index.html" وهو الاسم الذي حددناه في الملف app.py:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_782_17" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">index</span><span class="pun">.</span><span class="pln">html</span></pre>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_782_19" style="">
<span class="pun">{%</span><span class="pln"> extends </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">

</span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">h1</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="typ">Messages</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> message </span><span class="kwd">in</span><span class="pln"> messages </span><span class="pun">%}</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">'message'</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">h3</span><span class="pun">&gt;{{</span><span class="pln"> message</span><span class="pun">[</span><span class="str">'title'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">}}&lt;/</span><span class="pln">h3</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;{{</span><span class="pln"> message</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">{%</span><span class="pln"> endfor </span><span class="pun">%}</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	ثمّ نحفظ الملف ونغلقه.
</p>

<p>
	وسّعنا في الشيفرة السابقة ملف القالب "base.html"، واستبدلنا محتوى كتلة المحتوى <code>content</code> مُستخدمين تنسيق العنوان من المستوى الأوّل <code>&lt;h1&gt;</code> الذي يفي أيضًا بالغرض لعنوان الصفحة.
</p>

<p>
	كما استخدما حلقة <code>for</code> التكرارية وهي من ضمن تعليمات محرّك القوالب جينجا <a href="https://academy.hsoub.com/tags/jinja2/" rel="">Jinja</a> وذلك في السطر البرمجي <code>{% for message in messages %}</code> بهدف التنقل بين الرسائل في القائمة <code>messages</code> (والمُخزنة ضمن المتغير المسمّى <code>message</code>)؛ إذ سنعرض عناوين الرسائل ضمن تنسيق عنوان من المستوى الثالث باستخدام الوسم <code>&lt;h3&gt;</code>، ومحتواها ضمن تنسيق فقرة باستخدام الوسم <code><a href="https://wiki.hsoub.com/HTML/p" rel="external">&lt;p&gt;</a></code>، وكلاهما ضمن حافظة عناصر HTML باستخدام الوسم <code><a href="https://wiki.hsoub.com/HTML/div" rel="external">&lt;div&gt;</a></code>.
</p>

<p>
	ولتشغيل تطبيق الويب الذي أنشأناه، وبعد التأكّد من وجودنا ضمن المجلد "flask_app" مع تفعيل البيئة الافتراضية، لا بُدّ من إرشاد فلاسك إلى موقع التطبيق (في حالتنا الملف ذو الاسم "app.py") وذلك باستخدام متغير بيئة فلاسك <code>FLASK_APP</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_782_23" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_APP</span><span class="pun">=</span><span class="pln">app</span></pre>

<p>
	نضبط متغير بيئة فلاسك <code>Flask_ENV</code> على الوضع <code>development</code> لتشغيل التطبيق في وضع التطوير ما يمكنّنا من استخدام مُنقّح الأخطاء كما يلي (مع ملاحظة أنّنا نستخدم الأمر <code>set</code> في بيئة ويندوز عوضًا عن الأمر <code>export</code>):
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_782_25" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_ENV</span><span class="pun">=</span><span class="pln">development</span></pre>

<p>
	بعدها، سنشغل التطبيق باستخدام الأمر <code>flask run</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_782_27" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ flask run</span></pre>

<p>
	وبعد التأكد من كون خادم التطوير ما يزال قيد التشغيل، نذهب إلى الرابط التالي باستخدام المتصفح:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_782_29" style="">
<span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span></pre>

<p>
	فتظهر الرسائل المُخزّنة أصلًا ضمن القائمة "messages" في الصفحة الرئيسية للتطبيق كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="109084" href="https://academy.hsoub.com/uploads/monthly_2022_10/1st.png.3c432bb76e2f6d095ad0488c986e9772.png" rel=""><img alt="1st.png" class="ipsImage ipsImage_thumbnailed" data-fileid="109084" data-unique="cqfyrdtrf" src="https://academy.hsoub.com/uploads/monthly_2022_10/1st.thumb.png.1338038abd21759eb78daf9cceb822d4.png" style="width: 720px; height: auto;"></a>
</p>

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

<h2>
	الخطوة الثانية - إعداد نماذج الويب
</h2>

<p>
	سننشئ في هذه الخطوة صفحةً جديدةً في التطبيق تسمح للمستخدمين بإضافة رسائل جديدة إلى قائمة الرسائل من خلال نموذج ويب.
</p>

<p>
	لذا سنفتح نافذة طرفية جديدة مع بقاء <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">خادم</a> التطوير قيد التشغيل.
</p>

<p>
	ومنها نفتح الملف app.py:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_782_33" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ونضيف الوجهة التالية إلى نهاية الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_782_35" style="">
<span class="com"># ...</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/create/'</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=(</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">))</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> create</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'create.html'</span><span class="pun">)</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	تتضمّن الوجهة <code>‎/create</code> معامل طلبيات HTTP methods المتضمّن صفًا tuple هو <code>('GET', 'POST')</code> للتعامل معها سواءً بطريقة <code>GET</code>، أو <code>POST</code>، إذ أن طريقة "GET" هي الطريقة الافتراضية المُستخدمة لجلب البيانات من الخادم، مثل حالة طلب عرض الصفحة الرئيسية أو صفحة المعلومات حول التطبيق؛ أمّا طريقة "POST" فتُستخدم من قبل المتصفح عند إرسال نماذج الإدخال إلى وجهةٍ مُحدّدة، الأمر الذي سيغيّر من البيانات المُخزّنة في الخادم.
</p>

<p>
	وفي مثالنا هذا سيُطلب عرض صفحة إنشاء رسائل جديدة "create" باستخدام طلب من النوع "GET"، إذ ستتضمّن هذه الصفحة نموذج ويب بحقول إدخال وزر إرسال، ولدى ملء النموذج من قبل المستخدم وإرساله، يُرسل طلب من النوع "POST" إلى الوجهة <code>create/</code>، الذي يتعامل مع الطلب من حيث التحقّق من صحة البيانات المُدخلة للتأكّد من كون المُستخدم لم يرسل نموذجًا فارغًا، لتُضاف عندها هذه البيانات إلى قائمة الرسائل "messages".
</p>

<p>
	تؤدي دالة فلاسك <code>()create</code> حاليًا مهمةً واحدة، وهي تصيّير قالب HTML باسم "create.html" لدى استقبالها لطلب من نوع GET، لذا سننشئ الآن هذا القالب وسنعدّل الدالة <code>()create</code> لتصبح قادرةً أيضًا على التعامل مع الطلبات من النوع POST اللازمة في الخطوة التالية.
</p>

<p>
	لذا سنفتح ملف القالب المُسمّى "create.html":
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_782_37" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">create</span><span class="pun">.</span><span class="pln">html</span></pre>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_782_40" style="">
<span class="pun">{%</span><span class="pln"> extends </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">

</span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">h1</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="typ">Add</span><span class="pln"> a </span><span class="typ">New</span><span class="pln"> </span><span class="typ">Message</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">form method</span><span class="pun">=</span><span class="str">"post"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">label </span><span class="kwd">for</span><span class="pun">=</span><span class="str">"title"</span><span class="pun">&gt;</span><span class="typ">Title</span><span class="pun">&lt;/</span><span class="pln">label</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">br</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"text"</span><span class="pln"> name</span><span class="pun">=</span><span class="str">"title"</span><span class="pln">
               placeholder</span><span class="pun">=</span><span class="str">"Message title"</span><span class="pln">
               value</span><span class="pun">=</span><span class="str">"{{ request.form['title'] }}"</span><span class="pun">&gt;&lt;/</span><span class="pln">input</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">br</span><span class="pun">&gt;</span><span class="pln">

        </span><span class="pun">&lt;</span><span class="pln">label </span><span class="kwd">for</span><span class="pun">=</span><span class="str">"content"</span><span class="pun">&gt;</span><span class="typ">Message</span><span class="pln"> </span><span class="typ">Content</span><span class="pun">&lt;/</span><span class="pln">label</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">br</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">textarea name</span><span class="pun">=</span><span class="str">"content"</span><span class="pln">
                  placeholder</span><span class="pun">=</span><span class="str">"Message content"</span><span class="pln">
                  rows</span><span class="pun">=</span><span class="str">"15"</span><span class="pln">
                  cols</span><span class="pun">=</span><span class="str">"60"</span><span class="pln">
                  </span><span class="pun">&gt;{{</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">}}&lt;/</span><span class="pln">textarea</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">br</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">button type</span><span class="pun">=</span><span class="str">"submit"</span><span class="pun">&gt;</span><span class="typ">Submit</span><span class="pun">&lt;/</span><span class="pln">button</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">form</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	ثمّ نحفظ الملف ونغلقه.
</p>

<p>
	وسّعنا في الشيفرة السابقة قالب base.html واستبدلنا كتلة المحتوى <code>content</code> بكائن عنوان من المستوى الأوّل <code>&lt;h1&gt;</code> الذي سيعمل مثل عنوان للصفحة، وعيّنا في وسم كائن النموذج <code><a href="https://wiki.hsoub.com/HTML/form" rel="external">&lt;form&gt;</a></code> قيمة السمة <code>method</code> لتكون <code>post</code> بمعنى أنّ بيانات النموذج ستُرسل إلى الخادم مثل طلب من نوع <code>POST</code>.
</p>

<p>
	وفي كائن النموذج أضفنا حقل إدخال نصي باسم <code>title</code> وهو الاسم الذي سنستخدمه في التطبيق للوصول إلى بيانات نموذج العنوان، كما أسندنا القيمة <code>{{ request.form['title']‎ }}</code> لسمة <code>value</code> من الوسم <code><a href="https://wiki.hsoub.com/HTML/input" rel="external">&lt;input&gt;</a></code>، وهو أمرٌ مفيدٌ لتخزين البيانات التي يدخلها المستخدم مع إمكانية استعادتها في حال حدوث خطأ ما؛ فإذا نسي المستخدم مثلًا ملء الحقل النصي المُخصّص لمحتوى الرسالة <code>content</code> وهو حقل مطلوب، فسيُرسل طلب إلى الخادم لإرجاع رسالة خطأ في الاستجابة، ولكن البيانات في العنوان لن نفقدها لأنها محفوظة في الكائن العام <code>request</code> ويمكن الوصول لها من خلال القيمة <code>['request.from['title</code>.
</p>

<p>
	أضفنا بعد حقل الإدخال المُخصّص لعنوان الرسالة، حقلًا نصيًا مُتعدّد الأسطر لمحتواها باسم <code>content</code> وأسندنا أيضًا القيمة <code>{{ request.form['content']‎ }}</code> لسمة <code>value</code> لنفس السبب آنف الذكر.
</p>

<p>
	ونهايةً أضفنا زر إرسال إلى نهاية النموذج.
</p>

<p>
	الآن وبعد التأكد من كون خادم التطوير ما يزال قيد التشغيل، ننتقل إلى الوجهة الجديدة <code>create/</code> باستخدام المتصفح:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_782_44" style="">
<span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln">create</span></pre>

<p>
	فتظهر صفحة إضافة رسالة جديدة “Add a New Message” مع حقل إدخال لعنوان الرسالة وحقل نصي مُتعدّد الأسطر لإدخال محتوى الرسالة، وزر لتقديم النموذج عند الانتهاء من إدخال البيانات.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="109085" href="https://academy.hsoub.com/uploads/monthly_2022_10/2nd.png.1777bfbb828eabdc3dfa9341657863fa.png" rel=""><img alt="2nd.png" class="ipsImage ipsImage_thumbnailed" data-fileid="109085" data-unique="23mvajewq" src="https://academy.hsoub.com/uploads/monthly_2022_10/2nd.thumb.png.1794b0563fed96026fce99fd6275740e.png" style="width: 720px; height: auto;"></a>
</p>

<p>
	يُرسل هذا النموذج طلبًا من نوع "POST" إلى دالة فلاسك <code>()create</code>، ولكن حتى الآن ما من شيفرة في الدالة للتعامل مع الطلب هذا، لذا لن يحدث شيء بعد تعبئة النموذج وإرساله&gt; لذلك، سنكتب في الخطوة التالية الشيفرة اللازمة للتعامل مع طلب من النوع "POST" بعد إرسال النموذج، إذ سنتحقّق من صحة البيانات المرسلة (ليست فارغة)، كما سنضيف عنوان الرسالة ومحتواها إلى قائمة الرسائل "messages".
</p>

<h2>
	الخطوة الثالثة - التعامل مع الطلبات الواردة من نماذج الويب
</h2>

<p>
	سنتعامل في هذه الخطوة مع الطلبات الواردة من نماذج الإدخال إلى التطبيق، إذ سنعمل على الوصول إلى البيانات التي أرسلها المستخدم عبر نموذج الإدخال المُنشأ في الخطوة السابقة لإضافتها إلى قائمة الرسائل، كما سنستخدم <a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D9%84%D8%B1%D8%B3%D8%A7%D8%A6%D9%84-%D8%A7%D9%84%D8%AE%D8%A7%D8%B7%D9%81%D8%A9-flash-messages-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-flask-r422/" rel="">الرسائل الخاطفة message flashing</a> لإعلام المستخدم في حال إرسال بيانات غير صحيحة، فعندها ستظهر الرسالة لمرة واحدة وتختفي في الطلب التالي (مثل الانتقال إلى صفحة أُخرى) ومن هنا جاء سبب تسميتها بالخاطفة.
</p>

<p>
	لذا سنفتح الملف app.py لتحريره:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_782_47" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	لا بُد أولًا من استيراد ما يلي من إطار العمل فلاسك:
</p>

<ul>
<li>
		الكائن <code>request</code> العام المسؤول عن الوصول إلى بيانات الطلب والتي ستُرسل من خلال نموذج HTML المبني في الخطوة السابقة.
	</li>
	<li>
		الدالة <code>url_for()‎</code> لتوليد عناوين الروابط.
	</li>
	<li>
		الدالة <code>flash()‎</code> لعرض رسالةٍ وامضة عند انتهاء معالجة الطلب، لإعلام المُستخدم في حال سير الأمور على نحوٍ سليم أو في حال وجود مشاكل مثل كون البيانات المُدخلة خاطئة.
	</li>
	<li>
		الدالة <code>redirect()‎</code> لإعادة توجيه المستخدم إلى موقعٍ آخر في المتصفح.
	</li>
</ul>
<p>
	سنضيف هذه الاستيرادات إلى السطر الأوّل من الملف كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_782_49" style="">
<span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> render_template</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">,</span><span class="pln"> url_for</span><span class="pun">,</span><span class="pln"> flash</span><span class="pun">,</span><span class="pln"> redirect

</span><span class="com"># ...</span></pre>

<p>
	تخزّن الدالة <code>flash()‎</code> الرسائل في جلسة المتصفح لدى المستخدم، وهذا ما يتطلّب إعداد <strong>مفتاح أمان Secret Key</strong>، إذ سيُستخدم هذا المفتاح لجعل الجلسات آمنة، وبذلك يتمكّن فلاسك من الحفاظ على المعلومات عند الانتقال من طلبٍ إلى آخر، مثل حالة الانتقال من صفحة إنشاء رسالة جديدة إلى الصفحة الرئيسية، مع ملاحظة أن المستخدم يستطيع الوصول إلى المعلومات المُخزّنة في الجلسة، ولكنه لا يستطيع تعديلها إلّا إذا كان لديه مفتاح الأمان، وبالتالي لا يجب أن تسمح لأي أحدٍ بالوصول إلى مفتاح الأمان الخاص بك.
</p>

<p>
	تذكّر أنّ مفتاح الأمان يجب أن يكون سلسلةً نصيةً عشوائية بطولٍ مناسب، إذ من الممكن توليد مفتاح أمان باستخدام التابع <code>()os.urandom</code> من الوحدة <code>os</code>، إذ يعيد هذا التابع سلسلة من البايتات العشوائية المناسبة لاستخدامات التشفير (الحصول على سلسلة نصية خاصّة أو رقم خاص). للحصول على سلسلة نصية عشوائية باستخدام هذا التابع، سنفتح نافذة طرفية جديدة ومنها نفتح صَدفة بايثون التفاعلية باستخدام الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_782_51" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ python</span></pre>

<p>
	وضمن صَدفة بايثون التفاعلية سنستورد الوحدة <code>os</code> من مكتبة بايثون المعيارية، وسنستدعي التابع <code>()os.urandom</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_782_54" style="">
<span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> os
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">urandom</span><span class="pun">(</span><span class="lit">24</span><span class="pun">).</span><span class="pln">hex</span><span class="pun">()</span></pre>

<p>
	فنحصل في الخرج على سلسلة مُشابهة لما يلي:
</p>

<pre class="ipsCode">
'df0331cefc6c2b9a5d0208a726a5d1c0fd37324feba25506'
</pre>

<p>
	وبذلك يمكننا استخدام هذه السلسلة النصية مثل مفتاح أمان.
</p>

<p>
	ولإعداد مفتاح أمان، سنضيف ضبط <code>SECRET_KEY</code> إلى التطبيق من خلال الكائن <code>app.config</code>، الذي سنضيفه مباشرةً بعد تعريف الكائن <code>app</code> وقبل تعريف المتغير <code>messages‎</code>، على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_782_56" style="">
<span class="com"># ...</span><span class="pln">
app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">config</span><span class="pun">[</span><span class="str">'SECRET_KEY'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'your secret key'</span><span class="pln">


messages </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[{</span><span class="str">'title'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Message One'</span><span class="pun">,</span><span class="pln">
             </span><span class="str">'content'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Message One Content'</span><span class="pun">},</span><span class="pln">
            </span><span class="pun">{</span><span class="str">'title'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Message Two'</span><span class="pun">,</span><span class="pln">
             </span><span class="str">'content'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Message Two Content'</span><span class="pun">}</span><span class="pln">
            </span><span class="pun">]</span><span class="pln">
</span><span class="com"># ...</span></pre>

<p>
	ومن ثمّ نعدّل دالة العرض <code>()create</code> لتبدو كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_782_58" style="">
<span class="com"># ...</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/create/'</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=(</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">))</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> create</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">method </span><span class="pun">==</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">:</span><span class="pln">
        title </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'title'</span><span class="pun">]</span><span class="pln">
        content </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">]</span><span class="pln">

        </span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> title</span><span class="pun">:</span><span class="pln">
            flash</span><span class="pun">(</span><span class="str">'Title is required!'</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">elif</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> content</span><span class="pun">:</span><span class="pln">
            flash</span><span class="pun">(</span><span class="str">'Content is required!'</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
            messages</span><span class="pun">.</span><span class="pln">append</span><span class="pun">({</span><span class="str">'title'</span><span class="pun">:</span><span class="pln"> title</span><span class="pun">,</span><span class="pln"> </span><span class="str">'content'</span><span class="pun">:</span><span class="pln"> content</span><span class="pun">})</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> redirect</span><span class="pun">(</span><span class="pln">url_for</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">))</span><span class="pln">

    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'create.html'</span><span class="pun">)</span></pre>

<p>
	تَمكَّنا باستخدام العبارة الشرطية التي توازن قيمة <code>request.method</code> مع القيمة <code>POST</code> من التحقُّق بأنّ التعليمات التالية لها لن تُنفّذ إلّا إذا كان الطلب الحالي هو فعلًا بطريقة <code>POST</code>، ومن ثمّ قرأنا قيم العنوان والمحتوى المرسلين من الكائن <code>request.form</code> الذي يمكِّننا من الوصول إلى بيانات نموذج الإدخال المُضمّنة في الطلب؛ ففي حال عدم إدخال قيمةٍ للعنوان، فسيتحقق الشرط <code>if not title</code> وبالتالي ستظهر رسالة للمستخدم نعلمه من خلالها بأن العنوان مطلوب وذلك باستخدام الدالة <code>()flash</code>، وهذا ما يضيف هذه الرسالة إلى قائمة للرسائل الخاطفة والتي سنعرضها لاحقًا ضمن الصفحة بوصفها جزءًا من القالب الرئيسي "base.html"؛ وعلى نحوٍ مُشابه فيما يتعلّق بقسم المحتوى، ففي حال كان فارغًا، فسيتحقق الشرط <code>elif not content</code> وبالتالي ستظهر رسالة للمستخدم نعلمه من خلالها بأن المحتوى مطلوب، وستُضاف هذه الرسالة أيضًا إلى قائمة الرسائل الخاطفة.
</p>

<p>
	أمّا في حال وجود عنوان ومحتوى سليمين، فسيُضاف قاموس بايثون جديد إلى القائمة <code>messages</code> بالعنوان والمحتوى المدخلين من قبل المستخدم وذلك اعتمادًا على السطر البرمجي <code>({messages.append({'title': title, 'content': content</code>، ومن ثمّ نعيد توجيه الطلب إلى الصفحة الرئيسية باستخدام الدالة <code>redirect()‎</code> بتمرير الرابط الناتج عن التابع <code>url_for()‎</code> الذي قد مررنا له القيمة <code>index</code> وسيطًا.
</p>

<p>
	ثمّ نحفظ الملف ونغلقه.
</p>

<p>
	ننتقل إلى الوجهة <code>create/</code> عن طريق المتصفح كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_782_60" style="">
<span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln">create</span></pre>

<p>
	وبملء النموذج بعنوان اختياري ومحتوى ما، وحالما نرسل النموذج، ستظهر الرسالة الجديدة في الصفحة الرئيسية للتطبيق.
</p>

<p>
	ونهايةً سنعرّض كافّة الرسائل الخاطفة ضمن الصفحة الرئيسية للتطبيق، كما سنضيف رابطًا في شريط التصفح للقالب الرئيسي base.html، للانتقال إلى صفحة إضافة رسالة جديدة مباشرةً وبسهولة، لذا سنفتح ملف القالب الرئيسي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_782_62" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">base</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	الآن، سنعدّل الملف من خلال إضافة وسم رابط <code>&lt;a&gt;</code> جديد بعد رابط تطبيق فلاسك المتوضّع في شريط التصفُّح داخل الوسم <code><a href="https://wiki.hsoub.com/HTML/nav" rel="external">&lt;nav&gt;</a></code>، ثمّ سنضيف حلقة تكرارية أعلى كتلة المحتوى مباشرةً لعرض الرسائل الخاطفة أسفل شريط التصفح، إذ يوفّر فلاسك هذه الرسائل من خلال دالة فلاسك الخاصة <code>get_flashed_messages()‎</code>، وسنضيف ضمن الوسم <code>&lt;style&gt;</code> السمة <code>alert</code> لكل رسالة مُسندين إليها بعض الخصائص من CSS كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_782_65" style="">
<span class="dec">&lt;!DOCTYPE html&gt;</span><span class="pln">
</span><span class="tag">&lt;html</span><span class="pln"> </span><span class="atn">lang</span><span class="pun">=</span><span class="atv">"en"</span><span class="tag">&gt;</span><span class="pln">
</span><span class="tag">&lt;head&gt;</span><span class="pln">
    </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">"UTF-8"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;title&gt;</span><span class="pln">{% block title %} {% endblock %} - FlaskApp</span><span class="tag">&lt;/title&gt;</span><span class="pln">
    </span><span class="tag">&lt;style&gt;</span><span class="pln">
        </span><span class="pun">.</span><span class="pln">message </span><span class="pun">{</span><span class="pln">
            padding</span><span class="pun">:</span><span class="pln"> </span><span class="lit">10px</span><span class="pun">;</span><span class="pln">
            margin</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5px</span><span class="pun">;</span><span class="pln">
            background</span><span class="pun">-</span><span class="pln">color</span><span class="pun">:</span><span class="pln"> </span><span class="com">#f3f3f3</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        nav a </span><span class="pun">{</span><span class="pln">
            color</span><span class="pun">:</span><span class="pln"> </span><span class="com">#d64161;</span><span class="pln">
            font</span><span class="pun">-</span><span class="pln">size</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3em</span><span class="pun">;</span><span class="pln">
            margin</span><span class="pun">-</span><span class="pln">left</span><span class="pun">:</span><span class="pln"> </span><span class="lit">50px</span><span class="pun">;</span><span class="pln">
            text</span><span class="pun">-</span><span class="pln">decoration</span><span class="pun">:</span><span class="pln"> none</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        </span><span class="pun">.</span><span class="pln">alert </span><span class="pun">{</span><span class="pln">
            padding</span><span class="pun">:</span><span class="pln"> </span><span class="lit">20px</span><span class="pun">;</span><span class="pln">
            margin</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5px</span><span class="pun">;</span><span class="pln">
            color</span><span class="pun">:</span><span class="pln"> </span><span class="com">#970020;</span><span class="pln">
            background</span><span class="pun">-</span><span class="pln">color</span><span class="pun">:</span><span class="pln"> </span><span class="com">#ffd5de;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

    </span><span class="tag">&lt;/style&gt;</span><span class="pln">
</span><span class="tag">&lt;/head&gt;</span><span class="pln">
</span><span class="tag">&lt;body&gt;</span><span class="pln">
    </span><span class="tag">&lt;nav&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('index') }}"</span><span class="tag">&gt;</span><span class="pln">FlaskApp</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('create') }}"</span><span class="tag">&gt;</span><span class="pln">Create</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"#"</span><span class="tag">&gt;</span><span class="pln">About</span><span class="tag">&lt;/a&gt;</span><span class="pln">
    </span><span class="tag">&lt;/nav&gt;</span><span class="pln">
    </span><span class="tag">&lt;hr&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"content"</span><span class="tag">&gt;</span><span class="pln">
        {% for message in get_flashed_messages() %}
            </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"alert"</span><span class="tag">&gt;</span><span class="pln">{{ message }}</span><span class="tag">&lt;/div&gt;</span><span class="pln">
        {% endfor %}
        {% block content %} {% endblock %}
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
</span><span class="tag">&lt;/body&gt;</span><span class="pln">
</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	ثمّ نحفظ الملف ونغلقه، وبإعادة تحميل الرابط "https://127.0.0.1:5000" في المتصفح، ستظهر الصفحة الرئيسية للتطبيق بشريط تصفُّح مُتضمّن للعنصر "Create" الذي يرتبط بالوجهة <code>create/</code> الخاصّة بصفحة إنشاء رسالة جديدة.
</p>

<p>
	ولنرى كيفية عمل الرسائل الخاطفة، ننتقل إلى صفحة "Create" ونضغط على زر الإرسال في النموذج دون ملء أي من حقلي العنوان أوالمحتوى، فتظهر رسالة كما هو مبين بالشكل:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="109086" href="https://academy.hsoub.com/uploads/monthly_2022_10/3rd.png.97d6d1310395aa31a4905817ee10ded6.png" rel=""><img alt="3rd.png" class="ipsImage ipsImage_thumbnailed" data-fileid="109086" data-unique="vbcnsgea4" src="https://academy.hsoub.com/uploads/monthly_2022_10/3rd.thumb.png.fe4ea312a590470bd3d763ffee41936f.png" style="width: 620px; height: auto;"></a>
</p>

<p>
	وبالعودة إلى الصفحة الرئيسية نلاحظ أنّ الرسالة الخاطفة أسفل شريط التنقل قد اختفت، رغم عرضها على أنها جزء من القالب الأساسي، وهنا تكمن ميزتها، فلو لم تكن رسالة خاطفة لكانت ستُعرض في الصفحة الرئيسية أيضًا لأنها ترث من القالب الأساسي.
</p>

<p>
	ولو أرسلنا النموذج مع عنوان ومحتوى فارغ، فستظهر رسالة تفيد بأن حقل المحتوى مطلوب "!Content is required"، وبالضغط على رابط تطبيق فلاسك FlaskApp في شريط التصفُّح نعود إلى الصفحة الرئيسية للتطبيق؛ وهنا إذا ضغطنا في المُتصفّح على زر الرجوع، سنجد أن الرسالة الخاطفة المُتعلقّة بكون المحتوى فارغ لا تزال موجودة، ذلك لأنّ الطلب السابق هو المحفوظ حاليًا؛ أما لو انتقلنا إلى صفحة إنشاء رسالة جديدة من شريط التصفّح فسيُرسل طلب جديد، وبذلك تُمحى مُدخلات النموذج القديمة ومعها الرسالة الخاطفة.
</p>

<p>
	وبذلك نكون قد تعرّفنا على كيفية استلام مُدخلات المُستخدم والتحقّق من صحتها وإضافتها إلى مصدر البيانات (وهو قائمة بايثون في حالتنا).
</p>

<p>
	<strong>ملاحظة:</strong> ستختفي الرسائل المُضافة إلى الفائمة "messages" في التطبيق بمجرّد توقّف الخادم، لأنّ قوائم بايثون تُحفظ فقط في الذاكرة (المؤقتة)، أمّا لحفظ الرسائل بصورةٍ دائمة فلا بدّ من استخدام قاعدة بيانات مثل SQLite، وفي هذا الصدد ننصحك بقراءة المقال <a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D9%85%D8%A7%D8%B1%D9%83%D8%AF%D8%A7%D9%88%D9%86-%D9%85%D8%B9-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-%D9%88%D9%85%D8%AD%D8%B1%D9%83-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-sqlite-r1714/" rel="">كيفية استخدام علاقة نوع واحد-إلى-متعدد one-to-many مع إطار العمل فلاسك Flask ومحرك قواعد البيانات SQLite</a> لمزيد من التفاصيل حول هذه الفكرة.
</p>

<h2>
	الخاتمة
</h2>

<p>
	أنشأنا في هذا المقال تطبيق فلاسك يتيح للمستخدمين إضافة رسائل إلى قائمة رسائل لعرضها في الصفحة الرئيسية للتطبيق. كما أنشأنا نموذج ويب وتعلمنا كيفية التعامل مع البيانات التي يرسلها المستخدم عن طريق هذا النموذج لإضافتها إلى قائمة الرسائل، كما استخدمنا الرسائل الخاطفة لإعلام المستخدم في حال إرسال بيانات غير صحيحة.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-use-web-forms-in-a-flask-application" rel="external nofollow">How To Use Web Forms in a Flask Application</a> لصاحبه Abdelhadi Dyouri.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A3%D8%AE%D8%B7%D8%A7%D8%A1-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1738/" rel="">كيفية التعامل مع الأخطاء في تطبيقات فلاسك Flask</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1737/" rel="">كيفية استخدام القوالب في تطبيقات فلاسك Flask</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A3%D8%B7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%81%D9%8A-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D9%81%D9%84%D8%A7%D8%B3%D9%83-%D9%86%D9%85%D9%88%D8%B0%D8%AC%D8%A7-r1547/" rel="">استخدام أطر العمل في برمجة تطبيقات الويب: فلاسك نموذجا</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1739</guid><pubDate>Wed, 05 Oct 2022 07:35:09 +0000</pubDate></item><item><title>&#x643;&#x64A;&#x641;&#x64A;&#x629; &#x627;&#x644;&#x62A;&#x639;&#x627;&#x645;&#x644; &#x645;&#x639; &#x627;&#x644;&#x623;&#x62E;&#x637;&#x627;&#x621; &#x641;&#x64A; &#x62A;&#x637;&#x628;&#x64A;&#x642;&#x627;&#x62A; &#x641;&#x644;&#x627;&#x633;&#x643; Flask</title><link>https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A3%D8%AE%D8%B7%D8%A7%D8%A1-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1738/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_10/633d24d052a35_------.jpg.6a93de1f7c14e5a9a978a8b76c76cea6.jpg" /></p>

<p>
	يُعد <a href="https://academy.hsoub.com/programming/python/flask/" rel="">فلاسك</a> إطار عمل للويب مبني بلغة <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D9%85%D8%B1%D8%AC%D8%B9-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%AA%D8%B9%D9%84%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r735/" rel="">بايثون</a>، ويتميز بكونه صغير الحجم وسهل المعالجة، ويوفّر أيضًا عدة أدوات وميزات من شأنها إنشاء تطبيقات ويب في <a href="https://academy.hsoub.com/programming/python/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r211/" rel="">لغة بايثون</a>.
</p>

<p>
	ستواجه خلال تطوير تطبيقات الويب حتمًا حالاتٍ يعطي فيها تطبيقك نتائجًا مخالفةً لتوقعاتك، وذلك بسبب أخطاء من قبيل الكتابة الخاطئة لاسم أحد المتغيرات، أو الاستخدام الخاطئ لحلقة "for" التكرارية أو بناء عبارة "if" الشرطية بطريقة خاطئة مسبّبةً وقوع أحد استثناءات بايثون، كما في حال استدعاء دالة ما قبل التصريح عنها، أو البحث عن صفحة غير موجودة أصلًا، فممّا لا شكّ فيه أنّك ستجد تطوير تطبيقات الويب باستخدام فلاسك أسهل وأكثر مرونة حال تعلمّك لكيفية التعامل مع الأخطاء والاستثناءات.
</p>

<p>
	سنبني في هذا المقال تطبيق ويب مُصغّر بهدف تبيان كيفيّة التعامل مع الأخطاء الشائعة التي قد تواجهك أثناء تطوير تطبيقات الويب، كما سننشئ صفحات أخطاء مُخصّصة، وسنستخدم منقّح الأخطاء في فلاسك لاستكشاف الاستثناءات في حال حدوثها وإصلاحها، كما سنستخدم سجل الأحداث logging لتتبّع الأحداث في التطبيق بحثًا عن مصادر الأخطاء.
</p>

<h2>
	مستلزمات العمل
</h2>

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

<ul>
<li>
		توفُّر <a href="https://academy.hsoub.com/programming/python/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%AA%D9%87%D8%A7-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r714/" rel="">بيئة</a><a href="https://academy.hsoub.com/programming/python/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%AA%D9%87%D8%A7-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r714/" rel=""> برمجة بايثون 3 محلية</a>، مثبّتة على حاسوبك، وسنفترض في مقالنا أن اسم مجلد المشروع هو "flask_app".
	</li>
	<li>
		الفهم الجيد لأساسيات فلاسك، مثل مفهوم الوجهات routes ودوال العرض view في فلاسك، وفي هذا الصدد يمكنك الاطلاع على المقال كيفية بناء موقعك الإلكتروني الأول باستخدام إطار عمل فلاسك Flask من لغة بايثون لفهم مبادئ فلاسك.
	</li>
	<li>
		فهم أساسيات لغة <a href="https://academy.hsoub.com/programming/html/" rel="">HTML</a>.
	</li>
</ul>
<h2>
	الخطوة الأولى - استخدام منقح الأخطاء في فلاسك
</h2>

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

<p>
	لذلك، وبعد التأكّد من تفعيل البيئة البرمجية وتثبيت فلاسك، سننشئ ملفًا ضمن المجلد "flask_app" باسم "app.py" لتحريره:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_7" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_9" style="">
<span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pln">

app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln">


</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> index</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'index.html'</span><span class="pun">)</span></pre>

<p>
	استوردنا في الجزء السابق من الشيفرة كائن فلاسك من حزمة falsk، ثم استخدمناه لإنشاء نسخةٍ فعليةٍ موجودةٍ في الذاكرة لتطبيق فلاسك Flask application instance باسم <code>app</code>، ثمّ أنشأنا دالة العرض <code>()index</code> باستخدام المُزخرف <code>()app.route@</code>، الذي يحوّل دوال بايثون الاعتيادية إلى دوال عرض، وفيها تكون القيمة المُعادة استدعاءً للدالة <code>()render_template</code>، والتي تعرض بدورها قالب HTML المُسمى "index.html" في النتيحة. في هذه الشيفرة خطآن: الأوّل هو أنّنا لم نستورد دالة تصيّير القوالب <code>()render_template</code> من حزمة فلاسك قبل استدعائها، والثاني أنّ قالب HTML المسمّى "index.html" غير موجود بعد.
</p>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	والآن، لا بُدّ من إرشاد فلاسك إلى موقع التطبيق (في حالتنا الملف ذو الاسم app.py) وذلك باستخدام متغير بيئة فلاسك FLASK_APP على النحو التالي (مع ملاحظة أنّنا نستخدم الأمر <code>set</code> في بيئة ويندوز عوضًا عن الأمر <code>export</code>):
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_11" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_APP</span><span class="pun">=</span><span class="pln">app</span></pre>

<p>
	بعدها سنشغل خادم التطبيق باستخدام الأمر <code>flask run</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_13" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ flask run</span></pre>

<p>
	فتظهر في نافذة الطرفية المعلومات التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_16" style="">
<span class="pun">*</span><span class="pln"> </span><span class="typ">Serving</span><span class="pln"> </span><span class="typ">Flask</span><span class="pln"> app </span><span class="str">'app'</span><span class="pln"> </span><span class="pun">(</span><span class="pln">lazy loading</span><span class="pun">)</span><span class="pln">
 </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Environment</span><span class="pun">:</span><span class="pln"> production
   WARNING</span><span class="pun">:</span><span class="pln"> </span><span class="typ">This</span><span class="pln"> </span><span class="kwd">is</span><span class="pln"> a development server</span><span class="pun">.</span><span class="pln"> </span><span class="typ">Do</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> use it </span><span class="kwd">in</span><span class="pln"> a production deployment</span><span class="pun">.</span><span class="pln">
   </span><span class="typ">Use</span><span class="pln"> a production WSGI server instead</span><span class="pun">.</span><span class="pln">
 </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Debug</span><span class="pln"> mode</span><span class="pun">:</span><span class="pln"> off
 </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Running</span><span class="pln"> on http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Press</span><span class="pln"> CTRL</span><span class="pun">+</span><span class="pln">C to quit</span><span class="pun">)</span></pre>

<p>
	يحتوي الخرج السابق على عدة معلومات، مثل:
</p>

<ul>
<li>
		اسم التطبيق المُشغَّل، وهو "app.py" في حالتنا.
	</li>
	<li>
		بيئة التشغيل الحالية التي يعمل عليها التطبيق، وهي في حالتنا بيئة الاستخدام الفعلي للتطبيق (نشر المنتج) "production"، وتُشدّد الرسالة التحذيرية هذه على كون <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">الخادم</a> غير مُخصّص لمرحلة نشر المنتج، وبما أنّنا نستخدمه بالواقع بغية تطوير التطبيق، فمن الممكن تجاهل الرسالة التحذيرية هذه.
	</li>
	<li>
		عبارة "Debug mode:off"، التي تشير إلى أن منقّح أخطاء فلاسك ليس قيد التشغيل، وبالتالي لن تتلقّى أي رسائل مفيدة حول الأخطاء الحاصلة في التطبيق، وهو أمرٌ طبيعي كوننا نعمل الآن في بيئة نشر المُنتج، فعرض رسائل تفصيلية بالأخطاء الحاصلة في هذه البيئة (نشر المنتج) يعرّض التطبيق لمخاطر أمنية ويعدّ ثغرة أمنية بحد ذاته.
	</li>
	<li>
		التطبيق يعمل على الرابط "/http://127.0.0.1:5000"، إذ أن "127.0.0.1" هو عنوان IP الذي يمثِّل الخادم المحلي localhost، و "5000:" هو رقم المنفذ، ولإيقاف تشغيل الخادم يمكنك الضغط على مفتاحي "CTRL+C"، ولكن لا توقفه الآن.
	</li>
</ul>
<p>
	الآن، وبالانتقال إلى الصفحة الرئيسية للتطبيق في المتصفّح عبر الرابط:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_21" style="">
<span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span></pre>

<p>
	ستظهر رسالة شبيهة بما يلي في الخرج:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_23" style="">
<span class="typ">Internal</span><span class="pln"> </span><span class="typ">Server</span><span class="pln"> </span><span class="typ">Error</span><span class="pln">

</span><span class="typ">The</span><span class="pln"> server encountered an internal error </span><span class="kwd">and</span><span class="pln"> was unable to complete your request</span><span class="pun">.</span><span class="pln"> </span><span class="typ">Either</span><span class="pln"> the server </span><span class="kwd">is</span><span class="pln"> overloaded </span><span class="kwd">or</span><span class="pln"> there </span><span class="kwd">is</span><span class="pln"> an error </span><span class="kwd">in</span><span class="pln"> the application</span><span class="pun">.</span></pre>

<p>
	وهو <a href="https://www.google.com/url?q=https://academy.hsoub.com/apps/web/wordpress/%25D9%2583%25D9%258A%25D9%2581%25D9%258A%25D8%25A9-%25D8%25A5%25D8%25B5%25D9%2584%25D8%25A7%25D8%25AD-%25D8%25AE%25D8%25B7%25D8%25A3-%25D8%25A7%25D9%2584%25D8%25AE%25D8%25A7%25D8%25AF%25D9%2585-%25D8%25A7%25D9%2584%25D8%25AF%25D8%25A7%25D8%25AE%25D9%2584%25D9%258A-500-internal-server-error-%25D9%2581%25D9%258A-%25D9%2585%25D9%2588%25D8%25A7%25D9%2582%25D8%25B9-%25D9%2588%25D9%2588%25D8%25B1%25D8%25AF%25D8%25A8%25D8%25B1%25D9%258A%25D8%25B3-r564/&amp;sa=D&amp;source=docs&amp;ust=1655303428657370&amp;usg=AOvVaw0vFYObCloN_0acVGFeMVJB" rel="external nofollow">خطأ الخادم الداخلي</a> 500 Internal Server Error، الذي يمثّل استجابةً خاطئةً للخادم تشير لكونه يواجه خطأً داخليًا في شيفرة التطبيق.
</p>

<p>
	وسيظهر الخرج التالي في نافذة الطرفية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_25" style="">
<span class="pun">[</span><span class="lit">2021</span><span class="pun">-</span><span class="lit">09</span><span class="pun">-</span><span class="lit">12</span><span class="pln"> </span><span class="lit">15</span><span class="pun">:</span><span class="lit">16</span><span class="pun">:</span><span class="lit">56</span><span class="pun">,</span><span class="lit">441</span><span class="pun">]</span><span class="pln"> ERROR </span><span class="kwd">in</span><span class="pln"> app</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Exception</span><span class="pln"> on </span><span class="pun">/</span><span class="pln"> </span><span class="pun">[</span><span class="pln">GET</span><span class="pun">]</span><span class="pln">
</span><span class="typ">Traceback</span><span class="pln"> </span><span class="pun">(</span><span class="pln">most recent call last</span><span class="pun">):</span><span class="pln">
  </span><span class="typ">File</span><span class="pln"> </span><span class="str">"/home/abd/.local/lib/python3.9/site-packages/flask/app.py"</span><span class="pun">,</span><span class="pln"> line </span><span class="lit">2070</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> wsgi_app
    response </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">full_dispatch_request</span><span class="pun">()</span><span class="pln">
  </span><span class="typ">File</span><span class="pln"> </span><span class="str">"/home/abd/.local/lib/python3.9/site-packages/flask/app.py"</span><span class="pun">,</span><span class="pln"> line </span><span class="lit">1515</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> full_dispatch_request
    rv </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">handle_user_exception</span><span class="pun">(</span><span class="pln">e</span><span class="pun">)</span><span class="pln">
  </span><span class="typ">File</span><span class="pln"> </span><span class="str">"/home/abd/.local/lib/python3.9/site-packages/flask/app.py"</span><span class="pun">,</span><span class="pln"> line </span><span class="lit">1513</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> full_dispatch_request
    rv </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">dispatch_request</span><span class="pun">()</span><span class="pln">
  </span><span class="typ">File</span><span class="pln"> </span><span class="str">"/home/abd/.local/lib/python3.9/site-packages/flask/app.py"</span><span class="pun">,</span><span class="pln"> line </span><span class="lit">1499</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> dispatch_request
    </span><span class="kwd">return</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">ensure_sync</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">view_functions</span><span class="pun">[</span><span class="pln">rule</span><span class="pun">.</span><span class="pln">endpoint</span><span class="pun">])(**</span><span class="pln">req</span><span class="pun">.</span><span class="pln">view_args</span><span class="pun">)</span><span class="pln">
  </span><span class="typ">File</span><span class="pln"> </span><span class="str">"/home/abd/python/flask/series03/flask_app/app.py"</span><span class="pun">,</span><span class="pln"> line </span><span class="lit">8</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> index
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'index.html'</span><span class="pun">)</span><span class="pln">
</span><span class="typ">NameError</span><span class="pun">:</span><span class="pln"> name </span><span class="str">'render_template'</span><span class="pln"> </span><span class="kwd">is</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> defined
</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">[</span><span class="lit">12</span><span class="pun">/</span><span class="typ">Sep</span><span class="pun">/</span><span class="lit">2021</span><span class="pln"> </span><span class="lit">15</span><span class="pun">:</span><span class="lit">16</span><span class="pun">:</span><span class="lit">56</span><span class="pun">]</span><span class="pln"> </span><span class="str">"GET / HTTP/1.1"</span><span class="pln"> </span><span class="lit">500</span><span class="pln"> </span><span class="pun">-</span></pre>

<p>
	تمرُ عملية التعقّب العكسي أعلاه على الشيفرة المُسبّبة لخطأ الخادم الداخلي، إذ يشير السطر "NameError: name 'render_template' is not defined" إلى أصل المشكلة (خطأ في الاسم)، وهو في حالتنا أنّ الدالة <code>()render_template</code> غير مستوردة ما جعلها غير مُعرّفة.
</p>

<p>
	وهنا نلاحظ أنّه لا بدّ من الذهاب إلى نافذة الطرفية لاكتشاف الأخطاء (وليس ضمن خرج التطبيق نفسه)، والذي يُعد أمرًا غير ملائم في الاستخدام الفعلي.
</p>

<p>
	فمن الممكن الحصول على تجربة استكشاف أخطاء وإصلاحها بطريقة أفضل وأسهل عبر تمكين وضع تنقيح الأخطاء في خادم التطوير، ولإجراء ذلك سنوقف عمل الخادم عبر الضغط على مفتاحي "CTRL+C"، ثمّ سنضبط متغير بيئة فلاسك <code>FLASK_ENV</code> على الوضع <code>development</code>، ما يمكنّنا من تشغيل البرنامج في وضع التطوير ( الذي يُمكِّن منقّح الأخطاء) كما يلي (مع ملاحظة أنّنا نستخدم الأمر <code>set</code> في بيئة ويندوز عوضًا عن الأمر <code>export</code>):
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_27" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_ENV</span><span class="pun">=</span><span class="pln">development</span></pre>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_29" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ flask run</span></pre>

<p>
	فيظهر في نافذة الطرفية خرجٌ مُشابه لما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_31" style="">
<span class="pun">*</span><span class="pln"> </span><span class="typ">Serving</span><span class="pln"> </span><span class="typ">Flask</span><span class="pln"> app </span><span class="str">'app'</span><span class="pln"> </span><span class="pun">(</span><span class="pln">lazy loading</span><span class="pun">)</span><span class="pln">
 </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Environment</span><span class="pun">:</span><span class="pln"> development
 </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Debug</span><span class="pln"> mode</span><span class="pun">:</span><span class="pln"> on
 </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Running</span><span class="pln"> on http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Press</span><span class="pln"> CTRL</span><span class="pun">+</span><span class="pln">C to quit</span><span class="pun">)</span><span class="pln">
 </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Restarting</span><span class="pln"> </span><span class="kwd">with</span><span class="pln"> stat
 </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Debugger</span><span class="pln"> </span><span class="kwd">is</span><span class="pln"> active</span><span class="pun">!</span><span class="pln">
 </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Debugger</span><span class="pln"> PIN</span><span class="pun">:</span><span class="pln"> </span><span class="lit">120</span><span class="pun">-</span><span class="lit">484</span><span class="pun">-</span><span class="lit">907</span></pre>

<p>
	ومنه نلاحظ أنّ وضع التشغيل حاليًا هو وضع التطوير، وأنّ وضع تنقيح الأخطاء قيد التشغيل وبالتالي فإنّ مُنقّح الأخطاء مُفعّل، أمّا "Debugger PIN" فيمثّل الرمز السرّي اللازم لإلغاء قفل سجل المتصفّح (لوحة المراقبة) Browser console (وهي صَدفة shell بايثون تفاعلية يمكنك الوصول إليها بالضغط على رمز الطرفية الصغير المحاط بدائرة حمراء في الصورة أدناه).
</p>

<p>
	وبتحديث الصفحة الرئيسية للتطبيق ضمن المتصفح، ستظهر الصفحة بالشّكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="109081" href="https://academy.hsoub.com/uploads/monthly_2022_10/1st.png.aee7f9d7e846075df45b0a791e7e65a4.png" rel=""><img alt="نتائج تحديث الصفحة الرئيسية للتطبيق" class="ipsImage ipsImage_thumbnailed" data-fileid="109081" data-unique="5ljyk0kuk" src="https://academy.hsoub.com/uploads/monthly_2022_10/1st.thumb.png.27fb22820987e794cc366b8b661c4a2d.png" style="width: 800px; height: auto;"></a>
</p>

<p>
	وفي هذه الحالة نرى رسالة الخطأ بصياغة وطريقة عرض أسهل للفهم، إذ يشير العنوان الأوّل فيها إلى اسم استثناء بايثون المُتسبّب بالمشكلة (وهو خطأ في الاسم "NameError" في حالتنا).
</p>

<p>
	أما السطر الثاني من رسالة الخطأ فيشير إلى سبب الخطأ المباشر (وهو في حالتنا أنّ الدالة <code>()render_template</code> غير مُعرّفة ما يعني أنّها غير مستوردة أصلًا)، أمّا الأسطر التالية فهي نتيجة التعقّب العكسي لشيفرة فلاسك الداخلية المُنفّذة، ومن المُفضّل قراءة هذا الجزء من الأسفل إلى الأعلى، إذ يتضمّن عادةً السطر الأخير من نتيجة التعقّب العكسي أكثر المعلومات أهمية.
</p>

<p>
	<strong>ملاحظة:</strong> يمكنّك رمز الطرفية الصغير المحاط بدائرة حمراء في الصورة السابقة من تشغيل الشيفرة المكتوبة أصلًا بلغة بايثون ضمن المُتصفّح في أُطر عمل مُختلفة (وهنا ضمن ما يشبه صَدفة بايثون التفاعلية ولكن ضمن المتصفح نفسه)، وهذا أمرٌ مفيدٌ للتحقّق مثلًا من قيمة متغير ما بنفس الطريقة التي تؤديها صَدفة بايثون التفاعلية. عند الضغط على هذه الأيقونة يجب إدخال الرمز السري الخاص بالمنقّح Debugger PIN الذي حصلت عليه عند تشغيل الخادم في الخطوة السابقة، ولكن ضمن محاور هذا المقال لن نحتاج إلى استخدامها.
</p>

<p>
	الآن، لإصلاح الخطأ في الاسم "NameError" الحاصل، نترك الخادم بحالة تشغيل ونفتح نافذة طرفية جديدة، وفيها نفعّل البيئة البرمجية ومن ثمّ نفتح الملف app.py لتحريره:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_35" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ونعدله ليصبح كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_37" style="">
<span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> render_template

app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln">


</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> index</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'index.html'</span><span class="pun">)</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	استوردنا في الشيفرة السابقة دالة تصيّير القوالب <code>()render_template</code> من حزمة فلاسك <code>flask</code>، الأمر الذي كان ناقصًا فيما سبق مُتسبّبًا بالخطأ الحاصل.
</p>

<p>
	الآن وبعد التأكّد من كون خادم التطوير ما يزال قيد التشغيل، نحدّث الصفحة الرئيسية للتطبيق ضمن المتصفح، فتظهر صفحة خطأ في الخرج، تتضمّن معلومات شبيهة بما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_39" style="">
<span class="pln">jinja2</span><span class="pun">.</span><span class="pln">exceptions</span><span class="pun">.</span><span class="typ">TemplateNotFound</span><span class="pln">
jinja2</span><span class="pun">.</span><span class="pln">exceptions</span><span class="pun">.</span><span class="typ">TemplateNotFound</span><span class="pun">:</span><span class="pln"> index</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	وتشير رسالة الخطأ هذه لكون القالب "index.html" غير موجود.
</p>

<p>
	ولإصلاح هذا الخطأ، سننشئ ملف قالب باسم "base.html" لتتمكّن القوالب الأُخرى من وراثة شيفراته بغية تجنُّب تكرار الشيفرات، كما سننشئ القالب "index.html"، الذي سيرث من القالب الرئيسي.
</p>

<p>
	لذا سننشئ مجلد للقوالب باسم "templates" ضمن المجلد "flask_app"، إذ سيبحث فلاسك ضمنه عن القوالب، وبعدها سنفتح ملف القالب الأساسي المُسمى "base.html" باستخدام أي محرّر نصوص (هنا سنستخدم محرّر النصوص نانو nano:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_41" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ mkdir templates
</span><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">base</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	ثمّ نكتب الشيفرة التالية ضمن الملف base.html:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_3933_43" style="">
<span class="dec">&lt;!DOCTYPE html&gt;</span><span class="pln">
</span><span class="tag">&lt;html</span><span class="pln"> </span><span class="atn">lang</span><span class="pun">=</span><span class="atv">"en"</span><span class="tag">&gt;</span><span class="pln">
</span><span class="tag">&lt;head&gt;</span><span class="pln">
    </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">"UTF-8"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;title&gt;</span><span class="pln">{% block title %} {% endblock %} - FlaskApp</span><span class="tag">&lt;/title&gt;</span><span class="pln">
    </span><span class="tag">&lt;style&gt;</span><span class="pln">
        nav a </span><span class="pun">{</span><span class="pln">
            color</span><span class="pun">:</span><span class="pln"> </span><span class="com">#d64161;</span><span class="pln">
            font</span><span class="pun">-</span><span class="pln">size</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3em</span><span class="pun">;</span><span class="pln">
            margin</span><span class="pun">-</span><span class="pln">left</span><span class="pun">:</span><span class="pln"> </span><span class="lit">50px</span><span class="pun">;</span><span class="pln">
            text</span><span class="pun">-</span><span class="pln">decoration</span><span class="pun">:</span><span class="pln"> none</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="tag">&lt;/style&gt;</span><span class="pln">
</span><span class="tag">&lt;/head&gt;</span><span class="pln">
</span><span class="tag">&lt;body&gt;</span><span class="pln">
    </span><span class="tag">&lt;nav&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('index') }}"</span><span class="tag">&gt;</span><span class="pln">FlaskApp</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"#"</span><span class="tag">&gt;</span><span class="pln">About</span><span class="tag">&lt;/a&gt;</span><span class="pln">
    </span><span class="tag">&lt;/nav&gt;</span><span class="pln">
    </span><span class="tag">&lt;hr&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"content"</span><span class="tag">&gt;</span><span class="pln">
        {% block content %} {% endblock %}
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
</span><span class="tag">&lt;/body&gt;</span><span class="pln">
</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	يتضمّن القالب الأساسي كافّة الشيفرات المتداولة التي ستحتاجها في القوالب الأُخرى، وستُستبدل لاحقًا كتلة العنوان <code>title</code> بعنوان كل صفحة وكتلة المحتوى <code>content</code> بمحتواها، أمّا عن شريط التصفح فسيتضمّن رابطين، الأوّل ينقل المُستخدم إلى الصفحة الرئيسية للتطبيق باستخدام الدالة المساعدة <code>()url_for</code> لتحقيق الربط مع دالة العرض <code>()index</code>، والآخر لصفحة المعلومات حول التطبيق في حال قررت تضمينها في تطبيقك.
</p>

<p>
	لذا سنفتح ملف قالب index.html، الذي سيرث شيفراته من القالب الرئيسي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_45" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">index</span><span class="pun">.</span><span class="pln">html</span></pre>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_47" style="">
<span class="pun">{%</span><span class="pln"> extends </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">

</span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">h1</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="typ">Index</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">h2</span><span class="pun">&gt;</span><span class="typ">Welcome</span><span class="pln"> to </span><span class="typ">FlaskApp</span><span class="pun">!&lt;/</span><span class="pln">h2</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	ثمّ نحفظ الملف ونغلقه.
</p>

<p>
	وسّعنا في الشيفرة السابقة ملف القالب "base.html"، متجاوزين كتلة المحتوى <code>content</code>، ثمّ استخدمنا كتلة العنوان <code>title</code> لتعيين عنوان للصفحة وعرضه ضمن تنسيق عنوان من المستوى الأوّل <code>H1</code>، كما كتبنا الشيفرة اللازمة لعرض رسالة ترحيب ضمن تنسيق عنوان من المستوى الثاني <code>H2</code>.
</p>

<p>
	الآن وبعد التأكّد من كون خادم التطوير ما يزال قيد التشغيل، نحدّث الصفحة الرئيسية للتطبيق ضمن المتصفح، فنجد أنّ التطبيق لا يعرض أي أخطاء وأنّ الصفحة الرئيسية للتطبيق تبدو بالشكل المطلوب.
</p>

<p>
	ومع نهاية هذه الخطوة نكون قد استخدمنا وضع تنقيح الأخطاء وتعرفنا على كيفية التعامل مع رسائل الخطأ، وسنعمل في الخطوة التالية على إيقاف طلب ما ليستجيب برسالة خطأ من اختيارنا، وسنتعلّم كيفية إنشاء صفحات أخطاء مُخصّصة لتظهر بمثابة استجابة لطلبات معينة.
</p>

<h2>
	الخطوة الثانية - تخصيص صفحات أخطاء
</h2>

<p>
	سنتعرّف في هذه الخطوة على كيفية إلغاء طلب المستخدم في حال أراد الوصول إلى بيانات غير موجودة على الخادم والاستجابة برسالة <a href="https://academy.hsoub.com/devops/servers/%d9%83%d9%8a%d9%81%d9%8a%d8%a9-%d8%a7%d8%b3%d8%aa%d9%83%d8%b4%d8%a7%d9%81-%d9%88%d8%a5%d8%b5%d9%84%d8%a7%d8%ad-%d8%b1%d9%85%d9%88%d8%b2-%d8%a3%d8%ae%d8%b7%d8%a7%d8%a1-http-%d8%a7%d9%84%d8%b4%d8%a7%d8%a6%d8%b9%d8%a9-r116/" rel="">خطأ HTTP</a> من النوع 404، كما سنتعلّم كيفية إنشاء صفحات أخطاء مُخصّصة لتظهر بمثابة استجابة لأخطاء HTTP الشائعة، مثل خطأ الخادم الداخلي "‎500 Internal Server Error" وخطأ عدم توفّر البيانات المطلوبة "‎404 Not Found".
</p>

<p>
	ولنبيّن كيفية إلغاء طلب والاستجابة بصفحة خطأ مُخصّصة بخطأ HTTP من نوع 404، سننشئ صفحةً مهمتها عرض عدد من الرسائل، وفي حال طلب رسالة غير موجودة، ستستجيب بخطأ من النوع 404.
</p>

<p>
	لذا سنفتح الملف app.py لإضافة وجهةٍ route جديدة لصفحة الرسائل:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_50" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	وسنضيف الوجهة التالية إلى نهاية الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_52" style="">
<span class="com"># ...</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/messages/&lt;int:idx&gt;'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> message</span><span class="pun">(</span><span class="pln">idx</span><span class="pun">):</span><span class="pln">
    messages </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">'Message Zero'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Message One'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Message Two'</span><span class="pun">]</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'message.html'</span><span class="pun">,</span><span class="pln"> message</span><span class="pun">=</span><span class="pln">messages</span><span class="pun">[</span><span class="pln">idx</span><span class="pun">])</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	استخدمنا في الشيفرة السابقة متغيرًا يدل على رقم مؤشّر الروابط باسم <code>idx</code>، والمُتضمّن رقم المؤشر الذي من شأنه تحديد الرسالة المطلوب عرضها، بمعنى أنّه إذا كان الرابط هو "‎/messages/0"، فهذا يعني أنّ الرسالة الأولى ذات المؤشّر بالقيمة صفر هي المطلوب عرضها؛ كما استخدمنا محوّل القيم <code>int</code> المسؤول عن قبول قيم من النوع "عدد صحيح موجب" فقط، ذلك لأنّ متحولات العناوين تكون افتراضيًا بنمط بيانات من النوع "سلاسل نصية".
</p>

<p>
	تتضمّن دالة العرض <code>message()‎</code> قائمة بايثون اعتيادية باسم "messages" تحتوي على ثلاث رسائل (ولكن في الواقع العملي سيكون مصدر هذه الرسائل هو قاعدة البيانات، أو واجهة <abbr title="Application Programming Interface | واجهة برمجية">API</abbr>، أو أي مصدر خارجي للبيانات، وليست مخزّنة يدويًا كما هو الحال في مثالنا التوضيحي هذا)، وفيها تكون القيمة المُعادة استدعاءً للدالة <code>()render_template</code> مع وسيطين، هما: الملف "message.html" وسيطًا لملف القالب، والمتغير <code>message</code> الذي سيُمرّر بدوره إلى القالب، إذ تكون قيمة هذا المتغير هي أحد عناصر القائمة "messages" (أي إحدى الرسائل) وذلك تبعًا لقيمة المتغيّر <code>idx</code> الدال على رقم مؤشّر الرسالة المطلوبة والموجود ضمن الرابط.
</p>

<p>
	ومن ثمّ سننشئ ملف القالب "message.html":
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_54" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">message</span><span class="pun">.</span><span class="pln">html</span></pre>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_61" style="">
<span class="pun">{%</span><span class="pln"> extends </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">

</span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">h1</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="typ">Messages</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">h2</span><span class="pun">&gt;{{</span><span class="pln"> message </span><span class="pun">}}&lt;/</span><span class="pln">h2</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	وسّعنا في الشيفرة السابقة ملف القالب "base.html"، متجاوزين كتلة المحتوى <code>content</code>، ثمّ أضفنا عنوانًا وهو "Messages" ضمن تنسيق عنوان من المستوى الأوّل <code>H1</code>، كما عرضنا قيمة المتغير <code>message</code> المُتضمّن الرسالة المطلوبة حسب الرابط ضمن تنسيق عنوان من المستوى الثاني <code>H2</code>.
</p>

<p>
	الآن، ويعد التأكّد من كون خادم التطوير ما يزال قيد التشغيل، سنزور الروابط التالية تباعًا في المتصفح:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_63" style="">
<span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln">messages</span><span class="pun">/</span><span class="lit">0</span><span class="pln">
http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln">messages</span><span class="pun">/</span><span class="lit">1</span><span class="pln">
http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln">messages</span><span class="pun">/</span><span class="lit">2</span><span class="pln">
http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln">messages</span><span class="pun">/</span><span class="lit">3</span></pre>

<p>
	ولدى زيارة الروابط الثلاث الأولى، ستظهر النصوص "Message Zero" و "Message One" و "Message Two" على الترتيب، ضمن تنسيق عنوان من المستوى الثاني <code>H2</code>، في حين سيستجيب الخادم برسالة الخطأ التفصيليّة "IndexError: list index out of range" لدى زيارة الرابط الرابع (والتي تعني أنّ رقم مؤشّر الرسالة المطلوبة خارج نطاق الرسائل المتوفرّة)، لأنّنا في وضع التطوير؛ ولو كنا نعمل في بيئة نشر المنتج (التشغيل الفعلي للتطبيق) لظهر خطأ الخادم الداخلي "‎500 Internal Server Error"، إلّا أنّ الاستجابة الأنسب لمثل هذه الحالة بغية الإيضاح للمُستخدم ماهية الخطأ الحاصل هي الاستجابة بخطأ "‎404 Not Found" دلالةً على أنّ الخادم غير قادر على إيجاد رسالة برقم مؤشّر مساوٍ للرقم "3".
</p>

<p>
	من الممكن استخدام دالة فلاسك المساعدة <code>()abort</code> للاستجابة برسالة خطأ من النوع "404"، ولتطبيق هذا الأمر، سنفتح الملف app.py:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_65" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	وفيه سنعدّل السطر الأوّل لنستورد دالة <code>()abort</code>، ثمّ سنعدّل دالة العرض <code>()message</code> بإضافة البنية "try … except" كما هو موضّح في الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_67" style="">
<span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> render_template</span><span class="pun">,</span><span class="pln"> abort

</span><span class="com"># ...</span><span class="pln">
</span><span class="com"># ...</span><span class="pln">


</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/messages/&lt;int:idx&gt;'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> message</span><span class="pun">(</span><span class="pln">idx</span><span class="pun">):</span><span class="pln">
    messages </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">'Message Zero'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Message One'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Message Two'</span><span class="pun">]</span><span class="pln">
    </span><span class="kwd">try</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'message.html'</span><span class="pun">,</span><span class="pln"> message</span><span class="pun">=</span><span class="pln">messages</span><span class="pun">[</span><span class="pln">idx</span><span class="pun">])</span><span class="pln">
    </span><span class="kwd">except</span><span class="pln"> </span><span class="typ">IndexError</span><span class="pun">:</span><span class="pln">
        abort</span><span class="pun">(</span><span class="lit">404</span><span class="pun">)</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	استوردنا في الشيفرة السابقة دالة <code>()abort</code> المُستخدمة في إلغاء طلب ما والاستجابة برسالة خطأ مُحدّدة، كما استخدمنا البنية "try … except" في الدالة <code>()message</code> بغية تغليفها، إذ نحاول بدايةً الاستجابة لطلب المستخدم بالقالب <code>messages</code> المتضمّن الرسالة المطلوبة وفقًا لرقم المؤشّر المطلوب في الرابط، وفي حال عدم توفّر رسالة برقم مؤشّر موافق لذلك المطلوب، سيتحقّق الاستثناء <code>except</code> ونستخدمه لاكتشاف الخطأ مستخدمين استدعاء الدالة <code>(abort(404</code> لإلغاء هذا الطلب الخاطئ والاستجابة له بخطأ HTTP من النوع 404 "‎404 Not Found".
</p>

<p>
	الآن ويعد التأكّد من كون خادم التطوير ما يزال قيد التشغيل، وباستخدام المتصفح سنزور الرابط الذي تسبّب بظهور رسالة الخطأ <code>IndexError</code> في المرة الماضية (أو أي رايط برقم مؤشّر أكبر من الرقم "2"):
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_69" style="">
<span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln">messages</span><span class="pun">/</span><span class="lit">3</span></pre>

<p>
	فتظهر لنا الاستجابة التالية في النتيجة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_71" style="">
<span class="typ">Not</span><span class="pln"> </span><span class="typ">Found</span><span class="pln">

</span><span class="typ">The</span><span class="pln"> requested URL was </span><span class="kwd">not</span><span class="pln"> found on the server</span><span class="pun">.</span><span class="pln"> </span><span class="typ">If</span><span class="pln"> you entered the URL manually please check your spelling </span><span class="kwd">and</span><span class="pln"> </span><span class="kwd">try</span><span class="pln"> again</span><span class="pun">.</span></pre>

<p>
	وبذلك أصبح لدينا رسالة خطأ أفضل وأوضح تشير إلى عدم تمكُّن الخادم من إيجاد الرسالة المطلوبة.
</p>

<p>
	أمّا فيما يلي، فسنعمل على إنشاء قالب خاص بعرض صفحة بالخطأ من النوع 404 وآخر لعرض صفحة بالخطأ من النوع 500.
</p>

<p>
	بدايةً سنعرّف دالة باستخدام المُزخرف الخاص <code>()app.errorhandler</code> مهمتها التعامل مع الخطأ من النوع 404، لذا سنفتح الملف "app.py" لتحريره:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_73" style="">
<span class="pln">nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ونعدّل الملف ليصبح كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3933_75" style="">
<span class="pln">from flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> render_template</span><span class="pun">,</span><span class="pln"> abort

app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln">


</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">errorhandler</span><span class="pun">(</span><span class="lit">404</span><span class="pun">)</span><span class="pln">
def page_not_found</span><span class="pun">(</span><span class="pln">error</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'404.html'</span><span class="pun">),</span><span class="pln"> </span><span class="lit">404</span><span class="pln">


</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">)</span><span class="pln">
def index</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'index.html'</span><span class="pun">)</span><span class="pln">


</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/messages/&lt;int:idx&gt;'</span><span class="pun">)</span><span class="pln">
def message</span><span class="pun">(</span><span class="pln">idx</span><span class="pun">):</span><span class="pln">
    messages </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">'Message Zero'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Message One'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Message Two'</span><span class="pun">]</span><span class="pln">
    </span><span class="kwd">try</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'message.html'</span><span class="pun">,</span><span class="pln"> message</span><span class="pun">=</span><span class="pln">messages</span><span class="pun">[</span><span class="pln">idx</span><span class="pun">])</span><span class="pln">
    except </span><span class="typ">IndexError</span><span class="pun">:</span><span class="pln">
        abort</span><span class="pun">(</span><span class="lit">404</span><span class="pun">)</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	استخدمنا في الشيفرة السابقة المُزخرف <code>()app.errorhandler@</code> لتعريف الدالة <code>()page_not_found</code> وتخصيصها للتعامل مع نوع خطأ بحد ذاته، إذ نمرّر لها نوع الخطأ مثل وسيط، وتكون القيمة المُعادة استدعاءً للدالة <code>()render_template</code> مع القالب المُسمّى "404.html" الذي سننشئه لاحقًا وسيطًا لها؛ كما من الممكن تغيير اسم القالب بالاسم الذي نريد، لتكون بدورها القيمة المعادة من الدالة <code>()render_template</code> هي الرقم الصحيح 404، وهذا يدل فلاسك على أنّ رمز الخطأ في الاستجابة يجب أن يكون "404"، وفي حال عدم تحديد أي رقم لدى استدعاء الدالة، فيستجيب فلاسك افتراضيًا بالرقم "200" الدال على نجاح الطلب.
</p>

<p>
	الآن، سنفتح ملف القالب المُسمّى "404.html":
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_77" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="lit">404.html</span></pre>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_79" style="">
<span class="pun">{%</span><span class="pln"> extends </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">

</span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">h1</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="lit">404</span><span class="pln"> </span><span class="typ">Not</span><span class="pln"> </span><span class="typ">Found</span><span class="pun">.</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">OOPS</span><span class="pun">!</span><span class="pln"> </span><span class="typ">Sammy</span><span class="pln"> couldn</span><span class="str">'t find your page; looks like it doesn'</span><span class="pln">t exist</span><span class="pun">.&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="typ">If</span><span class="pln"> you entered the URL manually</span><span class="pun">,</span><span class="pln"> please check your spelling </span><span class="kwd">and</span><span class="pln"> </span><span class="kwd">try</span><span class="pln"> again</span><span class="pun">.&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	في الشيفرة السابقة وكما هو الحال في جميع القوالب، وسّعنا ملف القالب "base.html" واستبدلنا محتويات كتلتي المحتوى <code>content</code> والعنوان <code>title</code>، وأضفنا أي شيفرات HTML نريدها، إذ أضفنا عنوانًا مُنسقًا وفق المستوى الأوّل من العناوين باستخدام الوسم <code>&lt;h1&gt;</code>، كما استخدمنا وسم فقرة <code><a href="https://wiki.hsoub.com/HTML/p" rel="external">&lt;p&gt;</a></code> لإضافة رسالة خطأ مُخصّصة تُعلم المستخدم بكون الصفحة المطلوبة غير متوفرّة، إضافةً لرسالة تفيد أولئك المستخدمين الذين قد أدخلوا الرابط المطلوب يدويًا.
</p>

<p>
	كما من الممكن إضافة أي شيفرات HTML و <a href="https://academy.hsoub.com/programming/css/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84-%D9%84%D8%BA%D8%A9-css-r1049/" rel="">CSS</a> وجافا سكربت <a href="https://academy.hsoub.com/tags/%D8%AF%D9%84%D9%8A%D9%84%20%D8%AA%D8%B9%D9%84%D9%85%20%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA/" rel="">JavaScript</a> نريدها لبناء وتنسيق صفحات الأخطاء، كما هو الحال في أي قالب آخر.
</p>

<p>
	الآن ويعد التأكّد من كون خادم التطوير ما يزال قيد التشغيل، سننتقل مُجدّدًا إلى الرابط:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_82" style="">
<span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln">messages</span><span class="pun">/</span><span class="lit">3</span></pre>

<p>
	فتظهر الصفحة بشريط تصفُّح (الموروث من القالب الرئيسي) وبرسالة الخطأ المُخصّصة التي حددناها، وسنضيف بنفس الآلية صفحة خطأ مُخصّصة لأخطاء الخادم الداخلية "‎500 Internal Server Error"، لذا سنفتح الملف app.py لتحريره:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_84" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	سنضيف الشيفرة المسؤولة عن التعامل مع الخطأ من النوع "500" أسفل تلك المسؤولة عن التعامل مع الخطأ من النوع "404"، كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_86" style="">
<span class="com"># ...</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">errorhandler</span><span class="pun">(</span><span class="lit">404</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> page_not_found</span><span class="pun">(</span><span class="pln">error</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'404.html'</span><span class="pun">),</span><span class="pln"> </span><span class="lit">404</span><span class="pln">


</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">errorhandler</span><span class="pun">(</span><span class="lit">500</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> internal_error</span><span class="pun">(</span><span class="pln">error</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'500.html'</span><span class="pun">),</span><span class="pln"> </span><span class="lit">500</span><span class="pln">

</span><span class="com"># ...</span></pre>

<p>
	اعتمدنا في الشيفرة السابقة نفس الآلية المُتبعة للتعامل مع الخطأ 404، إذ استخدمنا المُزخرف <code>()app.errorhandler</code> مع الرقم "500" مثل وسيط لإنشاء دالّة باسم <code>()internal_error</code> للتعامل مع هذا النوع من الأخطاء. صيّر قالبًا باسم "500.html" ليستجيب برمز الخطأ ذو الرقم "500".
</p>

<p>
	والآن ولبيان كيفيّة عرض رسالة الخطأ المُخصّصة، سنضيف إلى نهاية الملف app.py وجهةً تستجيبُ بخطأ HTTP من النوع "500"، والذي سيعيد خطأ "‎500 Internal Server Error" سواءٌ كان منقّح الأخطاء قيد التشغيل أم لا:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_88" style="">
<span class="com"># ...</span><span class="pln">
</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/500'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> error500</span><span class="pun">():</span><span class="pln">
    abort</span><span class="pun">(</span><span class="lit">500</span><span class="pun">)</span></pre>

<p>
	أنشأنا في جزء الشيفرة السابق الوجهة <code>‎/500</code> واستخدمنا الدالة <code>abort()‎</code> لإنشاء استجابة بخطأ HTTP من النوع "500".
</p>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	ومن ثمّ سننشئ ملف القالب المُسمّى 500.html ضمن مجلد القوالب، كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_90" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="lit">500.html</span></pre>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_92" style="">
<span class="pun">{%</span><span class="pln"> extends </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">

</span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">h1</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="lit">500</span><span class="pln"> </span><span class="typ">Internal</span><span class="pln"> </span><span class="typ">Server</span><span class="pln"> </span><span class="typ">Error</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">OOOOPS</span><span class="pun">!</span><span class="pln"> </span><span class="typ">Something</span><span class="pln"> went wrong on the server</span><span class="pun">.&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="typ">Sammy</span><span class="pln"> </span><span class="kwd">is</span><span class="pln"> currently working on this issue</span><span class="pun">.</span><span class="pln"> </span><span class="typ">Please</span><span class="pln"> </span><span class="kwd">try</span><span class="pln"> again later</span><span class="pun">.&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	ثمّ نحفظ الملف ونغلقه.
</p>

<p>
	اتبعنا في الشيفرة السابقة نفس الآلية المُتبعة في القالب 404.html، إذ وسّعنا ملف القالب base.html، واستبدلنا محتويات كتلة المحتوى <code>content</code> بعنوان ورسالتين مُخصّصتين لإعلام المستخدم حول خطأ الخادم الداخلي الحاصل.
</p>

<p>
	الآن، وبعد التأكّد من كون خادم التطوير ما يزال قيد التشغيل، سننتقل إلى الوجهة الجديدة التي تستجيب بالخطأ من النوع "500" عبر الرابط:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_94" style="">
<span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="lit">500</span></pre>

<p>
	فستظهر صفحتنا المُخصّصة عوضًا عن صفحة الخطأ العامّة التي تظهر افتراضيًا.
</p>

<p>
	ومع نهاية هذه الخطوة نكون قد تعرفنا على كيفية استخدام صفحات أخطاء مُخصّصة لأخطاء HTTP في تطبيقات فلاسك. أمّا في الخطوة التالية فسنتعرّف على كيفية استخدام السجل لتتبّع الأحداث في التطبيق، والذي من شأنه تعزيز فهمنا لآلية عمل شيفرة التطبيق ما يساعد بدوره على تطوير التطبيق واكتشاف الأخطاء وإصلاحها.
</p>

<h2>
	الخطوة الثالثة - استخدام السجل لتتبع أحداث التطبيق
</h2>

<p>
	سنستخدم في هذه الخطوة السجل لتتّبع الأحداث الحاصلة لدى تشغيل الخادم واستخدام التطبيق، ما يساعدنا في معرفة ما يحدث لشيفرة التطبيق أثناء تنفيذها، وهذا سيسهّل بدوره اكتشاف الأخطاء وإصلاحها.
</p>

<p>
	لقد سبق واطلعنا على سجل الأحداث عند كل تشغيل لخادم التطوير، والذي يبدو عادةً كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_96" style="">
<span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">[</span><span class="lit">21</span><span class="pun">/</span><span class="typ">Sep</span><span class="pun">/</span><span class="lit">2021</span><span class="pln"> </span><span class="lit">14</span><span class="pun">:</span><span class="lit">36</span><span class="pun">:</span><span class="lit">45</span><span class="pun">]</span><span class="pln"> </span><span class="str">"GET /messages/1 HTTP/1.1"</span><span class="pln"> </span><span class="lit">200</span><span class="pln"> </span><span class="pun">-</span><span class="pln">
</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">[</span><span class="lit">21</span><span class="pun">/</span><span class="typ">Sep</span><span class="pun">/</span><span class="lit">2021</span><span class="pln"> </span><span class="lit">14</span><span class="pun">:</span><span class="lit">36</span><span class="pun">:</span><span class="lit">52</span><span class="pun">]</span><span class="pln"> </span><span class="str">"GET /messages/2 HTTP/1.1"</span><span class="pln"> </span><span class="lit">200</span><span class="pln"> </span><span class="pun">-</span><span class="pln">
</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">[</span><span class="lit">21</span><span class="pun">/</span><span class="typ">Sep</span><span class="pun">/</span><span class="lit">2021</span><span class="pln"> </span><span class="lit">14</span><span class="pun">:</span><span class="lit">36</span><span class="pun">:</span><span class="lit">54</span><span class="pun">]</span><span class="pln"> </span><span class="str">"GET /messages/3 HTTP/1.1"</span><span class="pln"> </span><span class="lit">404</span><span class="pln"> </span><span class="pun">-</span></pre>

<p>
	إذ يتضمّن سجل الأحداث السابق المعلومات التالية:
</p>

<ul>
<li>
		"127.0.0.1": عنوان المضيف الذي يعمل ضمنه الخادم.
	</li>
	<li>
		"[21/Sep/2021 14:36:45]": وقت وتاريخ الطلب.
	</li>
	<li>
		"GET": نوع طلب HTTP وهو في هذه الحالة من النوع "GET" المُستخدم لجلب البيانات.
	</li>
	<li>
		"messages/2/": المسار الذي طلبه المُستخدم.
	</li>
	<li>
		"HTTP/1.1": إصدار بروتوكول HTTP المُستخدَم.
	</li>
	<li>
		200 أو 404: رمز حالة الطلب.
	</li>
</ul>
<p>
	تساعد سجلات الأحداث هذه في تشخيص الأخطاء الحاصلة في التطبيق، كما من الممكن تسجيل مزيدٍ من المعلومات في سجلات الأحداث باستخدام مُسجّل الأحداث "app.logger" الذي يوفرّه فلاسك، وذلك بغية الحصول على معلومات تفصيلية حول طلباتٍ بحد ذاتها.
</p>

<p>
	يمكننا مع مُسجّل الأحداث هذا استخدام عدة دوال تعيد معلومات حول الأحداث باختلاف مستوياتها، إذ يدل كل مستوًى على حدثٍ ذي درجة معيّنة من الخطورة، ومن هذه الدوال:
</p>

<ul>
<li>
		<code>app.logger.debug()‎</code>: للحصول على معلومات تفصيلية حول الحدث.
	</li>
	<li>
		<code>app.logger.info()‎</code>: للتأكّد من كون الأمور تسير على النحو السليم.
	</li>
	<li>
		<code>app.logger.warning()‎</code>: تشير لوقوع حدث غير متوقّع، مثل انخفاض مساحة القرص الصلب التخزينية، مع بقاء التطبيق يعمل على النحو السليم.
	</li>
	<li>
		<code>app.logger.error()‎</code>: تشير لوقوع خطأ في أحد أجزاء التطبيق.
	</li>
	<li>
		<code>app.logger.critical()‎</code>: يشير لوقوع خطأ حرج يعرّض كامل التطبيق للتوقف عن العمل.
	</li>
</ul>
<p>
	ولبيان كيفيّة استخدام سجّل الأحداث في فلاسك، سنفتح الملف app.py لتحريره بغية تسجيل بعض الأحداث:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_98" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	سنعدّل الدالة <code>()message</code> لتصبح كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_100" style="">
<span class="com"># ...</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/messages/&lt;int:idx&gt;'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> message</span><span class="pun">(</span><span class="pln">idx</span><span class="pun">):</span><span class="pln">
    app</span><span class="pun">.</span><span class="pln">logger</span><span class="pun">.</span><span class="pln">info</span><span class="pun">(</span><span class="str">'Building the messages list...'</span><span class="pun">)</span><span class="pln">
    messages </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">'Message Zero'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Message One'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Message Two'</span><span class="pun">]</span><span class="pln">
    </span><span class="kwd">try</span><span class="pun">:</span><span class="pln">
        app</span><span class="pun">.</span><span class="pln">logger</span><span class="pun">.</span><span class="pln">debug</span><span class="pun">(</span><span class="str">'Get message with index: {}'</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="pln">idx</span><span class="pun">))</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'message.html'</span><span class="pun">,</span><span class="pln"> message</span><span class="pun">=</span><span class="pln">messages</span><span class="pun">[</span><span class="pln">idx</span><span class="pun">])</span><span class="pln">
    </span><span class="kwd">except</span><span class="pln"> </span><span class="typ">IndexError</span><span class="pun">:</span><span class="pln">
        app</span><span class="pun">.</span><span class="pln">logger</span><span class="pun">.</span><span class="pln">error</span><span class="pun">(</span><span class="str">'Index {} is causing an IndexError'</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="pln">idx</span><span class="pun">))</span><span class="pln">
        abort</span><span class="pun">(</span><span class="lit">404</span><span class="pun">)</span><span class="pln">

</span><span class="com"># ...</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	سجّلنا في الشيفرة السابقة عددًا من الأحداث من مستوياتٍ مختلفة، إذ استخدمنا الدالة <code>()app.logger.info</code> لتسجيل حدث يعمل بصورةٍ سليمة (أي من مستوى المعلومات <code>INFO</code>)؛ كما استخدمنا الدالة <code>()app.logger.debug</code> لتسجيل حدث بمعلوماتٍ تفصيلية (أي من مستوى التنقيح <code>DEBUG</code>) مشيرين لحصول التطبيق في هذا الحدث ضمن الطلب على رقم مؤشّر رسالة؛ ثمّ استخدمنا الدالة <code>()app.logger.error</code> لتسجيل حقيقة كون الاستثناء <code>IndexError</code> قد تحقّق بسبب ورود رقم مؤشّر خاطئ مُسبّبًا حدوث الخلل (أي حدث من مستوى الخطأ <code>ERROR</code>، ذلك لأنّ خطأً ما قد حدث فعلًا).
</p>

<p>
	الآن وبزيارة الرابط:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_102" style="">
<span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln">messages</span><span class="pun">/</span><span class="lit">1</span></pre>

<p>
	نحصل في الخرج على المعلومات التالية ضمن نافذة الطرفية التي يعمل ضمنها الخادم:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_104" style="">
<span class="pun">[</span><span class="lit">2021</span><span class="pun">-</span><span class="lit">09</span><span class="pun">-</span><span class="lit">21</span><span class="pln"> </span><span class="lit">15</span><span class="pun">:</span><span class="lit">17</span><span class="pun">:</span><span class="lit">02</span><span class="pun">,</span><span class="lit">625</span><span class="pun">]</span><span class="pln"> INFO </span><span class="kwd">in</span><span class="pln"> app</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Building</span><span class="pln"> the messages list</span><span class="pun">...</span><span class="pln">
</span><span class="pun">[</span><span class="lit">2021</span><span class="pun">-</span><span class="lit">09</span><span class="pun">-</span><span class="lit">21</span><span class="pln"> </span><span class="lit">15</span><span class="pun">:</span><span class="lit">17</span><span class="pun">:</span><span class="lit">02</span><span class="pun">,</span><span class="lit">626</span><span class="pun">]</span><span class="pln"> DEBUG </span><span class="kwd">in</span><span class="pln"> app</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Get</span><span class="pln"> message </span><span class="kwd">with</span><span class="pln"> index</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">[</span><span class="lit">21</span><span class="pun">/</span><span class="typ">Sep</span><span class="pun">/</span><span class="lit">2021</span><span class="pln"> </span><span class="lit">15</span><span class="pun">:</span><span class="lit">17</span><span class="pun">:</span><span class="lit">02</span><span class="pun">]</span><span class="pln"> </span><span class="str">"GET /messages/1 HTTP/1.1"</span><span class="pln"> </span><span class="lit">200</span><span class="pln"> </span><span class="pun">-</span></pre>

<p>
	وفيه نرى رسالة المعلومات <code>INFO</code> المُسجلّة من قبل الدالة <code>()app.logger.info</code>، ورسالة التنقيح <code>DEBUG</code> المُسجّلة من قبل الدالة <code>()app.logger.debug</code> والتي تبيّن رقم المؤشّر المطلوب.
</p>

<p>
	الآن وبزيارة رابط برقم مؤشّر خارج المجال المتوفّر، مثل:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_106" style="">
<span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln">messages</span><span class="pun">/</span><span class="lit">3</span></pre>

<p>
	نحصل على الخرج التالي ضمن نافذة الطرفية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3933_108" style="">
<span class="pun">[</span><span class="lit">2021</span><span class="pun">-</span><span class="lit">09</span><span class="pun">-</span><span class="lit">21</span><span class="pln"> </span><span class="lit">15</span><span class="pun">:</span><span class="lit">33</span><span class="pun">:</span><span class="lit">43</span><span class="pun">,</span><span class="lit">899</span><span class="pun">]</span><span class="pln"> INFO </span><span class="kwd">in</span><span class="pln"> app</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Building</span><span class="pln"> the messages list</span><span class="pun">...</span><span class="pln">
</span><span class="pun">[</span><span class="lit">2021</span><span class="pun">-</span><span class="lit">09</span><span class="pun">-</span><span class="lit">21</span><span class="pln"> </span><span class="lit">15</span><span class="pun">:</span><span class="lit">33</span><span class="pun">:</span><span class="lit">43</span><span class="pun">,</span><span class="lit">899</span><span class="pun">]</span><span class="pln"> DEBUG </span><span class="kwd">in</span><span class="pln"> app</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Get</span><span class="pln"> message </span><span class="kwd">with</span><span class="pln"> index</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3</span><span class="pln">
</span><span class="pun">[</span><span class="lit">2021</span><span class="pun">-</span><span class="lit">09</span><span class="pun">-</span><span class="lit">21</span><span class="pln"> </span><span class="lit">15</span><span class="pun">:</span><span class="lit">33</span><span class="pun">:</span><span class="lit">43</span><span class="pun">,</span><span class="lit">900</span><span class="pun">]</span><span class="pln"> ERROR </span><span class="kwd">in</span><span class="pln"> app</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Index</span><span class="pln"> </span><span class="lit">3</span><span class="pln"> </span><span class="kwd">is</span><span class="pln"> causing an </span><span class="typ">IndexError</span><span class="pln">
</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="pun">[</span><span class="lit">21</span><span class="pun">/</span><span class="typ">Sep</span><span class="pun">/</span><span class="lit">2021</span><span class="pln"> </span><span class="lit">15</span><span class="pun">:</span><span class="lit">33</span><span class="pun">:</span><span class="lit">43</span><span class="pun">]</span><span class="pln"> </span><span class="str">"GET /messages/3 HTTP/1.1"</span><span class="pln"> </span><span class="lit">404</span><span class="pln"> </span><span class="pun">-</span></pre>

<p>
	وهنا نلاحظ أنّنا قد حصلنا على كل من سجلات <code>INFO</code> و <code>DEBUG</code> التي حصلنا عليها المرة الماضية ولكن مع سجل جديد لحدث خطأ <code>ERROR</code> بسبب طلب رسالة برقم مؤشّر غير موجود وهو الرقم "3" في مثالنا.
</p>

<p>
	لهذه الأحداث المُسجّلة والمعلومات التفصيلية والأخطاء المُسجلة الدور المهم في المساعدة على تحديد مكان حدوث الخطأ ما يجعل اكتشافه وإصلاحه أسهل.
</p>

<p>
	وبذلك ومع نهاية هذه الخطوة نكون قد تعلمنا كيفية التعامل مع مُسجّل الأحداث في فلاسك.
</p>

<h1>
	الخاتمة
</h1>

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

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-handle-errors-in-a-flask-application" rel="external nofollow">How To Handle Errors in a Flask Application</a> لصاحبه Abdelhadi Dyouri.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1737/" rel="">كيفية استخدام القوالب في تطبيقات فلاسك Flask</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A3%D8%B7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%81%D9%8A-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D9%81%D9%84%D8%A7%D8%B3%D9%83-%D9%86%D9%85%D9%88%D8%B0%D8%AC%D8%A7-r1547/" rel="">استخدام أطر العمل في برمجة تطبيقات الويب: فلاسك نموذجا</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1738</guid><pubDate>Wed, 05 Oct 2022 07:00:50 +0000</pubDate></item><item><title>&#x643;&#x64A;&#x641;&#x64A;&#x629; &#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x627;&#x644;&#x642;&#x648;&#x627;&#x644;&#x628; &#x641;&#x64A; &#x62A;&#x637;&#x628;&#x64A;&#x642;&#x627;&#x62A; &#x641;&#x644;&#x627;&#x633;&#x643; Flask</title><link>https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1737/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_10/633d165f5c446_------Flask.jpg.c81c9b2f077561ad5d1ba8fb746e1182.jpg" /></p>

<p>
	يُعد <a href="https://academy.hsoub.com/programming/python/flask/" rel="">فلاسك</a> إطار عمل للويب مبني بلغة <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D9%85%D8%B1%D8%AC%D8%B9-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%AA%D8%B9%D9%84%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r735/" rel="">بايثون</a>، ويتميز بكونه صغير الحجم وسهل المعالجة، ويوفّر أيضًا عدة أدوات وميزات من شأنها جعل إنشاء تطبيقات الويب في <a href="https://academy.hsoub.com/programming/python/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r211/" rel="">لغة بايثون</a> أسهل.
</p>

<p>
	عند تطوير تطبيق ويب ما، من الضروري الفصل بين ما يدعى "منطق العمل business logic" و"منطق العرض presentation logic"؛ إذ يكون منطق العمل مسؤولاً عن التعامل مع طلبات المستخدمين، مُخاطبًا قاعدة البيانات لإنشاء الاستجابة المناسبة؛ في حين يُحدّد منطق العرض كيفيّة عرض البيانات للمستخدم، وذلك غالبًا باستخدام ملفات <a href="https://academy.hsoub.com/programming/html/" rel="">HTML</a> لإنشاء الهيكل الأساسي لصفحة الويب التي تستجيب لطلبات المستخدمين، مع استخدام تنسيقات <a href="https://academy.hsoub.com/programming/html/html-%D9%88-css-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86-%D9%83%D9%8A%D9%81-%D8%AA%D8%B5%D9%85%D9%85-%D8%A3%D9%88%D9%84-%D8%B5%D9%81%D8%AD%D8%A9-%D9%88%D9%8A%D8%A8-%D9%84%D9%83-r242/" rel="">CSS</a> لتنسيق مظهر تلك المكونات المبنية باستخدام HTML.
</p>

<p>
	فمثلًا في حال كان لدينا تطبيق للتواصل الاجتماعي، فيوجد عادةً حقلٌ لإدراج اسم المستخدم وحقل لإدراج كلمة المرور بحيث لا يظهران إلّا في حال كون المستخدم لم يسجل دخوله إلى التطبيق بعد، ولدى كتابة المستخدم اسم المستخدم وكلمة المرور يعمل فلاسك على تطبيق منطق العمل، إذ تُستخرج البيانات (اسم المستخدم وكلمة المرور) من الطلب ليُسجَّل دخول المستخدم إذا كانت هذه البيانات صحيحة، وإلّا ستظهر رسالة خطأ، ويجري التعامل مع كيفية ظهور رسالة الخطأ بالاعتماد على منطق العرض.
</p>

<p>
	يستخدم فلاسك محرّك القوالب جينجا <a href="https://academy.hsoub.com/tags/jinja2/" rel="">jinja</a> لبناء صفحات HTML ديناميكيًا باستخدام مفاهيم بايثون المألوفة، من متغيراتٍ وحلقاتٍ تكرارية وقوائم وغيرها، وبالتالي ستُستخدم هذه القوالب على أنها جزءٌ من هذا المشروع، إذ يُعرّف القالب بأنه ملف يحتوي على مكونات ثابتة وأُخرى ديناميكية، وعندما يطلب المستخدم عنصرًا من التطبيق، مثل الصفحة الرئيسية، أو صفحة تسجيل الدخول، يُمكنّنا جينجا من الاستجابة باستخدام قالب HTML، وبذلك يصبح من الممكن استخدام عددٍ كبيرٍ من الميزات غير المتوفرة أصلًا في لغة HTML المعيارية، مثل المتغيرات والعبارة الشرطية "if" والحلقات التكرارية مثل حلقة "for"، والمُرشّحات filters ومفهوم وراثة القوالب، إذ تسمح لنا هذه الخواص بكتابة صفحات HTML سهلة الإصلاح، كما تعزل جينجا شيفرات HTML تلقائيًا بهدف منع <a href="https://owasp.org/www-community/attacks/xss/" rel="external nofollow">هجمات البرمجة العابرة للمواقع Cross-Site Scripting</a> -أو اختصارًا XSS-.
</p>

<p>
	سنبني في هذا المقال تطبيق ويب صغير يعمل على تصييّر عدّة ملفات HTML ضمن المتصفح معًا، إذ سنستخدم المتغيرات لتمرير البيانات من الخادم إلى القوالب، كما سنطبّق مفهوم وراثة القوالب لتجنُّب التكرار في الشيفرات البرمجية.
</p>

<p>
	وسنستخدم كذلك التعابير المنطقية في بناء شيفرات القوالب من عبارات شرطية وحلقات، وسنطبّق المُرشّحات اللازمة لتعديل النصوص، وسنعمل من خلال إطار العمل <a href="https://academy.hsoub.com/programming/css/bootstrap/" rel="">بوتستراب bootstrap</a> على إضافة التنسيقات على التطبيق ليبدو أكثر جاذبية بصريًا.
</p>

<h2>
	مستلزمات العمل
</h2>

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

<ul>
<li>
		توفُّر <a href="https://academy.hsoub.com/programming/python/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%AA%D9%87%D8%A7-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r714/" rel="">بيئة برمجة بايثون 3 محلية</a>، مثبّتة على حاسوبك، وسنفترض في مقالنا أن اسم مجلد المشروع هو "flask_app".
	</li>
	<li>
		توفّر إطار العمل فلاسك مُثبّت ضمن البيئة البرمجبة كما هو مُبيّن في الخطوة الأولى من المقال <a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%B9%D9%84%D9%85-%D8%A8%D9%86%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9%D9%83-%D8%A7%D9%84%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1715/" rel="">كيفية بناء موقعك الإلكتروني الأول باستخدام إطار عمل فلاسك Flask والإصدار الثالث من لغة بايثون</a>.
	</li>
	<li>
		الفهم الجيد لأساسيات فلاسك، مثل مفهوم الوجهات routes ودوال العرض view في فلاسك، ويمكنك في هذا الصدد الاطلاع على المقال كيفية بناء موقعك الإلكتروني الأول باستخدام إطار عمل فلاسك Flask والإصدار الثالث من لغة بايثون المشار إليه أعلاه لفهم مبادئ فلاسك.
	</li>
	<li>
		فهم أساسيات لغة <a href="https://academy.hsoub.com/programming/html/" rel="">HTML</a>.
	</li>
</ul>
<h2>
	الخطوة الأولى - تصيير القوالب واستخدام المتغيرات
</h2>

<p>
	بعد التأكّد من تفعيل بيئة البرمجة وتثبيت فلاسك، سنبدأ ببناء التطبيق، إذ تتمثّل الخطوة الأولى بعرض رسالة ترحيب بالزوار في الصفحة الرئيسية، وسنستخدم لهذا الغرض دالة فلاسك المساعدة <code>()render_template</code> لتوفير قوالب HTML مثل استجابة ضمن الصفحة، وسنتعلّم في هذا الصدد كيفيّة تمرير المتغيرات من التطبيق إلى القوالب.
</p>

<p>
	بدايةً نفتح الملف "app.py" الموجود في المجلد "flask_app" لتعديله وذلك باستخدام محرّر النصوص نانو "nano" أو أي محرر آخر تفضّله:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_7" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln"> user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ونضيف الشيفرة التالية ضمنه:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_9" style="">
<span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> render_template

app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln">


</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> hello</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'index.html'</span><span class="pun">)</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	استوردنا في الشيفرة السابقة الصنف <code>Flask</code> الذي سنستخدمه لإنشاء نسخة من تطبيق باسم "app"، كما استوردنا الدالة <code>()render_template</code> من حزمة <code>flask</code>، ثمّ عرفنا دالة العرض <code>()hello</code> (وهي في الواقع دالة بايثون اعتيادية تعيد استجابةً من النمط <a href="https://academy.hsoub.com/programming/general/%d9%85%d8%af%d8%ae%d9%84-%d8%a5%d9%84%d9%89-http-r73/" rel="">HTTP</a>) باستخدام المُزخرف <code>()app.route</code>، الذي يحوّل <a href="https://academy.hsoub.com/programming/python/%d8%aa%d8%b9%d8%b1%d9%81-%d8%b9%d9%84%d9%89-%d8%a7%d9%84%d8%af%d9%88%d8%a7%d9%84-functions-%d9%81%d9%8a-%d8%a8%d8%a7%d9%8a%d8%ab%d9%88%d9%86-r292/" rel="">دوال بايثون</a> الاعتيادية إلى دوال عرض في فلاسك، تستخدم دالة العرض هذه الدالة <code>()render_template</code> لتصييّر ملف قالب HTML المُسمّى "index.html".
</p>

<p>
	الآن، سننشئ القالب "index.html" ضمن المجلد الفرعي "templates" من المجلد الرئيسي "flask_app"، إذ سيبحث فلاسك عن القوالب في المجلد المسمّى "templates" تحديدًا، لذا يُعدُّ الاسم مهمًا. لإنشاء هذا المجلد، نتأكد من وجودنا ضمن المجلد "flask_app" وننفذ الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_14" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln"> user@localhost</span><span class="pun">:</span><span class="pln">$ mkdir templates</span></pre>

<p>
	بعد ذلك، ننشئ ملفًا باسم "index.html" ضمن مجلد القوالب "templates" لنكتب ضمنه لاحقًا الشيفرات اللازمة، ومن الجدير بالملاحظة أنّه من غير الضروري التقيُّد باسم "index.html" على عكس اسم المجلد الرئيسي، فمن الممكن تغييّر اسم الملف ليصبح مثلًا "home.html"، أو "homepage.html"، أو أي اسم آخر تفضّله:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_2053_35" style="">
<span class="pln">(env) user@localhost:$ nano templates/index.html</span></pre>

<p>
	ثمّ نضيف شيفرة HTML التالية داخل الملف "index.html":
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_2053_18" style="">
<span class="dec">&lt;!DOCTYPE html&gt;</span><span class="pln">
</span><span class="tag">&lt;html</span><span class="pln"> </span><span class="atn">lang</span><span class="pun">=</span><span class="atv">"en"</span><span class="tag">&gt;</span><span class="pln">
</span><span class="tag">&lt;head&gt;</span><span class="pln">
    </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">"UTF-8"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;title&gt;</span><span class="pln">FlaskApp</span><span class="tag">&lt;/title&gt;</span><span class="pln">
</span><span class="tag">&lt;/head&gt;</span><span class="pln">
</span><span class="tag">&lt;body&gt;</span><span class="pln">
    </span><span class="tag">&lt;h1&gt;</span><span class="pln">Hello World!</span><span class="tag">&lt;/h1&gt;</span><span class="pln">
    </span><span class="tag">&lt;h2&gt;</span><span class="pln">Welcome to FlaskApp!</span><span class="tag">&lt;/h2&gt;</span><span class="pln">
</span><span class="tag">&lt;/body&gt;</span><span class="pln">
</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	اخترنا في الشيفرة السابقة عنوانًا للصفحة، كما أضفنا الرسالة "!Hello World" لتظهر ضمن تنسيق عنوان من المستوى الأوّل <code>H1</code>، والرسالة "!Welcome to FlasApp" لتظهر ضمن تنسيق عنوان من المستوى الثاني <code>H2</code>.
</p>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	ولتشغيل تطبيق الويب الذي أنشأناه، وبعد التأكّد من وجودنا ضمن المجلد "flask_app" مع تفعيل البيئة الافتراضية، لا بُدّ من إرشاد فلاسك إلى موقع التطبيق (في حالتنا الملف ذو الاسم app.py) باستخدام متغير بيئة فلاسك <code>FLASK_APP</code>، ولتشغيله بوضع التطوير، نضبط متغير بيئة فلاسك <code>Flask_ENV</code> على الوضع <code>development</code> على النحو التالي (مع ملاحظة أنّنا نستخدم الأمر <code>set</code> في بيئة ويندوز عوضًا عن الأمر <code>export</code>):
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_20" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln"> user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_APP</span><span class="pun">=</span><span class="pln">app
</span><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln"> user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_ENV</span><span class="pun">=</span><span class="pln">development</span></pre>

<p>
	بعدها سنشغل التطبيق باستخدام الأمر <code>flask run</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_23" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln"> user@localhost</span><span class="pun">:</span><span class="pln">$ flask run</span></pre>

<p>
	بعد التأكد من كون خادم التطوير ما يزال قيد التشغيل، نذهب إلى الرابط التالي باستخدام المتصفح:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_38" style="">
<span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span></pre>

<p>
	فتظهر العبارة "FlasApp" عنوانًا للصفحة، ويجري تصييّر الرسالتين المكتوبتين في HTML لتظهرا بالتنسيق المطلوب.
</p>

<p>
	تُمرّر البيانات عادةً في تطبيقات الويب من ملفات بايثون الخاصة بالتطبيق إلى قوالب HTML، ولنوضّح كيفية فعل ذلك في تطبيقنا هذا، سنمرّر متغيرًا يحتوي على التاريخ والوقت وفق التوقيت العالمي UTC إلى القالب الرئيسي، وبعدها سنعرض قيمة هذا المتغير في القالب.
</p>

<p>
	سنترك خادم التطوير قيد التشغيل وسنفتح الملف app.py في نافذة أسطر أوامر جديدة للتعديل عليه:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_25" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln"> user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	سنستورد الوحدة الخاصّة بالتعامل مع الوقت والتاريخ <code>datetime</code> من مكتبة بايثون المعيارية وسنعدّل الدالة <code>()index</code> ليبدو الملف كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_27" style="">
<span class="kwd">import</span><span class="pln"> datetime
</span><span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> render_template

app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln">


</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> hello</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'index.html'</span><span class="pun">,</span><span class="pln"> utc_dt</span><span class="pun">=</span><span class="pln">datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">.</span><span class="pln">utcnow</span><span class="pun">())</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	استوردنا في الشيفرة السابقة الوحدة <code>datetime</code> ومرّرنا المتغير المُسمّى <code>utc_dt</code> مع قيمة <code>()datetime.datetime.utcnow</code> التي تمثّل الوقت والتاريخ الحالي وفق التوقيت العالمي إلى القالب "index.html".
</p>

<p>
	ولعرض قيمة هذا المتغير في الصفحة الرئيسية، سنفتح الملف index.html من أجل التعديل عليه:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_2053_33" style="">
<span class="pln">(env) user@localhost:$ nano templates/index.html</span></pre>

<p>
	وسنعدّل الملف كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_2053_31" style="">
<span class="dec">&lt;!DOCTYPE html&gt;</span><span class="pln">
</span><span class="tag">&lt;html</span><span class="pln"> </span><span class="atn">lang</span><span class="pun">=</span><span class="atv">"en"</span><span class="tag">&gt;</span><span class="pln">
</span><span class="tag">&lt;head&gt;</span><span class="pln">
    </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">"UTF-8"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;title&gt;</span><span class="pln">FlaskApp</span><span class="tag">&lt;/title&gt;</span><span class="pln">
</span><span class="tag">&lt;/head&gt;</span><span class="pln">
</span><span class="tag">&lt;body&gt;</span><span class="pln">
    </span><span class="tag">&lt;h1&gt;</span><span class="pln">Hello World!</span><span class="tag">&lt;/h1&gt;</span><span class="pln">
    </span><span class="tag">&lt;h2&gt;</span><span class="pln">Welcome to FlaskApp!</span><span class="tag">&lt;/h2&gt;</span><span class="pln">
    </span><span class="tag">&lt;h3&gt;</span><span class="pln">{{ utc_dt }}</span><span class="tag">&lt;/h3&gt;</span><span class="pln">
</span><span class="tag">&lt;/body&gt;</span><span class="pln">
</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	أضفنا موضعًا لنص منسّق مثل عنوان من المستوى الثالث <code>H3</code> مع استخدام المُحدّد الخاص "{{ … }}" لطباعة قيمة المتغيّر <code>utc_dt</code> ضمنه.
</p>

<p>
	الآن، بالذهاب إلى صفحة التطبيق الرئيسية ضمن المتصفح:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_44" style="">
<span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span></pre>

<p>
	ستظهر صفحة مشابهة للصورة التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="109074" href="https://academy.hsoub.com/uploads/monthly_2022_10/633d166a84d70_firsthelloworld.png.c9ca8c82461ec8d4d79f7e40f0f17731.png" rel=""><img alt="first hello world.png" class="ipsImage ipsImage_thumbnailed" data-fileid="109074" data-unique="o8t3484da" src="https://academy.hsoub.com/uploads/monthly_2022_10/633d166a84d70_firsthelloworld.png.c9ca8c82461ec8d4d79f7e40f0f17731.png" style="width: 400px; height: auto;"></a>
</p>

<p>
	ومع نهاية هذه الخطوة نكون قد أنشأنا صفحةً رئيسيةً ذات قالب HTML داخل تطبيق فلاسك، وصيّرنا محتويات قالب آخر، ومرّرنا وأظهرنا قيمة مُتغير، وفيما يلي سنتجنّب تكرار الشيفرات البرمجية باستخدام مفهوم وراثة القوالب.
</p>

<h2>
	الخطوة الثانية – استخدام مفهوم وراثة القوالب
</h2>

<p>
	سننشئ في هذه الخطوة قالبًا أساسيًا ذا محتوًى قابل للمشاركة مع القوالب الأُخرى، وسنعدّل قالب الصفحة الرئيسية index template للتطبيق ليرث من القالب الأساسي هذا، ثمّ سننشئ صفحةً جديدةً مسؤولةً عن عرض معلومات التطبيق للمستخدمين.
</p>

<p>
	يحتوي القالب الأساسي على أهم مكونات HTML المُستخدمة عادةً في أي قالب، مثل عنوان التطبيق وأشرطة التنقّل والتذييلات.
</p>

<p>
	بدايةً، سننشئ ملفًا جديدًا باسم "base.html" ضمن مجلد القوالب templates:
</p>

<pre class="ipsCode prettyprint lang-css prettyprinted" id="ips_uid_2053_48" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln"> user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="kwd">base</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	وسنكتب ضمنه الشيفرات التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_50" style="">
<span class="pun">&lt;!</span><span class="pln">DOCTYPE html</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">html lang</span><span class="pun">=</span><span class="str">"en"</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">head</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">meta charset</span><span class="pun">=</span><span class="str">"UTF-8"</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">title</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="typ">FlaskApp</span><span class="pun">&lt;/</span><span class="pln">title</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">style</span><span class="pun">&gt;</span><span class="pln">
        nav a </span><span class="pun">{</span><span class="pln">
            color</span><span class="pun">:</span><span class="pln"> </span><span class="com">#d64161;</span><span class="pln">
            font</span><span class="pun">-</span><span class="pln">size</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3em</span><span class="pun">;</span><span class="pln">
            margin</span><span class="pun">-</span><span class="pln">left</span><span class="pun">:</span><span class="pln"> </span><span class="lit">50px</span><span class="pun">;</span><span class="pln">
            text</span><span class="pun">-</span><span class="pln">decoration</span><span class="pun">:</span><span class="pln"> none</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">style</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">head</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">body</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">nav</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">a href</span><span class="pun">=</span><span class="str">"#"</span><span class="pun">&gt;</span><span class="typ">FlaskApp</span><span class="pun">&lt;/</span><span class="pln">a</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">a href</span><span class="pun">=</span><span class="str">"#"</span><span class="pun">&gt;</span><span class="typ">About</span><span class="pun">&lt;/</span><span class="pln">a</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">nav</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">hr</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"content"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">body</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">html</span><span class="pun">&gt;</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	الجزء الأكبر من الشيفرة السابقة هو تعليمات <a href="https://wiki.hsoub.com/HTML" rel="external">HTML</a> معيارية تشمل عنوانًا وبعض التنسيقات لروابط التنقل، إضافةً إلى شريط تنقّل مع رابطين، أحدهما للوصول إلى الصفحة الرئيسية للتطبيق والآخر للوصول إلى صفحة المعلومات التي لم ننشئها بعد، إضافةً إلى عنصر الحافظة <code><a href="https://wiki.hsoub.com/HTML/div" rel="external">&lt;div&gt;</a></code> ليتضمّن محتويات الصفحة. لا تعمل هذه الروابط بعد لكننا سنوضح في الخطوة التالية كيفية إنشاء الربط بين الصفحات.
</p>

<p>
	أمّا الأجزاء من الشيفرة الموضحّة فيما يلي فهي خاصةٌ بمحرك القوالب جينجا:
</p>

<ul>
<li>
		<code>{% block title %} {% endblock %}</code>: كتلة برمجية تحجز مكانًا لعنوان الصفحة، الذي سنستخدمه لاحقًا في تحديد العنوان الخاص بكل صفحة في التطبيق دون الحاجة إلى إعادة كتابة قسم الترويسة <code>&lt;head&gt;</code> كاملًا في كل مرةٍ من أجل كل صفحة.
	</li>
	<li>
		<code>{% block content %} {% endblock %}</code>: كتلة برمجية أخرى ستُستبدل لاحقًا بالمحتوى الفعلي بالاعتماد على القالب الابن، أي القالب الذي يرث شيفرات القالب الرئيسي "base.html".
	</li>
</ul>
<p>
	والآن، بعد أن أصبح لديك قالبٌ رئيسي، يمكنك الاستفادة من ميزاته باستخدام فكرة الوراثة، لذلك افتح الملف "index.html":
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_2053_53" style="">
<span class="pln">(env) user@localhost:$ nano templates/index.html</span></pre>

<p>
	ثمّ استبدل محتوياته بما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_2053_55" style="">
<span class="pln">{% extends 'base.html' %}

{% block content %}
    </span><span class="tag">&lt;h1&gt;</span><span class="pln">{% block title %} Index {% endblock %}</span><span class="tag">&lt;/h1&gt;</span><span class="pln">
    </span><span class="tag">&lt;h1&gt;</span><span class="pln">Hello World!</span><span class="tag">&lt;/h1&gt;</span><span class="pln">
    </span><span class="tag">&lt;h2&gt;</span><span class="pln">Welcome to FlaskApp!</span><span class="tag">&lt;/h2&gt;</span><span class="pln">
    </span><span class="tag">&lt;h3&gt;</span><span class="pln">{{ utc_dt }}</span><span class="tag">&lt;/h3&gt;</span><span class="pln">
{% endblock %}</span></pre>

<p>
	استخدمنا في هذه النسخة الجديدة من القالب template.html الوسم <code>{% extends %}</code> للوراثة من القالب الرئيسي base.html، وذلك عن طريق استبدال الشيفرة السابقة بكتلة المحتوى <code>content</code> في القالب الرئيسي.
</p>

<p>
	تحوي كتلة المحتوى هذه على وسم <code>&lt;h1&gt;</code> وبداخله كتلة عنوان <code>title</code> تحتوي على العبارة "Index"، والتي بدورها تستبدل كتلة العنوان <code>title</code> الموجودة أصلًا في القالب الرئيسي base.html لتحتوي على العبارة "Index" وبذلك يصبح العنوان كاملًا "Index - FlaskApp"، وبهذه الطريقة نتفادى تكرار نفس النص مرتين، ذلك لأنّ هذا النص سيظهر في عنوان الصفحة وضمن الوسم <code><a href="https://wiki.hsoub.com/HTML/h1-h6" rel="external">&lt;h1&gt;</a></code> أسفل شريط التصفح الموروث من القالب الرئيسي.
</p>

<p>
	وبذلك يصبح لدينا عدة عناوين، الأوّل بتنسيق من المستوى الأوّل <code>&lt;h1&gt;</code> يتضمّن النص "!Hello World"، والثاني بتنسيق من المستوى الثاني <code>&lt;h2&gt;</code>، والثالث بتنسيق من المستوى الثالث <code>&lt;h3&gt;</code> والذي يحتوي على قيمة متغير الوقت والتاريخ <code>utc_dt</code>.
</p>

<p>
	تمكنّنا وراثة القوالب من إعادة استخدام شيفرة HTML الموجودة في القوالب الأخرى (القالب الرئيسي base.html في حالتنا) دون الحاجة لتكراره في كل مرة.
</p>

<p>
	اِحفظ الملف وأغلقه، ثم حدِّث الصفحة الرئيسية index في المتصفح، فستظهر الصفحة بشريط تصفح وعنوانٍ منسقٍ كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="109076" href="https://academy.hsoub.com/uploads/monthly_2022_10/633d16705b25e_secondabout.png.f6cb12fb71e88be6bf8f89ce6297fef5.png" rel=""><img alt="second about.png" class="ipsImage ipsImage_thumbnailed" data-fileid="109076" data-unique="3c0igpi7d" src="https://academy.hsoub.com/uploads/monthly_2022_10/633d16705b25e_secondabout.png.f6cb12fb71e88be6bf8f89ce6297fef5.png" style="width: 400px; height: auto;"></a>
</p>

<p>
	أمّا الآن فسننشئ صفحة معلومات التطبيق، لذا سنفتح الملف "app.py" ولنضيف ضمنه وجهةً جديدةً:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_61" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln"> user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ونضيف الوجهة التالية إلى نهايته:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_63" style="">
<span class="com"># ...</span><span class="pln">
</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/about/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> about</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'about.html'</span><span class="pun">)</span></pre>

<p>
	استخدمنا المزخرف <code>()app.route</code> لإنشاء دالة فلاسك باسم "()about"، إذ ترجع ضمنها نتيجة استدعاء الدالة <code>()render_template</code> لدى تمرير اسم ملف القالب "about.html" وسيطًا لها.
</p>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	الآن سننشئ ملف قالب باسم "about.html" كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_65" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln"> user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">about</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	ونضيف ضمنه الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_67" style="">
<span class="pun">{%</span><span class="pln"> extends </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">

</span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">h1</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="typ">About</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">h3</span><span class="pun">&gt;</span><span class="typ">FlaskApp</span><span class="pln"> </span><span class="kwd">is</span><span class="pln"> a </span><span class="typ">Flask</span><span class="pln"> web application written </span><span class="kwd">in</span><span class="pln"> </span><span class="typ">Python</span><span class="pun">.&lt;/</span><span class="pln">h3</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	استخدمنا في الشيفرة السابقة الوسم <code>extend</code> لوراثة الشيفرات من القالب الرئيسي، كما استبدلنا كتلة المحتوى <code>content</code> في القالب الرئيسي بوسم من النوع <code>&lt;h1&gt;</code> الذي يعمل أيضًا بمثابة عنوان للصفحة، وأضفنا بعض المعلومات حول التطبيق ضمن وسم من النوع <code>&lt;h3&gt;</code>.
</p>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	بعد التأكد من أنّ خادم التطوير ما يزال قيد التشغيل، ننتقل إلى الرابط التالي باستخدام المتصفح:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_69" style="">
<span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln">about</span></pre>

<p>
	فتظهر صفحةٌ مشابهة للصورة التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="109079" href="https://academy.hsoub.com/uploads/monthly_2022_10/633d1677503ea_thirdabout.png.f5e2d8f7bfeae696f8fc2109b459a930.png" rel=""><img alt="third about.png" class="ipsImage ipsImage_thumbnailed" data-fileid="109079" data-unique="pukgtlgwa" src="https://academy.hsoub.com/uploads/monthly_2022_10/633d1677503ea_thirdabout.png.f5e2d8f7bfeae696f8fc2109b459a930.png" style="width: 350px; height: auto;"></a>
</p>

<p>
	نلاحظ وراثة شريط التنقل وجزءٍ من العنوان من القالب الأساسي.
</p>

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

<h2>
	الخطوة الثالثة – الربط بين الصفحات
</h2>

<p>
	سنتعلّم في هذه الخطوة كيفيّة الربط بين الصفحات باستخدام الدالة المساعدة <code>()url_for</code>، إذ سنضيف رابطين إلى شريط التنقل في القالب الأساسي، أحدهما للوصول إلى الصفحة الرئيسية والآخر للوصول إلى صفحة معلومات التطبيق.
</p>

<p>
	بدايةً سنفتح القالب الأساسي لتعديله:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_72" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln"> user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">base</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	ونعدّل الملف كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_2053_74" style="">
<span class="dec">&lt;!DOCTYPE html&gt;</span><span class="pln">
</span><span class="tag">&lt;html</span><span class="pln"> </span><span class="atn">lang</span><span class="pun">=</span><span class="atv">"en"</span><span class="tag">&gt;</span><span class="pln">
</span><span class="tag">&lt;head&gt;</span><span class="pln">
    </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">"UTF-8"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;title&gt;</span><span class="pln">{% block title %} {% endblock %} - FlaskApp</span><span class="tag">&lt;/title&gt;</span><span class="pln">
    </span><span class="tag">&lt;style&gt;</span><span class="pln">
        nav a </span><span class="pun">{</span><span class="pln">
            color</span><span class="pun">:</span><span class="pln"> </span><span class="com">#d64161;</span><span class="pln">
            font</span><span class="pun">-</span><span class="pln">size</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3em</span><span class="pun">;</span><span class="pln">
            margin</span><span class="pun">-</span><span class="pln">left</span><span class="pun">:</span><span class="pln"> </span><span class="lit">50px</span><span class="pun">;</span><span class="pln">
            text</span><span class="pun">-</span><span class="pln">decoration</span><span class="pun">:</span><span class="pln"> none</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="tag">&lt;/style&gt;</span><span class="pln">
</span><span class="tag">&lt;/head&gt;</span><span class="pln">
</span><span class="tag">&lt;body&gt;</span><span class="pln">
    </span><span class="tag">&lt;nav&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('hello') }}"</span><span class="tag">&gt;</span><span class="pln">FlaskApp</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('about') }}"</span><span class="tag">&gt;</span><span class="pln">About</span><span class="tag">&lt;/a&gt;</span><span class="pln">
    </span><span class="tag">&lt;/nav&gt;</span><span class="pln">
    </span><span class="tag">&lt;hr&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"content"</span><span class="tag">&gt;</span><span class="pln">
        {% block content %} {% endblock %}
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
</span><span class="tag">&lt;/body&gt;</span><span class="pln">
</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	استخدمنا في الشيفرة السابقة دالة <code>()url_for</code> خاصّة، والتي ستُعيد الرابط الموافق لدالة فلاسك المُمرّرة لها، إذ أنّ الرابط الأول مرتبط بالوجهة الخاصة بدالة فلاسك <code>()hello</code> (وهي الصفحة الرئيسية للتطبيق)، في حين يرتبط الرابط الثاني بالوجهة الخاصة بدالة فلاسك <code>()about</code>، مع ملاحظة أنّنا مرّرنا اسم دالة فلاسك وسيطًا وليس اسم الوجهة (<code>/</code> أو <code>about/</code>).
</p>

<p>
	يساعد استخدام الدالة <code>()url_for</code> لبناء الروابط على إدارتها على نحوٍ أفضل، فلو كتبنا الروابط بصورةٍمحددة وبتعليمات برمجية ثابتة فإنها ستفشل مع أول تعديل على الوجهات، في حين أنّه ومع استخدام الدالة <code>()url_for</code> فمن الممكن التعديل على الوجهات مع ضمان محافظة الروابط على عملها الطبيعي المتوقع، ناهيك عن كون دالة <code>()url_for</code> مسؤولة أيضًا عن أمور أُخرى مثل عزل المحارف الخاصة.
</p>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	سنعود الآن إلى الصفحة الرئيسية ونجرّب الروابط الموجودة في شريط التنقل، فنجد أنها تعمل كما هو مطلوب.
</p>

<p>
	ومع نهاية هذه الخطوة نكون قد تعلمنا كيفية استخدام الدالة <code>()url_for</code> للربط بين الوجهات في القوالب، وفيما يلي سنضيف بعض العبارات الشرطية للتحكم بما يُعرض في القوالب بناءً على الشروط التي نضعها، كما سنستخدم حلقات "for" التكرارية في قوالبنا لعرض عناصر القائمة.
</p>

<h2>
	الخطوة الرابعة - استخدام العبارات الشرطية والحلقات التكرارية
</h2>

<p>
	سنستخدم في هذه الخطوة العبارة الشرطية "if" في القوالب للتحكم بما سيُعرض بناءً على شروط محدّدة، كما سنستخدم حلقات "for" التكرارية للتنقّل بين عناصر قوائم بايثون وعرضها، ثمّ سنضيف صفحةً جديدةً إلى التطبيق مهمتها عرض التعليقات ضمن قائمة، بحيث تكون التعليقات ذات رقم الدليل الزوجي بخلفية زرقاء والتعليقات ذات رقم الدليل الفردي بخلفية رمادية.
</p>

<p>
	سننشئ بدايةً وجهةً جديدةً لصفحة التعليقات، لذا سنفتح الملف app.py للتعديل عليه:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_76" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln"> user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ونضيف الوجهة التالية إلى نهاية الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_78" style="">
<span class="com"># ...</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/comments/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> comments</span><span class="pun">():</span><span class="pln">
    comments </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">'This is the first comment.'</span><span class="pun">,</span><span class="pln">
                </span><span class="str">'This is the second comment.'</span><span class="pun">,</span><span class="pln">
                </span><span class="str">'This is the third comment.'</span><span class="pun">,</span><span class="pln">
                </span><span class="str">'This is the fourth comment.'</span><span class="pln">
                </span><span class="pun">]</span><span class="pln">

    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'comments.html'</span><span class="pun">,</span><span class="pln"> comments</span><span class="pun">=</span><span class="pln">comments</span><span class="pun">)</span></pre>

<p>
	لدينا في الوجهة السابقة قائمة مبنية في بايثون باسم <code>comments</code> تحتوي على أربعة عناصر (في التطبيق العملي يمكن الحصول على هذه التعليقات من قاعدة البيانات بدلاً من كتابتها يدويًا وتثبيتها ضمن الشيفرة)، لتكون القيمة المعادة هي تصيّير لملف قالب باسم "comments.html" وذلك في السطر الأخير من الشيفرة، ممررين المتغير <code>comments</code> الذي يحتوي القائمة مثل وسيط إلى ملف القالب.
</p>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	بعدها ننشئ ملف جديد باسم comments.html ضمن مجلد القوالب templates للتعديل عليه:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_82" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln"> user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">comments</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	ونضيف ضمنه الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_86" style="">
<span class="pun">{%</span><span class="pln"> extends </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">

</span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">h1</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="typ">Comments</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">div style</span><span class="pun">=</span><span class="str">"width: 50%; margin: auto"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> comment </span><span class="kwd">in</span><span class="pln"> comments </span><span class="pun">%}</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">div style</span><span class="pun">=</span><span class="str">"padding: 10px; background-color: #EEE; margin: 20px"</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">p style</span><span class="pun">=</span><span class="str">"font-size: 24px"</span><span class="pun">&gt;{{</span><span class="pln"> comment </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">{%</span><span class="pln"> endfor </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	وفيه توسعنا بالقالب الأساسي base.html واستبدلنا محتويات كتلة المحتوى <code>content</code>. واستخدمنا تنسيق عنوان من النمط <code>&lt;h1&gt;</code> الذي سيعمل أيضًا بمثابة عنوانٍ للصفحة.
</p>

<p>
	كما استخدما حلقة <code>for</code> التكرارية وهي من ضمن تعليمات محرّك القوالب جينجا وذلك في السطر البرمجي <code>{% for comment in comments %}</code> بهدف التنقل بين التعليقات في القائمة <code>comments</code> المُخزنة ضمن المتغير المسمّى <code>comment</code>، إذ سنعرض التعليقات باستخدام الوسم <code>&lt;p style="font-size: 24px"&gt;{{ comment }}&lt;/p&gt;</code>، أي بنفس الآلية التي يُعرض فيها أي متغير في جينجا، مع ملاحظة أنّ نهاية حلقة <code>for</code> هنا تكون باستخدام الكلمة المفتاحية <code>{% endfor %}</code> الأمر المختلف عن طريقة بناء حلقات <code>for</code> في بايثون.
</p>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	الآن وبعد التأكد من كون خادم التطوير ما يزال قيد التشغيل، نفتح المتصفح وننتقل إلى صفحة التعليقات باستخدام الرابط:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_88" style="">
<span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln">comments</span></pre>

<p>
	فتظهر الصفحة بما يشبه التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="109075" href="https://academy.hsoub.com/uploads/monthly_2022_10/633d166d4f3d0_fourthabout.png.1e9d1049365355eb9dd22eddc136f085.png" rel=""><img alt="fourthabout" class="ipsImage ipsImage_thumbnailed" data-fileid="109075" data-unique="l4a3swo2v" src="https://academy.hsoub.com/uploads/monthly_2022_10/633d166e9f6e8_fourthabout.thumb.png.33c2ab42b38952f29c0acf192af7f724.png" style="width: 650px; height: auto;"></a>
</p>

<p>
	الآن سنستخدم عبارة <code>if</code> الشرطية في القوالب لعرض التعليقات ذات رقم الدليل الفردي بخلفية رمادية والتعليقات ذات رقم الدليل الزوجي بخلفية زرقاء.
</p>

<p>
	لذا سنفتح ملف القالب comments.html:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_93" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln"> user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">comments</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	ثمّ سنعدله ليصبح كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_95" style="">
<span class="pun">{%</span><span class="pln"> extends </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">

</span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">h1</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="typ">Comments</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">div style</span><span class="pun">=</span><span class="str">"width: 50%; margin: auto"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> comment </span><span class="kwd">in</span><span class="pln"> comments </span><span class="pun">%}</span><span class="pln">
            </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> loop</span><span class="pun">.</span><span class="pln">index </span><span class="pun">%</span><span class="pln"> </span><span class="lit">2</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">
                </span><span class="pun">{%</span><span class="pln"> set bg_color </span><span class="pun">=</span><span class="pln"> </span><span class="str">'#e6f9ff'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">
            </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">
                </span><span class="pun">{%</span><span class="pln"> set bg_color </span><span class="pun">=</span><span class="pln"> </span><span class="str">'#eee'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">
            </span><span class="pun">{%</span><span class="pln"> endif </span><span class="pun">%}</span><span class="pln">

            </span><span class="pun">&lt;</span><span class="pln">div style</span><span class="pun">=</span><span class="str">"padding: 10px; background-color: {{ bg_color }}; margin: 20px"</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;#{{</span><span class="pln"> loop</span><span class="pun">.</span><span class="pln">index </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">p style</span><span class="pun">=</span><span class="str">"font-size: 24px"</span><span class="pun">&gt;{{</span><span class="pln"> comment </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">{%</span><span class="pln"> endfor </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	وفي هذا التعديل أضفنا عبارة <code>if</code> الشرطية في السطر <code>{% if loop.index % 2 == 0 %}</code>، إذ أنّ المتغير <code>loop</code> هو متغير خاص في جينجا يمكنّنا من الوصول إلى معلومات حول الحلقة الحالية، واستخدمنا <code>loop.index</code> للحصول على دليل العنصر الحالي، الذي يبدأ من "1" وليس من "0" كما هو الحال في قوائم بايثون.
</p>

<p>
	تتحقق عبارة <code>if</code> ما إذا كان الدليل زوجياً باستخدام عملية باقي القسمة <code>%</code>، إذ تُفحص قيمة باقي قسمة الدليل على الرقم "2"، فإذا كان الباقي يساوي "0"، فهذا يعني أن الدليل زوجي، وإلّا فيكون الدليل فرديًا. استخدمنا الوسم <code>{% set %}</code> للتعريف عن متغير باسم <code>bg_color</code> ليتضمّن لون الخلفية المطلوب، فإذا كان الدليل زوجيًا، نضبط قيمة هذا المتغير إلى اللون الأزرق، وإلّا وفي حال كون الدليل فرديًا نضبطها إلى اللون الرمادي، ثم نخصّص المتغير <code>bg_color</code> لضبط لون الخلفية للوسم <code>&lt;div&gt;</code> الحاوي على التعليق. نستخدم <code>loop.index</code> في أعلى النص الخاص بالتعليق لعرض رقم الدليل الحالي ضمن وسمٍ من النوع <code>&lt;p&gt;</code>.
</p>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	الآن بفتح المتصفح والانتقال إلى صفحة التعليقات كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_97" style="">
<span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln">comments</span></pre>

<p>
	ستظهر صفحة التعليقات الجديدة كما في الصورة:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="109073" href="https://academy.hsoub.com/uploads/monthly_2022_10/633d166647fb9_fifthabout.png.cdc7156eaed1e24e473b37b37c4863c1.png" rel=""><img alt="fifthabout" class="ipsImage ipsImage_thumbnailed" data-fileid="109073" data-unique="dfez91v4h" src="https://academy.hsoub.com/uploads/monthly_2022_10/633d16685d281_fifthabout.thumb.png.e6136ce09546bd67db461719c2409866.png" style="width: 650px; height: auto;"></a>
</p>

<p>
	وضّحنا فيما سبق كيفية استخدام العبارة الشرطية <code>if</code>، ولكن من الممكن الحصول على نفس النتيجة باستخدام الدالة الخاصّة المساعدة <code>()loop.cycle</code> في جينجا، ولتوضيح هذه النقطة سنفتح الملف "comments.html":
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_102" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln"> user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">comments</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	ونعدله ليصبح كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_104" style="">
<span class="pun">{%</span><span class="pln"> extends </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">

</span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">h1</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="typ">Comments</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">div style</span><span class="pun">=</span><span class="str">"width: 50%; margin: auto"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> comment </span><span class="kwd">in</span><span class="pln"> comments </span><span class="pun">%}</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">div style</span><span class="pun">=</span><span class="str">"padding: 10px;
                        background-color: {{ loop.cycle('#EEE', '#e6f9ff') }};
                        margin: 20px"</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;#{{</span><span class="pln"> loop</span><span class="pun">.</span><span class="pln">index </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">p style</span><span class="pun">=</span><span class="str">"font-size: 24px"</span><span class="pun">&gt;{{</span><span class="pln"> comment </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">{%</span><span class="pln"> endfor </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	حذفنا في هذه الشيفرة العبارة الشرطية "if/else" واستخدمنا عوضًا عنها الدالة المساعدة <code>('loop.cycle('#EEE', '#e6f9ff</code> للتنقل بين اللونين، إذ ستكون قيمة المتغيّر <code>background-color</code> مساويةً للقيمة "‎#EEE" للون الرمادي مرّةً، ثمّ تتحول إلى القيمة "‎#e6f9ff" للون الأزرق مرةً أُخرى، وهكذا بالتناوب.
</p>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	لدى فتح صفحة التعليقات في المتصفح وتحديثها، ستظهر صفحةٌ مشابهةٌ لتلك التي ظهرت مع استخدام عبارة "if" الشرطية.
</p>

<p>
	يمكن استخدام عبارات "if" الشرطية لأغراض مختلفة، بما في ذلك التحكم بما يُعرض على الصفحة، فعلى سبيل المثال لعرض جميع التعليقات باستثناء التعليق الثاني، يمكننا استخدام عبارة <code>if</code> بالشرط <code>loop.index != 2</code> لترشيح التعليق الثاني.
</p>

<p>
	لذا سنفتح قالب التعليقات:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_106" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln"> user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">comments</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	ونعدله ليصبح كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_108" style="">
<span class="pun">{%</span><span class="pln"> extends </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">

</span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">h1</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="typ">Comments</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">div style</span><span class="pun">=</span><span class="str">"width: 50%; margin: auto"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> comment </span><span class="kwd">in</span><span class="pln"> comments </span><span class="pun">%}</span><span class="pln">
            </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> loop</span><span class="pun">.</span><span class="pln">index </span><span class="pun">!=</span><span class="pln"> </span><span class="lit">2</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">div style</span><span class="pun">=</span><span class="str">"padding: 10px;
                            background-color: #EEE;
                            margin: 20px"</span><span class="pun">&gt;</span><span class="pln">
                    </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;#{{</span><span class="pln"> loop</span><span class="pun">.</span><span class="pln">index </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                    </span><span class="pun">&lt;</span><span class="pln">p style</span><span class="pun">=</span><span class="str">"font-size: 24px"</span><span class="pun">&gt;{{</span><span class="pln"> comment </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">{%</span><span class="pln"> endif </span><span class="pun">%}</span><span class="pln">
        </span><span class="pun">{%</span><span class="pln"> endfor </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	استخدمنا العبارة الشرطية <code>{‎% if loop.index != 2 %‎}</code> لعرض التعليقات ذات رقم الدليل غير المساوي للرقم "2"، ما يشمل جميع التعليقات باستثناء التعليق الثاني، كما استخدمنا شيفرةً ثابتةً لتغيير لون الخلفية بدلًا من استخدام الدالة المساعدة <code>()loop.cycle</code> لتسهيل الأمور، أمّا بالنسبة لبقية أجزاء الشيفرة، فلم نجري عليها أي تغييرات، وأيضًا هنا تُنهى عبارة <code>if</code> الشرطية باستخدام الأمر <code>{% endif %}</code>.
</p>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	سنلاحظ بتحديث صفحة التعليقات عدم ظهور التعليق الثاني.
</p>

<p>
	سنضيف الآن رابط يسمح للمستخدم بالانتقال إلى صفحة التعليقات مباشرةً من شريط التنقل، لذا سنفتح القالب الأساسي للتعديل عليه:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_110" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln"> user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">base</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	وسنعدّل محتويات الوسم <code><a href="https://wiki.hsoub.com/HTML/nav" rel="external">&lt;nav&gt;</a></code> بإضافة رابط جديد <code><a href="https://wiki.hsoub.com/HTML/a" rel="external">&lt;a&gt;</a></code> إليه كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_2053_114" style="">
<span class="dec">&lt;!DOCTYPE html&gt;</span><span class="pln">
</span><span class="tag">&lt;html</span><span class="pln"> </span><span class="atn">lang</span><span class="pun">=</span><span class="atv">"en"</span><span class="tag">&gt;</span><span class="pln">
</span><span class="tag">&lt;head&gt;</span><span class="pln">
    </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">"UTF-8"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;title&gt;</span><span class="pln">{% block title %} {% endblock %} - FlaskApp</span><span class="tag">&lt;/title&gt;</span><span class="pln">
    </span><span class="tag">&lt;style&gt;</span><span class="pln">
        nav a </span><span class="pun">{</span><span class="pln">
            color</span><span class="pun">:</span><span class="pln"> </span><span class="com">#d64161;</span><span class="pln">
            font</span><span class="pun">-</span><span class="pln">size</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3em</span><span class="pun">;</span><span class="pln">
            margin</span><span class="pun">-</span><span class="pln">left</span><span class="pun">:</span><span class="pln"> </span><span class="lit">50px</span><span class="pun">;</span><span class="pln">
            text</span><span class="pun">-</span><span class="pln">decoration</span><span class="pun">:</span><span class="pln"> none</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="tag">&lt;/style&gt;</span><span class="pln">
</span><span class="tag">&lt;/head&gt;</span><span class="pln">
</span><span class="tag">&lt;body&gt;</span><span class="pln">
    </span><span class="tag">&lt;nav&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('hello') }}"</span><span class="tag">&gt;</span><span class="pln">FlaskApp</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('comments') }}"</span><span class="tag">&gt;</span><span class="pln">Comments</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('about') }}"</span><span class="tag">&gt;</span><span class="pln">About</span><span class="tag">&lt;/a&gt;</span><span class="pln">
    </span><span class="tag">&lt;/nav&gt;</span><span class="pln">
    </span><span class="tag">&lt;hr&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"content"</span><span class="tag">&gt;</span><span class="pln">
        {% block content %} {% endblock %}
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
</span><span class="tag">&lt;/body&gt;</span><span class="pln">
</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	استخدمنا الدالة المساعدة <code>()url_for</code> للوصول إلى دالة فلاسك <code>()comments</code>
</p>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	وبذلك أصبح شريط التنقل يحتوي على رابط جديد ينقل المستخدم إلى صفحة التعليقات مباشرةً.
</p>

<p>
	وبذلك نكون قد تعلمنا كيفية استخدام العبارة الشرطية "if" في القوالب للتحكّم بما يُعرَض وفقًا لشروط معينة، كما استخدمنا حلقات "for" التكرارية للتنقل بين عناصر قوائم بايثون وعرض كل منها، وتعلمنا كيفية استخدام المتغير "loop" الخاص في جينجا، وسنستخدم في الخطوة التالية مرشّحات جينجا للتحكّم بكيفية عرض بيانات المتغير.
</p>

<h2>
	الخطوة 5 – استخدام المرشحات
</h2>

<p>
	سنتعلّم في هذه الخطوة كيفيّة استخدام مُرشحات جينجا في القوالب، إذ سنستخدم المُرشّح <code>upper</code> لتحويل كامل نصوص التعليقات التي أضفناها في الخطوة السابقة (أو الواردة من قاعدة البيانات في الواقع العملي) إلى حالة أحرف كبيرة، في حين سنستخدم المرشح <code>join</code> لربط عدّة سلاسل نصيّة لتصبح سلسلةً واحدة، كما سنتعرّف على كيفيّة تصيّير شيفرة HTML موثوقة وآمنة دون الحاجة لعزلها باستخدام المرشح <code>safe</code>.
</p>

<p>
	بدايةً سنعمل على تحويل التعليقات في صفحة التعليقات إلى حالة الأحرف الكبيرة، لذا سنفتح القالب "comments.html" للتعديل عليه:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_116" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln"> user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">comments</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	ثمّ سنعدله ليصبح كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_118" style="">
<span class="pun">{%</span><span class="pln"> extends </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">

</span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">h1</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="typ">Comments</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">div style</span><span class="pun">=</span><span class="str">"width: 50%; margin: auto"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> comment </span><span class="kwd">in</span><span class="pln"> comments </span><span class="pun">%}</span><span class="pln">
            </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> loop</span><span class="pun">.</span><span class="pln">index </span><span class="pun">!=</span><span class="pln"> </span><span class="lit">2</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">div style</span><span class="pun">=</span><span class="str">"padding: 10px;
                            background-color: #EEE;
                            margin: 20px"</span><span class="pun">&gt;</span><span class="pln">
                    </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;#{{</span><span class="pln"> loop</span><span class="pun">.</span><span class="pln">index </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                    </span><span class="pun">&lt;</span><span class="pln">p style</span><span class="pun">=</span><span class="str">"font-size: 24px"</span><span class="pun">&gt;{{</span><span class="pln"> comment </span><span class="pun">|</span><span class="pln"> upper </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">{%</span><span class="pln"> endif </span><span class="pun">%}</span><span class="pln">
        </span><span class="pun">{%</span><span class="pln"> endfor </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	أضفنا مرشح <code>upper</code> باستخدام رمز <code>|</code> الذي يمثل أنبوبًا pipe، والذي سيعمل على تعديل قيمة متغير <code>comment</code> إلى حالة الأحرف الكبيرة.
</p>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	وبعد التأكد من كون خادم التطوير ما يزال قيد التشغيل، ننتقل إلى صفحة التعليقات باستخدام المتصفح:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_120" style="">
<span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln">comments</span></pre>

<p>
	ونلاحظ أنّ جميع التعليقات أصبحت في حالة الأحرف الكبيرة بعد تطبيق المرشح، كما من الممكن تمرير وسطاء للمُرشّحات بين أقواسها. لتوضيح هذه النقطة: سنستخدم المُرشّح <code>join</code> لدمج جميع التعليقات الموجودة في القائمة <code>comments</code> ضمن سلسلة واحدة.
</p>

<p>
	لذا سنفتح قالب التعليقات:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_122" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln"> user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">comments</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	ونعدله كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_125" style="">
<span class="pun">{%</span><span class="pln"> extends </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">

</span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">h1</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="typ">Comments</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">div style</span><span class="pun">=</span><span class="str">"width: 50%; margin: auto"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> comment </span><span class="kwd">in</span><span class="pln"> comments </span><span class="pun">%}</span><span class="pln">
            </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> loop</span><span class="pun">.</span><span class="pln">index </span><span class="pun">!=</span><span class="pln"> </span><span class="lit">2</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">div style</span><span class="pun">=</span><span class="str">"padding: 10px;
                            background-color: #EEE;
                            margin: 20px"</span><span class="pun">&gt;</span><span class="pln">
                    </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;#{{</span><span class="pln"> loop</span><span class="pun">.</span><span class="pln">index </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                    </span><span class="pun">&lt;</span><span class="pln">p style</span><span class="pun">=</span><span class="str">"font-size: 24px"</span><span class="pun">&gt;{{</span><span class="pln"> comment </span><span class="pun">|</span><span class="pln"> upper </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">{%</span><span class="pln"> endif </span><span class="pun">%}</span><span class="pln">
        </span><span class="pun">{%</span><span class="pln"> endfor </span><span class="pun">%}</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">hr</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;{{</span><span class="pln"> comments </span><span class="pun">|</span><span class="pln"> join</span><span class="pun">(</span><span class="str">" | "</span><span class="pun">)</span><span class="pln"> </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	أضفنا في الشيفرة السابقة الوسم <code>&lt;hr&gt;</code> (الذي يُصيّر مثل فاصل أفقي)، والوسم <code>&lt;div&gt;</code> (الذي يُستخدم لأغراض التنسيق وتجميع العناصر)، إذ دمجنا جميع التعليقات الموجودة في القائمة <code>comments</code> باستخدام المُرشّح <code>()join</code>.
</p>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	وبتحديث صفحة التعليقات ستظهر كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="109078" href="https://academy.hsoub.com/uploads/monthly_2022_10/633d1674c4088_sixthabout.png.8106b694163cc02d2144d80caac038fe.png" rel=""><img alt="sixthabout" class="ipsImage ipsImage_thumbnailed" data-fileid="109078" data-unique="bwhw7e31b" src="https://academy.hsoub.com/uploads/monthly_2022_10/633d167610999_sixthabout.thumb.png.0672f2eb050644976bc3d5451337f60f.png" style="width: 650px; height: auto;"></a>
</p>

<p>
	نلاحظ عرض كافّة محتويات القائمة <code>comments</code> بحيث تكون التعليقات مفصولة عن بعضها برمز الشريط العمودي pipe وهي القيمة المُمررة وسيطًا إلى المُرشّح <code>()join</code>.
</p>

<p>
	ومن المُرشّحات المهمّة الأُخرى المُرشّح <code>safe</code> الذي يساعد في تصيّير شيفرة HTML موثوقة في المتصفح، ولتوضيح ذلك سنضيف نصًا يحتوي على وسم HTML ما إلى قالب التعليقات (لنرى هل سيُصيّر في المُتصفّح أم سيُعامل على أنه نص عادي) باستخدام مُحدّد جينجا "{{ }}". نحصل في التطبيق العملي على هذا النص (التعليق) مثل متغير آتٍ من الخادم، ومن ثمّ سنعدّل وسيط المُرشّح <code>()join</code> ليكون وسم <code>&lt;hr&gt;</code> (أي الفصل بين التعليقات بشريط أفقي) بدلًا من الشريط العمودي pipe.
</p>

<p>
	لذا سنفتح قالب التعليقات:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_130" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln"> user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">comments</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	ونعدّله ليصبح كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_132" style="">
<span class="pun">{%</span><span class="pln"> extends </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">

</span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">h1</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="typ">Comments</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">div style</span><span class="pun">=</span><span class="str">"width: 50%; margin: auto"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> comment </span><span class="kwd">in</span><span class="pln"> comments </span><span class="pun">%}</span><span class="pln">
            </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> loop</span><span class="pun">.</span><span class="pln">index </span><span class="pun">!=</span><span class="pln"> </span><span class="lit">2</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">div style</span><span class="pun">=</span><span class="str">"padding: 10px;
                            background-color: #EEE;
                            margin: 20px"</span><span class="pun">&gt;</span><span class="pln">
                    </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;#{{</span><span class="pln"> loop</span><span class="pun">.</span><span class="pln">index </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                    </span><span class="pun">&lt;</span><span class="pln">p style</span><span class="pun">=</span><span class="str">"font-size: 24px"</span><span class="pun">&gt;{{</span><span class="pln"> comment </span><span class="pun">|</span><span class="pln"> upper </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">{%</span><span class="pln"> endif </span><span class="pun">%}</span><span class="pln">
        </span><span class="pun">{%</span><span class="pln"> endfor </span><span class="pun">%}</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">hr</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">{{</span><span class="pln"> </span><span class="str">"&lt;h1&gt;COMMENTS&lt;/h1&gt;"</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;{{</span><span class="pln"> comments </span><span class="pun">|</span><span class="pln"> join</span><span class="pun">(</span><span class="str">" &lt;hr&gt; "</span><span class="pun">)</span><span class="pln"> </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	أضفنا القيمة <code>&lt;h1&gt;COMMENTS&lt;/h1&gt;</code> وغيرنا وسيط مُرشّح الدمج ليصبح وسم <code>&lt;hr&gt;</code>.
</p>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	وبتحديث صفحة التعليقات تظهر صفحةٌ مشابهةٌ لما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="109077" href="https://academy.hsoub.com/uploads/monthly_2022_10/633d167160ada_seventhabout.png.074be436f0c8a3f76b2da1b013d43236.png" rel=""><img alt="seventhabout" class="ipsImage ipsImage_thumbnailed" data-fileid="109077" data-unique="16n119wo9" src="https://academy.hsoub.com/uploads/monthly_2022_10/633d1672d4475_seventhabout.thumb.png.a2d3ef3218107f5a3a190c6d30792c07.png" style="width: 650px; height: auto;"></a>
</p>

<p>
	نلاحظ أنّ وسوم HTML التي أضفناها ضمن التعليقات لم تُصيَّر، وهي ميزة أمان مهمّة في جينجا، لأنّ وسوم HTML قد تكون ضارة أو خبيثة، ويمكن أن تؤدي إلى هجمات البرمجة العابرة للمواقع XSS.
</p>

<p>
	أمّا لتصيير وسوم HTML المُضافة السابقة نفتح ملف قالب التعليقات:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_136" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln"> user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">comments</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	ونعدله بإضافة المُرشّح <code>safe</code>، للدلالة على أنّ هذه الوسوم آمنة ونريد تصييّرها:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_138" style="">
<span class="pun">{%</span><span class="pln"> extends </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">

</span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">h1</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="typ">Comments</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">div style</span><span class="pun">=</span><span class="str">"width: 50%; margin: auto"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> comment </span><span class="kwd">in</span><span class="pln"> comments </span><span class="pun">%}</span><span class="pln">
            </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> loop</span><span class="pun">.</span><span class="pln">index </span><span class="pun">!=</span><span class="pln"> </span><span class="lit">2</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">div style</span><span class="pun">=</span><span class="str">"padding: 10px;
                            background-color: #EEE;
                            margin: 20px"</span><span class="pun">&gt;</span><span class="pln">
                    </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;#{{</span><span class="pln"> loop</span><span class="pun">.</span><span class="pln">index </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                    </span><span class="pun">&lt;</span><span class="pln">p style</span><span class="pun">=</span><span class="str">"font-size: 24px"</span><span class="pun">&gt;{{</span><span class="pln"> comment </span><span class="pun">|</span><span class="pln"> upper </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">{%</span><span class="pln"> endif </span><span class="pun">%}</span><span class="pln">
        </span><span class="pun">{%</span><span class="pln"> endfor </span><span class="pun">%}</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">hr</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">{{</span><span class="pln"> </span><span class="str">"&lt;h1&gt;COMMENTS&lt;/h1&gt;"</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> safe </span><span class="pun">}}</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;{{</span><span class="pln"> comments </span><span class="pun">|</span><span class="pln"> join</span><span class="pun">(</span><span class="str">" &lt;hr&gt; "</span><span class="pun">)</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> safe </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	نلاحظ أنّه من الممكن ربط المُرشّحات كما هو ظاهر في السطر:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_2053_140" style="">
<span class="tag">&lt;p&gt;</span><span class="pln">{{ comments | join(" </span><span class="tag">&lt;hr&gt;</span><span class="pln"> ") | safe }}</span><span class="tag">&lt;/p&gt;</span></pre>

<p>
	إذ يُطبَّق كل مُرشّح على نتيجة خرج المُرشّح السابق له.
</p>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	وبتحديث صفحة التعليقات نجد أن وسوم HTML قد جرى تصييّرها كما هو مُتوقّع مع استخدام المُرشّح "safe":
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="109071" href="https://academy.hsoub.com/uploads/monthly_2022_10/633d166238069_8thcomments.png.3342e4eae8b1855944082ca7125759db.png" rel=""><img alt="8thcomments" class="ipsImage ipsImage_thumbnailed" data-fileid="109071" data-unique="0jzvpvela" src="https://academy.hsoub.com/uploads/monthly_2022_10/633d166374aea_8thcomments.thumb.png.868414fc1e0032462bdcbdb3c7b4cc25.png" style="width: 720px; height: auto;"></a>
</p>

<p>
	<strong>تنبيه:</strong> قد يجعل تطبيق المُرشّح "safe" لشيفرة HTML غير موثوقة المصدر تطبيقنا عرضةً لهجمات البرمجة عابرة المواقع، لذا لا يُفضّل استخدامه إلّا إذا كانت شيفرات HTML المُراد تصييّرها موثوقة المصدر.
</p>

<p>
	تعرّفنا في هذه الخطوة على كيفيّة استخدام المُرشّحات في قوالب جينجا لتعديل قيم المتغيرات، وفيما يلي سنستخدم حزمة بوتستراب Bootstrap لتنسيق مظهر التطبيق وجعله أكثر جاذبية بصريًا.
</p>

<h2>
	الخطوة السادسة – استخدام بوتستراب
</h2>

<p>
	سنتعرّف في هذه الخطوة على كيفيّة استخدام حزمة بوتستراب لتنسيق مظهر التطبيق، إذ سنضيف شريط تنقّل في القالب الأساسي بحيث يظهر في كافّة الصفحات التي ترث شيفراتها منه.
</p>

<p>
	يساعد إطار العمل <a href="https://academy.hsoub.com/programming/css/bootstrap/" rel="">بوتستراب bootstrap</a> على إضافة التنسيقات على التطبيق ليبدو أكثر جاذبية بصريًا، كما سيساعدنا على تمكين ميزة الصفحات المتوافقة مع المتصفحات في تطبيق الويب، ما يضمن عمله بصورةٍ جيدة في المتصفحات الخاصة بالجوال، دون كتابة شيفرات <a href="https://wiki.hsoub.com/HTML" rel="external">HTML</a> و <a href="https://wiki.hsoub.com/CSS" rel="external">CSS</a> وجافا سكربت <a href="https://wiki.hsoub.com/JavaScript" rel="external">JavaScript</a> لتحقيق هذه الغاية.
</p>

<p>
	وحتّى نستخدم بوتستراب سنضيفه إلى القالب الأساسي بحيث يسهل استخدامه في جميع القوالب الأُخرى.
</p>

<p>
	لذا سنفتح القالب الأساسي "base.html" للتعديل عليه:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_144" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln"> user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">base</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	ونعدله كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_146" style="">
<span class="pun">&lt;!</span><span class="pln">doctype html</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">html lang</span><span class="pun">=</span><span class="str">"en"</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="pun">&lt;</span><span class="pln">head</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;!--</span><span class="pln"> </span><span class="typ">Required</span><span class="pln"> meta tags </span><span class="pun">--&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">meta charset</span><span class="pun">=</span><span class="str">"utf-8"</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">meta name</span><span class="pun">=</span><span class="str">"viewport"</span><span class="pln"> content</span><span class="pun">=</span><span class="str">"width=device-width, initial-scale=1"</span><span class="pun">&gt;</span><span class="pln">

    </span><span class="pun">&lt;!--</span><span class="pln"> </span><span class="typ">Bootstrap</span><span class="pln"> CSS </span><span class="pun">--&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">link href</span><span class="pun">=</span><span class="str">"https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css"</span><span class="pln"> rel</span><span class="pun">=</span><span class="str">"stylesheet"</span><span class="pln"> integrity</span><span class="pun">=</span><span class="str">"sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We"</span><span class="pln"> crossorigin</span><span class="pun">=</span><span class="str">"anonymous"</span><span class="pun">&gt;</span><span class="pln">

    </span><span class="pun">&lt;</span><span class="pln">title</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="typ">FlaskApp</span><span class="pun">&lt;/</span><span class="pln">title</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="pun">&lt;/</span><span class="pln">head</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="pun">&lt;</span><span class="pln">body</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">nav </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"navbar navbar-expand-lg navbar-light bg-light"</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"container-fluid"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">a </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"navbar-brand"</span><span class="pln"> href</span><span class="pun">=</span><span class="str">"{{ url_for('hello') }}"</span><span class="pun">&gt;</span><span class="typ">FlaskApp</span><span class="pun">&lt;/</span><span class="pln">a</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">button </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"navbar-toggler"</span><span class="pln"> type</span><span class="pun">=</span><span class="str">"button"</span><span class="pln"> data</span><span class="pun">-</span><span class="pln">bs</span><span class="pun">-</span><span class="pln">toggle</span><span class="pun">=</span><span class="str">"collapse"</span><span class="pln"> data</span><span class="pun">-</span><span class="pln">bs</span><span class="pun">-</span><span class="pln">target</span><span class="pun">=</span><span class="str">"#navbarNav"</span><span class="pln"> aria</span><span class="pun">-</span><span class="pln">controls</span><span class="pun">=</span><span class="str">"navbarNav"</span><span class="pln"> aria</span><span class="pun">-</span><span class="pln">expanded</span><span class="pun">=</span><span class="str">"false"</span><span class="pln"> aria</span><span class="pun">-</span><span class="pln">label</span><span class="pun">=</span><span class="str">"Toggle navigation"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">span </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"navbar-toggler-icon"</span><span class="pun">&gt;&lt;/</span><span class="pln">span</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">button</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"collapse navbar-collapse"</span><span class="pln"> id</span><span class="pun">=</span><span class="str">"navbarNav"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">ul </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"navbar-nav"</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">li </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"nav-item"</span><span class="pun">&gt;</span><span class="pln">
              </span><span class="pun">&lt;</span><span class="pln">a </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"nav-link"</span><span class="pln"> href</span><span class="pun">=</span><span class="str">"{{ url_for('comments') }}"</span><span class="pun">&gt;</span><span class="typ">Comments</span><span class="pun">&lt;/</span><span class="pln">a</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">li</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">li </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"nav-item"</span><span class="pun">&gt;</span><span class="pln">
              </span><span class="pun">&lt;</span><span class="pln">a </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"nav-link"</span><span class="pln"> href</span><span class="pun">=</span><span class="str">"{{ url_for('about') }}"</span><span class="pun">&gt;</span><span class="typ">About</span><span class="pun">&lt;/</span><span class="pln">a</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">li</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">ul</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">nav</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"container"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">

    </span><span class="pun">&lt;!--</span><span class="pln"> </span><span class="typ">Optional</span><span class="pln"> </span><span class="typ">JavaScript</span><span class="pln"> </span><span class="pun">--&gt;</span><span class="pln">

    </span><span class="pun">&lt;</span><span class="pln">script src</span><span class="pun">=</span><span class="str">"https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js"</span><span class="pln"> integrity</span><span class="pun">=</span><span class="str">"sha384-U1DAWAznBHeqEIlVSCgzq+c9gqGAJn5c/t99JyeKa9xxaYpSvHU5awsuZVVFIhvj"</span><span class="pln"> crossorigin</span><span class="pun">=</span><span class="str">"anonymous"</span><span class="pun">&gt;&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">

  </span><span class="pun">&lt;/</span><span class="pln">body</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">html</span><span class="pun">&gt;</span></pre>

<p>
	الجزء الأكبر من الشيفرة السابقة هو تعليمات لازمة لعمل بوتستراب، إذ تزوّد الوسوم <code>&lt;meta&gt;</code> متصفح الويب بالمعلومات، في حين ينشئ الوسم <code>&lt;link&gt;</code> ضمن القسم <code>&lt;head&gt;</code> ارتباطًا إلى ملفات CSS الخاصة ببوتستراب، وفي الجزء الأخير منه يُضمِّن الوسم <code>&lt;script&gt;</code> ارتباطًا إلى شيفرة جافا سكربت اختيارية. كما تتضمّن الشيفرة أجزاءً خاصةً بمحرك القوالب جينجا والتي قد وضّحناها فيما سبق، وكذلك استخدامًا لوسوم مُحدّدة وأصناف من CSS لتحديد كيفيّة عرض كل عنصر في بوتستراب.
</p>

<p>
	وقد استخدمنا في الشيفرة أعلاه وسم رابط <code>&lt;a&gt;</code> من الصنف <code>navbar-brand</code> الخاص بإضافة رابط العلامة المميزة (شعار الشركة مثلًا) إلى شريط التصفّح وذلك ضمن الوسم <code>&lt;nav&gt;</code> (الخاص بشريط التصفّح)، وفيه نحدّد رابط العلامة المطلوب، أمّا عن الروابط الاعتيادية مثل الروابط المؤدية إلى صفحات أُخرى من التطبيق، فمن الممكن تضمينها في الوسم <code>&lt;"ul class="navbar-nav&gt;</code> الحاوي على عنصر قائمة <code><a href="https://wiki.hsoub.com/HTML/li" rel="external">&lt;li&gt;</a></code> ضمن عنصر روابط <code>&lt;a&gt;</code> وبالتالي من الممكن إضافة عدّة روابط.
</p>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	وبعد التأكد من كون خادم التطوير ما يزال قيد التشغيل، ننتقل إلى الصفحة الرئيسية من التطبيق في المتصفح:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2053_157" style="">
<span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span></pre>

<p>
	فتظهر صفحةٌ مشابهةٌ لما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="109072" href="https://academy.hsoub.com/uploads/monthly_2022_10/633d166452016_9thindex.png.8fcee16ff996eec1c4c19872f6e46062.png" rel=""><img alt="9thindex" class="ipsImage ipsImage_thumbnailed" data-fileid="109072" data-unique="gzml541pt" src="https://academy.hsoub.com/uploads/monthly_2022_10/633d16655cfe1_9thindex.thumb.png.1ddca47e7a9ee100cedba72f4956ecea.png" style="width: 720px; height: auto;"></a>
</p>

<p>
	وبذلك أصبح من الممكن استخدام مكونات بوتستراب لتنسيق مظهر العناصر في تطبيق فلاسك في جميع القوالب.
</p>

<h2>
	الخاتمة
</h2>

<p>
	تعرّفنا في هذا المقال على كيفيّة استخدام قوالب HTML في تطبيق فلاسك، إذ استخدمنا المتغيرات لتمرير البيانات من الخادم إلى القوالب مستفيدين من مفهوم وراثة القوالب لتجنُّب تكرار الشيفرات، كما استخدمنا عناصر مثل عبارات "if" الشرطية وحلقات "for" التكرارية، وربطنا بين صفحات مختلفة من صفحات التطبيق، كما تعرّفنا على كيفية تطبيق المُرشّحات لتعديل نص ما أو لعرض شيفرة HTML موثوقة، ونهايةً استخدمنا بوتستراب لتنسيق مظهر التطبيق.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-use-templates-in-a-flask-application" rel="external nofollow">How To Use Templates in a Flask Application</a> لصاحبه Abdelhadi Dyouri.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال التالي: <a href="https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A3%D8%AE%D8%B7%D8%A7%D8%A1-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1738/" rel="">كيفية التعامل مع الأخطاء في تطبيقات فلاسك Flask</a>
	</li>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%B9%D9%84%D9%85-%D8%A8%D9%86%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9%D9%83-%D8%A7%D9%84%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1715/" rel="">تعلم بناء موقعك الإلكتروني الأول باستخدام إطار عمل فلاسك Flask بلغة بايثون</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A3%D8%B7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%81%D9%8A-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D9%81%D9%84%D8%A7%D8%B3%D9%83-%D9%86%D9%85%D9%88%D8%B0%D8%AC%D8%A7-r1547/" rel="">استخدام أطر العمل في برمجة تطبيقات الويب: فلاسك نموذجا</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1737</guid><pubDate>Wed, 05 Oct 2022 07:50:02 +0000</pubDate></item><item><title>&#x62A;&#x639;&#x644;&#x645; &#x628;&#x646;&#x627;&#x621; &#x645;&#x648;&#x642;&#x639;&#x643; &#x627;&#x644;&#x625;&#x644;&#x643;&#x62A;&#x631;&#x648;&#x646;&#x64A; &#x627;&#x644;&#x623;&#x648;&#x644; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x625;&#x637;&#x627;&#x631; &#x639;&#x645;&#x644; &#x641;&#x644;&#x627;&#x633;&#x643; Flask &#x628;&#x644;&#x63A;&#x629; &#x628;&#x627;&#x64A;&#x62B;&#x648;&#x646;</title><link>https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%B9%D9%84%D9%85-%D8%A8%D9%86%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9%D9%83-%D8%A7%D9%84%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1715/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_09/63297126c834b_---------Flask---.jpg.33fc678ea3f21c92666d6886d6c838d2.jpg" /></p>

<p>
	يُعد <a href="https://academy.hsoub.com/programming/python/flask/" rel="">فلاسك</a> إطار عمل للويب مبني بلغة <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D9%85%D8%B1%D8%AC%D8%B9-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%AA%D8%B9%D9%84%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r735/" rel="">بايثون</a>، ويتميز بكونه صغير الحجم وسهل المعالجة، ويوفّر أيضًا عدة أدوات وميزات من شأنها جعل إنشاء تطبيقات الويب في <a href="https://wiki.hsoub.com/Python" rel="external">لغة بايثون</a> أسهل، مانحًا المطورين مرونةً في العمل، كما أنّه أبسط للاستخدام من قِبل المطورين المبتدئين نظرًا لإمكانية إنشاء تطبيق ويب كامل بسرعة باستخدام ملفٍ وحيدٍ مكتوب بلغة بايثون. إضافةً لما سبق، يتميز فلاسك بكونه قابلًا للتوسّع والوراثة دون أن يفرض أي بنية هرمية لطريقة عرض الملفات، كما أنّه لا يتطلب أي شيفرات برمجية معقدّة استهلالية قبل البدء باستخدامه.
</p>

<p>
	سيمكِّننا تعلُّم فلاسك من إنشاء تطبيقات ويب بسهولة وسرعة باستخدام لغة بايثون، إذ سنستخدم مكتبات بايثون لإضفاء ميزات إضافية على تطبيق الويب، مثل تخزين البيانات في <a href="https://academy.hsoub.com/programming/sql/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%B9%D9%86-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-r584/" rel="">قواعد البيانات</a> والتحقق من بيانات نماذج الإدخال في الويب.
</p>

<p>
	سنبني في هذا المقال تطبيق ويب صغير يعمل على تصيّير النصوص المكتوبة <a href="https://academy.hsoub.com/programming/html/%D8%AA%D8%B9%D9%84%D9%85-%D9%84%D8%BA%D8%A9-html-r1702/" rel="">بلغة HTML</a> ضمن المتصفح، إذ سنثبّت فلاسك، ثمّ سنكتب الشيفرة البرمجية الخاصّة بتطبيق فلاسك ونشغّله في وضع التطوير، وسنستخدم مفهوم التوجيه للتنقّل بين عدة صفحات ويب تؤدي وظائف مختلفة ضمن التطبيق؛ كما سنسمح للمستخدمين من خلال دوال فلاسك بالتفاعل مع التطبيق عبر وجهات routes ديناميكية، ونهايةً سنعمل على حل المشكلات الناتجة عن أي أخطاء باستخدام مُنقّح الأخطاء.
</p>

<h2>
	مستلزمات العمل
</h2>

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

<ul>
<li>
		توفُّر <a href="https://academy.hsoub.com/programming/python/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%AA%D9%87%D8%A7-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r714/" rel="">بيئة برمجة بايثون 3 محلية</a>، مثبّتة على حاسوبك، وسنفترض في مقالنا أن اسم مجلد المشروع هو "flask_app".
	</li>
	<li>
		فهم مبادئ بايثون 3، مثل أنماط البيانات والقوائم والدوال، وغيرها من المفاهيم المشابهة في <a href="https://academy.hsoub.com/tags/%D8%AF%D9%84%D9%8A%D9%84%20%D8%AA%D8%B9%D9%84%D9%85%20%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86/" rel="">بايثون 3</a>.
	</li>
	<li>
		فهم أساسيات لغة <a href="https://academy.hsoub.com/programming/html/" rel="">HTML</a>.
	</li>
</ul>
<h2>
	الخطوة الأولى – تثبيت فلاسك
</h2>

<p>
	سنفعّل في هذه الخطوة بيئة بايثون ونثبّت فلاسك باستخدام مثبِّت الحزم "pip".
</p>

<p>
	بدايةً، سنفعّل بيئة البرمجة في حال كانت غير مفعّلةٍ بعد كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_9" style="">
<span class="pln">$ source env</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">activate</span></pre>

<p>
	بعدها نثبّت فلاسك باستخدام الأمر <code>pip install</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_11" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ pip install flask</span></pre>

<p>
	وحالما ينتهي التثبيت، سيظهر الخرج مُتضمنًا مجموعة الحزم المُثبّتة كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_13" style="">
<span class="pun">...</span><span class="pln">
</span><span class="typ">Installing</span><span class="pln"> collected packages</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Werkzeug</span><span class="pun">,</span><span class="pln"> </span><span class="typ">MarkupSafe</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Jinja2</span><span class="pun">,</span><span class="pln"> itsdangerous</span><span class="pun">,</span><span class="pln"> click</span><span class="pun">,</span><span class="pln"> flask
</span><span class="typ">Successfully</span><span class="pln"> installed </span><span class="typ">Jinja2</span><span class="pun">-</span><span class="lit">3.0</span><span class="pun">.</span><span class="lit">1</span><span class="pln"> </span><span class="typ">MarkupSafe</span><span class="pun">-</span><span class="lit">2.0</span><span class="pun">.</span><span class="lit">1</span><span class="pln"> </span><span class="typ">Werkzeug</span><span class="pun">-</span><span class="lit">2.0</span><span class="pun">.</span><span class="lit">1</span><span class="pln"> click</span><span class="pun">-</span><span class="lit">8.0</span><span class="pun">.</span><span class="lit">1</span><span class="pln"> flask</span><span class="pun">-</span><span class="lit">2.0</span><span class="pun">.</span><span class="lit">1</span><span class="pln"> itsdangerous</span><span class="pun">-</span><span class="lit">2.0</span><span class="pun">.</span><span class="lit">1</span></pre>

<p>
	ومنه نلاحظ أنّه أثناء تثبيت فلاسك تُثبّت أيضًا عدة حزم ومستلزمات خاصة يستخدمها فلاسك في تنفيذ العديد من الدوال المختلفة.
</p>

<p>
	ومع نهاية هذه الخطوة نكون قد أنشأنا مجلد للتطبيق وفعّلنا البيئة الافتراضية وثبّتنا فلاسك، وبذلك نكون مستعدين للانتقال إلى الخطوة التالية المُتمثّلة بإنشاء تطبيق بسيط.
</p>

<h2>
	الخطوة الثانية – إنشاء تطبيق بسيط
</h2>

<p>
	يمكننا الآن البدء باستخدام فلاسك بعدما أصبحت البيئة البرمجية جاهزة، إذ سننشئ في هذه الخطوة تطبيق ويب مُصغّر باستخدام فلاسك وذلك ضمن ملف بايثون، الذي سنكتب فيه أيضًا شيفرات <a href="https://academy.hsoub.com/programming/html/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D9%84%D8%BA%D8%A9-html-r1687/" rel="">HTML </a>لإظهارها عند التشغيل ضمن المتصفح.
</p>

<p>
	الآن، سنفتح الملف المسمى "app.py" الموجود ضمن المجلد "flask_app" بهدف التعديل عليه وذلك باستخدام محرر النصوص نانو nano، أو أي محرّر آخر تفضّله:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_17" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ونكتب الشيفرة التالية ضمن الملف app.py:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_19" style="">
<span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pln">

app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln">


</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> hello</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="str">'&lt;h1&gt;Hello, World!&lt;/h1&gt;'</span></pre>

<p>
	نحفظ الملف ونغلقه. استوردنا في الجزء السابق من الشيفرة كائن فلاسك من حزمة فلاسك، ثم استخدمناه لإنشاء نسخةٍ instance فعليةٍ موجودةٍ في الذاكرة لتطبيق فلاسك Flask application instance باسم <code>app</code>، وليست مجرد كائن برمجي، ثمّ مررنا المتغير الخاص <code>__name__</code>، الذي سيخزّن اسم وحدة بايثون الحالية ليُعلم تطبيق فلاسك بمكان وجود هذه الوحدة، إذ لا بُدّ من إجراء هذه الخطوة كون فلاسك يهيّئ بعض المسارات اللازمة في الخلفية. وبمجرد إنشاء هذا التطبيق "app"، يمكنك استخدامه في معالجة طلبات الويب القادمة وإرسال الردود إلى المُستخدم.
</p>

<p>
	يكون المزخرف <code>app.route@</code> مسؤولًا عن تعديل دوال بايثون المألوفة لتصبح دوال عرض view في فلاسك، والتي تحوّل القيمة المُعادة من قِبل الدالة إلى استجابةٍ من نوع <a href="https://academy.hsoub.com/programming/general/%d9%85%d8%af%d8%ae%d9%84-%d8%a5%d9%84%d9%89-http-r73/" rel="">HTTP</a> تُعرض لدى عميل HTTP، الذي قد يكون متصفحًا مثلًا؛ فبمجرد تمرير "/" للدالة <code>()app.route@</code>، سينشئ الردود على طلبات الويب الواردة إلى الرابط "/"، والذي يمثّل الرابط الرئيسي في التطبيق، وبذلك ستعيد الدالة <code>()hello</code> السلسلة النصية "!Hello, World" ردًا على الطلب.
</p>

<p>
	وبذلك أصبح لدينا تطبيق فلاسك بسيط موجود ضمن ملف بايثون المُسمّى "app.py"، سنشغّل فيما يلي هذا التطبيق لتصيير نتيجة دالة العرض <code>()hello</code> ضمن متصفح ويب.
</p>

<h2>
	الخطوة الثالثة – تشغيل التطبيق
</h2>

<p>
	بعد إنشائنا الملف الذي يحتوي على تطبيق فلاسك، سنشغّله باستخدام واجهة أوامر فلاسك وذلك لتشغيل خادم التطوير وتصيير شيفرة HTML ضمن المتصفح والتي كتبناها لتكون القيمة المعادة من الدالة <code>()hello</code> في الخطوة السابقة.
</p>

<p>
	ولتشغيل تطبيق الويب الذي أنشأناه، وبعد التأكّد من وجودنا ضمن المجلد "flask_app" مع تفعيل البيئة الافتراضية، لا بُدّ من إرشاد فلاسك إلى موقع التطبيق (في حالتنا الملف ذو الاسم "app.py") وذلك باستخدام متغير بيئة فلاسك <code>FLASK_APP</code> على النحو التالي (مع ملاحظة أنّنا نستخدم الأمر <code>set</code> في بيئة ويندوز عوضًا عن الأمر <code>export</code>):
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_24" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_APP</span><span class="pun">=</span><span class="pln">app</span></pre>

<p>
	ثم نحدد وضع تشغيل التطبيق ليكون بوضع التطوير، وذلك باستخدام متغير بيئة فلاسك <code>Flask_ENV</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_26" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_ENV</span><span class="pun">=</span><span class="pln">development</span></pre>

<p>
	وبذلك نتمكّن من استخدام المنقّح لالتقاط الأخطاء.
</p>

<p>
	نهايةً، نشغّل التطبيق باستخدام الأمر <code>flask run</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_28" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ flask run</span></pre>

<p>
	حالما يعمل التطبيق، يظهر الخرج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_30" style="">
<span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Serving</span><span class="pln"> </span><span class="typ">Flask</span><span class="pln"> app </span><span class="str">"app"</span><span class="pln"> </span><span class="pun">(</span><span class="pln">lazy loading</span><span class="pun">)</span><span class="pln">
 </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Environment</span><span class="pun">:</span><span class="pln"> development
 </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Debug</span><span class="pln"> mode</span><span class="pun">:</span><span class="pln"> on
 </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Running</span><span class="pln"> on http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Press</span><span class="pln"> CTRL</span><span class="pun">+</span><span class="pln">C to quit</span><span class="pun">)</span><span class="pln">
 </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Restarting</span><span class="pln"> </span><span class="kwd">with</span><span class="pln"> stat
 </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Debugger</span><span class="pln"> </span><span class="kwd">is</span><span class="pln"> active</span><span class="pun">!</span><span class="pln">
 </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Debugger</span><span class="pln"> PIN</span><span class="pun">:</span><span class="pln"> </span><span class="lit">296</span><span class="pun">-</span><span class="lit">353</span><span class="pun">-</span><span class="lit">699</span></pre>

<p>
	يحتوي الخرج السابق على عدة معلومات، مثل:
</p>

<ul>
<li>
		اسم التطبيق المُشغَّل.
	</li>
	<li>
		بيئة التشغيل الحالية التي يعمل عليها التطبيق.
	</li>
	<li>
		عبارة <code>Debug mode:on</code> التي تشير إلى أن مُنقّح أخطاء فلاسك قيد التشغيل، وهو ذو فوائد عديدة أثناء عملية التطوير كونه يقدم رسائل خطأ مفصّلة عندما يحدث أي خلل، وهذا ما يجعل عملية تنقيح الأخطاء أسهل وأيسر.
	</li>
	<li>
		التطبيق يعمل على الحاسب المحلي وذلك على الرابط <code>/http://127.0.0.1:5000</code>، إذ أن "127.0.0.1" هو عنوان IP الذي يمثِّل الخادم المحلي localhost على حاسبك، و "5000:" هو رقم المنفذ.
	</li>
</ul>
<p>
	افتح المتصفح واكتب عنوان URL التالي "/http://127.0.0.1:5000"، ستظهر عبارة "!Hello, World" (ضمن تنسيق عنوان من المستوى الأوّل <code>&lt;h1&gt;</code>) استجابةً لهذا العنوان، وهذا ما يؤكد أن التطبيق يعمل بنجاح.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="108309" href="https://academy.hsoub.com/uploads/monthly_2022_09/6329712025ff5_FirstHelloworld.png.aea26a37b4fd6405625bc0e8ac0640f2.png" rel=""><img alt="First Hello world.png" class="ipsImage ipsImage_thumbnailed" data-fileid="108309" data-unique="wf62676ff" src="https://academy.hsoub.com/uploads/monthly_2022_09/6329712025ff5_FirstHelloworld.png.aea26a37b4fd6405625bc0e8ac0640f2.png"></a>
</p>

<p>
	وإذا أردنا إيقاف خادم التطوير، نضغط على "CTRL+C".
</p>

<p>
	<strong>تنبيه:</strong> يستخدم فلاسك <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">خادم ويب</a> مبسّط لاستضافة تطبيق الويب في بيئة التطوير، ما يعني أن مُنقّح أخطاء فلاسك قيد التشغيل أيضًا كي يجعل التقاط الأخطاء أسهل، ولا ينبغي استخدام خادم التطوير هذا عندما يُنقل التطبيق إلى مرحلة التشغيل الفعلي أي نشر المنتج.
</p>

<p>
	يمكن في هذه المرحلة الإبقاء على خادم التطوير قيد التشغيل في الطرفية terminal الخاصة به، ومن ثم فتح نافذة طرفية جديدة وتغيير المسار فيها إلى مسار مجلد المشروع حيث يتواجد ملف "hello.py"، ثم بعد ذلك تفعيل البيئة الافتراضية (السبب مبيّنٌ في الملاحظة أدناه)، وتهيّئة متغيرات البيئة <code>FLASK_ENV</code> و <code>FLASK_APP</code>، ومتابعة الخطوات التالية. (ذُكرت هذه الأوامر سابقًا في هذه الخطوة).
</p>

<p>
	<strong>ملاحظة:</strong> من الضروري تفعيل البيئة الافتراضية لدى فتح طرفية جديدة، أو إغلاق الطرفية الحالية التي تشغّل خادم التطوير عليها وتود إعادة تشغيله، ولا بدّ من إعداد متغيرات البيئة <code>FLASK_ENV</code> و <code>FLASK_APP</code> ليعمل الأمر <code>flask run</code> بصورةٍ صحيحة.
</p>

<p>
	كل ما عليك فعله هو تشغيل الخادم مرةً واحدةً في نافذة طرفية واحدة.
</p>

<p>
	لا يمكن تشغيل تطبيق فلاسك آخر باستخدام الأمر <code>flask run</code> نفسه خلال فترة عمل خادم تطوير تطبيقات فلاسك، كونه يستخدم المنفذ رقم 5000 افتراضيًا، وحالما يُحجَز هذا المنفذ يصبح غير متاحًا لتشغيل أي تطبيقٍ آخر، وفي حال فعلت ذلك ستظهر رسالة خطأ مشابهةٍ لما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_33" style="">
<span class="typ">OSError</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="typ">Errno</span><span class="pln"> </span><span class="lit">98</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Address</span><span class="pln"> already </span><span class="kwd">in</span><span class="pln"> use</span></pre>

<p>
	ويمكن حل لهذه المشكلة، إمّا بإيقاف الخادم العامل حاليًا عن طريق الضغط على "CTRL+C" ومن ثم تنفيذ الأمر <code>flask run</code> مجدّدًا، أو في حال رغبتك بتشغيل كلا التطبيقين في نفس الوقت، فمن الممكن تمرير رقم منفذٍ مختلف باستخدام الوسيط <code>p-</code>.
</p>

<p>
	سنستخدم الأمر التالي لتشغيل تطبيقٍ آخر يستخدم المنفذ "5001" مثالًا حول طريقة الحل هذه:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_35" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ flask run </span><span class="pun">-</span><span class="pln">p </span><span class="lit">5001</span></pre>

<p>
	وبذلك يصبح لدينا تطبيق أوّل يعمل على الرايط "/http://172.0.0.1:5000" وآخر يعمل على الرابط "/http://172.0.0.1:5001" إن احتجنا لذلك.
</p>

<p>
	وبذلك يكون قد أصبح لدينا مع نهاية هذه الخطوة تطبيق ويب صغير باستخدام فلاسك، وبعد أن شغّلنا وعرضنا المعلومات في متصفح الويب، سنتعلم فيما يلي كيفية إنشاء وجهات واستخدامها لتخديم عدّة صفحات ويب.
</p>

<h2>
	الخطوة الرابعة – الوجهات ودوال العرض في فلاسك
</h2>

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

<p>
	تُعرّف الوجهة route بأنها رابطٌ يُستخدم لتحديد ما سيستقبله المستخدم لدى زيارة تطبيق الويب على متصفحه، فمثلًا يشير الرابط "/http://127.0.0.1:5000" إلى الوجهة الرئيسية التي يمكن استخدامها لعرض الصفحة الرئيسية للتطبيق، بينما يشير الرابط "http://127.0.0.1:5000/about" إلى وجهةٍ أخرى وهو صفحة المعلومات التي تعطي الزوار معلومات حول تطبيق الويب، وعلى نحوٍ مشابه يمكن إنشاء وجهة تسمح للمستخدمين بتسجيل الدخول إلى تطبيق الويب عبر الرابط "http://127.0.0.1:5000/login" مثلًا.
</p>

<p>
	يستخدم تطبيق فلاسك الذي أنشأناه حاليًا وجهةً واحدةً تخدّم الزوار الذين يطلبون الرابط الرئيسي "/http://127.0.0.1:5000". الآن، ولنبين كيفية إضافة صفحة ويب جديدة إلى تطبيقنا، سنعدّل ملف التطبيق لإضافة وجهةٍ جديدة تؤمن معلومات حول التطبيق عبر الرابط "http://127.0.0.1:5000/about".
</p>

<p>
	لذا سنفتح الملف "app.py" من أجل التعديل:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_37" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ثمّ نعدّل الملف من خلال إضافة الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_39" style="">
<span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pln">

app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln">


</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> hello</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="str">'&lt;h1&gt;Hello, World!&lt;/h1&gt;'</span><span class="pln">


</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/about/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> about</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="str">'&lt;h3&gt;This is a Flask web application.&lt;/h3&gt;'</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	أضفنا في الشيفرة السابقة دالةً جديدةً باسم <code>()about</code>، والتي صمّمناها باستخدام المًزخرف <code>()app.route@</code>، الذي يحوّلها إلى دالة عرض قادرة على التعامل مع الطلبات الواردة إلى الرابط "http://127.0.0.1:5000/about".
</p>

<p>
	بعد التأكد من كون خادم التطوير ما يزال قيد التشغيل، نزور الرابط التالي في المتصفح:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_42" style="">
<span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln">about</span></pre>

<p>
	فيظهر النص "This is a Flask web application" مصّيرًا ضمن تنسيق عنوان من المستوى الثالث <code>&lt;h3&gt;</code> في HTML؛ كما من الممكن استخدام عدّة وجهات في دالة فلاسك الواحدة، فمثلًا من الممكن تخديم الصفحة الرئيسية للتطبيق بكل من الوجهتين "/" و "/index/"، ولتنفيذ ذلك سنفتح الملف "app.py" لتعديله:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_44" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	سنعدّل الملف بإضافة مُزخرف آخر إلى دالة فلاسك <code>()hello</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_46" style="">
<span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pln">

app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">)</span><span class="pln">
</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/index/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> hello</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="str">'&lt;h1&gt;Hello, World!&lt;/h1&gt;'</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/about/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> about</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="str">'&lt;h3&gt;This is a Flask web application.&lt;/h3&gt;'</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	وبعد إضافة المُزخرف الثاني أصبح بالإمكان الوصول إلى الصفحة الرئيسية للتطبيق من خلال أي من الرابطين "/http://127.0.0.1:5000" أو "http://127.0.0.1:5000/index".
</p>

<p>
	تعرّفنا فيما سبق على الوجهات وكيفية استخدامها لإنشاء دوال عرض في فلاسك وكيفية إضافة وجهات جديدة إلى التطبيق. فيما يلي سنستخدم الوجهات الديناميكية المرنة للسماح للمستخدمين بالتحكم باستجابة التطبيق.
</p>

<h2>
	الخطوة الخامسة - الوجهات الديناميكية
</h2>

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

<p>
	لا يتفاعل المستخدمون عادةً مع تطبيق الويب من خلال التعديل اليدوي للرابط، بل يتفاعلون مع عناصر موجودة في الصفحة تؤدي إلى روابط مختلفة بناءً على مدخلاتهم وتصرفاتهم ضمن الصفحة، ولكن وبهدف التبسيط في هذا المقال، سنعدّل الرابط لنبيّن كيفية جعل التطبيق يستجيب بصورةٍ مختلفة بروابط مختلفة.
</p>

<p>
	سنفتح بدايةً الملف app.py لتعديله:
</p>

<pre class="ipsCode">
(env)user@localhost:$ nano app.py
</pre>

<p>
	مع ملاحظة انّه إذا سمحنا للمستخدم بإدخال شيءٍ ما إلى التطبيق، مثل إدخال قيمة في الرابط كما سنفعل في التعديل التالي، فيجب الانتباه إلى أنّ التطبيق يجب ألا يُظهر بيانات غير موثوقة (البيانات التي أدخلها المستخدم)، ولكي نعرض بيانات المستخدم بصورةٍ آمنة، سنستخدم الدالة "()escape" الموجودة ضمن حزمة البناء الآمن "markupsafe" المُثبتة بالتوازي مع تثبيت فلاسك.
</p>

<p>
	الأن، سنعدّل الملف "app.py" بإضافة السطر التالي في بدايته أعلى أمر استيراد <code>Flask</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_48" style="">
<span class="kwd">from</span><span class="pln"> markupsafe </span><span class="kwd">import</span><span class="pln"> escape
</span><span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pln">

</span><span class="com"># ...</span></pre>

<p>
	ثمّ سنضيف الوجهة التالية إلى نهاية الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_50" style="">
<span class="com"># ...</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/capitalize/&lt;word&gt;/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> capitalize</span><span class="pun">(</span><span class="pln">word</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="str">'&lt;h1&gt;{}&lt;/h1&gt;'</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="pln">escape</span><span class="pun">(</span><span class="pln">word</span><span class="pun">.</span><span class="pln">capitalize</span><span class="pun">()))</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	تملك الوجهة الجديدة قسمًا متغيرًا مُضمّنًا في الوسم <code>&lt;word&gt;</code>، والذي يسمح لفلاسك بأخذ القيمة المُدخلة من الرابط وتمريرها إلى دالة فلاسك، إذ يُمرّر متغير الرابط <code>&lt;word&gt;</code> الكلمة مثل وسيط إلى دالة فلاسك <code>()capitalize</code>. وفي حالتنا فإنّ الوسيط هذا يملك نفس اسم متغير الرابط (وهو <code>word</code> في حالتنا)، وبذلك يمكننا الوصول إلى الكلمة المُمررة عبر الرابط وإنشاء استجابة بنفس الكلمة المُمررة ولكن بأحرف كبيرة وذلك باستخدام دالة العرض <code>()capitalize</code> العاملة أصلًا في بايثون.
</p>

<p>
	استخدمنا الدالة <code>()escape</code> التي استوردناها سابقًا لتصيير السلسلة التي أدخلها المُستخدم والموجودة ضمن المتغير <code>word</code> مثل نص وليس شيفرةً للتنفيذ، وهو أمر بالغ الأهمية لتجنب هجمات البرمجة العابرة للمواقع XSS، ففي حال إدخال المستخدم <a href="https://academy.hsoub.com/programming/javascript/%D9%86%D9%85%D8%B7-%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%B4%D9%8A%D9%81%D8%B1%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r785/" rel="">شيفرة JavaScript</a> خبيثة بدلًا من السلسلة النصية، تصيّرها الدالة <code>()escape</code> على أنها نص عادي وبذلك لن ينفّذها المتصفح، ما يبقي تطبيق الويب آمنًا.
</p>

<p>
	ولعرض الكلمات بأحرف كبيرة ضمن تنسيق عنوان من المستوى الأوّل <code><a href="https://wiki.hsoub.com/HTML/h1-h6" rel="external">&lt;h1&gt;</a></code> في HTML استخدمنا الدالة <code>()format</code> من دوال بايثون.
</p>

<p>
	الآن وبعد التأكد من أنّ خادم التطوير ما يزال قيد التشغيل، نفتح المتصفح ونذهب إلى الروابط التالية، مع إمكانية تبديل الكلمات "hello" و "flask" و "python" التي تنتهي الروابط بها في مثالنا بأي كلمة تريد:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_54" style="">
<span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln">capitalize</span><span class="pun">/</span><span class="pln">hello
http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln">capitalize</span><span class="pun">/</span><span class="pln">flask
http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln">capitalize</span><span class="pun">/</span><span class="pln">python</span></pre>

<p>
	وبذلك تظهر الكلمة ضمن المتصفح بأحرف كبيرة وبتنسيق عنوان من المستوى الأول <code>&lt;h1&gt;</code>.
</p>

<p>
	كما يمكننا استخدام عدّة متغيرات في الوجهة، ولتوضيح ذلك سنضيف وجهةً جديدةً لجمع رقمين صحيحين ويعرض النتيجة.
</p>

<p>
	لذا سنفتح الملف "app.py" لتعديله:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_56" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ثمّ نضيف الوجهة التالية إلى نهاية الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_58" style="">
<span class="com"># ...</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/add/&lt;int:n1&gt;/&lt;int:n2&gt;/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> add</span><span class="pun">(</span><span class="pln">n1</span><span class="pun">,</span><span class="pln"> n2</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="str">'&lt;h1&gt;{}&lt;/h1&gt;'</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="pln">n1 </span><span class="pun">+</span><span class="pln"> n2</span><span class="pun">)</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	نستخدم في هذه الوجهة المحوّل الخاص <code>int</code> مع المتغير الخاص بالرابط (<code>/add/&lt;int:n1&gt;/&lt;int:n2&gt;‎/</code>)؛ ومهمّة هذا المحوّل هي قبول القيم التي تُمثّل أعدادًا صحيحة موجبة فقط والتعامل معها على أنها أعداد، كونه افتراضيًا تُعدّ المتغيرات الآتية من الرابط سلاسلًا محرفية ويجري التعامل معها على هذا الأساس، وهذا أمرٌ غير مناسب لإتمام عملية الجمع العددي.
</p>

<p>
	الآن وبعد التأكد من أنّ خادم التطوير ما يزال قيد التشغيل، نفتح المتصفح ونذهب إلى الرابط التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_60" style="">
<span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln">add</span><span class="pun">/</span><span class="lit">5</span><span class="pun">/</span><span class="lit">5</span><span class="pun">/</span></pre>

<p>
	وستكون النتيجة مجموع الرقمين (وهي "10" في حالة مثالنا هذا).
</p>

<p>
	تعرفنا في هذه الخطوة على كيفية استخدام الوجهات الديناميكية لعرض استجابات مُختلفة ضمن الوجهة الواحدة اعتمادًا على الرابط المطلوب، وهذا ما يعزز التجربة التفاعلية للمُستخدم.
</p>

<p>
	سنتعرّف في الخطوة التالية على آليات تنقيح وتصحيح الأخطاء في تطبيق فلاسك في حال حدوثها.
</p>

<h2>
	الخطوة السادسة - تنقيح أخطاء تطبيق فلاسك
</h2>

<p>
	خلال مراحل تطوير أي تطبيق ويب، من الطبيعي أن نمر بحالات نحصل فيها على أخطاء لدى تشغيل التطبيق عوضًا عن النتيجة المتوقعة، والتي قد تكون ناتجةً مثلًا عن خطأ إملائي بسيط في كتابة اسم أحد المتغيرات أو نتيجة نسيان التصريح عن أو استيراد إحدى الدوال؛ ولإصلاح هذه الأخطاء بسهولة يوفّر فلاسك منقّح أخطاء يعمل لدى تشغيل التطبيق في وضع التطوير، إذ سنتعلّم في هذه الخطوة آلية تصحيح أخطاء التطبيق بالاعتماد على مُنقّح الأخطاء في فلاسك.
</p>

<p>
	لتوضيح كيفية التعامل مع الأخطاء في حال ظهورها، سننشئ وجهةً للترحيب بمستخدم ما من قائمة أسماء مستخدمين موجودة.
</p>

<p>
	لذا سنفتح الملف "app.py" للتعديل عليه:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_62" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ونضيف الوجهة التالية إلى نهاية الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_64" style="">
<span class="com"># ...</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/users/&lt;int:user_id&gt;/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> greet_user</span><span class="pun">(</span><span class="pln">user_id</span><span class="pun">):</span><span class="pln">
    users </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">'Ahmad'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Mohammad'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Adam'</span><span class="pun">]</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="str">'&lt;h2&gt;Hi {}&lt;/h2&gt;'</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="pln">users</span><span class="pun">[</span><span class="pln">user_id</span><span class="pun">])</span></pre>

<p>
	نحفظ الملف ونغلقه.
</p>

<p>
	تستقبل دالة فلاسك <code>()greet_user</code> في الوجهة السابقة متغير الرابط <code>user_id</code> مثل قيمة للوسيط <code>user_id</code>، إذ استخدمنا المحوّل الخاص <code>int</code> لقبول أعداد صحيحة موجبة فقط، حيث تتضمّن الدالة قائمةً مبنيةً في بايثون باسم <code>users</code> تحتوي على ثلاث سلاسل نصية تمثّل أسماء المستخدمين، وترجع دالة فلاسك السلسة الموافقة من القائمة اعتمادًا على قيمة المتغير <code>user_id</code> المُمرّر والذي يدل على رقم المُستخدم المراد الترحيب به بالنتيجة؛ فإذا كانت قيمة المتغير <code>user_id</code> تساوي "0" مثلًا، ستكون الاستجابة ظهور عبارة "Hi Ahmad" ضمن تنسيق عنوان من المستوى الثاني <code>&lt;h2&gt;</code> لأن <code>Ahmad</code> هو أول عنصر في القائمة المُقابل للقيمة "[user[0".
</p>

<p>
	الآن وبعد التأكد أن خادم التطوير ما يزال قيد التشغيل، سنفتح المتصفح لزيارة الروابط التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_66" style="">
<span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln">users</span><span class="pun">/</span><span class="lit">0</span><span class="pln">
http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln">users</span><span class="pun">/</span><span class="lit">1</span><span class="pln">
http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln">users</span><span class="pun">/</span><span class="lit">2</span></pre>

<p>
	فتكون النتيجة على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_68" style="">
<span class="typ">Hi</span><span class="pln"> </span><span class="typ">Ahmad</span><span class="pln">
</span><span class="typ">Hi</span><span class="pln"> </span><span class="typ">Mohammad</span><span class="pln">
</span><span class="typ">Hi</span><span class="pln"> </span><span class="typ">Adam</span></pre>

<p>
	نلاحظ أنّ الأمور تجري على خير ما يرام حتى الآن، ولكن ماذا لو طلبنا الترحيب بمستخدم غير موجود؟ سيظهر خطأ بالتأكيد، ولتوضيح كيفية عمل مُنقّح الأخطاء في فلاسك سنزور الرابط التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_70" style="">
<span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln">users</span><span class="pun">/</span><span class="lit">3</span></pre>

<p>
	فتظهر صفحةٌ كما في الصورة أدناه:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="108310" href="https://academy.hsoub.com/uploads/monthly_2022_09/632971220e22c_secondindexerror.png.2d801fe97f5b41e41414a3729959ba2d.png" rel=""><img alt="second index error.png" class="ipsImage ipsImage_thumbnailed" data-fileid="108310" data-unique="2vspz2szb" src="https://academy.hsoub.com/uploads/monthly_2022_09/632971236dc71_secondindexerror.thumb.png.92197b31295ace9005bf0cd68af9a149.png" style="width: 750px; height: auto;"></a>
</p>

<p>
	نلاحظ أنّ اسم الخطأ الحاصل في بايثون وهو <code>IndexError</code> في حالتنا يُعرض في أعلى الصفحة، ويشير هذا الخطأ إلى وقوع دليل القائمة ("3" في مثالنا) خارج المجال، إذ أن المجال في حالتنا محصورٌ بين "0" و "2" لأنّ القائمة مكونةٌ من ثلاثة عناصر، كما يظهر في مُنقح الأخطاء جميع أسطر الشيفرة التي أدّى تنفيذها إلى ظهور هذا الخطأ.
</p>

<p>
	وعادةً ما يتضمّن آخر سطرين من متتبّع الأخطاء مصدر الخطأ، وفي حالتنا سيبدوان على نحو مشابه لما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_73" style="">
<span class="typ">File</span><span class="pln"> </span><span class="str">"/home/USER/flask_app/app.py"</span><span class="pun">,</span><span class="pln"> line </span><span class="lit">28</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> greet_user
    </span><span class="kwd">return</span><span class="pln"> </span><span class="str">'&lt;h2&gt;Hi {}&lt;/h2&gt;'</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="pln">users</span><span class="pun">[</span><span class="pln">user_id</span><span class="pun">])</span></pre>

<p>
	ما يشير إلى أنّ الخطأ ناتج عن دالة الترحيب <code>()greet_user</code> ضمن الملف "app.py" وتحديدًا في السطر الحاوي غلى القيمة المُعادة <code>return</code>، ومع معرفتنا للسطر الذي تسبّب بالخطأ يصبح تحديد المشكلة وحلها أسهل.
</p>

<p>
	كما يمكننا لتجنّب توقّف عمل التطبيق في حالات مشابهة لهذه الحالة استخدام العبارة "try…except" لإصلاح الخطأ، بحيث إذا احتوى الرابط المطلوب على دليل خارج مجال القائمة يتلقى المستخدم عبارة خطأ "404 Not Found"، وهو خطأ HTTP يشير للمستخدم أن الصفحة التي يطلبها غير متوفرة.
</p>

<p>
	لذا سنفتح الملف "app.py" للتعديل عليه:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_75" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	وحتى تحدث الاستجابة بخطأ من النوع HTTP 404 سنستخدم دالة فلاسك <code>()abort</code> التي يمكن استخدامها لانشاء استجابات مكونة من أخطاء HTTP، لذا سنغير السطر الثاني في الملف ليصبح كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_77" style="">
<span class="kwd">from</span><span class="pln"> markupsafe </span><span class="kwd">import</span><span class="pln"> escape
</span><span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> abort</span></pre>

<p>
	ثمّ سنعدّل الدالة <code>()greet_user</code> لتصبح كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_79" style="">
<span class="com"># ...</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/users/&lt;int:user_id&gt;/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> greet_user</span><span class="pun">(</span><span class="pln">user_id</span><span class="pun">):</span><span class="pln">
    users </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">'Ahmad'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Mohammad'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Adam'</span><span class="pun">]</span><span class="pln">
    </span><span class="kwd">try</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="str">'&lt;h2&gt;Hi {}&lt;/h2&gt;'</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="pln">users</span><span class="pun">[</span><span class="pln">user_id</span><span class="pun">])</span><span class="pln">
    </span><span class="kwd">except</span><span class="pln"> </span><span class="typ">IndexError</span><span class="pun">:</span><span class="pln">
        abort</span><span class="pun">(</span><span class="lit">404</span><span class="pun">)</span></pre>

<p>
	استخدمنا <code>try</code> لاختبار التعبير <code>return</code> والتأكّد من خلوه من الأخطاء، فإذا لم يكن هناك خطأ، بمعنى أنّ قيمة المتغير <code>user_id</code> المُمرّرة صحيحةٌ ومن ضمن مجال قائمة <code>users</code>، فسيستجيب التطبيق بالترحيب المناسب، وإلّا وفي حال كون قيمة <code>user_id</code> خارج مجال القائمة، سيُفعَّل الاستثناء <code>IndexError</code>، ونستخدم الأمر <code>except</code> للتعامل مع الخطأ وبالتالي الاستجابة بخطأ من نوع HTTP 404 باستخدام دالة فلاسك المساعدة <code>()abort</code>.
</p>

<p>
	الآن وبعد التأكد من كون خادم التطوير قيد التشغيل، نزور الرابط مجددًا مع تمرير القيمة 3 لرقم المُستخدم المطلوب:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_81" style="">
<span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln">users</span><span class="pun">/</span><span class="lit">3</span></pre>

<p>
	فتظهر في هذه الحالة صفحة الخطأ 404 التقليدية لتشير للمستخدم أن الصفحة المطلوبة غير موجودة.
</p>

<p>
	ومع نهاية تطبيق الخطوات الواردة في هذا المقال، سيبدو الملف "app.py" على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1965_83" style="">
<span class="kwd">from</span><span class="pln"> markupsafe </span><span class="kwd">import</span><span class="pln"> escape
</span><span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> abort

app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln">


</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">)</span><span class="pln">
</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/index/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> hello</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="str">'&lt;h1&gt;Hello, World!&lt;/h1&gt;'</span><span class="pln">


</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/about/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> about</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="str">'&lt;h3&gt;This is a Flask web application.&lt;/h3&gt;'</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/capitalize/&lt;word&gt;/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> capitalize</span><span class="pun">(</span><span class="pln">word</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="str">'&lt;h1&gt;{}&lt;/h1&gt;'</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="pln">escape</span><span class="pun">(</span><span class="pln">word</span><span class="pun">.</span><span class="pln">capitalize</span><span class="pun">()))</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/add/&lt;int:n1&gt;/&lt;int:n2&gt;/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> add</span><span class="pun">(</span><span class="pln">n1</span><span class="pun">,</span><span class="pln"> n2</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="str">'&lt;h1&gt;{}&lt;/h1&gt;'</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="pln">n1 </span><span class="pun">+</span><span class="pln"> n2</span><span class="pun">)</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/users/&lt;int:user_id&gt;/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> greet_user</span><span class="pun">(</span><span class="pln">user_id</span><span class="pun">):</span><span class="pln">
    users </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">'Ahmad'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Mohammad'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Adam'</span><span class="pun">]</span><span class="pln">
    </span><span class="kwd">try</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="str">'&lt;h2&gt;Hi {}&lt;/h2&gt;'</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="pln">users</span><span class="pun">[</span><span class="pln">user_id</span><span class="pun">])</span><span class="pln">
    </span><span class="kwd">except</span><span class="pln"> </span><span class="typ">IndexError</span><span class="pun">:</span><span class="pln">
        abort</span><span class="pun">(</span><span class="lit">404</span><span class="pun">)</span></pre>

<p>
	وبذلك أصبح لديك تصوّر عام عن كيفية استخدام مُنقّح الأخطاء في فلاسك لالتقاط الأخطاء وتحديد الآلية المُثلى لإصلاحها والتعامل معها.
</p>

<h1>
	الخاتمة
</h1>

<p>
	تعرفنا في هذا المقال بصورة عامة على فلاسك وكيفية تثبيته واستخدامه لإنشاء تطبيق ويب، كما تعرفنا على آلية تشغيل خادم التطوير واستخدام الوجهات ودوال فلاسك لعرض صفحات ويب مختلفة تخدّم وظائفًا محددة، كما تعلمنا كيفية استخدام الوجهات الديناميكية للسماح للمستخدم بالتفاعل مع تطبيق ويب عن طريق تعديل الرابط المطلوب، وفي النهاية تعلمنا كيفية استخدام مُنقّح الأخطاء لتحديد المشاكل.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-create-your-first-web-application-using-flask-and-python-3" rel="external nofollow">How To Create Your First Web Application Using Flask and Python3</a> لصاحبه Abdelhadi Dyouri.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال التالي: <a href="https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1737/" rel="">كيفية استخدام القوالب في تطبيقات فلاسك Flask</a>
	</li>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/devops/deployment/%D8%AA%D8%AE%D8%AF%D9%8A%D9%85-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AE%D8%A7%D8%AF%D9%85%D9%8A-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-uwsgi-%D9%88-nginx-r649/" rel="">تخديم تطبيقات فلاسك باستخدام خادمي الويب uWSGI و Nginx</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%88%D9%8A%D8%A8-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D9%85%D9%86-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1622/" rel="">إنشاء تطبيق ويب باستخدام إطار عمل فلاسك Flask من لغة بايثون</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/workflow/git/%D8%A8%D9%86%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9%D9%83-%D9%88%D8%A7%D8%B3%D8%AA%D8%B6%D8%A7%D9%81%D8%AA%D9%87-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-git-r862/" rel="">بناء موقعك واستضافته باستخدام Git</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/general/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%AA%D8%AD%D8%AF%D9%8A%D8%AF-%D8%A7%D9%84%D8%AA%D9%83%D9%84%D9%81%D8%A9-%D8%A7%D9%84%D9%85%D8%A7%D8%AF%D9%8A%D8%A9-%D8%A7%D9%84%D9%83%D8%A7%D9%85%D9%84%D8%A9-%D9%84%D8%A8%D9%86%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-r1437/" rel="">أساسيات تحديد التكلفة المادية الكاملة لبناء موقع ويب</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1715</guid><pubDate>Tue, 27 Sep 2022 16:05:00 +0000</pubDate></item><item><title>&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x645;&#x643;&#x62A;&#x628;&#x629; &#x628;&#x627;&#x64A;&#x62B;&#x648;&#x646;-&#x645;&#x627;&#x631;&#x643;&#x62F;&#x627;&#x648;&#x646; &#x645;&#x639; &#x625;&#x637;&#x627;&#x631; &#x639;&#x645;&#x644; &#x641;&#x644;&#x627;&#x633;&#x643; &#x648;&#x645;&#x62D;&#x631;&#x643; &#x642;&#x648;&#x627;&#x639;&#x62F; &#x627;&#x644;&#x628;&#x64A;&#x627;&#x646;&#x627;&#x62A; SQLite</title><link>https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D9%85%D8%A7%D8%B1%D9%83%D8%AF%D8%A7%D9%88%D9%86-%D9%85%D8%B9-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-%D9%88%D9%85%D8%AD%D8%B1%D9%83-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-sqlite-r1714/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_09/63244060eca35_-----Python-Markdown-----Flask----SQLite.jpg.df9a7374244dcdec6c08c44d02c51ec2.jpg" /></p>
<p>
	يُعد <a href="https://academy.hsoub.com/programming/python/flask/" rel="">فلاسك</a> إطار عمل لبناء تطبيقات ويب باستخدام لغة <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D9%85%D8%B1%D8%AC%D8%B9-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%AA%D8%B9%D9%84%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r735/" rel="">بايثون</a> Python، ويمكن استخدام محرّك قواعد البيانات <a href="https://academy.hsoub.com/devops/servers/databases/%D9%83%D9%8A%D9%81-%D9%88%D9%85%D8%AA%D9%89-%D9%86%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-sqlite-r111/" rel="">SQLite</a> معه لتخزين بيانات التطبيق.
</p>

<p>
	أمّا <a href="https://academy.hsoub.com/apps/productivity/%D9%85%D8%A7%D8%B1%D9%83%D8%AF%D8%A7%D9%88%D9%86-%D9%84%D9%84%D9%85%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D9%86-r289/" rel="">ماركداون</a> Markdown فهي لغة لتوصيف تنسيق النصوص، شائعة الاستخدام لكتابة المحتوى بتنسيق سهل القراءة والتفسير من قِبل المتصفحات، إذ يمكن -باستخدام ماركداون- تنسيق النصوص الصرفة مع إضافة مميزات لها من عناوين وروابط وصور، ثمّ تحويلها إلى شيفرات HTML موافقة متضمّنةً نفس التنسيقات والمميزات.
</p>

<p>
	بايثون-ماركداون Python-Markdown هي مكتبة في لغة بايثون تسمح بتحويل النصوص المُنسقّة باستخدام ماركداون إلى شيفرات <a href="https://academy.hsoub.com/programming/html/%D8%AA%D8%B9%D9%84%D9%85-%D9%84%D8%BA%D8%A9-html-r1702/" rel="">HTML</a> موافقة، والتي تتبع معاييّر ماركداون نفسها مع فروق بسيطة في شكل التعليمات.
</p>

<p>
	سنستخدم في هذا المقال كل من إطار عمل فلاسك ومحرّك <a href="https://academy.hsoub.com/devops/servers/databases/%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-database/" rel="">قواعد البيانات</a> SQLite ومكتبة بايثون-ماركداون لبناء تطبيق ويب لتدوين الملاحظات، والذي يدعم تنسيق النصوص وفق ماركداون متيحًا للمستخدمين عرض وإنشاء وتنسيق الملاحظات لتتضمّن عناوينًا وروابطًا وقوائمًا وصور وغيرها من المميزات والتنسيقات، وسنستخدم حزمة <a href="https://academy.hsoub.com/programming/html/bootstrap/" rel="">بوتستراب Bootstrap</a> لإضافة التنسيقات إلى مظهر التطبيق.
</p>

<h2>
	مستلزمات العمل
</h2>

<p>
	قبل المتابعة في هذا المقال لابدّ من:
</p>

<ul>
	<li>
		توفُّر <a href="7https://academy.hsoub.com/programming/python/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%AA%D9%87%D8%A7-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r714/" rel="">بيئة برمجة بايثون 3 محلية</a>، مثبّتة على حاسوبك، وسنفترض في مقالنا أن اسم مجلد المشروع هو "flask_notes".
	</li>
	<li>
		يجب فهم مختلف مفاهيم فلاسك الأساسية مثل إنشاء الوجهات وتصييّر قوالب HTML والاتصال بقاعدة بيانات SQLite.
	</li>
</ul>

<h2>
	الخطوة الأولى – إعداد مستلزمات إنشاء التطبيق
</h2>

<p>
	سنفعّل في هذه الخطوة بيئة بايثون، وسنثبّت كل من فلاسك ومكتبة بايثون-ماركداون باستخدام مثبّت الحزمة "pip"، ومن ثمّ سننشئ قاعدة البيانات التي سنستخدمها لتخزين الملاحظات وبعض البيانات النموذجية.
</p>

<p>
	سنفعّل بدايةً البيئة البرمجية في حال لم تكن مُفعلّة أصلًا، كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5520_7" style=""><span class="pln">$ source env</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">activate</span></pre>

<p>
	الآن وبعد تفعيل البيئة البرمجية، سنثبّت كل من فلاسك ومكتبة بايثون-ماركداون باستخدام الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_5520_14" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ pip install flask markdown</span></pre>

<p>
	سننشئ الآن ملف تخطيط قاعدة البيانات باسم "schema.sql"، والذي سيتضمّن أوامر <a href="https://academy.hsoub.com/programming/sql/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D9%91%D8%A7%D8%AA-%D9%84%D8%BA%D8%A9-sql-r585/" rel="">SQL</a> اللازمة لإنشاء جدول الملاحظات "notes"، سننشئ هذا الملف ضمن مجلد المشروع "flask_notes":
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_5520_12" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano schema</span><span class="pun">.</span><span class="pln">sql</span></pre>

<p>
	وسنكتب أوامر SQL التالية ضمن الملف الذي أنشأناه:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_5520_18" style=""><span class="pln">DROP TABLE IF EXISTS notes</span><span class="pun">;</span><span class="pln">

CREATE TABLE notes </span><span class="pun">(</span><span class="pln">
    id INTEGER PRIMARY KEY AUTOINCREMENT</span><span class="pun">,</span><span class="pln">
    created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP</span><span class="pun">,</span><span class="pln">
    content TEXT NOT NULL
</span><span class="pun">);</span></pre>

<p>
	يعمل أوّل أمر من أوامر SQL وهو:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_5520_20" style=""><span class="pun">;</span><span class="pln">DROP TABLE IF EXISTS posts</span></pre>

<p>
	على حذف أي جداول موجودة مسبقًا باسم <code>notes</code> وهذا ما يجنبنا أي تضاربات قد تحصل مُستقبلًا نتيجة وجود جدولين بنفس الاسم وبأعمدة مُختلفة في قاعدة البيانات، والذي قد يؤدي لتوقف تنفيذ الشيفرة نتيجة عدم توافق الجدول الموجود مع تخطيط قاعدة البيانات، ومن الجدير بالملاحظة أنّ هذا الأمر سيحذف كل المحتويات في قاعدة البيانات في كل مرة تشغّل فيها أوامر SQL هذه، لذا لا تُدخل أي بيانات مهمّة في التطبيق حتى تنتهي من كافّة الخطوات التعليميّة في المقال وتجربة نتائجه النهائية؛ أمّا الأمر الثاني من أوامر SQL وهو
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_5520_22" style=""><span class="pln">CREATE TABLE notes</span></pre>

<p>
	فيعمل على إنشاء جدول باسم <code>notes</code> بالأعمدة التالية:
</p>

<ul>
	<li>
		<code>id</code>: وهو رقم صحيح موجب يمثّل المفتاح الأساسي للجدول، إذ سيُخصص رقم فريد لكل من مدخلات الجدول، والمدخلات في حالتنا هي الملاحظات.
	</li>
	<li>
		<code>created</code>: ويمثّل تاريخ إنشاء الملاحظة والذي يُملأ تلقائيًا بتاريخ ووقت إضافة الملاحظة إلى قاعدة البيانات.
	</li>
	<li>
		<code>content</code>: محتوى الملاحظة.
	</li>
</ul>

<p>
	احفظ الملف واغلقه.
</p>

<p>
	الآن سننشئ قاعدة البيانات باستخدام ملف تخطيط قاعدة البيانات "schema.sql"، لذا سنفتح ملف باسم "init_db.py" ضمن المجلد "flask_notes":
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5520_24" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano init_db</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	وسنكتب ضمنه الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5520_26" style=""><span class="kwd">import</span><span class="pln"> sqlite3

connection </span><span class="pun">=</span><span class="pln"> sqlite3</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="str">'database.db'</span><span class="pun">)</span><span class="pln">


</span><span class="kwd">with</span><span class="pln"> open</span><span class="pun">(</span><span class="str">'schema.sql'</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> f</span><span class="pun">:</span><span class="pln">
    connection</span><span class="pun">.</span><span class="pln">executescript</span><span class="pun">(</span><span class="pln">f</span><span class="pun">.</span><span class="pln">read</span><span class="pun">())</span><span class="pln">

cur </span><span class="pun">=</span><span class="pln"> connection</span><span class="pun">.</span><span class="pln">cursor</span><span class="pun">()</span><span class="pln">

cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO notes (content) VALUES (?)"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="str">'# The First Note'</span><span class="pun">,))</span><span class="pln">
cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO notes (content) VALUES (?)"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="str">'_Another note_'</span><span class="pun">,))</span><span class="pln">
cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO notes (content) VALUES (?)"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="str">'Visit [this page](https://www.digitalocean.com/community/tutorials) for more tutorials.'</span><span class="pun">,))</span><span class="pln">

connection</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">
connection</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span></pre>

<p>
	استوردنا في الشيفرة السابقة بدايةً وحدة <code>sqlite3</code>، ثمّ اتصلنا بملف اسمه "database.db"، والذي يُنشَأ تلقائيًا عند تنفيذ البرنامج، والمُمثّل لقاعدة البيانات التي ستتضمّن كافّة بيانات التطبيق، ثمّ فتحنا ملف تخطيط قاعدة البيانات "schema.sql" وشغّلناه باستخدام الدالة <code>()executescript</code> المسؤولة عن تنفيذ عدّة تعليمات SQL معًا، وبالنتيجة أنشأنا جدول الملاحظات <code>notes</code>.
</p>

<p>
	ثمّ نُفّذت عدّة أوامر إدخال <code>INSERT</code> من أوامر SQL باستخدام <a href="https://docs.python.org/3/library/sqlite3.html#cursor-objects" rel="external nofollow">كائن المؤشّر Cursor object</a> بهدف إنشاء ثلاث ملاحظات. استخدمنا تعليمات ماركداون بحيث تكون الملاحظة الأولى بتنسيق عنوان من المستوى الأوّل <code><a href="https://wiki.hsoub.com/HTML/h1-h6" rel="external">&lt;h1&gt;</a></code>، والملاحظة الثانية بخط مائل، والملاحظة الثالثة تتضمّن رابطًا، كما استخدمنا الموضع المؤقّت <code>?</code> في الدالة <code>()execute</code> ومرّرنا إليها السجل الحاوي على محتويات الملاحظة بما يضمن إدخال البيانات إلى القاعدة بأمان.
</p>

<p>
	نهايةً حفظنا التغييرات وأغلقنا الاتصال مع قاعدة البيانات.
</p>

<p>
	احفظ الملف واغلقه.
</p>

<p>
	الآن نشغّل البرنامج كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5520_29" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ python init_db</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	سيظهر بعد تنفيذ البرنامج ملفٌ جديد باسم "database.db" ضمن المجلّد "flask_notes"، ومع نهاية هذه الخطوة نكون قد فعّلنا البيئة البرمجية، وثبّتنا كلًا من فلاسك ومكتبة بايثون-ماركداون، كما أنشأنا قاعدة بيانات SQLite، وسنجلب في الخطوة التالية الملاحظات من قاعدة البيانات ونحوّلها إلى ملف من نوع HTML لعرضها ضمن الصفحة الرئيسية للتطبيق.
</p>

<h2>
	الخطوة الثانية – عرض الملاحظات في الصفحة الرئيسية للتطبيق
</h2>

<p>
	سننشئ في هذه الخطوة تطبيق فلاسك يتصل بقاعدة البيانات ليعرض البيانات المُخزّنة فيها، إذ سنحوّل النص المُنسّق باستخدام ماركداون إلى ملف HTML والذي سيُصيّر ضمن الصفحة الرئيسية للتطبيق.
</p>

<p>
	بدايةً، سننشئ ملف التطبيق "app.py" ضمن المجلد "flask_notes" على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5520_34" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	وسنكتب ضمنه الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5520_36" style=""><span class="kwd">import</span><span class="pln"> sqlite3
</span><span class="kwd">import</span><span class="pln"> markdown
</span><span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> render_template</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">,</span><span class="pln"> flash</span><span class="pun">,</span><span class="pln"> redirect</span><span class="pun">,</span><span class="pln"> url_for

</span><span class="kwd">def</span><span class="pln"> get_db_connection</span><span class="pun">():</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> sqlite3</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="str">'database.db'</span><span class="pun">)</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">row_factory </span><span class="pun">=</span><span class="pln"> sqlite3</span><span class="pun">.</span><span class="typ">Row</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> conn</span></pre>

<p>
	استوردنا في بداية الشيفرة السابقة كلًا من وحدة <code>sqlite3</code> وحزمة <code>markdown</code> ومُساعد فلاسك، إذ اتصلنا مع ملف قاعدة البيانات "database.db" بواسطة الدالة <code>()get_db_connection</code>، ثمّ أسندنا القيمة <code>sqlite3.Row</code> للسمة <code>row_factory</code>، وهذا ما سيمكنّنا من الوصول إلى الجداول بأسمائها، وبالنتيجة سيعيد الاتصال مع قاعدة البيانات سجلات تسلك سلوك قواميس بايثون اعتيادية. نهايةً، ستعيد الدالة كائن الاتصال <code>conn</code> الذي سنستخدمه في الوصول إلى قاعدة البيانات.
</p>

<p>
	سنضيف بعد الدالة السابقة جزء الشيفرة التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5520_38" style=""><span class="com">#. . .</span><span class="pln">

app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">config</span><span class="pun">[</span><span class="str">'SECRET_KEY'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'this should be a secret random string'</span></pre>

<p>
	وبذلك نكون أنشأنا كائن تطبيق فلاسك وخصّصنا له مفتاح أمان، ما يضمن كون جلسات العمل آمنة.
</p>

<p>
	الآن سنضيف الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5520_40" style=""><span class="com">#. . .</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> index</span><span class="pun">():</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">
    db_notes </span><span class="pun">=</span><span class="pln"> conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'SELECT id, created, content FROM notes;'</span><span class="pun">).</span><span class="pln">fetchall</span><span class="pun">()</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">

    notes </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> note </span><span class="kwd">in</span><span class="pln"> db_notes</span><span class="pun">:</span><span class="pln">
       note </span><span class="pun">=</span><span class="pln"> dict</span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln">
       note</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> markdown</span><span class="pun">.</span><span class="pln">markdown</span><span class="pun">(</span><span class="pln">note</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">])</span><span class="pln">
       notes</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'index.html'</span><span class="pun">,</span><span class="pln"> notes</span><span class="pun">=</span><span class="pln">notes</span><span class="pun">)</span></pre>

<p>
	دالة <code>()index</code> العاملة في فلاسك هي دالةٌ مُصمّمة باستخدام المُزخرف <code>app.route@</code>، ويحوّل فلاسك القيمة المعادة من هذه الدالة إلى استجابة من نوع HTTP لتُعرض ضمن عميل HTTP مثل متصفح الويب.
</p>

<p>
	ونفتح ضمن هذه الدالة اتصالًا مع قاعدة البيانات وننفّذ تعليمة الاختيار <code>SELECT</code> من تعليمات SQL المسؤولة عن جلب المعرّف ID، وتاريخ الإنشاء والمحتوى لكل سجل (ملاحظة) من محتويات جدول الملاحظات <code>notes</code>، ونستخدم الدالة <code>()fetchall</code> لجلب قائمة بجميع السجلات وحفظها ضمن المتغير <code>db_notes</code>، ونهايةً نغلق الاتصال.
</p>

<p>
	الآن، لتحويل محتوى الملاحظات من تنسيق ماركداون إلى HTML، سننشئ قائمةً جديدةً فارغةً باسم <code>notes</code>، لنمر على كافّة عناصر القائمة المحفوظة في المتغير <code>db_notes</code> ونحوّل كل ملاحظة فيها من النمط <code>sqlite3.Row</code> إلى قاموس بايثون عادي باستخدام دالة بايثون <code>()dict</code> لتمكين عملية التحويل، ثمّ سنستخدم الدالة <code>()markdown.markdown</code> لتغيير قيمة محتوى الملاحظة <code>['note['content</code> إلى HTML، إذ سيعيد مثلًا استدعاء الدالة <code>('markdown.markdown('#Hi</code> سيعيد السلسلة النصية <code>'&lt;h1&gt;Hi&lt;/h1&gt;'</code> وذلك لأنّ الرمز <code>#</code> في ماركداون يدل على عنوان من المستوى الأوّل <code>&lt;h1&gt;</code>. الآن، وبعد تعديل محتوى <code>['note['content</code> نضيف الملاحظة إلى القائمة الجديدة المُسمّاة <code>notes</code>، ونهايةً نصيّر قالب HTML باسم "index.html" عن طريق تمرير القائمة <code>notes</code> إليه.
</p>

<p>
	وبعد هذه التعديلات ستبدو الشيفرة على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5520_42" style=""><span class="kwd">import</span><span class="pln"> sqlite3
</span><span class="kwd">import</span><span class="pln"> markdown
</span><span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> render_template</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">,</span><span class="pln"> flash</span><span class="pun">,</span><span class="pln"> redirect</span><span class="pun">,</span><span class="pln"> url_for

</span><span class="kwd">def</span><span class="pln"> get_db_connection</span><span class="pun">():</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> sqlite3</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="str">'database.db'</span><span class="pun">)</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">row_factory </span><span class="pun">=</span><span class="pln"> sqlite3</span><span class="pun">.</span><span class="typ">Row</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> conn


app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">config</span><span class="pun">[</span><span class="str">'SECRET_KEY'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'this should be a secret random string'</span><span class="pln">


</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> index</span><span class="pun">():</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">
    db_notes </span><span class="pun">=</span><span class="pln"> conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'SELECT id, created, content FROM notes;'</span><span class="pun">).</span><span class="pln">fetchall</span><span class="pun">()</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">

    notes </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> note </span><span class="kwd">in</span><span class="pln"> db_notes</span><span class="pun">:</span><span class="pln">
       note </span><span class="pun">=</span><span class="pln"> dict</span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln">
       note</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> markdown</span><span class="pun">.</span><span class="pln">markdown</span><span class="pun">(</span><span class="pln">note</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">])</span><span class="pln">
       notes</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">note</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'index.html'</span><span class="pun">,</span><span class="pln"> notes</span><span class="pun">=</span><span class="pln">notes</span><span class="pun">)</span></pre>

<p>
	احفظ الملف واغلقه.
</p>

<p>
	الآن سننشئ القالب الرئيسي وملف القالب index.html، لذا سنضيف مجلدًا للقوالب باسم "templates" ضمن المجلد "flask_notes"، وفيه سنفتح ملف باسم "base.html" كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5520_44" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ mkdir templates
</span><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">base</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	ونضيف الشيفرة التالية ضمن الملف "base.html"، مع ملاحظة أنّنا نستخدم بوتستراب هنا.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_5520_46" style=""><span class="dec">&lt;!doctype html&gt;</span><span class="pln">
</span><span class="tag">&lt;html</span><span class="pln"> </span><span class="atn">lang</span><span class="pun">=</span><span class="atv">"en"</span><span class="tag">&gt;</span><span class="pln">
  </span><span class="tag">&lt;head&gt;</span><span class="pln">
    </span><span class="com">&lt;!-- Required meta tags --&gt;</span><span class="pln">
    </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">"utf-8"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"viewport"</span><span class="pln"> </span><span class="atn">content</span><span class="pun">=</span><span class="atv">"width=device-width, initial-scale=1, shrink-to-fit=no"</span><span class="tag">&gt;</span><span class="pln">

    </span><span class="com">&lt;!-- Bootstrap CSS --&gt;</span><span class="pln">
    </span><span class="tag">&lt;link</span><span class="pln"> </span><span class="atn">rel</span><span class="pun">=</span><span class="atv">"stylesheet"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"</span><span class="pln"> </span><span class="atn">integrity</span><span class="pun">=</span><span class="atv">"sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"</span><span class="pln"> </span><span class="atn">crossorigin</span><span class="pun">=</span><span class="atv">"anonymous"</span><span class="tag">&gt;</span><span class="pln">

    </span><span class="tag">&lt;title&gt;</span><span class="pln">{% block title %} {% endblock %}</span><span class="tag">&lt;/title&gt;</span><span class="pln">
  </span><span class="tag">&lt;/head&gt;</span><span class="pln">
  </span><span class="tag">&lt;body&gt;</span><span class="pln">
    </span><span class="tag">&lt;nav</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar navbar-expand-md navbar-light bg-light"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar-brand"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('index')}}"</span><span class="tag">&gt;</span><span class="pln">FlaskNotes</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;button</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar-toggler"</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"button"</span><span class="pln"> </span><span class="atn">data-toggle</span><span class="pun">=</span><span class="atv">"collapse"</span><span class="pln"> </span><span class="atn">data-target</span><span class="pun">=</span><span class="atv">"#navbarNav"</span><span class="pln"> </span><span class="atn">aria-controls</span><span class="pun">=</span><span class="atv">"navbarNav"</span><span class="pln"> </span><span class="atn">aria-expanded</span><span class="pun">=</span><span class="atv">"false"</span><span class="pln"> </span><span class="atn">aria-label</span><span class="pun">=</span><span class="atv">"Toggle navigation"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;span</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar-toggler-icon"</span><span class="tag">&gt;&lt;/span&gt;</span><span class="pln">
        </span><span class="tag">&lt;/button&gt;</span><span class="pln">
        </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"collapse navbar-collapse"</span><span class="pln"> </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"navbarNav"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;ul</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar-nav"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;li</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"nav-item active"</span><span class="tag">&gt;</span><span class="pln">
                </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"nav-link"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"#"</span><span class="tag">&gt;</span><span class="pln">About</span><span class="tag">&lt;/a&gt;</span><span class="pln">
            </span><span class="tag">&lt;/li&gt;</span><span class="pln">
            </span><span class="tag">&lt;/ul&gt;</span><span class="pln">
        </span><span class="tag">&lt;/div&gt;</span><span class="pln">
    </span><span class="tag">&lt;/nav&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"container"</span><span class="tag">&gt;</span><span class="pln">
        {% for message in get_flashed_messages() %}
            </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"alert alert-danger"</span><span class="tag">&gt;</span><span class="pln">{{ message }}</span><span class="tag">&lt;/div&gt;</span><span class="pln">
        {% endfor %}
        {% block content %} {% endblock %}
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">

    </span><span class="com">&lt;!-- Optional JavaScript --&gt;</span><span class="pln">
    </span><span class="com">&lt;!-- jQuery first, then Popper.js, then Bootstrap JS --&gt;</span><span class="pln">
    </span><span class="tag">&lt;script</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"https://code.jquery.com/jquery-3.3.1.slim.min.js"</span><span class="pln"> </span><span class="atn">integrity</span><span class="pun">=</span><span class="atv">"sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"</span><span class="pln"> </span><span class="atn">crossorigin</span><span class="pun">=</span><span class="atv">"anonymous"</span><span class="tag">&gt;&lt;/script&gt;</span><span class="pln">
    </span><span class="tag">&lt;script</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"</span><span class="pln"> </span><span class="atn">integrity</span><span class="pun">=</span><span class="atv">"sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"</span><span class="pln"> </span><span class="atn">crossorigin</span><span class="pun">=</span><span class="atv">"anonymous"</span><span class="tag">&gt;&lt;/script&gt;</span><span class="pln">
    </span><span class="tag">&lt;script</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"</span><span class="pln"> </span><span class="atn">integrity</span><span class="pun">=</span><span class="atv">"sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"</span><span class="pln"> </span><span class="atn">crossorigin</span><span class="pun">=</span><span class="atv">"anonymous"</span><span class="tag">&gt;&lt;/script&gt;</span><span class="pln">
  </span><span class="tag">&lt;/body&gt;</span><span class="pln">
</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	الجزء الأكبر من الشيفرة السابقة هو تعليمات HTML معيارية وشيفرات لازمة لعمل بوتستراب، إذ تزوّد الوسوم <code>&lt;meta&gt;</code> متصفح الويب بالمعلومات، في حين ينشئ الوسم <code>&lt;link&gt;</code> ارتباطًا إلى ملفات <a href="https://wiki.hsoub.com/CSS" rel="external">CSS</a> الخاصة ببوتستراب، ويضمِّن الوسم <code>&lt;script&gt;</code> شيفرة جافا سكربت مسؤولة عن بعض ميزات بوتستراب الإضافية.
</p>

<p>
	يسمح الوسم <code>&lt;title&gt;{% block title %} {% endblock %}&lt;/title&gt;</code> للقوالب الوارثة (التي ترث خصائصها من قالب HTML الرئيسي) بتعريف عناوين خاصّة بها. استخدمنا الحلقة التكرارية <code>()for message in get_flashed_messages</code> لعرض الرسائل الخاطفة التحذيرية والتنبيهية؛ أما محتوى القوالب الوارثة فيُضمّن في الموضع المؤقت <code>{% block content %} {% endblock %}</code> الذي يوفرّه القالب الرئيسي مثل إضافة خاصّة بكل قالب يرث منه ما يجنّب تكرار التعليمات.
</p>

<p>
	احفظ الملف واغلقه.
</p>

<p>
	الآن سننشئ الملف "index.html" الذي <a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%8A%D8%B1%D8%A7%D8%AF-%D9%81%D9%8A-%D9%85%D9%8F%D8%AD%D8%B1%D9%91%D9%83-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-jinja-r497/" rel="">سيرث</a> قالب HTML الرئيسي "base.html":
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_5520_49" style=""><span class="pln">(env)user@localhost:$ nano templates/index.html</span></pre>

<p>
	وضمنه سنكتب الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_5520_51" style=""><span class="pln">{% extends 'base.html' %}

{% block content %}
    </span><span class="tag">&lt;h1&gt;</span><span class="pln">{% block title %} Welcome to FlaskNotes {% endblock %}</span><span class="tag">&lt;/h1&gt;</span><span class="pln">
    {% for note in notes %}
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"card"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"card-body"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;span</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"badge badge-primary"</span><span class="tag">&gt;</span><span class="pln">#{{ note['id'] }}</span><span class="tag">&lt;/span&gt;</span><span class="pln">
            </span><span class="tag">&lt;span</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"badge badge-default"</span><span class="tag">&gt;</span><span class="pln">{{ note['created'] }}</span><span class="tag">&lt;/span&gt;</span><span class="pln">
            </span><span class="tag">&lt;hr&gt;</span><span class="pln">
            </span><span class="tag">&lt;p&gt;</span><span class="pln">{{ note['content'] |safe }}</span><span class="tag">&lt;/p&gt;</span><span class="pln">
        </span><span class="tag">&lt;/div&gt;</span><span class="pln">
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
    </span><span class="tag">&lt;hr&gt;</span><span class="pln">
    {% endfor %}
{% endblock %}</span></pre>

<p>
	وبذلك نكون قد ورثنا خصائص القالب "base.html"، ثمّ عرّفنا عنوان title الملف، ومررنا باستخدام حلقة "for" التكرارية على الملاحظات لنعرض كل منها ضمن <a href="https://academy.hsoub.com/programming/html/%D9%85%D9%83%D9%88%D9%86-%D8%A7%D9%84%D8%A8%D8%B7%D8%A7%D9%82%D8%A9-card-%D9%88%D9%85%D9%83%D9%88%D9%86-%D8%A7%D9%84%D8%B4%D8%B1%D8%A7%D8%A6%D8%AD-%D8%A7%D9%84%D8%AF%D9%88%D8%A7%D8%B1%D8%A9-carousel-%D9%81%D9%8A-%D8%A8%D9%88%D8%AA%D8%B3%D8%AA%D8%B1%D8%A7%D8%A8-r1458/" rel="">بطاقة بوتستراب</a> Bootstrap card، بما يتضمّن معرّف وتاريخ إنشاء ومحتوى الملاحظة، والتي قد حوّلناها أصلًا إلى تنسيق HTML ضمن دالة فلاسك <code>()index</code>.
</p>

<p>
	وقد طبقنا المُرشّح <code>safe|</code> من محرّك القوالب جينجا <a href="https://academy.hsoub.com/tags/jinja2" rel="">jinja</a> على محتوى الملاحظات باستخدام <code>|</code> وهذا يُشبه مبدأ استدعاء دالة ما في لغة بايثون (أي أنّ الأمر <code>safe|</code> من أوامر جينجا يشابه بناء الأمر <code>safe(note['content‎'])‎</code> في لغة بايثون)، ويُمكّن المُرشّح المتصفح من تصييّر شيفرة HTML، وبدونه ستظهر تعليمات HTML ضمن الصفحة كما هي أي نص صرف، وتُعد هذه ميزة أمان تسمّى "تهريب شيفرة" HTML escaping، من شأنها منع المتصفح من تفسير شيفرات HTML الخبيثة المُسببة لثغرة أمنية خطيرة تسمّى <a href="https://academy.hsoub.com/programming/python/flask/%D8%A3%D9%87%D9%85-%D8%A7%D9%84%D9%85%D8%B1%D8%B4%D8%AD%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%AA%D9%88%D9%81%D8%B1%D8%A9-%D8%A8%D8%B4%D9%83%D9%84-%D9%82%D9%8A%D8%A7%D8%B3%D9%8A-%D9%81%D9%8A-%D9%85%D8%AD%D8%B1%D9%83-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-jinja-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-r447/" rel="">حقن الشيفرات البرمجية cross-site scripting</a> -أو اختصارًا XSS-، وبما أنّ مكتبة بايثون-مارك داون تعيد بالضرورة شيفرات HTML آمنة، فبإمكانك استخدام المُرشّح <code>safe|</code> معها ليتمكّن المتصفح من تصييّر هذه الشيفرات، ومن المهم عدم استخدام هذا المُرشّح إلّا مع شيفرات HTML الآمنة وموثوقة المصدر مثل تلك الناتجة عن مكتبة بايثون-ماركداون.
</p>

<p>
	احفظ الملف واغلقه.
</p>

<p>
	الآن سنسند القيم اللازمة لمتغيرات بيئة فلاسك، ثمّ سنشغّل التطبيق باستخدام الأوامر التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5520_53" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_APP</span><span class="pun">=</span><span class="pln">app
</span><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_ENV</span><span class="pun">=</span><span class="pln">development
</span><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ flask run</span></pre>

<p>
	يحدّد متغير فلاسك <code>FLASK_APP</code> في الشيفرة السابقة التطبيق المراد تشغيله وهو في حالتنا الملف "app.py"، في حين يحدّد المتغير <code>FLASK_ENV</code> وضع التشغيل وهنا قد اخترنا وضع التطوير <code>development</code>، الذي يعني أنّ التطبيق سيعمل في وضع التطوير مع تشغيل مُنقّح الأخطاء (ومن المهم عدم اختيار هذا الوضع في مرحلة الاستخدام الفعلي للتطبيق أي مرحلة نشر المُنتج)، ونهايةً شغلنا التطبيق باستخدام الأمر <code>flask run</code>.
</p>

<p>
	الآن وبالذهاب إلى الرابط "/http://127.0.0.1:5000" في المتصفّح سنحصل على الشكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="108084" href="https://academy.hsoub.com/uploads/monthly_2022_09/first_step2a.png.a1c18e01acee4690d8c8b9b672bc323b.png" rel="" data-fileext="png"><img alt="first_step2a.png" class="ipsImage ipsImage_thumbnailed" data-fileid="108084" data-unique="v7gxbd0d7" style="width: 700px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2022_09/first_step2a.thumb.png.2bf267eca46598f5ea1a39ed2cb4cb89.png"></a>
</p>

<p>
	وبذلك نجد أنّ المتصفح قد صيّر كافّة الملاحظات على أنّها مُصاغة في HTML ولم تظهر نصًا عاديًا كما هو.
</p>

<p>
	ومع نهاية هذه الخطوة نكون قد أنشأنا تطبيق فلاسك قادر على الاتصال مع قاعدة البيانات لجلب الملاحظات المحفوظة وتحويل محتوياتها من نص مُنسّق باستخدام ماركداون إلى ما يقابلها في HTML، ليجري تصيير شيفرات HTML هذه في الصفحة الرئيسية للتطبيق.
</p>

<p>
	سنضيف في الخطوة التالية اتجاه route يُمكّن المستخدمين من إضافة ملاحظات جديدة والذي بإمكانهم كتابته بتنسيق مارك داون.
</p>

<h2>
	الخطوة الثالثة – إضافة ملاحظات جديدة
</h2>

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

<p>
	سنستخدم نموذج ويب ليُدخل المستخدم ضمنه الملاحظة الجديدة لإضافتها إلى التطبيق وحفظها في قاعدة البيانات.
</p>

<p>
	لذا سنفتح الملف "app.py" بهدف إضافة الاتجاه الجديد:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5520_56" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	وضمنه سنضيف الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5520_58" style=""><span class="com">#. . .</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/create/'</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=(</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">))</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> create</span><span class="pun">():</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">method </span><span class="pun">==</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">:</span><span class="pln">
        content </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">]</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> content</span><span class="pun">:</span><span class="pln">
            flash</span><span class="pun">(</span><span class="str">'Content is required!'</span><span class="pun">)</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> redirect</span><span class="pun">(</span><span class="pln">url_for</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">))</span><span class="pln">
        conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'INSERT INTO notes (content) VALUES (?)'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">content</span><span class="pun">,))</span><span class="pln">
        conn</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">
        conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> redirect</span><span class="pun">(</span><span class="pln">url_for</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">))</span><span class="pln">

    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'create.html'</span><span class="pun">)</span></pre>

<p>
	وطالما أنّنا سنستخدم هذا الاتجاه لإدخال بيانات جديدة إلى قاعدة البيانات عبر نموذج ويب فكان لا بُدّ من تمكين كلا طريقتي الطلبات GET و POST باستخدام التعليمة <code>('methods=('GET', 'POST</code> في المُزخرف <code>()app.route</code>، ثمّ فتحنا اتصالًا مع قاعدة البيانات باستخدام دالة فلاسك <code>()create</code>.
</p>

<p>
	في الشيفرة السابقة: عندما يُدخل المستخدم بيانات في النموذج ويرسلها سيتحقق الشرط <code>'request.method == 'POST</code> لأنّ هذا الطلب من النوع POST، وعندها نستخلص محتوى الملاحظة باستخدام <code>['request.form['content</code> لنحفظه ضمن المتغير <code>content</code>، فإذا كان حقل المحتوى فارغًا نعرض للمستخدم رسالةً تحذيريةً تفيد بأن حقل المحتوى مطلوب "'!Content is required" ونعيد توجيهه إلى صفحة التطبيق الرئيسية، وإلّا وفي حال كون حقل المحتوى في النموذج ليس فارغًا فنستخدم تعليمة الإدخال <code>INSERT</code> في SQL لإضافة محتوى الملاحظة إلى جدول الملاحظات <code>notes</code> في قاعدة البيانات، ثمّ نحفظ التغييرات ونغلق الاتصال مع قاعدة البيانات ونعيد توجيه المستخدم إلى الصفحة الرئيسية.
</p>

<p>
	إذا كان الطلب من النوع GET، فهذا يعني أنّ المستخدم قد زار الصفحة فقط، وعندها نصيّر قالب HTML ذا الاسم "create.html".
</p>

<p>
	احفظ الملف واغلقه.
</p>

<p>
	نفتح الآن ملف القالب "create.html":
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_5520_60" style=""><span class="pln">(env)user@localhost:$ nano templates/create.html</span></pre>

<p>
	ونضيف ضمنه الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_5520_62" style=""><span class="pln">{% extends 'base.html' %}

{% block content %}
    </span><span class="tag">&lt;h1&gt;</span><span class="pln">{% block title %} Add a New Note {% endblock %}</span><span class="tag">&lt;/h1&gt;</span><span class="pln">
    </span><span class="tag">&lt;form</span><span class="pln"> </span><span class="atn">method</span><span class="pun">=</span><span class="atv">"post"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"form-group"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;label</span><span class="pln"> </span><span class="atn">for</span><span class="pun">=</span><span class="atv">"content"</span><span class="tag">&gt;</span><span class="pln">Note Content</span><span class="tag">&lt;/label&gt;</span><span class="pln">
        </span><span class="tag">&lt;textarea</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"text"</span><span class="pln"> </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"content"</span><span class="pln">
               </span><span class="atn">placeholder</span><span class="pun">=</span><span class="atv">"Note content, you can use Markdown syntax"</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"form-control"</span><span class="pln">
               </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"{{ request.form['content'] }}"</span><span class="pln"> </span><span class="atn">autofocus</span><span class="tag">&gt;&lt;/textarea&gt;</span><span class="pln">
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">

    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"form-group"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;button</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"submit"</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"btn btn-primary"</span><span class="tag">&gt;</span><span class="pln">Submit</span><span class="tag">&lt;/button&gt;</span><span class="pln">
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
    </span><span class="tag">&lt;/form&gt;</span><span class="pln">

{% endblock %}</span></pre>

<p>
	استخدمنا في الشيفرة المضافة إلى القالب نموذجًا للإدخال ذا حقلٍ نصي يتيح للمستخدم كتابة محتوى الملاحظة، وللحفاظ على إمكانية الوصول إلى البيانات المدخلة في حال حدوث أي أخطاء (مثل حالة ترك حقل محتوى الملاحظة فارغًا)، نستخدم الكائن <code>request.form</code> لحفظ البيانات المدخلة في النموذج، كما أضفنا زرًا لتأكيد النموذج أسفل الحقل النصي بحيث يتيح للمستخدم تأكيد النموذج وإرساله ضمن طلب بطريقة POST.
</p>

<p>
	احفظ الملف واغلقه.
</p>

<p>
	الآن سنفتح ملف قالب HTML الرئيسي "base.html" لإضافة زر "ملاحظة جديدة "New Note" إلى شريط التصفّح:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5520_64" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">base</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	ونعدّل الشيفرة في الملف لتصبح كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_5520_66" style=""><span class="dec">&lt;!doctype html&gt;</span><span class="pln">
</span><span class="tag">&lt;html</span><span class="pln"> </span><span class="atn">lang</span><span class="pun">=</span><span class="atv">"en"</span><span class="tag">&gt;</span><span class="pln">
  </span><span class="tag">&lt;head&gt;</span><span class="pln">
    </span><span class="com">&lt;!-- Required meta tags --&gt;</span><span class="pln">
    </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">"utf-8"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"viewport"</span><span class="pln"> </span><span class="atn">content</span><span class="pun">=</span><span class="atv">"width=device-width, initial-scale=1, shrink-to-fit=no"</span><span class="tag">&gt;</span><span class="pln">

    </span><span class="com">&lt;!-- Bootstrap CSS --&gt;</span><span class="pln">
    </span><span class="tag">&lt;link</span><span class="pln"> </span><span class="atn">rel</span><span class="pun">=</span><span class="atv">"stylesheet"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"</span><span class="pln"> </span><span class="atn">integrity</span><span class="pun">=</span><span class="atv">"sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"</span><span class="pln"> </span><span class="atn">crossorigin</span><span class="pun">=</span><span class="atv">"anonymous"</span><span class="tag">&gt;</span><span class="pln">

    </span><span class="tag">&lt;title&gt;</span><span class="pln">{% block title %} {% endblock %}</span><span class="tag">&lt;/title&gt;</span><span class="pln">
  </span><span class="tag">&lt;/head&gt;</span><span class="pln">
  </span><span class="tag">&lt;body&gt;</span><span class="pln">
    </span><span class="tag">&lt;nav</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar navbar-expand-md navbar-light bg-light"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar-brand"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('index')}}"</span><span class="tag">&gt;</span><span class="pln">FlaskNotes</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;button</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar-toggler"</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"button"</span><span class="pln"> </span><span class="atn">data-toggle</span><span class="pun">=</span><span class="atv">"collapse"</span><span class="pln"> </span><span class="atn">data-target</span><span class="pun">=</span><span class="atv">"#navbarNav"</span><span class="pln"> </span><span class="atn">aria-controls</span><span class="pun">=</span><span class="atv">"navbarNav"</span><span class="pln"> </span><span class="atn">aria-expanded</span><span class="pun">=</span><span class="atv">"false"</span><span class="pln"> </span><span class="atn">aria-label</span><span class="pun">=</span><span class="atv">"Toggle navigation"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;span</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar-toggler-icon"</span><span class="tag">&gt;&lt;/span&gt;</span><span class="pln">
        </span><span class="tag">&lt;/button&gt;</span><span class="pln">
        </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"collapse navbar-collapse"</span><span class="pln"> </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"navbarNav"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;ul</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar-nav"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;li</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"nav-item active"</span><span class="tag">&gt;</span><span class="pln">
                </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"nav-link"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"#"</span><span class="tag">&gt;</span><span class="pln">About</span><span class="tag">&lt;/a&gt;</span><span class="pln">
            </span><span class="tag">&lt;/li&gt;</span><span class="pln">

            </span><span class="tag">&lt;li</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"nav-item active"</span><span class="tag">&gt;</span><span class="pln">
                </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"nav-link"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('create') }}"</span><span class="tag">&gt;</span><span class="pln">New Note</span><span class="tag">&lt;/a&gt;</span><span class="pln">
           </span><span class="tag">&lt;/li&gt;</span><span class="pln">
            </span><span class="tag">&lt;/ul&gt;</span><span class="pln">
        </span><span class="tag">&lt;/div&gt;</span><span class="pln">
    </span><span class="tag">&lt;/nav&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"container"</span><span class="tag">&gt;</span><span class="pln">
        {% for message in get_flashed_messages() %}
            </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"alert alert-danger"</span><span class="tag">&gt;</span><span class="pln">{{ message }}</span><span class="tag">&lt;/div&gt;</span><span class="pln">
        {% endfor %}
        {% block content %} {% endblock %}
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">

    </span><span class="com">&lt;!-- Optional JavaScript --&gt;</span><span class="pln">
    </span><span class="com">&lt;!-- jQuery first, then Popper.js, then Bootstrap JS --&gt;</span><span class="pln">
    </span><span class="tag">&lt;script</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"https://code.jquery.com/jquery-3.3.1.slim.min.js"</span><span class="pln"> </span><span class="atn">integrity</span><span class="pun">=</span><span class="atv">"sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"</span><span class="pln"> </span><span class="atn">crossorigin</span><span class="pun">=</span><span class="atv">"anonymous"</span><span class="tag">&gt;&lt;/script&gt;</span><span class="pln">
    </span><span class="tag">&lt;script</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"</span><span class="pln"> </span><span class="atn">integrity</span><span class="pun">=</span><span class="atv">"sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"</span><span class="pln"> </span><span class="atn">crossorigin</span><span class="pun">=</span><span class="atv">"anonymous"</span><span class="tag">&gt;&lt;/script&gt;</span><span class="pln">
    </span><span class="tag">&lt;script</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"</span><span class="pln"> </span><span class="atn">integrity</span><span class="pun">=</span><span class="atv">"sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"</span><span class="pln"> </span><span class="atn">crossorigin</span><span class="pun">=</span><span class="atv">"anonymous"</span><span class="tag">&gt;&lt;/script&gt;</span><span class="pln">
  </span><span class="tag">&lt;/body&gt;</span><span class="pln">
</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	أضفنا في الشيفرة السابقة عنصر قائمة <code><a href="https://wiki.hsoub.com/HTML/li" rel="external">&lt;li&gt;</a></code> إلى شريط التصفح واستخدمنا فيه الدالة <code>()url_for</code> لتأمين الربط مع دالة فلاسك <code>()create</code>، وهذا ما يضمن الوصول إلى صفحة إنشاء ملاحظة جديدة من شريط التصفح.
</p>

<p>
	الآن شغّل خادم التطوير إن لم يكن مُشغّلًا أصلًا باستخدام الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5520_69" style=""><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ flask run</span></pre>

<p>
	الآن، استخدم الرابط "http://127.0.0.1:5000/create" في المتصفّح وأضف الملاحظة التالية المُنسقّة باستخدام مارك داون:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5520_71" style=""><span class="com">### Flask</span><span class="pln">
</span><span class="typ">Flask</span><span class="pln"> </span><span class="kwd">is</span><span class="pln"> a </span><span class="pun">**</span><span class="pln">web</span><span class="pun">**</span><span class="pln"> framework </span><span class="kwd">for</span><span class="pln"> </span><span class="typ">_Python_</span><span class="pun">.</span><span class="pln">

</span><span class="typ">Here</span><span class="pln"> </span><span class="kwd">is</span><span class="pln"> the </span><span class="typ">Flask</span><span class="pln"> logo</span><span class="pun">:</span><span class="pln">

</span><span class="pun">![</span><span class="typ">Flask</span><span class="pln"> </span><span class="typ">Logo</span><span class="pun">](</span><span class="pln">https</span><span class="pun">://</span><span class="pln">flask</span><span class="pun">.</span><span class="pln">palletsprojects</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">en</span><span class="pun">/</span><span class="lit">1.1</span><span class="pun">.</span><span class="pln">x</span><span class="pun">/</span><span class="pln">_images</span><span class="pun">/</span><span class="pln">flask</span><span class="pun">-</span><span class="pln">logo</span><span class="pun">.</span><span class="pln">png</span><span class="pun">)</span></pre>

<p>
	يتضمّن تنسيق مارك داون للملاحظة السابقة عنوانًا من المستوى الثالث <code>h3</code> ويجعل كلمة <code>web</code> بخط غامق وكلمة <code>python</code> بخط مائل، كما يتضمّن صورة.
</p>

<p>
	وبتأكيد الملاحظة وإرسالها سيحوّل التطبيق التنسيقات إلى مقابلاتها في HTML لتظهر بالشكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="108085" href="https://academy.hsoub.com/uploads/monthly_2022_09/second_step3a.png.153e526ac086ab3cfa70f29437c52855.png" rel="" data-fileext="png"><img alt="second_step3a.png" class="ipsImage ipsImage_thumbnailed" data-fileid="108085" data-unique="8w1tzgvg8" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2022_09/second_step3a.thumb.png.76e99f78847ed5f8470389b5394e86db.png"></a>
</p>

<h2>
	الخاتمة
</h2>

<p>
	أنشأنا في هذا المقال تطبيق في فلاسك لإدخال الملاحظات المُنسّقة باستخدام ماركداون، ما يسمح للمستخدمين بإضافة التنسيقات لملاحظاتهم بما يتضمّن العناوين باختلاف مستوياتها، والخطوط الغامقة والمائلة، وإضافة الصور والروابط.
</p>

<p>
	وربطنا التطبيق مع قاعدة بيانات SQLite لحفظ البيانات وجلبها، إذ سيفسّر التطبيق النصوص المُنسقّة باستخدام ماركداون إلى مقابلاتها من شيفرات HTML ليتمكّن المتصفح من تصييّرها بشكلها المطلوب.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-use-python-markdown-with-flask-and-sqlite" rel="external nofollow">How To Use Python-Markdown with Flask and SQLite</a> لصاحبه Abdelhadi Dyouri.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال التالي: <a href="https://academy.hsoub.com/devops/deployment/%D8%AA%D8%AE%D8%AF%D9%8A%D9%85-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AE%D8%A7%D8%AF%D9%85%D9%8A-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-uwsgi-%D9%88-nginx-r649/" rel="">تخديم تطبيقات فلاسك باستخدام خادمي الويب uWSGI و Nginx</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D8%A3%D9%87%D9%85-8-%D9%85%D9%83%D8%AA%D8%A8%D8%A7%D8%AA-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D8%AA%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-%D9%81%D9%8A-%D8%A7%D9%84%D9%85%D8%B4%D8%A7%D8%B1%D9%8A%D8%B9-%D8%A7%D9%84%D8%B5%D8%BA%D9%8A%D8%B1%D8%A9-r654/" rel="">أهم 8 مكتبات بلغة البايثون تستخدم في المشاريع الصغيرة</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%88%D9%8A%D8%A8-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D9%85%D9%86-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1622/" rel="">إنشاء تطبيق ويب باستخدام إطار عمل فلاسك Flask من لغة بايثون</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/servers/databases/%d9%83%d9%8a%d9%81-%d9%88%d9%85%d8%aa%d9%89-%d9%86%d8%b3%d8%aa%d8%ae%d8%af%d9%85-sqlite-r111/" rel="">كيف ومتى نستخدم SQLite</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-sqlite-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-flask-r346/" rel="">التعامل مع قواعد البيانات SQLite في تطبيقات Flask</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1714</guid><pubDate>Tue, 20 Sep 2022 18:08:00 +0000</pubDate></item><item><title>&#x628;&#x646;&#x627;&#x621; &#x645;&#x62E;&#x62A;&#x635;&#x631; &#x631;&#x648;&#x627;&#x628;&#x637; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x625;&#x637;&#x627;&#x631; &#x627;&#x644;&#x639;&#x645;&#x644; &#x641;&#x644;&#x627;&#x633;&#x643; Flask &#x648;&#x645;&#x62D;&#x631;&#x643; &#x642;&#x648;&#x627;&#x639;&#x62F; &#x627;&#x644;&#x628;&#x64A;&#x627;&#x646;&#x627;&#x62A; SQLite</title><link>https://academy.hsoub.com/programming/python/flask/%D8%A8%D9%86%D8%A7%D8%A1-%D9%85%D8%AE%D8%AA%D8%B5%D8%B1-%D8%B1%D9%88%D8%A7%D8%A8%D8%B7-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D9%88%D9%85%D8%AD%D8%B1%D9%83-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-sqlite-r1633/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_07/62d2666507841_URLShortenerFlaskSQLite.png.831405ebe4fae1bef28f6bf87119919b.png" /></p>

<p>
	يُعد <a href="https://academy.hsoub.com/programming/python/flask/" rel="">فلاسك</a> إطار عمل لبناء تطبيقات الويب مبني باستخدام لغة <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D9%85%D8%B1%D8%AC%D8%B9-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%AA%D8%B9%D9%84%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r735/" rel="">بايثون</a>، أما <a href="https://academy.hsoub.com/devops/servers/databases/%D9%83%D9%8A%D9%81-%D9%88%D9%85%D8%AA%D9%89-%D9%86%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-sqlite-r111/" rel="">SQLite</a>، فهو محرك قواعد بيانات يُستخدم مع بايثون لتخزين بيانات التطبيقات. سنبني في هذا المقال مختصِرًا للروابط URL shortener ليحوّل أي رابط إلى آخر مُختصَر وأكثر موثوقية بما يشبه مبدأ عمل موقع <a href="https://bitly.com/" rel="external nofollow">bit.ly</a>.
</p>

<p>
	سنستخدم في بناء تطبيقنا مكتبة التعمية <a href="https://academy.hsoub.com/programming/advanced/%D9%85%D9%81%D9%87%D9%88%D9%85-%D8%AF%D9%88%D8%A7%D9%84-%D8%A7%D9%84%D8%AA%D9%82%D8%B7%D9%8A%D8%B9-hash-functions-%D9%81%D9%8A-%D8%A7%D9%84%D8%AE%D9%88%D8%A7%D8%B1%D8%B2%D9%85%D9%8A%D8%A7%D8%AA-r1428/" rel="">Hashids</a> لتوليد سلاسل فريدة لمُعرّفات الروابط؛ إذ أن هذه المكتبة مُختصّةٌ بتوليد مُعرّفات مميزة قصيرة unique ID انطلاقًا من أرقام صحيحة، إذ تحوّل مثلاً الرقم "12" إلى سلسلة فريدة مثل "1Xcld".
</p>

<p>
	يمكن استخدام السلاسل الفريدة هذه لتوليد معرّفات لا يمكن التنبؤ بها سواءٌ للفيديوهات على مواقع مشاركة الفيديوهات، أو الصور في خدمة رفع الصور وغيرها. ففي حال وصول المستخدم مثلًا إلى صورةٍ ذات رابط "your_domain/image/J32Fr"، فلا يمكنه التنبؤ بمواقع الصور الأخرى، على عكس حالة استخدام معرفّات مؤلفّة من أرقام صحيحة في مختصر الروابط، فمثلاً لو كان رابط الصورة "your_domain/image/33"، فسيتمكن المستخدم من التنبؤ بمواقع الصور الأخرى بسهولة، وبالتالي يقدِّم استخدام روابط بمعرّفات غير قابلة للتنبؤ نوعًا من الخصوصية <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">للخادم</a>، كونها تمنع المستخدمين من اكتشاف الروابط المختصرة المُستخدمة من قِبل المستخدمين الآخرين.
</p>

<p>
	سنبني إذًا مختصِرًا للروابط باستخدام فلاسك وSQLite ومكتبة Hashids، بحيث يتمكّن المستخدمون من إدخال روابط وتوليد نسخٍ مختصرةٍ منها، إضافةً إلى توفير صفحة إحصاءات يمكن للمستخدمين من خلالها متابعة عدد مرات زيارة الرابط، كما سنستخدم <a href="https://academy.hsoub.com/programming/css/bootstrap/" rel="">بوتستراب bootstrap</a> لإضافة التنسيقات للتطبيق.
</p>

<h2>
	مستلزمات العمل
</h2>

<p>
	قبل المتابعة في هذا المقال لا بدّ من:
</p>

<ul>
<li>
		توفُّر <a href="https://academy.hsoub.com/programming/python/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%AA%D9%87%D8%A7-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r714/" rel="">بيئة برمجة بايثون 3 محلية</a>، مثبّتة على حاسوبك، وسنفترض في مقالنا أن اسم مجلد المشروع هو flask_shortener.
	</li>
	<li>
		فهم مبادئ فلاسك، مثل بناء الوجهات وإخراج نماذج <a href="https://wiki.hsoub.com/HTML" rel="external">HTML</a> والاتصال بقواعد بيانات SQLite، ويمكنك الاطلاع على [هذا المقال](رابط المقال الأوّل بعد نشره) لمزيدٍ من المعلومات حول هذه النقاط.
	</li>
</ul>
<h2>
	الخطوة الأولى- إعداد المتطلبات
</h2>

<p>
	سنفعّل في هذه الخطوة بيئة بايثون ونثبّت فلاسك باستخدام مثبِّت الحزم pip، ثم سننشئ قاعدة بيانات سنستخدمها لتخزين الروابط، وفي حال كون بيئة البرمجة غير مفعّلة بعد، سنستخدم الأمر التالي لتفعيلها:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1980_9" style="">
<span class="pln">$ source env</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">activate</span></pre>

<p>
	وبعد تفعيل بيئة البرمجة، سنثبّت فلاسك ومكتبة Hashids باستخدام الأمر التالي:
</p>

<pre class="ipsCode">
(env)user@localhost:$ pip install flask hashids
</pre>

<p>
	ثمّ سننشئ ملف تخطيط قاعدة البيانات باسم schema.sql، ليحتوي أوامر <a href="https://wiki.hsoub.com/SQL" rel="external">SQL</a> اللازمة لإنشاء جدول للعناوين باسم urls.
</p>

<p>
	لذا، سننشئ الملف schema.sql داخل المجلد flask_shortener كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1980_11" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano schema</span><span class="pun">.</span><span class="pln">sql</span></pre>

<p>
	ونكتب أوامر SQL التالية فيه:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_1980_13" style="">
<span class="pln">DROP TABLE IF EXISTS urls</span><span class="pun">;</span><span class="pln">

CREATE TABLE urls </span><span class="pun">(</span><span class="pln">
    id INTEGER PRIMARY KEY AUTOINCREMENT</span><span class="pun">,</span><span class="pln">
    created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP</span><span class="pun">,</span><span class="pln">
    original_url TEXT NOT NULL</span><span class="pun">,</span><span class="pln">
    clicks INTEGER NOT NULL DEFAULT </span><span class="lit">0</span><span class="pln">
</span><span class="pun">);</span></pre>

<p>
	أول سطرٍ من شيفرة SQL السابقة مسؤولٌ عن حذف أي جداول موجودةٍ سابقًا ضمن قاعدة البيانات urls، وبذلك لن يكون هناك أي سلوك متضارب أو عشوائي ناجمٍ عن تشابه الأسماء ضمن قاعدة البيانات، والجدير بالملاحظة أنّ تطبيق هذين الأمر سيؤدي إلى حذف كامل محتوى قاعدة البيانات الحالي، لذا لا تُدخل أي بياناتٍ مهمّة في التطبيق حتى تنتهي من كافّة الخطوات التعليميّة في المقال وتجربة نتائجه النهائية.
</p>

<p>
	بعد ذلك، سننشئ جدولًا يحتوي الأعمدة التالية:
</p>

<ul>
<li>
		"id": ويحتوي بياناتٍ من نوع رقم صحيح ويمثِّل المعرّف الفريد لكل رابطٍ مُدخل، وسنستخدم هذا المعرّف لاحقًا في استنتاج الرابط الأصلي من سلسلة محارف عشوائية معمّاة Hash string.
	</li>
	<li>
		"created": يحتوي على تاريخ ووقت اختصار الرابط.
	</li>
	<li>
		"original_url": يحتوي على الرابط الكامل الأصلي قبل الاختصار والذي سيُعاد توجيه المستخدمين إليه.
	</li>
	<li>
		"clicks": يحتوي على عدد مرات النقر على الرابط. ستكون قيمته الابتدائية 0، وتزداد مع كل إعادة توجيه.
	</li>
</ul>
<p>
	اِحفظ الملف وأغلقه.
</p>

<p>
	الآن لتنفيذ ملف schema.sql المسؤول عن بناء جدول الروابط urls، سننشئ ملفًا باسم int_db.py داخل مجلد flasks_hortener:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1980_16" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano init_db</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ونضيف الشيفرة التالية إليه:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1980_18" style="">
<span class="kwd">import</span><span class="pln"> sqlite3

connection </span><span class="pun">=</span><span class="pln"> sqlite3</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="str">'database.db'</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">with</span><span class="pln"> open</span><span class="pun">(</span><span class="str">'schema.sql'</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> f</span><span class="pun">:</span><span class="pln">
    connection</span><span class="pun">.</span><span class="pln">executescript</span><span class="pun">(</span><span class="pln">f</span><span class="pun">.</span><span class="pln">read</span><span class="pun">())</span><span class="pln">

connection</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">
connection</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span></pre>

<p>
	إذ يحدث في البداية اتصالٌ مع الملف database.db المُنشأ تلقائيًا عند تنفيذ البرنامج السابق، ويمثّل هذا الملف قاعدة البيانات التي ستخزّن كامل بيانات التطبيق، ثم يُفتح بعد ذلك ملف تخطيط قاعدة البيانات schema.sql ويُشغَّل باستخدام التابع <code>()executscript</code> القادر على تنفيذ عدّة تعليمات SQL معًا، وهذا ما سينشئ بالنتيجة جدول الروابط <code>urls</code>، وفي النهاية نؤكِّد على التغييرات ويُغلق الاتصال مع قاعدة البيانات.
</p>

<p>
	اِحفظ الملف وأغلقه.
</p>

<p>
	الآن، نشغّل البرنامج:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1980_22" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ python init_db</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	وبعد انتهاء تنفيذه، سيظهر ملفٌ جديدٌ باسم database.db ضمن المجلد flask_shortener.
</p>

<p>
	وبنهاية الخطوة الأولى، نكون قد ثبّتنا فلاسك ومكتبة Hashids، وأنشأنا مخطّط قاعدة البيانات إضافةً لقاعدة بيانات SQLite تحتوي جدولًا باسم "urls" مخصّصًا لتخزين العناوين الأصلية لتلك المُختصرة.
</p>

<p>
	سنستخدم فيما يلي فلاسك لإنشاء الصفحة الرئيسية index والتي سيتمكّن المستخدمون من خلالها من إدخال الروابط المطلوب اختصارها.
</p>

<h2>
	الخطوة الثانية - إنشاء الصفحة الرئيسية لتطبيق اختصار الروابط
</h2>

<p>
	سننشئ في هذه الخطوة الوجهة اللازم لعمل الصفحة الرئيسية index، والذي يمكّن المستخدمين من إدخال الروابط الأصلية المراد اختصارها؛ إذ ستُخزَّن هذه الروابط في قاعدة البيانات بدايةً، ويستخدم المعرّف الفريد للرابط لتوليد سلسلة محارف قصيرة معمّاة باستخدام مكتبة Hashids، ويبني الرابط المُختصر ويخرجه نتيجةً.
</p>

<p>
	سننشئ الآن الملف الأساسي لتطبيق فلاسك باسم app.py ضمن المجلد flask_shortener:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1980_25" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ونضيف الشيفرة التالية ضمنه:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1980_27" style="">
<span class="kwd">import</span><span class="pln"> sqlite3
</span><span class="kwd">from</span><span class="pln"> hashids </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Hashids</span><span class="pln">
</span><span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> render_template</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">,</span><span class="pln"> flash</span><span class="pun">,</span><span class="pln"> redirect</span><span class="pun">,</span><span class="pln"> url_for


</span><span class="kwd">def</span><span class="pln"> get_db_connection</span><span class="pun">():</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> sqlite3</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="str">'database.db'</span><span class="pun">)</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">row_factory </span><span class="pun">=</span><span class="pln"> sqlite3</span><span class="pun">.</span><span class="typ">Row</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> conn</span></pre>

<p>
	نبدأ بالشيفرة السابقة باستيراد الوحدة <code>sqlite3</code>، والصنف <code>Hashids</code> من المكتبة <code>hashids</code>، إضافةً للمساعد الخاص بإطار فلاسك، ثم تفتح الدالة <code>()get_db_connection</code> اتصالًا مع ملف قاعدة البيانات <code>database.db</code>، وتعيّن قيمة السمة <code>row_factory</code> لتكون <code>sqlite3.Row</code> لنتمكّن من الوصول لأعمدة جدول <a href="https://academy.hsoub.com/programming/sql/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%B9%D9%86-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-r584/" rel="">قاعدة البيانات</a> باستخدام أسمائها؛ وهذا يعني أنّ اتصال قاعدة البيانات سيعيد سجلات يمكننا التعامل معها كما هو الحال مع قواميس بايثون Dictionaries الاعتيادية (وحدات تخزين القيم المنظمّة في بايثون)، وفي النهاية تعيد الدالة كائن الاتصال <code>conn</code>، الذي سيُستخدم للوصول إلى قاعدة البيانات.
</p>

<p>
	سنضيف الآن الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1980_30" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">config</span><span class="pun">[</span><span class="str">'SECRET_KEY'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'this should be a secret random string'</span><span class="pln">

hashids </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Hashids</span><span class="pun">(</span><span class="pln">min_length</span><span class="pun">=</span><span class="lit">4</span><span class="pun">,</span><span class="pln"> salt</span><span class="pun">=</span><span class="pln">app</span><span class="pun">.</span><span class="pln">config</span><span class="pun">[</span><span class="str">'SECRET_KEY'</span><span class="pun">])</span></pre>

<p>
	التي تنشئ كائن تطبيق فلاسك وتحدِّد مفتاح الأمان لجعل الجلسات آمنة، وبما أنّ مفتاح الأمان هو أيضًا سلسلة محارف عشوائية وسريّة، فسنستخدمها مصدرًا لمحارف الإغفال salt لمكتبة التعمية Hashids بما يضمن كون السلاسل المعمّاة الناتجة غير قابلة للتنبؤ كونها ستتغير بتغيّر محارف الإغفال المتغيرة أصلًا باستمرار مع تغيُّر مفتاح الأمان.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<div class="ipsQuote_contents ipsClearfix">
		<p>
			<strong>ملاحظة</strong>: محارف الإغفال salt هي سلاسل عشوائية تُزوَّد بها دالة التعمية <code>()hashids.encode</code>، بحيث تكون السلسلة المعمّاة الناتجة مبنيةً باستخدام محارف الإغفال هذه، بما يضمن كون السلاسل الناتجة فريدةً وغير قابلةٍ للتخمين، مثل مبدأ كلمات المرور السرية والتي لا يستخدمها سوى مالكها لتشفير وفك تشفير السلاسل المعمّاة.
		</p>
	</div>
</blockquote>

<p>
	احرص على إبقاء محارف الإغفال سريةً حفاظًا على الأمان، ولهذا السبب ارتأينا أنّه من الأفضل استخدام مفتاح الأمان الخاص بالتطبيق على هيئة محارف إغفال.
</p>

<p>
	وبذلك نكون أنشأنا كائن تعمية <code>hashids</code> يضمن كون كل سلسلة معمّاة مؤلفّة من <code>4</code> محارف على الأقل عبر تمرير هذه القيمة إلى المعامل <code>min_length</code> من هذا الكائن، واستخدمنا مفتاح أمان التطبيق مثل محرف إغفال.
</p>

<p>
	سنضيف الآن الشيفرة التالية إلى نهاية الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1980_32" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=(</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">))</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> index</span><span class="pun">():</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">method </span><span class="pun">==</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">:</span><span class="pln">
        url </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'url'</span><span class="pun">]</span><span class="pln">

        </span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> url</span><span class="pun">:</span><span class="pln">
            flash</span><span class="pun">(</span><span class="str">'The URL is required!'</span><span class="pun">)</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> redirect</span><span class="pun">(</span><span class="pln">url_for</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">))</span><span class="pln">

        url_data </span><span class="pun">=</span><span class="pln"> conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'INSERT INTO urls (original_url) VALUES (?)'</span><span class="pun">,</span><span class="pln">
                                </span><span class="pun">(</span><span class="pln">url</span><span class="pun">,))</span><span class="pln">
        conn</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">
        conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">

        url_id </span><span class="pun">=</span><span class="pln"> url_data</span><span class="pun">.</span><span class="pln">lastrowid
        hashid </span><span class="pun">=</span><span class="pln"> hashids</span><span class="pun">.</span><span class="pln">encode</span><span class="pun">(</span><span class="pln">url_id</span><span class="pun">)</span><span class="pln">
        short_url </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">host_url </span><span class="pun">+</span><span class="pln"> hashid

        </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'index.html'</span><span class="pun">,</span><span class="pln"> short_url</span><span class="pun">=</span><span class="pln">short_url</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'index.html'</span><span class="pun">)</span></pre>

<p>
	يعدِّل <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D9%85%D8%B2%D8%AE%D8%B1%D9%81%D8%A7%D8%AA-decorators-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r303/" rel="">المزخرف</a> <code>app.route@</code> دوال بايثون المألوفة لتصبح دوالًا عاملةً في فلاسك مثل الدالة <code>()index</code>، التي تُحوَّل القيمة المعادة منها إلى استجابةٍ من نوع <a href="https://academy.hsoub.com/programming/general/%d9%85%d8%af%d8%ae%d9%84-%d8%a5%d9%84%d9%89-http-r73/" rel="">HTTP</a> تُعرض لدى عميل HTTP الذي قد يكون متصفحًا مثلًا.
</p>

<p>
	وضمن بناء دالة فلاسك <code>()index</code>، سنمرّر السطر التالي:
</p>

<pre class="ipsCode">
methods=('GET', 'POST')
</pre>

<p>
	الحاوي على طرائق الطلبات المقبولة إلى وسيط الطرائق <code>methods</code> في المزخرف <code>()app.route</code>، ما يضمن قبول الطلبات سواءً بطريقة GET أو POST.
</p>

<p>
	الآن سيُفتح اتصالٌ مع قاعدة البيانات، فإذا كان الطلب بطريقة GET، نتجاوز الشرط:
</p>

<pre class="ipsCode">
if request.method == 'POST'
</pre>

<p>
	وصولًا إلى آخر سطر في الشيفرة والمسؤول عن إخراج قالب HTML باسم index.html، الذي سيحتوي على نموذج إدخال الروابط المُراد اختصارها؛ أما في حال تحقّق الشرط السابق، فهذا يعني أنّه قد أُدخل رابطٌ من قِبل المستخدم لاختصاره، فيُخزَّن هذا الرابط ضمن المتغير <code>url</code>؛ أمّا في حال إرسال المستخدم النموذج دون إدخال رابط، فستظهر له رسالةً تخبره بأنّ حقل الرابط مطلوب "!The URL is required" ويُعاد توجيهه إلى الصفحة الرئيسية index مجدّدًا.
</p>

<p>
	وعندما يرسل المستخدم نموذجًا حاويًا على رابط، يُخزّن هذا الرابط في جدول <code>urls</code> من قاعدة البيانات باستخدام تعليمة <code>INSERT INTO</code> في SQL، كما يُضمَّن الموضع المؤقت <code>?</code> (الذي سيُستبدل لاحقًا برقمٍ ما) في الدالة <code>()execute</code>، ومن ثمّ يُمرّر السجل الحاوي على الرابط المطلوب اختصاره ليُحفظ في قاعدة البيانات بأمان، وفي النهاية تؤكَّد العملية ويُقطع الاتصال مع قاعدة البيانات.
</p>

<p>
	بعد ذلك، يُخزَّن المعرّف الخاص بالرابط المُدخل آنفًا في قاعدة البيانات ضمن متغيّر باسم <code>url_id</code>، و يمكننا الوصول إلى معرّف الرابط باستخدام السمة <code>lastrowid</code> التي تعيد معرّف آخر سجل مُدخلٍ في قاعدة البيانات.
</p>

<p>
	وتُبنى السلسلة المعمّاة بتمرير معرّف الرابط إلى التابع <code>()hashids.encode</code>، لتخزَّن في المتغير <code>hashid</code>، فمثلًا ستكون القيمة المعادة لدى استدعاء التابع <code>(hashids.encode(1</code> هي السلسلة المُعمّاة <code>KJ34</code> وذلك اعتمادًا على محارف الإغفال المُستخدمة.
</p>

<p>
	ننتقل الآن إلى مرحلة بناء الرابط المُختصر باستخدام السمة <code>request.host_url</code>، التي يوفرّها عادةً كائن فلاسك <code>request</code> للوصول إلى رابط مضيف التطبيق، والذي سيكون <code>/http://127.0.0.1:5000</code> في بيئة التطوير، ويصبح "your_domain" لدى نشر التطبيق؛ فمثلًا ستكون قيمة المتغير <code>short_url</code> مساويةً <code>http://127.0.0.1:5000/KJ34</code>، والتي تمثّل الرابط المختصر الذي سيعيد توجيه المُستخدم إلى الرابط الأصلي ذي المعرّف الموافق للسلسلة المعمّاة <code>KJ34</code> والمحفوظ في قاعدة البيانات .
</p>

<p>
	نهايةً، يُخرج قالب HTML المسمّى index.html بتمرير المتغير <code>short_url</code> إليه. وستبدو الشيفرة كما يلي بعد الإضافات السابقة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1980_35" style="">
<span class="kwd">import</span><span class="pln"> sqlite3
</span><span class="kwd">from</span><span class="pln"> hashids </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Hashids</span><span class="pln">
</span><span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> render_template</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">,</span><span class="pln"> flash</span><span class="pun">,</span><span class="pln"> redirect</span><span class="pun">,</span><span class="pln"> url_for


</span><span class="kwd">def</span><span class="pln"> get_db_connection</span><span class="pun">():</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> sqlite3</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="str">'database.db'</span><span class="pun">)</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">row_factory </span><span class="pun">=</span><span class="pln"> sqlite3</span><span class="pun">.</span><span class="typ">Row</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> conn


app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">config</span><span class="pun">[</span><span class="str">'SECRET_KEY'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'this should be a secret random string'</span><span class="pln">

hashids </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Hashids</span><span class="pun">(</span><span class="pln">min_length</span><span class="pun">=</span><span class="lit">4</span><span class="pun">,</span><span class="pln"> salt</span><span class="pun">=</span><span class="pln">app</span><span class="pun">.</span><span class="pln">config</span><span class="pun">[</span><span class="str">'SECRET_KEY'</span><span class="pun">])</span><span class="pln">


</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=(</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">))</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> index</span><span class="pun">():</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">method </span><span class="pun">==</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">:</span><span class="pln">
        url </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'url'</span><span class="pun">]</span><span class="pln">

        </span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> url</span><span class="pun">:</span><span class="pln">
            flash</span><span class="pun">(</span><span class="str">'The URL is required!'</span><span class="pun">)</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> redirect</span><span class="pun">(</span><span class="pln">url_for</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">))</span><span class="pln">

        url_data </span><span class="pun">=</span><span class="pln"> conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'INSERT INTO urls (original_url) VALUES (?)'</span><span class="pun">,</span><span class="pln">
                                </span><span class="pun">(</span><span class="pln">url</span><span class="pun">,))</span><span class="pln">
        conn</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">
        conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">

        url_id </span><span class="pun">=</span><span class="pln"> url_data</span><span class="pun">.</span><span class="pln">lastrowid
        hashid </span><span class="pun">=</span><span class="pln"> hashids</span><span class="pun">.</span><span class="pln">encode</span><span class="pun">(</span><span class="pln">url_id</span><span class="pun">)</span><span class="pln">
        short_url </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">host_url </span><span class="pun">+</span><span class="pln"> hashid

        </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'index.html'</span><span class="pun">,</span><span class="pln"> short_url</span><span class="pun">=</span><span class="pln">short_url</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'index.html'</span><span class="pun">)</span></pre>

<p>
	اِحفظ الملف وأغلقه.
</p>

<p>
	الآن سننشئ ملف القالب الأساسي وملف قالب HTML المسمّى index.html، لذا سنضيف مجلدًا جديدًا للقوالب باسم templates ضمن المجلد flask_shortener، وسنفتح ملفًا باسم base.html ضمنه:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1980_37" style="">
<span class="pln">(env)user@localhost:$ mkdir templates
(env)user@localhost:$ nano templates/base.html</span></pre>

<p>
	والآن سنكتب شيفرة <a href="https://academy.hsoub.com/programming/css/bootstrap/" rel="">بوتستراب bootstrap</a> التالية ضمن الملف base.html، وإن لم تكن قوالب HTML في فلاسك مألوفةً بالنسبة لك، يمكنك قراءة <a href="https://academy.hsoub.com/programming/python/flask/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%88%D9%8A%D8%A8-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D9%85%D9%86-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1622/" rel="">الخطوة الثالثة</a> من المقال كيفية إنشاء تطبيق ويب باستخدام إطار فلاسك في لغة بايثون 3:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1980_39" style="">
<span class="dec">&lt;!doctype html&gt;</span><span class="pln">
</span><span class="tag">&lt;html</span><span class="pln"> </span><span class="atn">lang</span><span class="pun">=</span><span class="atv">"en"</span><span class="tag">&gt;</span><span class="pln">
  </span><span class="tag">&lt;head&gt;</span><span class="pln">
    </span><span class="com">&lt;!-- Required meta tags --&gt;</span><span class="pln">
    </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">"utf-8"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"viewport"</span><span class="pln"> </span><span class="atn">content</span><span class="pun">=</span><span class="atv">"width=device-width, initial-scale=1, shrink-to-fit=no"</span><span class="tag">&gt;</span><span class="pln">

    </span><span class="com">&lt;!-- Bootstrap CSS --&gt;</span><span class="pln">
    </span><span class="tag">&lt;link</span><span class="pln"> </span><span class="atn">rel</span><span class="pun">=</span><span class="atv">"stylesheet"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"</span><span class="pln"> </span><span class="atn">integrity</span><span class="pun">=</span><span class="atv">"sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"</span><span class="pln"> </span><span class="atn">crossorigin</span><span class="pun">=</span><span class="atv">"anonymous"</span><span class="tag">&gt;</span><span class="pln">

    </span><span class="tag">&lt;title&gt;</span><span class="pln">{% block title %} {% endblock %}</span><span class="tag">&lt;/title&gt;</span><span class="pln">
  </span><span class="tag">&lt;/head&gt;</span><span class="pln">
  </span><span class="tag">&lt;body&gt;</span><span class="pln">
    </span><span class="tag">&lt;nav</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar navbar-expand-md navbar-light bg-light"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar-brand"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('index')}}"</span><span class="tag">&gt;</span><span class="pln">FlaskShortener</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;button</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar-toggler"</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"button"</span><span class="pln"> </span><span class="atn">data-toggle</span><span class="pun">=</span><span class="atv">"collapse"</span><span class="pln"> </span><span class="atn">data-target</span><span class="pun">=</span><span class="atv">"#navbarNav"</span><span class="pln"> </span><span class="atn">aria-controls</span><span class="pun">=</span><span class="atv">"navbarNav"</span><span class="pln"> </span><span class="atn">aria-expanded</span><span class="pun">=</span><span class="atv">"false"</span><span class="pln"> </span><span class="atn">aria-label</span><span class="pun">=</span><span class="atv">"Toggle navigation"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;span</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar-toggler-icon"</span><span class="tag">&gt;&lt;/span&gt;</span><span class="pln">
        </span><span class="tag">&lt;/button&gt;</span><span class="pln">
        </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"collapse navbar-collapse"</span><span class="pln"> </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"navbarNav"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;ul</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar-nav"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;li</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"nav-item active"</span><span class="tag">&gt;</span><span class="pln">
                </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"nav-link"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"#"</span><span class="tag">&gt;</span><span class="pln">About</span><span class="tag">&lt;/a&gt;</span><span class="pln">
            </span><span class="tag">&lt;/li&gt;</span><span class="pln">
            </span><span class="tag">&lt;/ul&gt;</span><span class="pln">
        </span><span class="tag">&lt;/div&gt;</span><span class="pln">
    </span><span class="tag">&lt;/nav&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"container"</span><span class="tag">&gt;</span><span class="pln">
        {% for message in get_flashed_messages() %}
            </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"alert alert-danger"</span><span class="tag">&gt;</span><span class="pln">{{ message }}</span><span class="tag">&lt;/div&gt;</span><span class="pln">
        {% endfor %}
        {% block content %} {% endblock %}
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">

    </span><span class="com">&lt;!-- Optional JavaScript --&gt;</span><span class="pln">
    </span><span class="com">&lt;!-- jQuery first, then Popper.js, then Bootstrap JS --&gt;</span><span class="pln">
    </span><span class="tag">&lt;script</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"https://code.jquery.com/jquery-3.3.1.slim.min.js"</span><span class="pln"> </span><span class="atn">integrity</span><span class="pun">=</span><span class="atv">"sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"</span><span class="pln"> </span><span class="atn">crossorigin</span><span class="pun">=</span><span class="atv">"anonymous"</span><span class="tag">&gt;&lt;/script&gt;</span><span class="pln">
    </span><span class="tag">&lt;script</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"</span><span class="pln"> </span><span class="atn">integrity</span><span class="pun">=</span><span class="atv">"sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"</span><span class="pln"> </span><span class="atn">crossorigin</span><span class="pun">=</span><span class="atv">"anonymous"</span><span class="tag">&gt;&lt;/script&gt;</span><span class="pln">
    </span><span class="tag">&lt;script</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"</span><span class="pln"> </span><span class="atn">integrity</span><span class="pun">=</span><span class="atv">"sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"</span><span class="pln"> </span><span class="atn">crossorigin</span><span class="pun">=</span><span class="atv">"anonymous"</span><span class="tag">&gt;&lt;/script&gt;</span><span class="pln">
  </span><span class="tag">&lt;/body&gt;</span><span class="pln">
</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	الجزء الأكبر من الشيفرة السابقة هو تعليمات <a href="https://wiki.hsoub.com/HTML" rel="external">HTML</a> معيارية وشيفرات لازمة لعمل بوتستراب، إذ تزوّد الوسوم <a href="https://wiki.hsoub.com/HTML/meta" rel="external"><code>&lt;meta&gt;</code></a> متصفح الويب بالمعلومات، في حين ينشئ الوسم <a href="https://wiki.hsoub.com/HTML/link" rel="external"><code>&lt;link&gt;</code></a> ارتباطًا إلى ملفات CSS الخاصة ببوتستراب، ويضمِّن الوسم <a href="https://wiki.hsoub.com/HTML/script" rel="external"><code>&lt;script&gt;</code></a> شيفرة جافاسكربت مسؤولة عن بعض ميزات بوتستراب الإضافية.
</p>

<p>
	يسمح الوسم التالي لكل قالب موروث بأن يُحدّد عنوانه الخاص:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1980_41" style="">
<span class="tag">&lt;title&gt;</span><span class="pln">{% block title %} {% endblock %}</span><span class="tag">&lt;/title&gt;</span></pre>

<p>
	وتُستخدم الحلقة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1980_43" style="">
<span class="kwd">for</span><span class="pln"> message </span><span class="kwd">in</span><span class="pln"> get_flashed_messages</span><span class="pun">()</span></pre>

<p>
	لعرض الرسائل الخاطفة (من تحذيرات وتنبيهات وغيرها)، في حين يُضمَّن محتوى القوالب الموروثة في الموضع المؤقت <code>{% block content %} {% endblock %}</code>، بما يضمن وصول كافّة القوالب إلى القالب الأساسي وهذا يمنع تكرار الشيفرات.
</p>

<p>
	اِحفظ الملف وأغلقه.
</p>

<p>
	الآن سننشئ الملف index.html، الذي سيرث الملف base.html على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1980_47" style="">
<span class="pln">(env)user@localhost:$ nano templates/index.html</span></pre>

<p>
	وسنكتب ضمنه الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1980_49" style="">
<span class="pln">{% extends 'base.html' %}

{% block content %}
    </span><span class="tag">&lt;h1&gt;</span><span class="pln">{% block title %} Welcome to FlaskShortener {% endblock %}</span><span class="tag">&lt;/h1&gt;</span><span class="pln">
    </span><span class="tag">&lt;form</span><span class="pln"> </span><span class="atn">method</span><span class="pun">=</span><span class="atv">"post"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"form-group"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;label</span><span class="pln"> </span><span class="atn">for</span><span class="pun">=</span><span class="atv">"url"</span><span class="tag">&gt;</span><span class="pln">URL</span><span class="tag">&lt;/label&gt;</span><span class="pln">
        </span><span class="tag">&lt;input</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"text"</span><span class="pln"> </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"url"</span><span class="pln">
               </span><span class="atn">placeholder</span><span class="pun">=</span><span class="atv">"URL to shorten"</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"form-control"</span><span class="pln">
               </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"{{ request.form['url'] }}"</span><span class="pln"> </span><span class="atn">autofocus</span><span class="tag">&gt;&lt;/input&gt;</span><span class="pln">
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">

    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"form-group"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;button</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"submit"</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"btn btn-primary"</span><span class="tag">&gt;</span><span class="pln">Submit</span><span class="tag">&lt;/button&gt;</span><span class="pln">
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
    </span><span class="tag">&lt;/form&gt;</span><span class="pln">

    {% if short_url %}
    </span><span class="tag">&lt;hr&gt;</span><span class="pln">
    </span><span class="tag">&lt;span&gt;</span><span class="pln">{{ short_url }}</span><span class="tag">&lt;/span&gt;</span><span class="pln">
    {% endif %}
{% endblock %}</span></pre>

<p>
	ورِث الملف <code>index.html</code> في الشيفرة السابقة شيفرة القالب <code>base.html</code>، وعرّفنا عنوانًا خاصًّا به، ثمّ أنشأنا نموذجًا ذا حقل إدخال باسم <code>url</code>، والذي سيسمح للمستخدمين بإدخال الروابط المُراد اختصارها، وتُخزَّن القيمة ضمن متغير الكائن <code>['request.form['url</code> لتجنّب فقدان المعلومات في حال حدوث أخطاء، كأن يُرسل المستخدم النموذج دون إدخال رابط. كما يحتوي النموذج على زرٍ لتأكيد النموذج وإرساله.
</p>

<p>
	ثمّ يُختبر شرط كون المتحول <code>short_url</code> الحاوي على الرابط المختصر ليس فارغًا، ويتحقّق هذا الشرط عند إرسال النموذج وتوليد الرابط المختصر بنجاح، عندها يُعرض هذا الرابط المختصر URL أسفل نموذج الإدخال.
</p>

<p>
	الآن، سنضبط متغيرات البيئة التي يحتاجها فلاسك لتشغيل التطبيق كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1980_51" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_APP</span><span class="pun">=</span><span class="pln">app
</span><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_ENV</span><span class="pun">=</span><span class="pln">development
</span><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ flask run</span></pre>

<p>
	يحدّد متغير البيئة <code>FLASK_APP</code> في الشيفرة المُبينة أعلاه التطبيق المراد تشغيله (وهو في حالتنا الملف app.py)؛ بينما يحدّد المتغير <code>FLASK_ENV</code> وضع التشغيل وهو وضع التطوير <code>development</code> في حالتنا مع تشغيل منقّح الأخطاء، ولا يجب اختيار وضع التطوير في مرحلة التشغيل الفعلي للتطبيق (نشر المُنتج)؛ أمّا الأمر <code>flask run</code> فهو المسؤول عن تشغيل التطبيق.
</p>

<p>
	الآن، نفتح المتصفح ونذهب إلى الرابط "/http://127.0.0.1:5000"، فتظهر الصفحة الرئيسية الحاوية على العبارة "Welcome to FlaskShortener" كما هو موضح في الشكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="103059" href="https://academy.hsoub.com/uploads/monthly_2022_07/first_step2a.png.ca8b7a7e4d9b184a27669ffde31cc8a6.png" rel=""><img alt="first_step2a.png" class="ipsImage ipsImage_thumbnailed" data-fileid="103059" data-unique="6t1af1snk" src="https://academy.hsoub.com/uploads/monthly_2022_07/first_step2a.thumb.png.e3fada2fd1ee4f2d5e005ff8b93e1b9f.png" style="width: 600px; height: auto;"></a>
</p>

<p>
	وبإدخال رابط URL ما، نحصل على الرابط المختصر كما هو موضح في الشكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="103060" href="https://academy.hsoub.com/uploads/monthly_2022_07/second_step2b.png.a9b97f835bf6dcd85e55a44602232580.png" rel=""><img alt="second_step2b.png" class="ipsImage ipsImage_thumbnailed" data-fileid="103060" data-unique="fov94eit6" src="https://academy.hsoub.com/uploads/monthly_2022_07/second_step2b.thumb.png.a01c5f6feda20adf75d3f8df4372383b.png" style="width: 600px; height: auto;"></a>
</p>

<p>
	وبنهاية هذه الخطوة نكون قد أنشأنا تطبيق فلاسك يستقبل روابط لاختصارها، ولكن حتى هذه اللحظة لم تفعل هذه الروابط أي شيء، لذا في الخطوة التالية سنضيف وجهة مهمتها استخلاص السلسلة المُعمّاة من الرابط المُختصَر، ليوجد الرابط الأصلي ويعيد توجيه المُستخدم إليه.
</p>

<h2>
	الخطوة الثالثة - إنشاء وجهة لإعادة التوجيه إلى الرابط الأصلي
</h2>

<p>
	سننشئ في هذه الخطوة وجهة مهمتها فك تشفير السلسلة المعمّاة القصيرة التي ولدّها التطبيق للرابط المختصر للحصول على القيمة العددية الصحيحة الموافقة، والتي تمثّل معرّف الرابط الأصلي في قاعدة البيانات، وبالتالي فهي تستخدم هذا المعرّف لجلب الرابط الأصلي من قاعدة البيانات واحتساب الزيادة في عدد مرات النقر على هذا الرابط <code>clicks</code>، أي عدد مرات طلبه.
</p>

<p>
	لذا، بدايةً سنفتح الملف app.py لإضافة الوجهة الجديدة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1980_55" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ثم سنضيف الشيفرة التالية إلى نهاية الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1980_57" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/&lt;id&gt;'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> url_redirect</span><span class="pun">(</span><span class="pln">id</span><span class="pun">):</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">

    original_id </span><span class="pun">=</span><span class="pln"> hashids</span><span class="pun">.</span><span class="pln">decode</span><span class="pun">(</span><span class="pln">id</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> original_id</span><span class="pun">:</span><span class="pln">
        original_id </span><span class="pun">=</span><span class="pln"> original_id</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln">
        url_data </span><span class="pun">=</span><span class="pln"> conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'SELECT original_url, clicks FROM urls'</span><span class="pln">
                                </span><span class="str">' WHERE id = (?)'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">original_id</span><span class="pun">,)</span><span class="pln">
                                </span><span class="pun">).</span><span class="pln">fetchone</span><span class="pun">()</span><span class="pln">
        original_url </span><span class="pun">=</span><span class="pln"> url_data</span><span class="pun">[</span><span class="str">'original_url'</span><span class="pun">]</span><span class="pln">
        clicks </span><span class="pun">=</span><span class="pln"> url_data</span><span class="pun">[</span><span class="str">'clicks'</span><span class="pun">]</span><span class="pln">

        conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'UPDATE urls SET clicks = ? WHERE id = ?'</span><span class="pun">,</span><span class="pln">
                     </span><span class="pun">(</span><span class="pln">clicks</span><span class="pun">+</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> original_id</span><span class="pun">))</span><span class="pln">

        conn</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">
        conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> redirect</span><span class="pun">(</span><span class="pln">original_url</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
        flash</span><span class="pun">(</span><span class="str">'Invalid URL'</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> redirect</span><span class="pun">(</span><span class="pln">url_for</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">))</span></pre>

<p>
	تستقبل الوجهة في الشيفرة السابقة القيمة <code>id</code> من الرابط ويمررها وسيطًا لدالة فلاسك <code>()url_redirect</code>، فإذا كان الرابط <code>http://127.0.0.1:5000/KJ34</code> مثلًا، ستُمرّر السلسلة <code>KJ34</code> للمعامل <code>id</code>.
</p>

<p>
	وضمن دالة عرض فلاسك نفتح اتصالًا مع قاعدة البيانات، ثمّ نحوّل السلسلة المعمّاة إلى القيمة العددية الصحيحة الأصلية لها باستخدام التابع <code>()decode</code> للكائن <code>hashids</code>، لتُخزّن في المتغير <code>original_id</code>، فإذا لم يكن هذا المتغير فارغًا، يُستخلص المعرّف ID منه.
</p>

<p>
	وبما أنّ التابع <code>()decode</code> يُعيد سجلًا كاملًا، فسنجلب الجزء الأوّل منه، الذي يمثّل المعرّف الأصلي باستخدام التابع <code>[original_id[0</code>، ثمّ سنستخدم التعليمة <code>SELECT</code> في SQL لجلب الرابط الأصلي الذي يتوافق معرّفه في قاعدة البيانات مع المعرّف المُستخلص من السلسلة المعمّاة، إضافةً لجلب عدد الزيارات لهذا الرابط من الجدول <code>urls</code>؛ أما بيانات الرابط فستُجلب باستخدام التابع <code>()fetchone</code>، ثم سنخزّن هذه البيانات في المتغيرين <code>original_url</code> و <code>clicks</code>.
</p>

<p>
	الآن وبعد زيارة هذا الرابط، سنزيد عدد الزيارات له باستخدام تعليمة <code>UPDATE</code> في SQL، ونهايةً نؤكّد العملية ونغلق الاتصال مع قاعدة البيانات، وسنعيد التوجيه إلى الرابط الأصلي باستخدام دالة فلاسك المُساعدة <code>()redirect</code>؛ وفي حال فشل فك تشفير السلسلة المُعمّاة، ستُعرض رسالةٌ لإعلام المستخدم بأن الرابط الذي أدخله غير صالح، ثمّ يعاد توجيهه إلى الصفحة الرئيسية index.
</p>

<p>
	اِحفظ الملف وأغلقه.
</p>

<p>
	الآن، سنشغل خادم التطوير:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1980_59" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ flask run</span></pre>

<p>
	وبالذهاب إلى الرابط "/http://127.0.0.1:5000" باستخدام المتصفح، وبإدخال رابطٍ ما وزيارة الرابط المُختصَر الناتج، سيعيد التطبيق توجيه المستخدم إلى الرابط الأصلي.
</p>

<p>
	وبذلك نكون قد أنشأنا وجهة مهمتها إعادة توجيه مستخدم التطبيق من الرابط المُختصَر إلى الرابط الأصلي. سننشئ في الخطوة التالية صفحة إحصائيات لبيان عدد مرات زيارة كل رابط.
</p>

<h2>
	الخطوة الرابعة - إضافة صفحة إحصائيات
</h2>

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

<p>
	لنفتح الملف app.py لإضافة الوجهة الجديدة الخاص بصفحة الإحصائيات:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1980_63" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ونضيف الشيفرات التالية إلى نهاية الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1980_65" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/stats'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> stats</span><span class="pun">():</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">
    db_urls </span><span class="pun">=</span><span class="pln"> conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'SELECT id, created, original_url, clicks FROM urls'</span><span class="pln">
                           </span><span class="pun">).</span><span class="pln">fetchall</span><span class="pun">()</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">

    urls </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> url </span><span class="kwd">in</span><span class="pln"> db_urls</span><span class="pun">:</span><span class="pln">
        url </span><span class="pun">=</span><span class="pln"> dict</span><span class="pun">(</span><span class="pln">url</span><span class="pun">)</span><span class="pln">
        url</span><span class="pun">[</span><span class="str">'short_url'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">host_url </span><span class="pun">+</span><span class="pln"> hashids</span><span class="pun">.</span><span class="pln">encode</span><span class="pun">(</span><span class="pln">url</span><span class="pun">[</span><span class="str">'id'</span><span class="pun">])</span><span class="pln">
        urls</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">url</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'stats.html'</span><span class="pun">,</span><span class="pln"> urls</span><span class="pun">=</span><span class="pln">urls</span><span class="pun">)</span></pre>

<p>
	سنبدأ في دالة عرض فلاسك هذه بفتح اتصالٍ مع قاعدة البيانات، ثمّ نجلب كل من معرّف الرابط وتاريخ إنشائه والرابط الأصلي وعدد مرات زيارته باستخدام التابع <code>()fetchall</code> لكامل مُدخلات الجدول <code>urls</code> في قاعدة البيانات، وذلك من أجل تنظيم كامل هذه السجلات ضمن قائمة، لتُحفظ في المتغير <code>db_urls</code>، وفي النهاية نغلق الاتصال مع قاعدة البيانات.
</p>

<p>
	الآن ولعرض الرابط المُختصَر لكل مُدخل، لا بُدّ من بنائه وإضافته إلى كل عنصر من القائمة السابقة <code>db_urls</code> المجلوبة من قاعدة البيانات، لذا سننشئ قائمةً فارغةً باسم <code>urls</code> لنخزّن بها حقل الروابط فقط من القائمة <code>db_urls</code> باستخدام الحلقة التكرارية <code>for url in db_urls</code>.
</p>

<p>
	سنحوّل الكائن <code>sqlite3.Row</code> إلى قاموس dictionary قابل لإسناد القيم إليه باستخدام دالة بايثون <code>()dict</code>، ثمّ سنضيف إليه مفتاحًا جديدًا باسم <code>short_url</code> ليحتوي القيمة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1980_67" style="">
<span class="pln">request</span><span class="pun">.</span><span class="pln">host_url </span><span class="pun">+</span><span class="pln"> hashids</span><span class="pun">.</span><span class="pln">encode</span><span class="pun">(</span><span class="pln">url</span><span class="pun">[</span><span class="str">'id'</span><span class="pun">])</span></pre>

<p>
	المُستخدمة سابقًا في دالة عرض فلاسك <code>()index</code> لبناء الروابط المُختصرة، ونهايةً سنضيف القاموس الذي يحتوي على الروابط المُختصَرة إلى القائمة urls.
</p>

<p>
	نهايةً سنخرج قالب HTML باسم stats.html ممررين قائمة <code>urls</code> إليه.
</p>

<p>
	اِحفظ الملف وأغلقه.
</p>

<p>
	سننشئ الآن ملف القالب stats.html:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1980_69" style="">
<span class="pln">(env)user@localhost:$ nano templates/stats.html</span></pre>

<p>
	وسنكتب الشيفرات التالية فيه:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1980_71" style="">
<span class="pln">{% extends 'base.html' %}

{% block content %}
    </span><span class="tag">&lt;h1&gt;</span><span class="pln">{% block title %} FlaskShortener Statistics {% endblock %}</span><span class="tag">&lt;/h1&gt;</span><span class="pln">
    </span><span class="tag">&lt;table</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"table"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;thead&gt;</span><span class="pln">
            </span><span class="tag">&lt;tr&gt;</span><span class="pln">
            </span><span class="tag">&lt;th</span><span class="pln"> </span><span class="atn">scope</span><span class="pun">=</span><span class="atv">"col"</span><span class="tag">&gt;</span><span class="pln">#</span><span class="tag">&lt;/th&gt;</span><span class="pln">
            </span><span class="tag">&lt;th</span><span class="pln"> </span><span class="atn">scope</span><span class="pun">=</span><span class="atv">"col"</span><span class="tag">&gt;</span><span class="pln">Short</span><span class="tag">&lt;/th&gt;</span><span class="pln">
            </span><span class="tag">&lt;th</span><span class="pln"> </span><span class="atn">scope</span><span class="pun">=</span><span class="atv">"col"</span><span class="tag">&gt;</span><span class="pln">Original</span><span class="tag">&lt;/th&gt;</span><span class="pln">
            </span><span class="tag">&lt;th</span><span class="pln"> </span><span class="atn">scope</span><span class="pun">=</span><span class="atv">"col"</span><span class="tag">&gt;</span><span class="pln">Clicks</span><span class="tag">&lt;/th&gt;</span><span class="pln">
            </span><span class="tag">&lt;th</span><span class="pln"> </span><span class="atn">scope</span><span class="pun">=</span><span class="atv">"col"</span><span class="tag">&gt;</span><span class="pln">Creation Date</span><span class="tag">&lt;/th&gt;</span><span class="pln">
            </span><span class="tag">&lt;/tr&gt;</span><span class="pln">
        </span><span class="tag">&lt;/thead&gt;</span><span class="pln">
        </span><span class="tag">&lt;tbody&gt;</span><span class="pln">
            {% for url in urls %}
                </span><span class="tag">&lt;tr&gt;</span><span class="pln">
                    </span><span class="tag">&lt;th</span><span class="pln"> </span><span class="atn">scope</span><span class="pun">=</span><span class="atv">"row"</span><span class="tag">&gt;</span><span class="pln">{{ url['id'] }}</span><span class="tag">&lt;/th&gt;</span><span class="pln">
                    </span><span class="tag">&lt;td&gt;</span><span class="pln">{{ url['short_url'] }}</span><span class="tag">&lt;/td&gt;</span><span class="pln">
                    </span><span class="tag">&lt;td&gt;</span><span class="pln">{{ url['original_url'] }}</span><span class="tag">&lt;/td&gt;</span><span class="pln">
                    </span><span class="tag">&lt;td&gt;</span><span class="pln">{{ url['clicks'] }}</span><span class="tag">&lt;/td&gt;</span><span class="pln">
                    </span><span class="tag">&lt;td&gt;</span><span class="pln">{{ url['created'] }}</span><span class="tag">&lt;/td&gt;</span><span class="pln">
                </span><span class="tag">&lt;/tr&gt;</span><span class="pln">
            {% endfor %}
        </span><span class="tag">&lt;/tbody&gt;</span><span class="pln">
    </span><span class="tag">&lt;/table&gt;</span><span class="pln">

{% endblock %}</span></pre>

<p>
	في الشفرة السابقة، ورثنا شيفرة قالب HTML الرئيسي base.html وحدّدنا عنوانًا، وعرّفنا جدولًا يضم الأعمدة التالية:
</p>

<ul>
<li>
		<code>#</code>: معرّف الرابط.
	</li>
	<li>
		<code>Short</code>: الرابط المُختصَر.
	</li>
	<li>
		<code>Original</code>: الرابط الأصلي.
	</li>
	<li>
		<code>Clicks</code>: عدد مرات زيارة الرابط المُختصَر.
	</li>
	<li>
		<code>Creation Date</code>: تاريخ إنشاء الرابط المُختصَر.
	</li>
</ul>
<p>
	وجرت عملية تعبئة سجلات الجدول باستخدام حلقة <code>for</code> تكرارية تجلب البيانات من القائمة <code>urls</code> لتعرض القيم الموافقة لكل من الأعمدة السابقة لكل رابط.
</p>

<p>
	الآن سنشغّل خادم التطوير:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1980_73" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ flask run</span></pre>

<p>
	وباستخدام المتصفح، وبالذهاب إلى الرابط "http://127.0.0.1:5000/stats" ستظهر كافّة الروابط في جدول كما في الشكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="103061" href="https://academy.hsoub.com/uploads/monthly_2022_07/third_step4a.png.acfcbf78499c0cb894563c841e6ad78a.png" rel=""><img alt="third_step4a.png" class="ipsImage ipsImage_thumbnailed" data-fileid="103061" data-unique="tivhy1ave" src="https://academy.hsoub.com/uploads/monthly_2022_07/third_step4a.thumb.png.d13280e45efd048c1f209c11441dbae1.png" style="width: 700px; height: auto;"></a>
</p>

<p>
	الآن سنضيف زر أوامر باسم <strong>Stats</strong> في شريط التصفّح للوصول إلى صفحة الإحصائيات، لذا سنفتح الملف base.html:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1980_77" style="">
<span class="pln">(env)user@localhost:$ nano templates/base.html</span></pre>

<p>
	وسنعدّل الشيفرة فيه وفق الأسطر المُحدّدة كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1980_79" style="">
<span class="dec">&lt;!doctype html&gt;</span><span class="pln">
</span><span class="tag">&lt;html</span><span class="pln"> </span><span class="atn">lang</span><span class="pun">=</span><span class="atv">"en"</span><span class="tag">&gt;</span><span class="pln">
  </span><span class="tag">&lt;head&gt;</span><span class="pln">
    </span><span class="com">&lt;!-- Required meta tags --&gt;</span><span class="pln">
    </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">"utf-8"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"viewport"</span><span class="pln"> </span><span class="atn">content</span><span class="pun">=</span><span class="atv">"width=device-width, initial-scale=1, shrink-to-fit=no"</span><span class="tag">&gt;</span><span class="pln">

    </span><span class="com">&lt;!-- Bootstrap CSS --&gt;</span><span class="pln">
    </span><span class="tag">&lt;link</span><span class="pln"> </span><span class="atn">rel</span><span class="pun">=</span><span class="atv">"stylesheet"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"</span><span class="pln"> </span><span class="atn">integrity</span><span class="pun">=</span><span class="atv">"sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"</span><span class="pln"> </span><span class="atn">crossorigin</span><span class="pun">=</span><span class="atv">"anonymous"</span><span class="tag">&gt;</span><span class="pln">

    </span><span class="tag">&lt;title&gt;</span><span class="pln">{% block title %} {% endblock %}</span><span class="tag">&lt;/title&gt;</span><span class="pln">
  </span><span class="tag">&lt;/head&gt;</span><span class="pln">
  </span><span class="tag">&lt;body&gt;</span><span class="pln">
    </span><span class="tag">&lt;nav</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar navbar-expand-md navbar-light bg-light"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar-brand"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('index')}}"</span><span class="tag">&gt;</span><span class="pln">FlaskTodo</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;button</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar-toggler"</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"button"</span><span class="pln"> </span><span class="atn">data-toggle</span><span class="pun">=</span><span class="atv">"collapse"</span><span class="pln"> </span><span class="atn">data-target</span><span class="pun">=</span><span class="atv">"#navbarNav"</span><span class="pln"> </span><span class="atn">aria-controls</span><span class="pun">=</span><span class="atv">"navbarNav"</span><span class="pln"> </span><span class="atn">aria-expanded</span><span class="pun">=</span><span class="atv">"false"</span><span class="pln"> </span><span class="atn">aria-label</span><span class="pun">=</span><span class="atv">"Toggle navigation"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;span</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar-toggler-icon"</span><span class="tag">&gt;&lt;/span&gt;</span><span class="pln">
        </span><span class="tag">&lt;/button&gt;</span><span class="pln">
        </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"collapse navbar-collapse"</span><span class="pln"> </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"navbarNav"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;ul</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar-nav"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;li</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"nav-item active"</span><span class="tag">&gt;</span><span class="pln">
                </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"nav-link"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"#"</span><span class="tag">&gt;</span><span class="pln">About</span><span class="tag">&lt;/a&gt;</span><span class="pln">
            </span><span class="tag">&lt;/li&gt;</span><span class="pln">

            </span><span class="tag">&lt;li</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"nav-item active"</span><span class="tag">&gt;</span><span class="pln">
                </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"nav-link"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('stats')}}"</span><span class="tag">&gt;</span><span class="pln">Stats</span><span class="tag">&lt;/a&gt;</span><span class="pln">
            </span><span class="tag">&lt;/li&gt;</span><span class="pln">
            </span><span class="tag">&lt;/ul&gt;</span><span class="pln">
        </span><span class="tag">&lt;/div&gt;</span><span class="pln">
    </span><span class="tag">&lt;/nav&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"container"</span><span class="tag">&gt;</span><span class="pln">
        {% for message in get_flashed_messages() %}
            </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"alert alert-danger"</span><span class="tag">&gt;</span><span class="pln">{{ message }}</span><span class="tag">&lt;/div&gt;</span><span class="pln">
        {% endfor %}
        {% block content %} {% endblock %}
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">

    </span><span class="com">&lt;!-- Optional JavaScript --&gt;</span><span class="pln">
    </span><span class="com">&lt;!-- jQuery first, then Popper.js, then Bootstrap JS --&gt;</span><span class="pln">
    </span><span class="tag">&lt;script</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"https://code.jquery.com/jquery-3.3.1.slim.min.js"</span><span class="pln"> </span><span class="atn">integrity</span><span class="pun">=</span><span class="atv">"sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"</span><span class="pln"> </span><span class="atn">crossorigin</span><span class="pun">=</span><span class="atv">"anonymous"</span><span class="tag">&gt;&lt;/script&gt;</span><span class="pln">
    </span><span class="tag">&lt;script</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"</span><span class="pln"> </span><span class="atn">integrity</span><span class="pun">=</span><span class="atv">"sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"</span><span class="pln"> </span><span class="atn">crossorigin</span><span class="pun">=</span><span class="atv">"anonymous"</span><span class="tag">&gt;&lt;/script&gt;</span><span class="pln">
    </span><span class="tag">&lt;script</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"</span><span class="pln"> </span><span class="atn">integrity</span><span class="pun">=</span><span class="atv">"sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"</span><span class="pln"> </span><span class="atn">crossorigin</span><span class="pun">=</span><span class="atv">"anonymous"</span><span class="tag">&gt;&lt;/script&gt;</span><span class="pln">
  </span><span class="tag">&lt;/body&gt;</span><span class="pln">
</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	ضمنّا في الشفرة السابقة عنصر قائمة <code>&lt;li&gt;</code> في شريط التصفّح، وربطناه مع دالة فلاسك <code>()stats</code> باستخدام دالة <code>()url_for</code>، وبذلك يصبح بالإمكان الوصول إلى صفحة الإحصائيات من شريط التصفّح، التي ستعرض معلومات عن كل رابط، تتضمّن الرابط المُختصر المكافئ وعدد مرات زيارته.
</p>

<p>
	هذه الشيفرة قابلة لإعادة الاستخدام في تطبيقات أُخرى، لمراقبة عدد النقرات، وهذا يفيد مثلًا في متابعة عدد مرات الإعجاب بمنشور ما أو عدد مرات تحديثه في أحد مواقع التواصل الاجتماعي، أو عدد مرات مشاهدة صورة أو مقطع فيديو ما.
</p>

<p>
	نهايةً يمكنك الاطلاع على <a href="https://github.com/do-community/flask_shortener" rel="external nofollow">شيفرة تطبيق اختصار الروابط كاملةً</a>.
</p>

<h2>
	الخاتمة
</h2>

<p>
	ستحصل مع نهاية هذا المقال وبتطبيق الخطوات الواردة فيه على تطبيق يسمح للمستخدمين بإدخال روابط طويلة لاختصارها، إذ يحوِّل الأعداد الصحيحة إلى سلاسل معمّاة قصيرة، لإعادة توجيه المستخدمين من رابطٍ لآخر، كما يمكّنك من خلاله مراقبة الروابط المُختصرة عن طريق صفحة الإحصائيات.
</p>

<p>
	يمكنك الاطلاع على مزيدٍ من المقالات التعليمية حول فلاسك من خلال الروابط التالية:
</p>

<ul>
<li>
		روابط المقال الأوّل والثاني والثالث بعد النشر في الأكاديمية.
	</li>
	<li>
		كيفية إنشاء تطبيق ويب باستخدام إطار عمل فلاسك Flask في لغة بايثون 3.
	</li>
	<li>
		كيفية استخدام علاقات قاعدة البيانات من نوع واحد-إلى-متعدد one-to-many مع إطار العمل فلاسك Flask ومحرك قواعد البيانات SQLite.
	</li>
	<li>
		الثالث.
	</li>
</ul>
<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-make-a-url-shortener-with-flask-and-sqlite" rel="external nofollow">How To Make a URL Shortener with Flask and SQLite</a> لصاحبه Abdelhadi Dyouri.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%88%D9%8A%D8%A8-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D9%85%D9%86-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1622/" rel="">إنشاء تطبيق ويب باستخدام إطار عمل فلاسك Flask من لغة بايثون</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B9%D9%84%D8%A7%D9%82%D8%A9-one-to-many-%D9%85%D8%B9-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D9%88%D9%85%D8%AD%D8%B1%D9%83-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-sqlite-r1623/" rel="">استخدام علاقة one-to-many مع إطار العمل فلاسك Flask ومحرك قواعد البيانات SQLite</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B9%D9%84%D8%A7%D9%82%D8%A9-many-to-many-%D9%85%D8%B9-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D9%88%D9%85%D8%AD%D8%B1%D9%83-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-sqlite-r1632/" rel="">استخدام علاقة many-to-many مع فلاسك Flask ومحرك قواعد البيانات SQLite </a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%B9%D8%AF%D9%8A%D9%84-%D8%A7%D9%84%D8%B9%D9%86%D8%A7%D8%B5%D8%B1-%D9%81%D9%8A-%D8%B9%D9%84%D8%A7%D9%82%D8%A7%D8%AA-one-to-many-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D9%88%D9%85%D8%AD%D8%B1%D9%83-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-sqlite-r1631/" rel="">تعديل العناصر في علاقات one-to-many باستخدام فلاسك Flask ومحرك قاعدة البيانات SQLite </a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1633</guid><pubDate>Mon, 25 Jul 2022 16:00:01 +0000</pubDate></item><item><title>&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x639;&#x644;&#x627;&#x642;&#x629; many-to-many &#x645;&#x639; &#x641;&#x644;&#x627;&#x633;&#x643; Flask &#x648;&#x645;&#x62D;&#x631;&#x643; &#x642;&#x648;&#x627;&#x639;&#x62F; &#x627;&#x644;&#x628;&#x64A;&#x627;&#x646;&#x627;&#x62A; SQLite</title><link>https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B9%D9%84%D8%A7%D9%82%D8%A9-many-to-many-%D9%85%D8%B9-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D9%88%D9%85%D8%AD%D8%B1%D9%83-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-sqlite-r1632/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_07/62d25de1bd1a6_--many-to-manyFlaskSQLite.png.8278e11abb3458f26836b87607d1568c.png" /></p>

<p>
	يُعد <a href="https://academy.hsoub.com/programming/python/flask/" rel="">فلاسك</a> إطار عمل يستخدم لغة البرمجة <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D9%85%D8%B1%D8%AC%D8%B9-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%AA%D8%B9%D9%84%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r735/" rel="">بايثون</a> Python لبناء تطبيقات الويب، أما <a href="https://academy.hsoub.com/devops/servers/databases/%D9%83%D9%8A%D9%81-%D9%88%D9%85%D8%AA%D9%89-%D9%86%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-sqlite-r111/" rel="">SQLite</a> فهو محرّك قواعد بيانات يستخدم لغة البرمجة بايثون لتخزين بيانات تطبيق الويب. سنعمل في هذا المقال على تعديل تطبيق ويب مبني باستخدام فلاسك و SQLite من خلال إضافة علاقة من نوع متعدد-إلى-متعدد إليه.
</p>

<p>
	يمكنك البدء بقراءة هذا المقال مباشرةً، إلّا أنّه متابعةٌ <a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%B9%D8%AF%D9%8A%D9%84-%D8%A7%D9%84%D8%B9%D9%86%D8%A7%D8%B5%D8%B1-%D9%81%D9%8A-%D8%B9%D9%84%D8%A7%D9%82%D8%A7%D8%AA-one-to-many-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D9%88%D9%85%D8%AD%D8%B1%D9%83-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-sqlite-r1631/" rel="">للمقال السابق</a>، لذلك من الأفضل قراءة المقال السابق بدايةً، إذ بيّنا فيه طريقة إدارة قاعدة بيانات متعدّدة الجداول ذات علاقة واحد-إلى-متعدد من خلال إنشاء تطبيق لإدارة المهام، ويمكِّن هذا التطبيق مستخدميه من إضافة عناصر مهام جديدة وتصنيفها ضمن قوائم مختلفة مع إمكانية تعديل العناصر.
</p>

<p>
	تُعرَّف علاقة قاعدة البيانات متعدّد-إلى-متعدّد على أنها علاقةٌ بين جدولين، إذ يمكن لأي سجلٍ من الجدولين الارتباط مع عدّة سجلات من الآخر، فمثلًا في تطبيق مدونة يمكن لجدول التدوينات posts أن يرتبط بعلاقة من نوع متعدد-إلى-متعدّد مع جدول أسماء المؤلفين (كُتَّاب التدوينات)، بمعنى أن كل تدوينة قد ترتبط بعدّة أسماء مؤلفين، وأيضًا قد يرتبط كل اسم مؤلف بعدّة تدوينات، وبالتالي فإنّ العلاقة ما بين التدوينات وأسماء المؤلفين هي علاقة متعدّد-إلى-متعدّد. أيُّ تطبيقٍ آخر من تطبيقات <a href="https://academy.hsoub.com/marketing/social-media/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D9%88%D8%B3%D8%A7%D8%A6%D9%84-%D8%A7%D9%84%D8%AA%D9%88%D8%A7%D8%B5%D9%84-%D8%A7%D9%84%D8%A7%D8%AC%D8%AA%D9%85%D8%A7%D8%B9%D9%8A-%D9%88%D8%A2%D9%84%D9%8A%D8%A9-%D8%B9%D9%85%D9%84%D9%87%D8%A7-r526/" rel="">التواصل الاجتماعي</a> هو مثالٌ آخر، إذ أن كل منشور قد يحتوي عدّة إشارات مرجعية، وكل إشارة مرجعية قد تتضمّن عدة منشورات.
</p>

<p>
	ومع اتباع الخطوات الواردة في هذا المقال، سنضيف على تطبيق إدارة المهام المُنشأ سابقًا ميزة تخصيص عناصر المهام لمستخدمين مختلفين، وسندعو المستخدمين الذين خُصَّصت لهم مهام بالمُخَصَّصين؛ فبفرض لدينا عنصر مهام من قائمة المهام المنزلية وهو "تنظيف المطبخ"، والتي من الممكن تخصيصها لشخصين مُختلفين وليكونا مثلًا أحمد ومحمّد، فكل مهمّة يمكن أن تملك عدّة مُخَصَّصين وهم في مثالنا أحمد ومحمّد، وبالمقابل يمكن تخصيص عدّة مهام للمستخدم الواحد، فمن الممكن مثلًا تخصيص عدّة مهام لأحمد، وبذلك نجد أنّ العلاقة ما بين عناصر المهام والمُخَصَّصين هي فعليًا من نوع متعدّد-إلى-متعدّد.
</p>

<p>
	سيُضاف إلى التطبيق مع نهاية هذا المقال وسم <strong>مُخصّص إلى Assigned to</strong> مع قائمة بأسماء المُخَصَّصين.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="103055" href="https://academy.hsoub.com/uploads/monthly_2022_07/first_intro.png.17a9ef95965909b71c883d63dae9ec97.png" rel=""><img alt="first_intro.png" class="ipsImage ipsImage_thumbnailed" data-fileid="103055" data-unique="34hzurjwb" src="https://academy.hsoub.com/uploads/monthly_2022_07/first_intro.thumb.png.649565a1ffe86d65235025a9708ed700.png" style="width: 600px; height: auto;"></a>
</p>

<h2>
	مستلزمات العمل
</h2>

<p>
	قبل المتابعة في هذا المقال لا بدّ من:
</p>

<ul>
<li>
		توفُّر <a href="https://academy.hsoub.com/programming/python/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%AA%D9%87%D8%A7-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r714/" rel="">بيئة برمجة بايثون 3 محلية</a>، وسنستدعي في مقالنا مجلد المشروع flask_todo المُنشأ مُسبقًا من الخطوات في المقال السابق.
	</li>
	<li>
		توفّر تطبيق إدارة المهام المُنجز في المقال السابق، وفي الخطوة الأولى من هذا المقال سيكون لديك الخيار إما باستنساخ التطبيق مُباشرةً دون إعداده من قبلك، أو اتباع الخطوات التفصيلية في المقال السابق لبناء التطبيق ومن ثمّ الإكمال في هذا المقال، كما يمكنك الحصول على <a href="https://github.com/do-community/flask-todo-2" rel="external nofollow">الشيفرة الكاملة للتطبيق</a>.
	</li>
	<li>
		يجب فهم مختلف مفاهيم فلاسك الأساسية، مثل إنشاء الوجهات وإخراج قوالب <a href="https://wiki.hsoub.com/HTML" rel="external">HTML</a> والاتصال بقاعدة بيانات SQLite.
	</li>
</ul>
<p>
	الخطوة الأولى - إعداد تطبيق الويب الخاص بإدارة المهام سنُعدّ في هذه الخطوة تطبيق الويب الخاص بإدارة المهام ليكون جاهزاً لإجراء التعديلات عليه، ولكن إذا كنت قد اتبعت الخطوات الواردة في المقال السابق (المذكور ضمن فقرة مستلزمات العمل من مقالنا هذا)، فستكون لديك شيفرة التطبيق إضافةً إلى البيئة الافتراضية مُفعلّةً على حاسوبك، وبإمكانك تجاوز هذه الخطوة مباشرةً.
</p>

<p>
	سنستخدم لشرح آلية إضافة علاقة من النوع متعدّد-إلى-متعدّد لتطبيق ويب مبني باستخدام فلاسك شيفرة تطبيق إدارة المهام الذي أنشأناه سابقًا (راجع فقرة مستلزمات العمل) والمبني باستخدام فلاسك و SQLite وإطار العمل <a href="https://academy.hsoub.com/programming/html/bootstrap/" rel="">بوتستراب Bootstrap</a>، إذ يمكِّن هذا التطبيق مستخدميه من إنشاء مهام جديدة وتعديلها وحذفها وتمييزها على أنها "مُنجزة".
</p>

<p>
	بدايةً، استخدم الأمر <code>Git</code> لاستنساخ شيفرة تطبيق إدارة المهام المُنشأ سابقًا:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_9" style="">
<span class="pln">$ git clone https</span><span class="pun">://</span><span class="pln">github</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="kwd">do</span><span class="pun">-</span><span class="pln">community</span><span class="pun">/</span><span class="pln">flask</span><span class="pun">-</span><span class="pln">todo</span></pre>

<p>
	بالانتقال إلى المجلد flask-todo:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_12" style="">
<span class="pln">$ cd flask_todo</span></pre>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_14" style="">
<span class="pln">$ python </span><span class="pun">-</span><span class="pln">m venv env</span></pre>

<p>
	ثمّ سنفعِّلها كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_16" style="">
<span class="pln">$ source env</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">activate</span></pre>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_18" style="">
<span class="pln">$ pip install </span><span class="typ">Flask</span></pre>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_20" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ python init_db</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	بعد ذلك، سنسند القيم اللازمة إلى متغيرات بيئة فلاسك على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_22" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_APP</span><span class="pun">=</span><span class="pln">app
</span><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_ENV</span><span class="pun">=</span><span class="pln">development</span></pre>

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

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_24" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ flask run</span></pre>

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

<p>
	أمّا الآن فسنطّلع على ملف تخطيط قاعدة البيانات لفهم بنية قاعدة البيانات الحالية والعلاقات ما بين الجداول فيها. إذا كنت قد اطلعت مُسبقًا من المقال السابق على الملف schema.sql وأصبح مألوفًا بالنسبة لك، فبإمكانك تجاوز هذه الخطوة.
</p>

<p>
	سنفتح الملف schema.sql:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_2232_26" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano schema</span><span class="pun">.</span><span class="pln">sql</span></pre>

<p>
	فتظهر محتويات الملف على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_32" style="">
<span class="pln">DROP TABLE IF EXISTS lists</span><span class="pun">;</span><span class="pln">
DROP TABLE IF EXISTS items</span><span class="pun">;</span><span class="pln">

CREATE TABLE lists </span><span class="pun">(</span><span class="pln">
    id INTEGER PRIMARY KEY AUTOINCREMENT</span><span class="pun">,</span><span class="pln">
    created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP</span><span class="pun">,</span><span class="pln">
    title TEXT NOT NULL
</span><span class="pun">);</span><span class="pln">

CREATE TABLE items </span><span class="pun">(</span><span class="pln">
    id INTEGER PRIMARY KEY AUTOINCREMENT</span><span class="pun">,</span><span class="pln">
    list_id INTEGER NOT NULL</span><span class="pun">,</span><span class="pln">
    created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP</span><span class="pun">,</span><span class="pln">
    content TEXT NOT NULL</span><span class="pun">,</span><span class="pln">
    done INTEGER NOT NULL DEFAULT </span><span class="lit">0</span><span class="pun">,</span><span class="pln">
    FOREIGN KEY </span><span class="pun">(</span><span class="pln">list_id</span><span class="pun">)</span><span class="pln"> REFERENCES lists </span><span class="pun">(</span><span class="pln">id</span><span class="pun">)</span><span class="pln">
</span><span class="pun">);</span></pre>

<p>
	وفيه جدولين، الأول lists لتخزين القوائم، مثل قائمة المهام المنزلية Home أو قائمة المهام الدراسية study، والثاني items لتخزين عناصر المهام، مثل مهمّة غسل الأطباق Do the dishes أو مهمّة تعلّم فلاسك Learn Flask.
</p>

<p>
	ويحتوي جدول القوائم lists على الأعمدة التالية:
</p>

<ul>
<li>
		"id": ويحتوي على معرّف ID القائمة.
	</li>
	<li>
		"created": يحتوي على تاريخ ووقت إنشاء قائمة المهام.
	</li>
	<li>
		"title": عنوان قائمة المهام.
	</li>
</ul>
<p>
	أمّا جدول عناصر المهام، فيحتوي على الأعمدة التالية:
</p>

<ul>
<li>
		"id": يحتوي على معرّف ID العنصر.
	</li>
	<li>
		"list_id": يحتوي على معرّف القائمة التي ينتمي إليها العنصر.
	</li>
	<li>
		"created": يحتوي تاريخ إنشاء العنصر.
	</li>
	<li>
		"content": محتوى العنصر.
	</li>
	<li>
		"done": يحدّد حالة عنصر المهام من حيث الإنجاز، إذ تدل القيمة "0" على أنّ المهمّة لم تُنجز بعد، أمّا القيمة "1" فتعني أنّ المهمّة قد أُنجزت.
	</li>
</ul>
<p>
	ويتضمّن جدول العناصر items مفتاحًا أجنبيًا foreign key، إذ يشير العمود list_id إلى العمود id من جدول القوائم lists الأب، وهذا ما يمثّل علاقةً من النوع واحد-إلى-متعدّد one-to-many ما بين العناصر والقوائم، بمعنى أنّ القائمة الواحدة يمكن أن تضم عدّة عناصر في حين ينتمي كل عنصر لقائمة واحدة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_34" style="">
<span class="pln">FOREIGN KEY </span><span class="pun">(</span><span class="pln">list_id</span><span class="pun">)</span><span class="pln"> REFERENCES lists </span><span class="pun">(</span><span class="pln">id</span><span class="pun">)</span></pre>

<p>
	سنستخدم في الخطوة التالية العلاقة من نوع متعدّد-إلى-متعدّد للربط بين جدولين في قاعدة البيانات.
</p>

<h2>
	الخطوة الثانية - إضافة جدول المخصصين
</h2>

<p>
	سنستعرض في هذه الخطوة كيفية تطبيق علاقة من نوع متعدّد-إلى-متعدّد وكيفية دمج الجداول، ومن ثمّ سنضيف جدولًا جديدًا إلى <a href="https://academy.hsoub.com/programming/sql/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%B9%D9%86-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-r584/" rel="">قاعدة البيانات</a> لتخزين المُخَصَّصين.
</p>

<p>
	العلاقة من نوع متعدّد-إلى-متعدّد هي طريقةٌ لربط جدولين، بحيث يكون لكل عنصر من أحدهما عدّة علاقات مع عدّة عناصر من الجدول الآخر.
</p>

<p>
	بفرض لدينا جدولٌ بسيط لعناصر المهام على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_36" style="">
<span class="typ">Items</span><span class="pln">
</span><span class="pun">+----+-------------------+</span><span class="pln">
</span><span class="pun">|</span><span class="pln"> id </span><span class="pun">|</span><span class="pln"> content           </span><span class="pun">|</span><span class="pln">
</span><span class="pun">+----+-------------------+</span><span class="pln">
</span><span class="pun">|</span><span class="pln"> </span><span class="lit">1</span><span class="pln">  </span><span class="pun">|</span><span class="pln"> </span><span class="typ">Buy</span><span class="pln"> eggs          </span><span class="pun">|</span><span class="pln">
</span><span class="pun">|</span><span class="pln"> </span><span class="lit">2</span><span class="pln">  </span><span class="pun">|</span><span class="pln"> </span><span class="typ">Fix</span><span class="pln"> lighting      </span><span class="pun">|</span><span class="pln">
</span><span class="pun">|</span><span class="pln"> </span><span class="lit">3</span><span class="pln">  </span><span class="pun">|</span><span class="pln"> </span><span class="typ">Paint</span><span class="pln"> the bedroom </span><span class="pun">|</span><span class="pln">
</span><span class="pun">+----+-------------------+</span></pre>

<p>
	وجدول للمُخَصَّصين على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_39" style="">
<span class="pln">assignees
</span><span class="pun">+----+------+</span><span class="pln">
</span><span class="pun">|</span><span class="pln"> id </span><span class="pun">|</span><span class="pln"> name </span><span class="pun">|</span><span class="pln">
</span><span class="pun">+----+------+</span><span class="pln">
</span><span class="pun">|</span><span class="pln"> </span><span class="lit">1</span><span class="pln">  </span><span class="pun">|</span><span class="pln"> </span><span class="typ">Ahmad</span><span class="pun">|</span><span class="pln">
</span><span class="pun">|</span><span class="pln"> </span><span class="lit">2</span><span class="pln">  </span><span class="pun">|</span><span class="pln"> </span><span class="typ">Mohammad</span><span class="pln">   </span><span class="pun">|</span><span class="pln">
</span><span class="pun">+----+------+</span></pre>

<p>
	وعلى فرض أنّنا نريد تخصيص مهمّة إصلاح المصباح Fix lighting لكلٍ من أحمد ومحمّد، فمن الممكن إنجاز ذلك بإضافة صفٍ جديد لجدول العناصر كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_41" style="">
<span class="pln">items
</span><span class="pun">+----+-------------------+-----------+</span><span class="pln">
</span><span class="pun">|</span><span class="pln"> id </span><span class="pun">|</span><span class="pln"> content           </span><span class="pun">|</span><span class="pln"> assignees </span><span class="pun">|</span><span class="pln">
</span><span class="pun">+----+-------------------+-----------+</span><span class="pln">
</span><span class="pun">|</span><span class="pln"> </span><span class="lit">1</span><span class="pln">  </span><span class="pun">|</span><span class="pln"> </span><span class="typ">Buy</span><span class="pln"> eggs          </span><span class="pun">|</span><span class="pln">           </span><span class="pun">|</span><span class="pln">
</span><span class="pun">|</span><span class="pln"> </span><span class="lit">2</span><span class="pln">  </span><span class="pun">|</span><span class="pln"> </span><span class="typ">Fix</span><span class="pln"> lighting      </span><span class="pun">|</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pln">      </span><span class="pun">|</span><span class="pln">
</span><span class="pun">|</span><span class="pln"> </span><span class="lit">3</span><span class="pln">  </span><span class="pun">|</span><span class="pln"> </span><span class="typ">Paint</span><span class="pln"> the bedroom </span><span class="pun">|</span><span class="pln">           </span><span class="pun">|</span><span class="pln">
</span><span class="pun">+----+-------------------+-----------+</span></pre>

<p>
	ولكن وبما أنّ كل عمود ينبغي أن يحتوي على قيمةٍ واحدة لضمان سلاسة العمليات، نجد أنّ المنهجية أعلاه خاطئة؛ فعند إسناد قيم متعدّدة للسجل الواحد في العمود، ستغدو العمليات الأساسية البسيطة، مثل إضافة، أو تحديث البيانات معقّدةً وبطيئة، والحل يكون باستخدام جدول ثالث يحوي أعمدةً تشير إلى المفاتيح الأساسية للجداول ذات الصلة، وهو ما ندعوه عادةً <strong>جدول الدمج join table</strong>، وهو يخزّن المعرّفات لكل عنصر في كل جدول.
</p>

<p>
	وفيما يلي مثالٌ لجدول دمج يربط بين جدولي العناصر والمُخَصَّصين:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_43" style="">
<span class="pln">item_assignees
</span><span class="pun">+----+---------+-------------+</span><span class="pln">
</span><span class="pun">|</span><span class="pln"> id </span><span class="pun">|</span><span class="pln"> item_id </span><span class="pun">|</span><span class="pln"> assignee_id </span><span class="pun">|</span><span class="pln">
</span><span class="pun">+----+---------+-------------+</span><span class="pln">
</span><span class="pun">|</span><span class="pln"> </span><span class="lit">1</span><span class="pln">  </span><span class="pun">|</span><span class="pln"> </span><span class="lit">2</span><span class="pln">       </span><span class="pun">|</span><span class="pln"> </span><span class="lit">1</span><span class="pln">           </span><span class="pun">|</span><span class="pln">
</span><span class="pun">|</span><span class="pln"> </span><span class="lit">2</span><span class="pln">  </span><span class="pun">|</span><span class="pln"> </span><span class="lit">2</span><span class="pln">       </span><span class="pun">|</span><span class="pln"> </span><span class="lit">2</span><span class="pln">           </span><span class="pun">|</span><span class="pln">
</span><span class="pun">+----+---------+-------------+</span></pre>

<p>
	ففي السطر الأوّل من جدول الدمج أعلاه، يرتبط العنصّر ذو المعرّف <code>2</code>، الذي يمثّل مهمّة إصلاح المصباح <code>Fix lighting</code> مع المُخّصَّص ذو المعرّف <code>1</code> (أحمد)، أمّا في السطر الثاني فإنّ عنصر المهام نفسه يرتبط مع المُخّصَّص ذو المعرّف <code>2</code> (محمّد)، ما يعني أنّ عنصر المهام قد خُصِّص لكل من أحمد ومحمّد، ويمكننا تخصيص عدّة عناصر مهام على نحوٍ مشابه لشخص مُخَصَّص واحد.
</p>

<p>
	سنعمل الآن على تعديل قاعدة بيانات تطبيق المهام لإضافة الجدول الذي يخزّن المُخَصَّصين.
</p>

<p>
	لذا بدايةً، سنفتح ملف تخطيط قاعدة البيانات schema.sql لإضافة جدولٍ جديد باسم <code>assignees</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_45" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano schema</span><span class="pun">.</span><span class="pln">sql</span></pre>

<p>
	وسنضيف سطرًا برمجيًا لحذف أي جداول موجودة أصلًا في قاعدة البيانات باسم <code>assignees</code>، وهذا ما يجنبنا أي تضاربات قد تحصل مُستقبلًا نتيجة وجود جدولين بنفس الاسم وبأعمدة مُختلفة في قاعدة البيانات، والذي قد يؤدي لتوقف تنفيذ الشيفرة نتيجة عدم توافق الجدول الموجود مع تخطيط قاعدة البيانات.
</p>

<p>
	الآن، سنضيف شيفرة <a href="https://wiki.hsoub.com/SQL" rel="external">SQL</a> اللازمة للجدول الجديد:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_2232_48" style="">
<span class="pln">DROP TABLE IF EXISTS assignees</span><span class="pun">;</span><span class="pln">
DROP TABLE IF EXISTS lists</span><span class="pun">;</span><span class="pln">
DROP TABLE IF EXISTS items</span><span class="pun">;</span><span class="pln">

CREATE TABLE lists </span><span class="pun">(</span><span class="pln">
    id INTEGER PRIMARY KEY AUTOINCREMENT</span><span class="pun">,</span><span class="pln">
    created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP</span><span class="pun">,</span><span class="pln">
    title TEXT NOT NULL
</span><span class="pun">);</span><span class="pln">

CREATE TABLE items </span><span class="pun">(</span><span class="pln">
    id INTEGER PRIMARY KEY AUTOINCREMENT</span><span class="pun">,</span><span class="pln">
    list_id INTEGER NOT NULL</span><span class="pun">,</span><span class="pln">
    created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP</span><span class="pun">,</span><span class="pln">
    content TEXT NOT NULL</span><span class="pun">,</span><span class="pln">
    </span><span class="kwd">done</span><span class="pln"> INTEGER NOT NULL DEFAULT </span><span class="lit">0</span><span class="pun">,</span><span class="pln">
    FOREIGN KEY </span><span class="pun">(</span><span class="pln">list_id</span><span class="pun">)</span><span class="pln"> REFERENCES lists </span><span class="pun">(</span><span class="pln">id</span><span class="pun">)</span><span class="pln">
</span><span class="pun">);</span><span class="pln">

CREATE TABLE assignees </span><span class="pun">(</span><span class="pln">
    id INTEGER PRIMARY KEY AUTOINCREMENT</span><span class="pun">,</span><span class="pln">
    name TEXT NOT NULL
</span><span class="pun">);</span></pre>

<p>
	اِحفظ الملف وأغلقه.
</p>

<p>
	يحتوي جدول المُخَصَّصين assignees الجديد على الأعمدة التالية:
</p>

<ul>
<li>
		"id": ويحتوي على معرّف ID المُخَصَّص.
	</li>
	<li>
		"name": يحتوي على اسم المُخَصَّص.
	</li>
</ul>
<p>
	الآن، سنعدّل ملف تهيئة قاعدة البيانات init_db.py بغية إضافة بعض المُخَصَّصين إلى قاعدة البيانات:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_50" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano init_db</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	عدّل الملف ليصبح كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_52" style="">
<span class="kwd">import</span><span class="pln"> sqlite3

connection </span><span class="pun">=</span><span class="pln"> sqlite3</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="str">'database.db'</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">with</span><span class="pln"> open</span><span class="pun">(</span><span class="str">'schema.sql'</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> f</span><span class="pun">:</span><span class="pln">
    connection</span><span class="pun">.</span><span class="pln">executescript</span><span class="pun">(</span><span class="pln">f</span><span class="pun">.</span><span class="pln">read</span><span class="pun">())</span><span class="pln">

cur </span><span class="pun">=</span><span class="pln"> connection</span><span class="pun">.</span><span class="pln">cursor</span><span class="pun">()</span><span class="pln">

cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO lists (title) VALUES (?)"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="str">'Work'</span><span class="pun">,))</span><span class="pln">
cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO lists (title) VALUES (?)"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="str">'Home'</span><span class="pun">,))</span><span class="pln">
cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO lists (title) VALUES (?)"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="str">'Study'</span><span class="pun">,))</span><span class="pln">

cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO items (list_id, content) VALUES (?, ?)"</span><span class="pun">,</span><span class="pln">
            </span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Morning meeting'</span><span class="pun">)</span><span class="pln">
            </span><span class="pun">)</span><span class="pln">

cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO items (list_id, content) VALUES (?, ?)"</span><span class="pun">,</span><span class="pln">
            </span><span class="pun">(</span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Buy fruit'</span><span class="pun">)</span><span class="pln">
            </span><span class="pun">)</span><span class="pln">

cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO items (list_id, content) VALUES (?, ?)"</span><span class="pun">,</span><span class="pln">
            </span><span class="pun">(</span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Cook dinner'</span><span class="pun">)</span><span class="pln">
            </span><span class="pun">)</span><span class="pln">

cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO items (list_id, content) VALUES (?, ?)"</span><span class="pun">,</span><span class="pln">
            </span><span class="pun">(</span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Learn Flask'</span><span class="pun">)</span><span class="pln">
            </span><span class="pun">)</span><span class="pln">

cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO items (list_id, content) VALUES (?, ?)"</span><span class="pun">,</span><span class="pln">
            </span><span class="pun">(</span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Learn SQLite'</span><span class="pun">)</span><span class="pln">
            </span><span class="pun">)</span><span class="pln">

cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO assignees (name) VALUES (?)"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="str">'Ahmad'</span><span class="pun">,))</span><span class="pln">
cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO assignees (name) VALUES (?)"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="str">'Mohammad'</span><span class="pun">,))</span><span class="pln">
cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO assignees (name) VALUES (?)"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="str">'sami'</span><span class="pun">,))</span><span class="pln">
cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO assignees (name) VALUES (?)"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="str">'Yousef'</span><span class="pun">,))</span><span class="pln">

connection</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">
connection</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span></pre>

<p>
	اِحفظ الملف وأغلقه.
</p>

<p>
	استخدمنا في الشيفرة السابقة كائن المؤشّر <code>cursor</code> لتنفيذ تعليمة <code>INSERT</code> في SQL اللازمة لإدخال أربعة أسماء إلى جدول المُخّصَّصين <code>assignees</code>، كما استخدمنا الموضع المؤقت <code>?</code> في الدالة <code>()execute</code> ومرّرنا له السجل الحاوي على اسم المُخَصًّص بما يضمن إدخال البيانات في قاعدة البيانات بأمان، ثمّ حفظنا التغييرات باستخدام الدالة <code>()connection.commit</code>، وأُغلق الاتصال مع قاعدة البيانات باستخدام الدالة <code>()connection.close</code>.
</p>

<p>
	وبذلك سيُضاف أربعة مُخّصَّصين إلى قاعدة البيانات، أسماؤهم أحمد ومحمّد وسامي ويوسف.
</p>

<p>
	الآن سنعيد تهيئة قاعدة البيانات بتشغيل البرنامج init_db.py:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_54" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ python init_db</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	وبذلك يصبح لدينا جدول لتخزين المُخّصَّصين في قاعدة البيانات. فيما يلي سنضيف جدول الدمج بهدف إنشاء علاقةٍ من نوع متعدّد-إلى-متعدّد بين عناصر المهام والمُخّصَّصين.
</p>

<h2>
	الخطوة الثالثة - إضافة جدول دمج ذو علاقة متعدد-إلى-متعدد
</h2>

<p>
	سنستخدم في هذه الخطوة جدول دمج لربط عناصر المهام بالمُخَصَّصين، لذا بدايةً سنعدّل ملف تخطيط قاعدة البيانات بغية إضافة جدول دمج جديد، كما سنعدّل ملف تهيئة قاعدة البيانات لإضافة بعض التخصيصات، أي تخصيص بعض عناصر المهام لبعض المُخَصَّصين، ثمّ سنستعرض المُخَصَّصين لكل مهمّة باستخدام برنامج تجريبي لفهم التغييرات الحاصلة في قاعدة البيانات.
</p>

<p>
	سنفتح بدايةً ملف تخطيط قاعدة البيانات schema.sql لإضافة جدول جديد:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_2232_57" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano schema</span><span class="pun">.</span><span class="pln">sql</span></pre>

<p>
	وبما أنّ هذا الجدول يدمج العناصر مع المُخَصَّصين فسنسميه جدول عناصر مُخَصَّصين item_assignees، وسنضيف سطرًا برمجيًا لحذف أي جداول موجودة أصلًا في قاعدة البيانات بنفس الاسم، ومن ثمّ سنضيف شيفرة SQL اللازمة لهذا الجدول:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_2232_61" style="">
<span class="pln">DROP TABLE IF EXISTS assignees</span><span class="pun">;</span><span class="pln">
DROP TABLE IF EXISTS lists</span><span class="pun">;</span><span class="pln">
DROP TABLE IF EXISTS items</span><span class="pun">;</span><span class="pln">
DROP TABLE IF EXISTS item_assignees</span><span class="pun">;</span><span class="pln">


CREATE TABLE lists </span><span class="pun">(</span><span class="pln">
    id INTEGER PRIMARY KEY AUTOINCREMENT</span><span class="pun">,</span><span class="pln">
    created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP</span><span class="pun">,</span><span class="pln">
    title TEXT NOT NULL
</span><span class="pun">);</span><span class="pln">

CREATE TABLE items </span><span class="pun">(</span><span class="pln">
    id INTEGER PRIMARY KEY AUTOINCREMENT</span><span class="pun">,</span><span class="pln">
    list_id INTEGER NOT NULL</span><span class="pun">,</span><span class="pln">
    created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP</span><span class="pun">,</span><span class="pln">
    content TEXT NOT NULL</span><span class="pun">,</span><span class="pln">
    </span><span class="kwd">done</span><span class="pln"> INTEGER NOT NULL DEFAULT </span><span class="lit">0</span><span class="pun">,</span><span class="pln">
    FOREIGN KEY </span><span class="pun">(</span><span class="pln">list_id</span><span class="pun">)</span><span class="pln"> REFERENCES lists </span><span class="pun">(</span><span class="pln">id</span><span class="pun">)</span><span class="pln">
</span><span class="pun">);</span><span class="pln">

CREATE TABLE assignees </span><span class="pun">(</span><span class="pln">
    id INTEGER PRIMARY KEY AUTOINCREMENT</span><span class="pun">,</span><span class="pln">
    name TEXT NOT NULL
</span><span class="pun">);</span><span class="pln">

CREATE TABLE item_assignees </span><span class="pun">(</span><span class="pln">
    id INTEGER PRIMARY KEY AUTOINCREMENT</span><span class="pun">,</span><span class="pln">
    item_id INTEGER</span><span class="pun">,</span><span class="pln">
    assignee_id INTEGER</span><span class="pun">,</span><span class="pln">
    FOREIGN KEY</span><span class="pun">(</span><span class="pln">item_id</span><span class="pun">)</span><span class="pln"> REFERENCES items</span><span class="pun">(</span><span class="pln">id</span><span class="pun">),</span><span class="pln">
    FOREIGN KEY</span><span class="pun">(</span><span class="pln">assignee_id</span><span class="pun">)</span><span class="pln"> REFERENCES assignees</span><span class="pun">(</span><span class="pln">id</span><span class="pun">)</span><span class="pln">
</span><span class="pun">);</span></pre>

<p>
	اِحفظ الملف وأغلقه.
</p>

<p>
	يحتوي جدول عناصر مُخَصَّصين item_assignees الجديد على الأعمدة التالية:
</p>

<ul>
<li>
		"id": ويحتوي على معرّف ID المُدخَل والدال على العلاقة ما بين مهمّة ومُخَصَّص، إذ يمثّل كل سطر من هذا الجدول علاقةً ما بين مهمّة ومُخَصَّص.
	</li>
	<li>
		"item_id": ويحتوي على معرّف ID لعنصر المهام المُسند للمُخَصَّص ذا المعرّف "assignee_id" الموافق.
	</li>
	<li>
		"assignee_id": ويحتوي على معرّف ID للمُخَصَّص المُسند إليه عنصر المهام ذا المعرّف "item_id" الموافق.
	</li>
</ul>
<p>
	ويملك الجدول item_assignees مفتاحين أجنبيين، الأول يربط عمود معرّف العنصر item_id في هذا الجدول مع عمود المعرّف id من جدول العناصر items، والثاني يربط عمود معرّف المُخَصَّص من هذا الجدول مع عمود المعرّف id من جدول المُخَصَّصين assignees.
</p>

<p>
	والآن سنفتح ملف تهيئة قاعدة البيانات لإضافة بعض التخصيصات، أي إسناد مهام لمُخَصَّصين:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_63" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano init_db</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	وسنعدّل الملف ليصبح كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_65" style="">
<span class="kwd">import</span><span class="pln"> sqlite3

connection </span><span class="pun">=</span><span class="pln"> sqlite3</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="str">'database.db'</span><span class="pun">)</span><span class="pln">


</span><span class="kwd">with</span><span class="pln"> open</span><span class="pun">(</span><span class="str">'schema.sql'</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> f</span><span class="pun">:</span><span class="pln">
    connection</span><span class="pun">.</span><span class="pln">executescript</span><span class="pun">(</span><span class="pln">f</span><span class="pun">.</span><span class="pln">read</span><span class="pun">())</span><span class="pln">

cur </span><span class="pun">=</span><span class="pln"> connection</span><span class="pun">.</span><span class="pln">cursor</span><span class="pun">()</span><span class="pln">

cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO lists (title) VALUES (?)"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="str">'Work'</span><span class="pun">,))</span><span class="pln">
cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO lists (title) VALUES (?)"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="str">'Home'</span><span class="pun">,))</span><span class="pln">
cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO lists (title) VALUES (?)"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="str">'Study'</span><span class="pun">,))</span><span class="pln">

cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO items (list_id, content) VALUES (?, ?)"</span><span class="pun">,</span><span class="pln">
            </span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Morning meeting'</span><span class="pun">)</span><span class="pln">
            </span><span class="pun">)</span><span class="pln">

cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO items (list_id, content) VALUES (?, ?)"</span><span class="pun">,</span><span class="pln">
            </span><span class="pun">(</span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Buy fruit'</span><span class="pun">)</span><span class="pln">
            </span><span class="pun">)</span><span class="pln">

cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO items (list_id, content) VALUES (?, ?)"</span><span class="pun">,</span><span class="pln">
            </span><span class="pun">(</span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Cook dinner'</span><span class="pun">)</span><span class="pln">
            </span><span class="pun">)</span><span class="pln">

cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO items (list_id, content) VALUES (?, ?)"</span><span class="pun">,</span><span class="pln">
            </span><span class="pun">(</span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Learn Flask'</span><span class="pun">)</span><span class="pln">
            </span><span class="pun">)</span><span class="pln">

cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO items (list_id, content) VALUES (?, ?)"</span><span class="pun">,</span><span class="pln">
            </span><span class="pun">(</span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Learn SQLite'</span><span class="pun">)</span><span class="pln">
            </span><span class="pun">)</span><span class="pln">

cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO assignees (name) VALUES (?)"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Ahmad</span><span class="pun">,))</span><span class="pln">
cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO assignees (name) VALUES (?)"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="str">'Mohammad'</span><span class="pun">,))</span><span class="pln">
cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO assignees (name) VALUES (?)"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="str">'Sami'</span><span class="pun">,))</span><span class="pln">
cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO assignees (name) VALUES (?)"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="str">'Yousef'</span><span class="pun">,))</span><span class="pln">

</span><span class="com"># Assign "Morning meeting" to "Ahmad"</span><span class="pln">
cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO item_assignees (item_id, assignee_id) VALUES (?, ?)"</span><span class="pun">,</span><span class="pln">
            </span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">))</span><span class="pln">

</span><span class="com"># Assign "Morning meeting" to "Mohammad"</span><span class="pln">
cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO item_assignees (item_id, assignee_id) VALUES (?, ?)"</span><span class="pun">,</span><span class="pln">
            </span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">))</span><span class="pln">

</span><span class="com"># Assign "Morning meeting" to "Yousef"</span><span class="pln">
cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO item_assignees (item_id, assignee_id) VALUES (?, ?)"</span><span class="pun">,</span><span class="pln">
            </span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">4</span><span class="pun">))</span><span class="pln">

</span><span class="com"># Assign "Buy fruit" to "Ahmad"</span><span class="pln">
cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO item_assignees (item_id, assignee_id) VALUES (?, ?)"</span><span class="pun">,</span><span class="pln">
            </span><span class="pun">(</span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">))</span><span class="pln">

connection</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">
connection</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span></pre>

<p>
	يُخصِّص الجزء الجديد المُضاف إلى الشيفرة السابقة عناصر مهام إلى مُخَصَّصين وذلك بإدخال كل منهما في جدول الدمج item_assignees، بحيث نُدخل معرّف قائمة المهام <code>item_id</code> المُراد تخصيصها (إسنادها) مع معرّف الشخص (المُخَصَّص) الموافق <code>assignee_id</code>. فمثلًا، أسندنا في الشيفرة أعلاه مهمّة الاجتماع الصباحي <code>Morning meeting</code> ذات المعرّف <code>1</code> إلى المُخَصَّص <code>Ahmad</code> ذو المعرّف <code>1</code>، وأكملنا الأسطر التالية وفق النمط ذاته، ومجدّدًا استخدمنا الموضع المؤقت <code>?</code> لتمرير القيم المراد إدخالها على هيئة مجموعة إلى التابع <code>()cur.execute</code> بأمان.
</p>

<p>
	اِحفظ الملف وأغلقه.
</p>

<p>
	الآن سنعيد تهيئة قاعدة البيانات مجدّدًا بتشغيل البرنامج في الملف init_db.py كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_67" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ python init_db</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ثم سنشغّل البرنامج التجريبي list_example.py، الذي يعرض عناصر المهام الموجودة في قاعدة البيانات:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_69" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ python list_example</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ويكون الخرج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_71" style="">
<span class="typ">Home</span><span class="pln">
     </span><span class="typ">Buy</span><span class="pln"> fruit </span><span class="pun">|</span><span class="pln"> id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">2</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> done</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln">
     </span><span class="typ">Cook</span><span class="pln"> dinner </span><span class="pun">|</span><span class="pln"> id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> done</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln">
</span><span class="typ">Study</span><span class="pln">
     </span><span class="typ">Learn</span><span class="pln"> </span><span class="typ">Flask</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">4</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> done</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln">
     </span><span class="typ">Learn</span><span class="pln"> </span><span class="typ">SQLite</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> done</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln">
</span><span class="typ">Work</span><span class="pln">
     </span><span class="typ">Morning</span><span class="pln"> meeting </span><span class="pun">|</span><span class="pln"> id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> done</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span></pre>

<p>
	نلاحظ من الخرج أنه يعرض كل عناصر المهام تحت القائمة التي يتبع لها، إضافةً لعرض معرّف وحالة كل عنصر (مُنجز / غير مُنجز)، إذ تدل القيمة <code>0</code> على عدم إنجازه بعد، أمّا القيمة <code>1</code> فتعني أنّه مُنجز.
</p>

<p>
	الآن، سنعمل على عرض المُخَصَّصين لكل عنصر مهام، لذلك سنفتح الملف list_example.py:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_75" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano list_example</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ونعدله بحيث يعرض المُخَصَّصين لكل عنصر مهام كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_73" style="">
<span class="kwd">from</span><span class="pln"> itertools </span><span class="kwd">import</span><span class="pln"> groupby
</span><span class="kwd">from</span><span class="pln"> app </span><span class="kwd">import</span><span class="pln"> get_db_connection

conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">
todos </span><span class="pun">=</span><span class="pln"> conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'SELECT i.id, i.done, i.content, l.title \
                      FROM items i JOIN lists l \
                      ON i.list_id = l.id ORDER BY l.title;'</span><span class="pun">).</span><span class="pln">fetchall</span><span class="pun">()</span><span class="pln">

lists </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{}</span><span class="pln">

</span><span class="kwd">for</span><span class="pln"> k</span><span class="pun">,</span><span class="pln"> g </span><span class="kwd">in</span><span class="pln"> groupby</span><span class="pun">(</span><span class="pln">todos</span><span class="pun">,</span><span class="pln"> key</span><span class="pun">=</span><span class="kwd">lambda</span><span class="pln"> t</span><span class="pun">:</span><span class="pln"> t</span><span class="pun">[</span><span class="str">'title'</span><span class="pun">]):</span><span class="pln">
    </span><span class="com"># Create an empty list for items</span><span class="pln">
    items </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">
    </span><span class="com"># Go through each to-do item row in the groupby() grouper object</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> item </span><span class="kwd">in</span><span class="pln"> g</span><span class="pun">:</span><span class="pln">
        </span><span class="com"># Get the assignees of the current to-do item</span><span class="pln">
        assignees </span><span class="pun">=</span><span class="pln"> conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'SELECT a.id, a.name FROM assignees a \
                                  JOIN item_assignees i_a \
                                  ON a.id = i_a.assignee_id \
                                  WHERE i_a.item_id = ?'</span><span class="pun">,</span><span class="pln">
                                  </span><span class="pun">(</span><span class="pln">item</span><span class="pun">[</span><span class="str">'id'</span><span class="pun">],)).</span><span class="pln">fetchall</span><span class="pun">()</span><span class="pln">
        </span><span class="com"># Convert the item row into a dictionary to add assignees</span><span class="pln">
        item </span><span class="pun">=</span><span class="pln"> dict</span><span class="pun">(</span><span class="pln">item</span><span class="pun">)</span><span class="pln">
        item</span><span class="pun">[</span><span class="str">'assignees'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> assignees

        items</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">item</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># Build the list of dictionaries</span><span class="pln">
    </span><span class="com"># the list's name (ex: Home/Study/Work) as the key</span><span class="pln">

    </span><span class="com"># and a list of dictionaries of to-do items</span><span class="pln">
    </span><span class="com"># belonging to that list as the value</span><span class="pln">
    lists</span><span class="pun">[</span><span class="pln">k</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> list</span><span class="pun">(</span><span class="pln">items</span><span class="pun">)</span><span class="pln">


</span><span class="kwd">for</span><span class="pln"> list_</span><span class="pun">,</span><span class="pln"> items </span><span class="kwd">in</span><span class="pln"> lists</span><span class="pun">.</span><span class="pln">items</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">list_</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> item </span><span class="kwd">in</span><span class="pln"> items</span><span class="pun">:</span><span class="pln">
        assignee_names </span><span class="pun">=</span><span class="pln"> </span><span class="str">', '</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="pln">a</span><span class="pun">[</span><span class="str">'name'</span><span class="pun">]</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> a </span><span class="kwd">in</span><span class="pln"> item</span><span class="pun">[</span><span class="str">'assignees'</span><span class="pun">])</span><span class="pln">

        </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'    '</span><span class="pun">,</span><span class="pln"> item</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">],</span><span class="pln"> </span><span class="str">'| id:'</span><span class="pun">,</span><span class="pln">
              item</span><span class="pun">[</span><span class="str">'id'</span><span class="pun">],</span><span class="pln"> </span><span class="str">'| done:'</span><span class="pun">,</span><span class="pln"> item</span><span class="pun">[</span><span class="str">'done'</span><span class="pun">],</span><span class="pln">
              </span><span class="str">'| assignees:'</span><span class="pun">,</span><span class="pln"> assignee_names</span><span class="pun">)</span></pre>

<p>
	اِحفظ الملف وأغلقه.
</p>

<p>
	استخدمنا في الشيفرة السابقة دالة الفرز <code>()groupby</code> لفرز عناصر المهام بحسب عناوين القوائم التي ينتمي لها كل عنصر (لمزيد من التفاصيل حول هذه النقطة يمكنك الاطلاع على الخطوة الثانية من مقال <a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B9%D9%84%D8%A7%D9%82%D8%A9-one-to-many-%D9%85%D8%B9-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D9%88%D9%85%D8%AD%D8%B1%D9%83-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-sqlite-r1623/" rel="">استخدام علاقات قاعدة البيانات من نوع واحد-إلى-متعدد</a>، وخلال إجراء عملية الفرز السابقة، أنشأنا قائمةً فارغةً باسم <code>items</code>، وهي ستحتوي كافّة بيانات عناصر المهام، من معرّف العنصر ومحتواه والمُخَصَّص
</p>

<p>
	ومن ثمّ نمر ضمن حلقة for المُخصّصة للعناصر وخلال <code>g</code> دورة (تكرار) على كافّة عناصر المهام لجلب المُخَصَّص لكل عنصر وحفظه ضمن المتغير <code>assignees</code>، الذي سيخزّن نتائج الاستعلام <code>SELECT</code> في SQL؛ إذ يجلب هذا الاستعلام معرّف المُخَصَّص <code>a.id</code> واسمه <code>a.name</code> من جدول المُخَصَّصين <code>assignees</code> (وقد رمزنا لهذا الجدول بالحرف <code>a</code> بهدف اختصار الاستعلام)، ثمّ يدمج معرّف واسم المُخَصَّص <code>item_assignees</code> المرتبط بالقائمة المطلوبة في جدول الدمج (رمزه المُختصَر <code>i_a</code>) وذلك من خلال تحقيق شرط كون معرّف المُخَصّص المأخوذ من جدول المُخَصَّصين مساويًا للمعرّف في جدول الدمج <code>a.id = i_a.assignee_id</code> وذلك عند كون قيمة معرّف عنصر المهام <code>i_a.item_id</code> في جدول الدمج مساويةً لقيمة معرّف العنصر الحالي <code>['item['id'</code>، ومن ثمّ نحصل على نتائج الاستعلام ضمن قائمة باستخدام التابع<code>()fetchall</code>.
</p>

<p>
	وبما أنّ كائن <code>sqlite3.Row</code> العادي لا يدعم مبدأ الإسناد، أضفنا السطر البرمجي التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_78" style="">
<span class="pln">item </span><span class="pun">=</span><span class="pln"> dict</span><span class="pun">(</span><span class="pln">item</span><span class="pun">)</span></pre>

<p>
	لتحويل العناصر إلى قواميس بايثون، والتي سنستخدمها لإضافة المُخَصَّصين إلى العناصر، ثمّ أضفنا مفتاحًا جديدًا وهو <code>assignees</code> إلى قاموس العناصر <code>item</code> باستخدام السطر البرمجي التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_80" style="">
<span class="pln">item</span><span class="pun">[</span><span class="str">'assignees'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> assignees</span></pre>

<p>
	وذلك بهدف الوصول إلى مُخَصَّصي العناصر مباشرةً من قاموس العناصر، ثمّ أضفنا العنصر المعدّل هذا إلى قائمة العناصر <code>items</code>، ثم بنينا قائمة القواميس المحتوية لكامل البيانات، فكل مفتاح ضمن القاموس يمثّل عنوان قائمة المهام وقيمة هذا المفتاح تضم كافّة العناصر التابعة لهذه القائمة.
</p>

<p>
	أما لطباعة النتائج استُخدمت الحلقة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_83" style="">
<span class="pln"> </span><span class="kwd">for</span><span class="pln"> list_</span><span class="pun">,</span><span class="pln"> items </span><span class="kwd">in</span><span class="pln"> lists</span><span class="pun">.</span><span class="pln">items</span><span class="pun">()</span></pre>

<p>
	للمرور على كافّة عناوين قوائم المهام والعناصر التابعة لكل منها، بحيث يُطبَع عنوان القائمة <code>_list</code>، ثمّ نمر باستخدام حلقة على كافّة العناصر التابعة لها؛ كما أُضيف متغيّرٌ باسم <code>assignee_names</code>، بحيث تستخدم قيمته دالة الدمج <code>()join</code> لدمج العناصر الناتجة عن تعليمة البناء generator expression التالية، التي تجلب اسم المُخَصَّص <code>['a['name</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_85" style="">
<span class="pln">a</span><span class="pun">[</span><span class="str">'name'</span><span class="pun">]</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> a </span><span class="kwd">in</span><span class="pln"> item</span><span class="pun">[</span><span class="str">'assignees'</span><span class="pun">]</span></pre>

<p>
	مع بيانات هذا المُخَصَّص من القائمة <code>['item['assignees</code>، وهذا ما يضمن إضافة أسماء المُخَصَّصين، ونهايةً تُطبع باقي بيانات عناصر المهام باستخدام الدالة <code>()print</code>.
</p>

<p>
	نشغِّل الآن البرنامج التجريبي list_example.py:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_87" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ python list_example</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	فيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_89" style="">
<span class="typ">Home</span><span class="pln">
     </span><span class="typ">Buy</span><span class="pln"> fruit </span><span class="pun">|</span><span class="pln"> id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">2</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> done</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> assignees</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Ahmad</span><span class="pln">
     </span><span class="typ">Cook</span><span class="pln"> dinner </span><span class="pun">|</span><span class="pln"> id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> done</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> assignees</span><span class="pun">:</span><span class="pln">
</span><span class="typ">Study</span><span class="pln">
     </span><span class="typ">Learn</span><span class="pln"> </span><span class="typ">Flask</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">4</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> done</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> assignees</span><span class="pun">:</span><span class="pln">
     </span><span class="typ">Learn</span><span class="pln"> </span><span class="typ">SQLite</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> done</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> assignees</span><span class="pun">:</span><span class="pln">
</span><span class="typ">Work</span><span class="pln">
     </span><span class="typ">Morning</span><span class="pln"> meeting </span><span class="pun">|</span><span class="pln"> id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> done</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> assignees</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Ahmad</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Mohammad</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Yousef</span></pre>

<p>
	وبذلك أصبح بالإمكان عرض المُخَصَّصين لكل عنصر مهام لكامل البيانات.
</p>

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

<p>
	الخطوة الرابعة - عرض المخصصين في الصفحة الرئيسية للتطبيق سنعدّل في هذه الخطوة الصفحة الرئيسية لتطبيق إدارة المهام، بحيث تعرض المُخَصَّصين لكل عنصر مهام. سنعدّل بدايةً الملف app.py، الذي يحتوي على شيفرة تطبيق فلاسك، ثمّ سنعدّل القالب index.html لعرض المُخَصَّصين تحت كل عنصر مهام في الصفحة الرئيسية.
</p>

<p>
	لذا، سنفتح الملف app.py لتعديل دالة فلاسك <code>()index</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_91" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	وسنعدّل الدالة لتصبح كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_93" style="">
<span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> index</span><span class="pun">():</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">
    todos </span><span class="pun">=</span><span class="pln"> conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'SELECT i.id, i.done, i.content, l.title \
                          FROM items i JOIN lists l \
                          ON i.list_id = l.id ORDER BY l.title;'</span><span class="pun">).</span><span class="pln">fetchall</span><span class="pun">()</span><span class="pln">

    lists </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{}</span><span class="pln">

    </span><span class="kwd">for</span><span class="pln"> k</span><span class="pun">,</span><span class="pln"> g </span><span class="kwd">in</span><span class="pln"> groupby</span><span class="pun">(</span><span class="pln">todos</span><span class="pun">,</span><span class="pln"> key</span><span class="pun">=</span><span class="kwd">lambda</span><span class="pln"> t</span><span class="pun">:</span><span class="pln"> t</span><span class="pun">[</span><span class="str">'title'</span><span class="pun">]):</span><span class="pln">
        </span><span class="com"># Create an empty list for items</span><span class="pln">
        items </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">
        </span><span class="com"># Go through each to-do item row in the groupby() grouper object</span><span class="pln">
        </span><span class="kwd">for</span><span class="pln"> item </span><span class="kwd">in</span><span class="pln"> g</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># Get the assignees of the current to-do item</span><span class="pln">
            assignees </span><span class="pun">=</span><span class="pln"> conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'SELECT a.id, a.name FROM assignees a \
                                    JOIN item_assignees i_a \
                                    ON a.id = i_a.assignee_id \
                                    WHERE i_a.item_id = ?'</span><span class="pun">,</span><span class="pln">
                                    </span><span class="pun">(</span><span class="pln">item</span><span class="pun">[</span><span class="str">'id'</span><span class="pun">],)).</span><span class="pln">fetchall</span><span class="pun">()</span><span class="pln">
            </span><span class="com"># Convert the item row into a dictionary to add assignees</span><span class="pln">
            item </span><span class="pun">=</span><span class="pln"> dict</span><span class="pun">(</span><span class="pln">item</span><span class="pun">)</span><span class="pln">
            item</span><span class="pun">[</span><span class="str">'assignees'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> assignees

            items</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">item</span><span class="pun">)</span><span class="pln">

        </span><span class="com"># Build the list of dictionaries</span><span class="pln">
        </span><span class="com"># the list's name (ex: Home/Study/Work) as the key</span><span class="pln">

        </span><span class="com"># and a list of dictionaries of to-do items</span><span class="pln">
        </span><span class="com"># belonging to that list as the value</span><span class="pln">
        lists</span><span class="pun">[</span><span class="pln">k</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> list</span><span class="pun">(</span><span class="pln">items</span><span class="pun">)</span><span class="pln">

    conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'index.html'</span><span class="pun">,</span><span class="pln"> lists</span><span class="pun">=</span><span class="pln">lists</span><span class="pun">)</span></pre>

<p>
	اِحفظ الملف وأغلقه.
</p>

<p>
	الشيفرة السابقة مماثلةٌ لتلك المُستخدمة في البرنامج التجريبي list_example.py في الخطوة الثالثة، وباستخدامنا لها سيحتوي المتغير <code>lists</code> على كافّة البيانات التي نحتاج بما فيها بيانات المُخَصًّصين التي سنستخدمها في القالب index.html للوصول إلى أسمائهم.
</p>

<p>
	لذا سنفتح الملف index.html:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_95" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">index</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	ثم نعدّله بهدف إضافة أسماء المُخَصًّصين بعد كل عنصر ليصبح كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_97" style="">
<span class="pun">{%</span><span class="pln"> extends </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">

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

                        </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"col-12 col-md-3"</span><span class="pun">&gt;</span><span class="pln">
                            </span><span class="pun">&lt;</span><span class="pln">a </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"btn btn-warning btn-sm"</span><span class="pln">
                            href</span><span class="pun">=</span><span class="str">"{{ url_for('edit', id=item['id']) }}"</span><span class="pun">&gt;</span><span class="typ">Edit</span><span class="pun">&lt;/</span><span class="pln">a</span><span class="pun">&gt;</span><span class="pln">
                        </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">

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

                    </span><span class="pun">&lt;</span><span class="pln">hr</span><span class="pun">&gt;</span><span class="pln">
                    </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> item</span><span class="pun">[</span><span class="str">'assignees'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">
                        </span><span class="pun">&lt;</span><span class="pln">span style</span><span class="pun">=</span><span class="str">"color: #6a6a6a"</span><span class="pun">&gt;</span><span class="typ">Assigned</span><span class="pln"> to</span><span class="pun">&lt;/</span><span class="pln">span</span><span class="pun">&gt;</span><span class="pln">
                        </span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> assignee </span><span class="kwd">in</span><span class="pln"> item</span><span class="pun">[</span><span class="str">'assignees'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">
                            </span><span class="pun">&lt;</span><span class="pln">span </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"badge badge-primary"</span><span class="pun">&gt;</span><span class="pln">
                                </span><span class="pun">{{</span><span class="pln"> assignee</span><span class="pun">[</span><span class="str">'name'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">
                            </span><span class="pun">&lt;/</span><span class="pln">span</span><span class="pun">&gt;</span><span class="pln">
                        </span><span class="pun">{%</span><span class="pln"> endfor </span><span class="pun">%}</span><span class="pln">
                    </span><span class="pun">{%</span><span class="pln"> endif </span><span class="pun">%}</span><span class="pln">

                    </span><span class="pun">&lt;/</span><span class="pln">li</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">{%</span><span class="pln"> endfor </span><span class="pun">%}</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">ul</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">{%</span><span class="pln"> endfor </span><span class="pun">%}</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	اِحفظ الملف وأغلقه.
</p>

<p>
	وبهذه التعديلات أعلاه نكون قد أضفنا فاصل أسطر تحت كل عنصر باستخدام وسم الفاصل الأفقي <code>&lt;hr&gt;</code>، وبالتالي في حال أُسندت مهامٌ معينة إلى مُخَصَّصين بواسطة التعليمة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_99" style="">
<span class="kwd">if</span><span class="pln"> item</span><span class="pun">[</span><span class="str">'assignees'</span><span class="pun">]</span></pre>

<p>
	ستظهر عبارة <code>Assigned to</code> وإلى جانبها أسماء المُخَصَّصين <code>['assignee['name</code> الموافقين من القائمة <code>['item['assignees</code>
</p>

<p>
	الآن، نشغِّل <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">خادم</a> التطوير:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2232_102" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ flask run</span></pre>

<p>
	ونصل إلى الصفحة الرئيسية index للتطبيق من خلال الذهاب إلى الرابط "/http://127.0.0.1:5000" في المتصفح.
</p>

<p>
	وبذلك أصبح من الممكن إسناد المهمّة الواحدة إلى عدّة مُخَصَّصين وإسناد عدّة مهام للمُخَصًّص الواحد.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="103056" href="https://academy.hsoub.com/uploads/monthly_2022_07/second_intro.png.9a7fe37ccae1a59bda7a071f24778713.png" rel=""><img alt="second_intro.png" class="ipsImage ipsImage_thumbnailed" data-fileid="103056" data-unique="q8towwr3n" src="https://academy.hsoub.com/uploads/monthly_2022_07/second_intro.thumb.png.24d79d875ee0fa6d947eab9cc4c90c03.png" style="width: 600px; height: auto;"></a>
</p>

<p>
	يمكنك الاطلاع على <a href="https://github.com/do-community/flask-todo-3" rel="external nofollow">شيفرة التطبيق كاملةً</a>.
</p>

<h2>
	الخاتمة
</h2>

<p>
	تعرفّنا في هذا المقال على علاقة قاعدة البيانات من نوع متعدّد-إلى-متعدّد، وكيفية استخدامها في تطبيق ويب مبني باستخدام فلاسك و SQLite، وكيفية دمج جدولين وفرز وتصنيف البيانات المترابطة باستخدام بايثون.
</p>

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

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-use-many-to-many-database-relationships-with-flask-and-sqlite" rel="external nofollow">How To Use Many-to-Many Database Relationships with Flask and SQLite</a> لصاحبه Abdelhadi Dyouri.
</p>

<h2>
	اقرا أيضًا
</h2>

<ul>
<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%88%D9%8A%D8%A8-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D9%85%D9%86-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1622/" rel="">إنشاء تطبيق ويب باستخدام إطار عمل فلاسك Flask من لغة بايثون</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B9%D9%84%D8%A7%D9%82%D8%A9-one-to-many-%D9%85%D8%B9-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D9%88%D9%85%D8%AD%D8%B1%D9%83-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-sqlite-r1623/" rel="">استخدام علاقة one-to-many مع إطار العمل فلاسك Flask ومحرك قواعد البيانات SQLite</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%B9%D8%AF%D9%8A%D9%84-%D8%A7%D9%84%D8%B9%D9%86%D8%A7%D8%B5%D8%B1-%D9%81%D9%8A-%D8%B9%D9%84%D8%A7%D9%82%D8%A7%D8%AA-one-to-many-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D9%88%D9%85%D8%AD%D8%B1%D9%83-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-sqlite-r1631/" rel="">تعديل العناصر في علاقات one-to-many باستخدام فلاسك Flask ومحرك قاعدة البيانات SQLite </a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1632</guid><pubDate>Sat, 16 Jul 2022 07:08:48 +0000</pubDate></item><item><title>&#x62A;&#x639;&#x62F;&#x64A;&#x644; &#x627;&#x644;&#x639;&#x646;&#x627;&#x635;&#x631; &#x641;&#x64A; &#x639;&#x644;&#x627;&#x642;&#x627;&#x62A; one-to-many &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x641;&#x644;&#x627;&#x633;&#x643; Flask &#x648;&#x645;&#x62D;&#x631;&#x643; &#x642;&#x627;&#x639;&#x62F;&#x629; &#x627;&#x644;&#x628;&#x64A;&#x627;&#x646;&#x627;&#x62A; SQLite</title><link>https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%B9%D8%AF%D9%8A%D9%84-%D8%A7%D9%84%D8%B9%D9%86%D8%A7%D8%B5%D8%B1-%D9%81%D9%8A-%D8%B9%D9%84%D8%A7%D9%82%D8%A7%D8%AA-one-to-many-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D9%88%D9%85%D8%AD%D8%B1%D9%83-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-sqlite-r1631/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_07/62d253c7112a1_------------one-to-many-----Flask----SQLite.png.38939bb5149078d3a80b8df5b66c1142.png" /></p>

<p>
	يُعد <a href="https://academy.hsoub.com/programming/python/flask/" rel="">فلاسك</a> إطار عمل لبناء تطبيقات الويب مبني باستخدام لغة البرمجة بايثون <a href="https://wiki.hsoub.com/Python" rel="external">Python</a>، أمّا <a href="https://academy.hsoub.com/devops/servers/databases/%D9%83%D9%8A%D9%81-%D9%88%D9%85%D8%AA%D9%89-%D9%86%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-sqlite-r111/" rel="">SQLite</a> فيُعرَّف على أنّه محرّك قاعدة بيانات يُستخدم ضمن بايثون لتخزين بيانات التطبيقات. سنتعلّم في هذا المقال كيفية التعديل على عناصر تطبيق مبني باستخدام فلاسك و SQLite.
</p>

<p>
	يأتي مقالنا هذا استكمالًا <a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B9%D9%84%D8%A7%D9%82%D8%A9-%D9%86%D9%88%D8%B9-%D9%88%D8%A7%D8%AD%D8%AF-%D8%A5%D9%84%D9%89-%D9%85%D8%AA%D8%B9%D8%AF%D8%AF-one-to-many-%D9%85%D8%B9-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D9%88%D9%85%D8%AD%D8%B1%D9%83-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-sqlite-r1623/" rel="">للمقال السابق</a> الذي أنشأنا فيه تطبيق ويب لإدارة المهام باستخدام فلاسك و SQLite، إذ أتاح لمستخدميه إمكانية إدارة عناصر المهام وتنظيمها ضمن قوائم، وإضافة عناصر جديدة إلى <a href="https://academy.hsoub.com/programming/sql/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%B9%D9%86-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-r584/" rel="">قاعدة البيانات</a>؛ وفي مقالنا هذا سنضيف للتطبيق السابق الآليات اللازمة لإتاحة إمكانية تخصيص عناصر مهام مُنجزة، وتعديل عناصر موجودة أصلًا وحذف عناصر وإضافة قوائم جديدة إلى قاعدة البيانات، وباتبّاع الخطوات التالية سيُضاف للتطبيق السابق أزرار تحرير وحذف، إضافةً لإمكانية وضع خط متوسط لتمييّز المهام المُنجزة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="103053" href="https://academy.hsoub.com/uploads/monthly_2022_07/first_image_b.png.6ef4f525fe03b3aa0b6004815b056a3a.png" rel=""><img alt="first_image_b.png" class="ipsImage ipsImage_thumbnailed" data-fileid="103053" data-unique="my5ejtds7" src="https://academy.hsoub.com/uploads/monthly_2022_07/first_image_b.thumb.png.e0129e018d4d68e4873a0838cec1547f.png" style="width: 500px; height: auto;"></a>
</p>

<h2>
	مستلزمات العمل
</h2>

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

<ul>
<li>
		توفُّر <a href="https://academy.hsoub.com/programming/python/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%AA%D9%87%D8%A7-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r714/" rel="">بيئة برمجة بايثون 3 محلية</a>، وسنستدعي في مقالنا مجلد المشروع flask_todo المُنشأ مُسبقًا من الخطوات في المقال السابق.
	</li>
	<li>
		توفّر تطبيق إدارة المهام المُنجز في المقال السابق، وفي الخطوة الأولى من هذا المقال سيكون لديك الخيار إما باستنساخ التطبيق مُباشرةً دون إعداده من قبلك، أو باتباع الخطوات التفصيلية في المقال السابق كيفية استخدام علاقات قاعدة البيانات من نوع واحد-إلى-متعدد one-to-many مع إطار العمل فلاسك Flask ومحرك قواعد البيانات SQLite لبناء التطبيق، ومن ثمّ الإكمال في هذا المقال، كما يمكنك الحصول على <a href="https://github.com/do-community/flask-todo" rel="external nofollow">الشيفرة الكاملة للتطبيق</a>.
	</li>
	<li>
		يجب فهم مختلف مفاهيم فلاسك الأساسية، مثل إنشاء الوجهات وإخراج قوالب <a href="https://wiki.hsoub.com/HTML" rel="external">HTML</a> والاتصال بقاعدة بيانات SQLite.
	</li>
</ul>
<h2>
	الخطوة الأولى - إعداد تطبيق الويب الخاص بإدارة المهام
</h2>

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

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7536_14" style="">
<span class="pln">$ git clone https</span><span class="pun">:</span><span class="com">//github.com/do-community/flask-todo</span></pre>

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

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_7536_16" style="">
<span class="pln">$ cd flask</span><span class="pun">-</span><span class="pln">todo</span></pre>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7536_18" style="">
<span class="pln">$ python </span><span class="pun">-</span><span class="pln">m venv env</span></pre>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7536_20" style="">
<span class="pln">$ source env</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">activate</span></pre>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7536_22" style="">
<span class="pln">$ pip install </span><span class="typ">Flask</span></pre>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7536_24" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ python init_db</span><span class="pun">.</span><span class="pln">py</span></pre>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7536_26" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_APP</span><span class="pun">=</span><span class="pln">app
</span><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_ENV</span><span class="pun">=</span><span class="pln">development</span></pre>

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

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

<pre class="ipsCode">
(env)user@localhost:$ flask run
</pre>

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

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

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

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

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

<ul>
<li>
		"id": يحتوي معرّف العنصر.
	</li>
	<li>
		"list_id": يحتوي معرّف القائمة التي ينتمي إليها العنصر.
	</li>
	<li>
		"created": يحوي تاريخ إنشاء العنصر.
	</li>
	<li>
		"content": محتوى العنصر.
	</li>
</ul>
<p>
	بدايةً، سنفتح ملف تخطيط قاعدة البيانات schema.sql لتعديل جدول العناصر items:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7536_88" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano schema</span><span class="pun">.</span><span class="pln">sql</span></pre>

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

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7536_90" style="">
<span class="pln">DROP TABLE IF EXISTS lists</span><span class="pun">;</span><span class="pln">
DROP TABLE IF EXISTS items</span><span class="pun">;</span><span class="pln">

CREATE TABLE lists </span><span class="pun">(</span><span class="pln">
    id INTEGER PRIMARY KEY AUTOINCREMENT</span><span class="pun">,</span><span class="pln">
    created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP</span><span class="pun">,</span><span class="pln">
    title TEXT NOT NULL
</span><span class="pun">);</span><span class="pln">

CREATE TABLE items </span><span class="pun">(</span><span class="pln">
    id INTEGER PRIMARY KEY AUTOINCREMENT</span><span class="pun">,</span><span class="pln">
    list_id INTEGER NOT NULL</span><span class="pun">,</span><span class="pln">
    created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP</span><span class="pun">,</span><span class="pln">
    content TEXT NOT NULL</span><span class="pun">,</span><span class="pln">
    </span><span class="kwd">done</span><span class="pln"> INTEGER NOT NULL DEFAULT </span><span class="lit">0</span><span class="pun">,</span><span class="pln">
    FOREIGN KEY </span><span class="pun">(</span><span class="pln">list_id</span><span class="pun">)</span><span class="pln"> REFERENCES lists </span><span class="pun">(</span><span class="pln">id</span><span class="pun">)</span><span class="pln">
</span><span class="pun">);</span></pre>

<p>
	اِحفظ الملف وأغلقه.
</p>

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

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7536_32" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ python init_db</span><span class="pun">.</span><span class="pln">py</span></pre>

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

<pre class="ipsCode">
(env)user@localhost:$ nano app.py
</pre>

<p>
	ثم سنجلب كلًا من معرّف العنصر وقيمة عمود done الموافقة له إلى الدالة <code>()index</code>، التي تجلب أصلًا القوائم والعناصر من قاعدة البيانات، مرسلةً إياها إلى ملف HTML المُسمّى index.html ليعمل على عرضها، لذا عدّلنا على تعليمات <a href="https://academy.hsoub.com/programming/sql/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-sql-r844/" rel="">SQL</a> السابقة لتصبح مثل الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7536_36" style="">
<span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> index</span><span class="pun">():</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">
    todos </span><span class="pun">=</span><span class="pln"> conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'SELECT i.id, i.done, i.content, l.title \
                          FROM items i JOIN lists l \
                          ON i.list_id = l.id ORDER BY l.title;'</span><span class="pun">).</span><span class="pln">fetchall</span><span class="pun">()</span><span class="pln">
    lists </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{}</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> k</span><span class="pun">,</span><span class="pln"> g </span><span class="kwd">in</span><span class="pln"> groupby</span><span class="pun">(</span><span class="pln">todos</span><span class="pun">,</span><span class="pln"> key</span><span class="pun">=</span><span class="kwd">lambda</span><span class="pln"> t</span><span class="pun">:</span><span class="pln"> t</span><span class="pun">[</span><span class="str">'title'</span><span class="pun">]):</span><span class="pln">
        lists</span><span class="pun">[</span><span class="pln">k</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> list</span><span class="pun">(</span><span class="pln">g</span><span class="pun">)</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'index.html'</span><span class="pun">,</span><span class="pln"> lists</span><span class="pun">=</span><span class="pln">lists</span><span class="pun">)</span></pre>

<p>
	اِحفظ الملف وأغلقه.
</p>

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

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7536_38" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano list_example</span><span class="pun">.</span><span class="pln">py</span></pre>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7536_40" style="">
<span class="kwd">from</span><span class="pln"> itertools </span><span class="kwd">import</span><span class="pln"> groupby
</span><span class="kwd">from</span><span class="pln"> app </span><span class="kwd">import</span><span class="pln"> get_db_connection
conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">
todos </span><span class="pun">=</span><span class="pln"> conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'SELECT i.id, i.done, i.content, l.title \
                      FROM items i JOIN lists l \
                      ON i.list_id = l.id ORDER BY l.title;'</span><span class="pun">).</span><span class="pln">fetchall</span><span class="pun">()</span><span class="pln">
lists </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{}</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> k</span><span class="pun">,</span><span class="pln"> g </span><span class="kwd">in</span><span class="pln"> groupby</span><span class="pun">(</span><span class="pln">todos</span><span class="pun">,</span><span class="pln"> key</span><span class="pun">=</span><span class="kwd">lambda</span><span class="pln"> t</span><span class="pun">:</span><span class="pln"> t</span><span class="pun">[</span><span class="str">'title'</span><span class="pun">]):</span><span class="pln">
    lists</span><span class="pun">[</span><span class="pln">k</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> list</span><span class="pun">(</span><span class="pln">g</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> list_</span><span class="pun">,</span><span class="pln"> items </span><span class="kwd">in</span><span class="pln"> lists</span><span class="pun">.</span><span class="pln">items</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">list_</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> item </span><span class="kwd">in</span><span class="pln"> items</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'    '</span><span class="pun">,</span><span class="pln"> item</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">],</span><span class="pln"> </span><span class="str">'| id:'</span><span class="pun">,</span><span class="pln">
              item</span><span class="pun">[</span><span class="str">'id'</span><span class="pun">],</span><span class="pln"> </span><span class="str">'| done:'</span><span class="pun">,</span><span class="pln"> item</span><span class="pun">[</span><span class="str">'done'</span><span class="pun">])</span></pre>

<p>
	اِحفظ الملف وأغلقه.
</p>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7536_42" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ python list_example</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	لنحصل على الخرج التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7536_44" style="">
<span class="typ">Home</span><span class="pln">
     </span><span class="typ">Buy</span><span class="pln"> fruit </span><span class="pun">|</span><span class="pln"> id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">2</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> done</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln">
     </span><span class="typ">Cook</span><span class="pln"> dinner </span><span class="pun">|</span><span class="pln"> id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> done</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln">
</span><span class="typ">Study</span><span class="pln">
     </span><span class="typ">Learn</span><span class="pln"> </span><span class="typ">Flask</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">4</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> done</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln">
     </span><span class="typ">Learn</span><span class="pln"> </span><span class="typ">SQLite</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> done</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln">
</span><span class="typ">Work</span><span class="pln">
     </span><span class="typ">Morning</span><span class="pln"> meeting </span><span class="pun">|</span><span class="pln"> id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> done</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span></pre>

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

<p>
	لذلك، سنفتح الملف app.py:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7536_46" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7536_48" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/&lt;int:id&gt;/do/'</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=(</span><span class="str">'POST'</span><span class="pun">,))</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> </span><span class="kwd">do</span><span class="pun">(</span><span class="pln">id</span><span class="pun">):</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'UPDATE items SET done = 1 WHERE id = ?'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">id</span><span class="pun">,))</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> redirect</span><span class="pun">(</span><span class="pln">url_for</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">))</span></pre>

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

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

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7536_50" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/&lt;int:id&gt;/undo/'</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=(</span><span class="str">'POST'</span><span class="pun">,))</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> undo</span><span class="pun">(</span><span class="pln">id</span><span class="pun">):</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'UPDATE items SET done = 0 WHERE id = ?'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">id</span><span class="pun">,))</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> redirect</span><span class="pun">(</span><span class="pln">url_for</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">))</span></pre>

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

<p>
	اِحفظ الملف وأغلقه.
</p>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7536_52" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">index</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	وسنعدّل محتويات حلقة for التكرارية داخل عنصر القائمة غير المرتبة <code>&lt;ul&gt;</code> لتصبح على النحو التالي:
</p>

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

<p>
	أسندنا في الشيفرة السابقة وضمن الحلقة for القيمة <code>line-through</code> لسمة تنسيق النص <code>text-decoration</code> في <a href="https://academy.hsoub.com/programming/css/%d8%aa%d8%b9%d8%b1%d9%91%d9%81-%d8%b9%d9%84%d9%89-%d8%a3%d8%b3%d8%a7%d8%b3%d9%8a%d8%a7%d8%aa-css-r70/" rel="">CSS</a>، والتي تضيف خطًّا متوسطًا للنص في حال كان عنصر القوائم مُنجزًا تبعًا لقيمة السجل الموافق للعنصر من العمود <code>done</code> في قاعدة البيانات، ثمّ استخدمنا تعليمة <code>set</code> من تعليمات محرّك القوالب جينجا Jinja لنصرّح عن متغيرين، هما <code>URL</code> و <code>BUTTON</code>؛ فإذا كان العنصر غير مُحدّدٍ على أنه مُنجز، سيأخذ المتغير <code>BUTTON</code> القيمة <strong>Do</strong> وسيشير المتغيّر <code>URL</code> إلى الوجهة <code>/do/</code>؛ أمّا إذا كان العنصر محّددًا على أنه مُنجَز، فسيأخذ المتغير <code>BUTTON</code> القيمة <strong>Undo</strong>، وسيشير المتغيّر <code>URL</code> إلى الوجهة <code>/undo/</code>. استخدمنا بعد ذلك هذين المتغيرين في نموذج إدخال يُرسل الطلب المناسب اعتمادًا على حالة عنصر المهام.
</p>

<p>
	نشغّل <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">الخادم</a>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7536_57" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ flask run</span></pre>

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

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

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

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

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7536_61" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7536_63" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/&lt;int:id&gt;/edit/'</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=(</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">))</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> edit</span><span class="pun">(</span><span class="pln">id</span><span class="pun">):</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">

    todo </span><span class="pun">=</span><span class="pln"> conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'SELECT i.id, i.list_id, i.done, i.content, l.title \
                         FROM items i JOIN lists l \
                         ON i.list_id = l.id WHERE i.id = ?'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">id</span><span class="pun">,)).</span><span class="pln">fetchone</span><span class="pun">()</span><span class="pln">

    lists </span><span class="pun">=</span><span class="pln"> conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'SELECT title FROM lists;'</span><span class="pun">).</span><span class="pln">fetchall</span><span class="pun">()</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">method </span><span class="pun">==</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">:</span><span class="pln">
        content </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">]</span><span class="pln">
        list_title </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'list'</span><span class="pun">]</span><span class="pln">

        </span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> content</span><span class="pun">:</span><span class="pln">
            flash</span><span class="pun">(</span><span class="str">'Content is required!'</span><span class="pun">)</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> redirect</span><span class="pun">(</span><span class="pln">url_for</span><span class="pun">(</span><span class="str">'edit'</span><span class="pun">,</span><span class="pln"> id</span><span class="pun">=</span><span class="pln">id</span><span class="pun">))</span><span class="pln">

        list_id </span><span class="pun">=</span><span class="pln"> conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'SELECT id FROM lists WHERE title = (?);'</span><span class="pun">,</span><span class="pln">
                                 </span><span class="pun">(</span><span class="pln">list_title</span><span class="pun">,)).</span><span class="pln">fetchone</span><span class="pun">()[</span><span class="str">'id'</span><span class="pun">]</span><span class="pln">

        conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'UPDATE items SET content = ?, list_id = ?\
                      WHERE id = ?'</span><span class="pun">,</span><span class="pln">
                     </span><span class="pun">(</span><span class="pln">content</span><span class="pun">,</span><span class="pln"> list_id</span><span class="pun">,</span><span class="pln"> id</span><span class="pun">))</span><span class="pln">
        conn</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">
        conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> redirect</span><span class="pun">(</span><span class="pln">url_for</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">))</span><span class="pln">

    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'edit.html'</span><span class="pun">,</span><span class="pln"> todo</span><span class="pun">=</span><span class="pln">todo</span><span class="pun">,</span><span class="pln"> lists</span><span class="pun">=</span><span class="pln">lists</span><span class="pun">)</span></pre>

<p>
	تُستخدم القيمة <code>id</code> وسيطًا في دالة فلاسك الجديدة المُبيّنة في الشيفرة السابقة لجلب معرّف عنصر المهام المُراد تحريره، إضافةً إلى معرّف وعنوان القائمة التي ينتمي إليها، وقيمة السجل الموافق من العمود done، ومحتوى العنصر، وذلك باستخدام التعليمة <code>JOIN</code> في <a href="https://wiki.hsoub.com/SQL" rel="external">SQL</a>. تُحفظ هذه البيانات في متغيرٍ باسم <code>todo</code>، ثمّ تُجلب كافّة قوائم المهام من قاعدة البيانات وتُخزّن في المتغير <code>lists</code>.
</p>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7536_65" style="">
<span class="kwd">if</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">method </span><span class="pun">==</span><span class="pln"> </span><span class="str">'POST'</span></pre>

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

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7536_96" style="">
<span class="pln">request</span><span class="pun">.</span><span class="pln">method </span><span class="pun">==</span><span class="pln"> </span><span class="str">'POST'</span></pre>

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

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

<p>
	اِحفظ الملف وأغلقه.
</p>

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

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_7536_98" style="">
<span class="pln">(env)user@localhost:$ nano templates/edit.html</span></pre>

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

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_7536_94" style="">
<span class="pln">{% extends 'base.html' %}

{% block content %}

</span><span class="tag">&lt;h1&gt;</span><span class="pln">{% block title %} Edit an Item {% endblock %}</span><span class="tag">&lt;/h1&gt;</span><span class="pln">

</span><span class="tag">&lt;form</span><span class="pln"> </span><span class="atn">method</span><span class="pun">=</span><span class="atv">"post"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"form-group"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;label</span><span class="pln"> </span><span class="atn">for</span><span class="pun">=</span><span class="atv">"content"</span><span class="tag">&gt;</span><span class="pln">Content</span><span class="tag">&lt;/label&gt;</span><span class="pln">
        </span><span class="tag">&lt;input</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"text"</span><span class="pln"> </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"content"</span><span class="pln">
               </span><span class="atn">placeholder</span><span class="pun">=</span><span class="atv">"Todo content"</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"form-control"</span><span class="pln">
               </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"{{ todo['content'] or request.form['content'] }}"</span><span class="tag">&gt;&lt;/input&gt;</span><span class="pln">
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">

    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"form-group"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;label</span><span class="pln"> </span><span class="atn">for</span><span class="pun">=</span><span class="atv">"list"</span><span class="tag">&gt;</span><span class="pln">List</span><span class="tag">&lt;/label&gt;</span><span class="pln">
        </span><span class="tag">&lt;select</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"form-control"</span><span class="pln"> </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"list"</span><span class="tag">&gt;</span><span class="pln">
            {% for list in lists %}
                {% if list['title'] == request.form['list'] %}
                    </span><span class="tag">&lt;option</span><span class="pln"> </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"{{ request.form['list'] }}"</span><span class="pln"> </span><span class="atn">selected</span><span class="tag">&gt;</span><span class="pln">
                        {{ request.form['list'] }}
                    </span><span class="tag">&lt;/option&gt;</span><span class="pln">

                {% elif list['title'] == todo['title'] %}
                    </span><span class="tag">&lt;option</span><span class="pln"> </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"{{ todo['title'] }}"</span><span class="pln"> </span><span class="atn">selected</span><span class="tag">&gt;</span><span class="pln">
                        {{ todo['title'] }}
                    </span><span class="tag">&lt;/option&gt;</span><span class="pln">

                {% else %}
                    </span><span class="tag">&lt;option</span><span class="pln"> </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"{{ list['title'] }}"</span><span class="tag">&gt;</span><span class="pln">
                        {{ list['title'] }}
                    </span><span class="tag">&lt;/option&gt;</span><span class="pln">
                {% endif %}
            {% endfor %}
        </span><span class="tag">&lt;/select&gt;</span><span class="pln">
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"form-group"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;button</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"submit"</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"btn btn-primary"</span><span class="tag">&gt;</span><span class="pln">Submit</span><span class="tag">&lt;/button&gt;</span><span class="pln">
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
</span><span class="tag">&lt;/form&gt;</span><span class="pln">
{% endblock %}</span></pre>

<p>
	تُستخدم التعليمة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7536_101" style="">
<span class="pun">{{</span><span class="pln"> todo</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">]</span><span class="pln"> </span><span class="kwd">or</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">]‎</span><span class="pln"> </span><span class="pun">}}</span></pre>

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

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

<p>
	اِحفظ الملف وأغلقه.
</p>

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

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_7536_103" style="">
<span class="pln">(env)user@localhost:$ nano templates/index.html</span></pre>

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

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_7536_106" style="">
<span class="pln">. . .
</span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"row"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"col-12 col-md-3"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;form</span><span class="pln"> </span><span class="atn">action</span><span class="pun">=</span><span class="atv">"{{ url_for(URL, id=item['id']) }}"</span><span class="pln">
            </span><span class="atn">method</span><span class="pun">=</span><span class="atv">"POST"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;input</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"submit"</span><span class="pln"> </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"{{ BUTTON }}"</span><span class="pln">
                </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"btn btn-success btn-sm"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;/form&gt;</span><span class="pln">
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"col-12 col-md-3"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"btn btn-warning btn-sm"</span><span class="pln">
        </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('edit', id=item['id']) }}"</span><span class="tag">&gt;</span><span class="pln">Edit</span><span class="tag">&lt;/a&gt;</span><span class="pln">
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
</span><span class="tag">&lt;/div&gt;</span></pre>

<p>
	اِحفظ الملف وأغلقه.
</p>

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

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7536_108" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ flask run</span></pre>

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

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

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7536_110" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7536_112" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/&lt;int:id&gt;/delete/'</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=(</span><span class="str">'POST'</span><span class="pun">,))</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> delete</span><span class="pun">(</span><span class="pln">id</span><span class="pun">):</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'DELETE FROM items WHERE id = ?'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">id</span><span class="pun">,))</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> redirect</span><span class="pun">(</span><span class="pln">url_for</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">))</span></pre>

<p>
	اِحفظ الملف وأغلقه.
</p>

<p>
	تستخدم دالة فلاسك <code>()delete</code> معرّف <code>id</code> العنصر وسيطًا، فعند إرسال طلب <a href="https://academy.hsoub.com/programming/general/%d9%85%d8%af%d8%ae%d9%84-%d8%a5%d9%84%d9%89-http-r73/" rel="">HTTP</a> من نوع POST، نستخدم تعليمة <code>DELETE</code> في SQL لحذف العنصر الموافق لهذا المعرّف، ثمّ نحفظ التغييرات ونغلق الاتصال مع قاعدة البيانات ونعيد توجيه المستخدم إلى الصفحة الرئيسية index.
</p>

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

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_7536_70" style="">
<span class="pln">(env)user@localhost:$ nano templates/index.html</span></pre>

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

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_7536_72" style="">
<span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"row"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"col-12 col-md-3"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;form</span><span class="pln"> </span><span class="atn">action</span><span class="pun">=</span><span class="atv">"{{ url_for(URL, id=item['id']) }}"</span><span class="pln">
            </span><span class="atn">method</span><span class="pun">=</span><span class="atv">"POST"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;input</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"submit"</span><span class="pln"> </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"{{ BUTTON }}"</span><span class="pln">
                </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"btn btn-success btn-sm"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;/form&gt;</span><span class="pln">
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">

    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"col-12 col-md-3"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"btn btn-warning btn-sm"</span><span class="pln">
        </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('edit', id=item['id']) }}"</span><span class="tag">&gt;</span><span class="pln">Edit</span><span class="tag">&lt;/a&gt;</span><span class="pln">
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">

    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"col-12 col-md-3"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;form</span><span class="pln"> </span><span class="atn">action</span><span class="pun">=</span><span class="atv">"{{ url_for('delete', id=item['id']) }}"</span><span class="pln">
            </span><span class="atn">method</span><span class="pun">=</span><span class="atv">"POST"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;input</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"submit"</span><span class="pln"> </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"Delete"</span><span class="pln">
                </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"btn btn-danger btn-sm"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;/form&gt;</span><span class="pln">
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
</span><span class="tag">&lt;/div&gt;</span></pre>

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

<p>
	اِحفظ الملف وأغلقه.
</p>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7536_115" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ flask run</span></pre>

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

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

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

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7536_117" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7536_119" style="">
<span class="kwd">if</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">method </span><span class="pun">==</span><span class="pln"> </span><span class="str">'POST'</span></pre>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7536_121" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/create/'</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=(</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">))</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> create</span><span class="pun">():</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">method </span><span class="pun">==</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">:</span><span class="pln">
        content </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">]</span><span class="pln">
        list_title </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'list'</span><span class="pun">]</span><span class="pln">

        new_list </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'new_list'</span><span class="pun">]</span><span class="pln">

        </span><span class="com"># If a new list title is submitted, add it to the database</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> list_title </span><span class="pun">==</span><span class="pln"> </span><span class="str">'New List'</span><span class="pln"> </span><span class="kwd">and</span><span class="pln"> new_list</span><span class="pun">:</span><span class="pln">
            conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'INSERT INTO lists (title) VALUES (?)'</span><span class="pun">,</span><span class="pln">
                         </span><span class="pun">(</span><span class="pln">new_list</span><span class="pun">,))</span><span class="pln">
            conn</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">
            </span><span class="com"># Update list_title to refer to the newly added list</span><span class="pln">
            list_title </span><span class="pun">=</span><span class="pln"> new_list

        </span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> content</span><span class="pun">:</span><span class="pln">
            flash</span><span class="pun">(</span><span class="str">'Content is required!'</span><span class="pun">)</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> redirect</span><span class="pun">(</span><span class="pln">url_for</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">))</span><span class="pln">

        list_id </span><span class="pun">=</span><span class="pln"> conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'SELECT id FROM lists WHERE title = (?);'</span><span class="pun">,</span><span class="pln">
                                 </span><span class="pun">(</span><span class="pln">list_title</span><span class="pun">,)).</span><span class="pln">fetchone</span><span class="pun">()[</span><span class="str">'id'</span><span class="pun">]</span><span class="pln">
        conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'INSERT INTO items (content, list_id) VALUES (?, ?)'</span><span class="pun">,</span><span class="pln">
                     </span><span class="pun">(</span><span class="pln">content</span><span class="pun">,</span><span class="pln"> list_id</span><span class="pun">))</span><span class="pln">
        conn</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">
        conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> redirect</span><span class="pun">(</span><span class="pln">url_for</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">))</span><span class="pln">

    lists </span><span class="pun">=</span><span class="pln"> conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'SELECT title FROM lists;'</span><span class="pun">).</span><span class="pln">fetchall</span><span class="pun">()</span><span class="pln">

    conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'create.html'</span><span class="pun">,</span><span class="pln"> lists</span><span class="pun">=</span><span class="pln">lists</span><span class="pun">)</span></pre>

<p>
	اِحفظ الملف وأغلقه.
</p>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7536_124" style="">
<span class="pln">list_title </span><span class="pun">==</span><span class="pln"> </span><span class="str">'New List'</span><span class="pln"> </span><span class="kwd">and</span><span class="pln"> new_list</span></pre>

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

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

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_7536_76" style="">
<span class="pln">(env)user@localhost:$ nano templates/create.html</span></pre>

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

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_7536_78" style="">
<span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"form-group"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;label</span><span class="pln"> </span><span class="atn">for</span><span class="pun">=</span><span class="atv">"list"</span><span class="tag">&gt;</span><span class="pln">List</span><span class="tag">&lt;/label&gt;</span><span class="pln">
        </span><span class="tag">&lt;select</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"form-control"</span><span class="pln"> </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"list"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;option</span><span class="pln"> </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"New List"</span><span class="pln"> </span><span class="atn">selected</span><span class="tag">&gt;</span><span class="pln">New List</span><span class="tag">&lt;/option&gt;</span><span class="pln">
            {% for list in lists %}
                {% if list['title'] == request.form['list'] %}
                    </span><span class="tag">&lt;option</span><span class="pln"> </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"{{ request.form['list'] }}"</span><span class="pln"> </span><span class="atn">selected</span><span class="tag">&gt;</span><span class="pln">
                        {{ request.form['list'] }}
                    </span><span class="tag">&lt;/option&gt;</span><span class="pln">
                {% else %}
                    </span><span class="tag">&lt;option</span><span class="pln"> </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"{{ list['title'] }}"</span><span class="tag">&gt;</span><span class="pln">
                        {{ list['title'] }}
                    </span><span class="tag">&lt;/option&gt;</span><span class="pln">
                {% endif %}
            {% endfor %}
        </span><span class="tag">&lt;/select&gt;</span><span class="pln">
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">

    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"form-group"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;label</span><span class="pln"> </span><span class="atn">for</span><span class="pun">=</span><span class="atv">"new_list"</span><span class="tag">&gt;</span><span class="pln">New List</span><span class="tag">&lt;/label&gt;</span><span class="pln">
        </span><span class="tag">&lt;input</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"text"</span><span class="pln"> </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"new_list"</span><span class="pln">
                </span><span class="atn">placeholder</span><span class="pun">=</span><span class="atv">"New list name"</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"form-control"</span><span class="pln">
                </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"{{ request.form['new_list'] }}"</span><span class="tag">&gt;&lt;/input&gt;</span><span class="pln">
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">

    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"form-group"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;button</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"submit"</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"btn btn-primary"</span><span class="tag">&gt;</span><span class="pln">Submit</span><span class="tag">&lt;/button&gt;</span><span class="pln">
    </span><span class="tag">&lt;/div&gt;</span></pre>

<p>
	اِحفظ الملف وأغلقه.
</p>

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

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7536_81" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ flask run</span></pre>

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

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="103054" href="https://academy.hsoub.com/uploads/monthly_2022_07/Second_image_b.png.f557991df324383c3be78de7a5002497.png" rel=""><img alt="Second_image_b.png" class="ipsImage ipsImage_thumbnailed" data-fileid="103054" data-unique="z3zikzvsx" src="https://academy.hsoub.com/uploads/monthly_2022_07/Second_image_b.thumb.png.acbdeb3826c760901335bd9332b9ceab.png" style="width: 500px; height: auto;"></a>
</p>

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

<p>
	يمكنك الاطلاع على <a href="https://github.com/do-community/flask-todo-2" rel="external nofollow">شيفرة البرنامج كاملةً</a>.
</p>

<h2>
	الخاتمة
</h2>

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

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

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-modify-items-in-a-one-to-many-database-relationships-with-flask-and-sqlite" rel="external nofollow">How To Modify Items in a One-to-Many Database Relationships with Flask and SQLite</a> لصاحبه Abdelhadi Dyouri.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%88%D9%8A%D8%A8-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D9%85%D9%86-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1622/" rel="">إنشاء تطبيق ويب باستخدام إطار عمل فلاسك Flask من لغة بايثون</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B9%D9%84%D8%A7%D9%82%D8%A9-one-to-many-%D9%85%D8%B9-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D9%88%D9%85%D8%AD%D8%B1%D9%83-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-sqlite-r1623/" rel="">استخدام علاقة one-to-many مع إطار العمل فلاسك Flask ومحرك قواعد البيانات SQLite</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1631</guid><pubDate>Tue, 19 Jul 2022 16:02:00 +0000</pubDate></item><item><title>&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x639;&#x644;&#x627;&#x642;&#x629; one-to-many &#x645;&#x639; &#x625;&#x637;&#x627;&#x631; &#x627;&#x644;&#x639;&#x645;&#x644; &#x641;&#x644;&#x627;&#x633;&#x643; Flask &#x648;&#x645;&#x62D;&#x631;&#x643; &#x642;&#x648;&#x627;&#x639;&#x62F; &#x627;&#x644;&#x628;&#x64A;&#x627;&#x646;&#x627;&#x62A; SQLite</title><link>https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B9%D9%84%D8%A7%D9%82%D8%A9-one-to-many-%D9%85%D8%B9-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D9%88%D9%85%D8%AD%D8%B1%D9%83-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-sqlite-r1623/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_07/62c69e1e9f0c1_-------one-to-many-----Flask----SQLite.png.11cf40d3cc6a2a9bd820df1ba929af50.png" /></p>

<p>
	<a href="https://academy.hsoub.com/programming/python/flask/" rel="">فلاسك</a> هو إطار عمل يستخدم لغة <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D9%85%D8%B1%D8%AC%D8%B9-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%AA%D8%B9%D9%84%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r735/" rel="">بايثون</a> Python لبناء تطبيقات الويب؛ أما <a href="https://academy.hsoub.com/devops/servers/databases/%D9%83%D9%8A%D9%81-%D9%88%D9%85%D8%AA%D9%89-%D9%86%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-sqlite-r111/" rel="">SQLite</a> فهو محرّك قواعد بيانات يُستخدم لتخزين بيانات تطبيق الويب.
</p>

<p>
	سننشئ في هذا المقال تطبيق ويب لإدارة المهام باستخدام كلٍ من إطار العمل فلاسك ومحرّك قاعدة البيانات SQLite، إذ سيتمكّن مستخدمو التطبيق من إنشاء قوائم تمثّل عناصر المهام المطلوبة.
</p>

<p>
	تتمثّل الفائدة العلمية والعملية من هذا المقال بتعلمّك كيفيّة استخدام SQLite مع فلاسك، بالإضافة لتعلّم كيفيّة إنشاء علاقات قاعدة البيانات من النوع واحد-إلى-متعدد.
</p>

<p>
	تُعرَّف علاقة قاعدة البيانات من النوع واحد-إلى-متعدد one-to-many بكونها علاقةٌ بين جدولين من قاعدة البيانات، بحيث يمكن لسجلٍ موجودٍ في أحد الجدولين الإشارة إلى عدّة سجلات من الجدول الثاني، ويشكّل تطبيق المدونة خير مثال على العلاقات في قواعد البيانات من النوع واحد-إلى-متعدد، نظرًا لارتباط الجدول الخاص بتخزين التدوينات posts مع الجدول الخاص بتخزين التعليقات comments بعلاقة من نوع واحد-إلى-متعدد؛ إذ يمكن أن يكون لكل تدوينة عدّة تعليقاتٍ مرتبطةٍ بها، وبالمقابل يرتبط كل تعليق بتدوينة واحدةٍ فقط، وبذلك فإنّ للتدوينة الواحدة علاقةٌ بالعديد من التعليقات. وفي هذه الحالة يُعد الجدول الخاص بالتدوينات "الجدول الأب parent"، في حين يُعد الجدول الخاص بالتعليقات "الجدول الابن child"؛ إذ يمكن للسجل الموجود في الجدول الأب الإشارة إلى عدّة سجلات موجودة في الجدول الابن، وهو أمرٌ أساسي لإتاحة إمكانية الوصول إلى البيانات المطلوبة في كل جدول.
</p>

<p>
	تتجلى إحدى دوافع استخدامنا لمحرّك قاعدة البيانات SQLite بكونه مرنًا من ناحية نقل البيانات عبر الأجهزة وبيئات التشغيل المختلفة Portable من جهة؛ وبكونه يعمل بسهولة مع لغة البرمجة <a href="https://wiki.hsoub.com/Python" rel="external">بايثون </a>دون الحاجة لتطبيق أي إعدادات إضافية من جهة أخرى؛ أمّا الدافع الآخر لاستخدامه فيتمثل بكونه مناسبًا جدًا لتصميم النماذج الأولية لتطبيقات الويب المنشئة قبل الانتقال إلى العمل على قاعدة البيانات الأكبر، مثل قاعدة بيانات MySQL، أو <a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%AC%D9%87%D9%8A%D8%B2-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-postgresql-%D9%88%D8%A7%D9%84%D8%AA%D9%91%D8%B9%D8%B1%D9%8A%D9%81-%D8%A8%D9%85%D9%81%D9%87%D9%88%D9%85%D9%8A-orm-%D9%88%D8%A5%D8%B6%D8%A7%D9%81%D8%A7%D8%AA-flask-r506/" rel="">Postgres</a>.
</p>

<h2>
	مستلزمات العمل
</h2>

<p>
	توجد مجموعةٌ من المتطلبات الواجب توفرّها وإعدادها قبل البدء في اتباع الخطوات التفصيلية المشروحة تاليًا ضمن هذا المقال، وهذه المتطلبات هي:
</p>

<ul>
<li>
		توفُّر <a href="https://academy.hsoub.com/programming/python/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%AA%D9%87%D8%A7-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r714/" rel="">بيئة برمجة بايثون 3 محلية</a>، وفي مقالنا سنسمّي مجلد المشروع بالاسم flask_todo.
	</li>
	<li>
		فهم مختلف مفاهيم فلاسك الأساسية، مثل إنشاء الوجهات وإخراج قوالب HTML والاتصال بقاعدة بيانات SQLite.
	</li>
</ul>
<h2>
	الخطوة الأولى - إنشاء قاعدة البيانات
</h2>

<p>
	سننفّذ في هذه الخطوة مجموعةً من الخطوات الفرعية المتمثّلة بتنشيط البيئة البرمجية التي سنعمل عليها، وتثبيت فلاسك، وإنشاء قاعدة بيانات SQLite، وتعبئتها ببيانات معياريّة، وسنتعلم كذلك كيفيّة استخدام <a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D9%84%D8%B1%D9%91%D8%A8%D8%B7-%D8%A8%D9%8A%D9%86-%D8%AC%D8%AF%D9%88%D9%84%D9%8A-%D8%A7%D9%84%D9%85%D9%82%D8%A7%D9%84%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D9%85%D9%8F%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D9%8A%D9%86-%D8%A8%D8%B9%D9%84%D8%A7%D9%82%D8%A9-%D9%88%D8%A7%D8%AD%D8%AF-%D9%84%D9%84%D8%B9%D8%AF%D9%8A%D8%AF-one-to-many-relationship-r512/" rel="">المفاتيح الأجنبية foreign keys</a>، لإنشاء علاقة واحد-إلى-متعدّد بين القوائم والعناصر؛ إذ يمثّل المفتاح الأجنبي صلة الوصل لربط جدول من قاعدة البيانات بآخر، فهو الرابط بين الجدول الابن والجدول الأب في قاعدة البيانات هذه، والمفتاح الأساسي لجدولٍ ما هو مفتاحٌ أجنبي للجداول الأُخرى.
</p>

<p>
	إذا لم تنشّط البيئة البرمجية الخاصة بك بعد، فاستخدم الأمر التالي لتنشيطها بعد التأكّد من كون موجه الأوامر يشير إلى مسار مجلد المشروع flask_todo:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7111_18" style="">
<span class="pln">$ source env</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">activate</span></pre>

<p>
	وبمجرّد تنشيط البيئة البرمجية، ثبّت فلاسك باستخدام الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7111_20" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ pip install flask</span></pre>

<p>
	ومع اكتمال تثبيت فلاسك، أصبح بإمكانك الآن إنشاء مخطّط قاعدة البيانات المتضمِّن أوامر <a href="https://wiki.hsoub.com/SQL" rel="external">SQL</a> اللازمة لإنشاء الجداول التي تحتاجها لتخزين بيانات المهام المطلوبة، إذ ستحتاج إلى جدولين، جدول يسمى lists لتخزين قوائم المهام، وجدول يسمى items لتخزين عناصر كل قائمة.
</p>

<p>
	الآن سننشئ ملفًا باسم schema.sql ضمن المجلد flask_todo:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7111_12" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano schema</span><span class="pun">.</span><span class="pln">sql</span></pre>

<p>
	وسنكتب أوامر SQL التالية داخل هذا الملف:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7111_16" style="">
<span class="pln">DROP TABLE IF EXISTS lists</span><span class="pun">;</span><span class="pln">
DROP TABLE IF EXISTS items</span><span class="pun">;</span><span class="pln">

CREATE TABLE lists </span><span class="pun">(</span><span class="pln">
    id INTEGER PRIMARY KEY AUTOINCREMENT</span><span class="pun">,</span><span class="pln">
    created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP</span><span class="pun">,</span><span class="pln">
    title TEXT NOT NULL
</span><span class="pun">);</span><span class="pln">

CREATE TABLE items </span><span class="pun">(</span><span class="pln">
    id INTEGER PRIMARY KEY AUTOINCREMENT</span><span class="pun">,</span><span class="pln">
    list_id INTEGER NOT NULL</span><span class="pun">,</span><span class="pln">
    created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP</span><span class="pun">,</span><span class="pln">
    content TEXT NOT NULL</span><span class="pun">,</span><span class="pln">
    FOREIGN KEY </span><span class="pun">(</span><span class="pln">list_id</span><span class="pun">)</span><span class="pln"> REFERENCES lists </span><span class="pun">(</span><span class="pln">id</span><span class="pun">)</span><span class="pln">
</span><span class="pun">);</span></pre>

<p>
	اِحفظ الملف وأغلقه.
</p>

<p>
	أوّل سطرين من شيفرة SQL السابقة مسؤولان عن حذف أي جداول موجودة سابقًا ضمن قاعدة البيانات باسم lists أو items، وهما:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7111_23" style="">
<span class="pln">DROP TABLE IF EXISTS lists</span><span class="pun">;</span><span class="pln">
DROP TABLE IF EXISTS items</span><span class="pun">;</span></pre>

<p>
	وبذلك لن يكون هناك أي سلوكٍ متضاربٍ أو عشوائي ناجمٍ عن تشابه الأسماء ضمن قاعدة البيانات، ومن الجدير بالملاحظة أنّ تطبيق هذين الأمرين سيؤدي إلى حذف كامل محتوى قاعدة البيانات الحالي، لذا لا تُدخل أي بيانات مهمّة في التطبيق حتى تنتهي من كافّة الخطوات التعليميّة في المقال وتجربة نتائجه النهائية.
</p>

<p>
	بعد ذلك، يمكنك استخدام الأمر <code>CREATE TABLE lists</code> لإنشاء جدول القوائم lists، الذي سيخزن قوائم المهام المطلوبة، مثل قائمة الدراسة وقائمة العمل والقائمة الرئيسية، وما إلى ذلك، باستخدام الأعمدة التالية:
</p>

<ul>
<li>
		"id": ويحتوي على بيانات من نوع الرقم الصحيح، ويمثّل مفتاحًا أساسيًا للجدول lists، وسيتضمّن قيم فريدة في قاعدة البيانات من أجل كل سجل، والسجل هو نوعٌ من أنواع قوائم المهام في حالتنا.
	</li>
	<li>
		"created": يحتوي على تاريخ ووقت إنشاء قائمة المهام، وتشير <code>NOT NULL</code> لكون هذا العمود لا يجب أن يحتوي على قيم فارغة، أما القيمة الافتراضية فهي <code>CURRENT_TIMESTAMP</code>، والتي تمثّل تاريخ ووقت إضافة قائمة المهام إلى قاعدة البيانات، وكما هو الحال في عمود "id" لا يتوجب عليك تحديد قيم لهذا العمود، لأن تعبئتها تلقائية.
	</li>
	<li>
		"title": عنوان قائمة المهام.
	</li>
</ul>
<p>
	وبعد إنشاء جدول القوائم lists، سننشئ الآن جدول العناصر items لتخزين عناصر المهام، إذ يحتوي هذا الجدول على معرّف "ID"، وعمود "list_id" يحوي أعدادًا صحيحة تحدّد معرّف القائمة التي ينتمي إليها العنصر، إضافةً لعمود تاريخ إنشاء العنصر، وعمود محتوى المهمّة.
</p>

<p>
	يمكنك استخدام مفتاح أجنبي لربط عنصرٍ ما بقائمةٍ معيّنة في قاعدة البيانات عن طريق كتابة الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7111_25" style="">
<span class="pln"> FOREIGN KEY </span><span class="pun">(</span><span class="pln">list_id</span><span class="pun">)</span><span class="pln"> REFERENCES lists </span><span class="pun">(</span><span class="pln">id</span><span class="pun">)</span></pre>

<p>
	يمثّل جدول القوائم lists الجدول الأب المربوط بالجدول الآخر باستخدام المفتاح الأجنبي، وهذا يبيّن امكانية احتواء القائمة الواحدة على عدّة عناصر؛ في حين يمثّل جدول العناصر items الجدول الحاوي على المفتاح الأجنبي، وهذا يعني أن كل عنصر ينتمي إلى قائمة واحدةٍ فقط. يشير العمود list_id من جدول العناصر items الابن إلى عمود المعرِّف id من جدول القوائم lists الأب.
</p>

<p>
	وبذلك نجد أنّه قد حققنا فعليًا علاقة واحد-إلى-متعدّد بين جدولي lists و items، نظرًا لأنّ القائمة يمكن أن تحتوي على <strong>عدّة</strong> عناصر، وأنّ العنصر الواحد سينتمي إلى قائمةٍ واحدةٍ فقط.
</p>

<p>
	بعد الانتهاء من إنشاء الجداول، سنستخدم ملف schema.sql لإنشاء قاعدة البيانات. لذلك، أنشئ ملفًا باسم init_db.py ضمن المجلد flask_todo على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7111_29" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano init_db</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ثم أضِف فيه الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7111_27" style="">
<span class="kwd">import</span><span class="pln"> sqlite3

connection </span><span class="pun">=</span><span class="pln"> sqlite3</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="str">'database.db'</span><span class="pun">)</span><span class="pln">


</span><span class="kwd">with</span><span class="pln"> open</span><span class="pun">(</span><span class="str">'schema.sql'</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> f</span><span class="pun">:</span><span class="pln">
    connection</span><span class="pun">.</span><span class="pln">executescript</span><span class="pun">(</span><span class="pln">f</span><span class="pun">.</span><span class="pln">read</span><span class="pun">())</span><span class="pln">

cur </span><span class="pun">=</span><span class="pln"> connection</span><span class="pun">.</span><span class="pln">cursor</span><span class="pun">()</span><span class="pln">

cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO lists (title) VALUES (?)"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="str">'Work'</span><span class="pun">,))</span><span class="pln">
cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO lists (title) VALUES (?)"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="str">'Home'</span><span class="pun">,))</span><span class="pln">
cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO lists (title) VALUES (?)"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="str">'Study'</span><span class="pun">,))</span><span class="pln">

cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO items (list_id, content) VALUES (?, ?)"</span><span class="pun">,</span><span class="pln">
            </span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Morning meeting'</span><span class="pun">)</span><span class="pln">
            </span><span class="pun">)</span><span class="pln">

cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO items (list_id, content) VALUES (?, ?)"</span><span class="pun">,</span><span class="pln">
            </span><span class="pun">(</span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Buy fruit'</span><span class="pun">)</span><span class="pln">
            </span><span class="pun">)</span><span class="pln">

cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO items (list_id, content) VALUES (?, ?)"</span><span class="pun">,</span><span class="pln">
            </span><span class="pun">(</span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Cook dinner'</span><span class="pun">)</span><span class="pln">
            </span><span class="pun">)</span><span class="pln">

cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO items (list_id, content) VALUES (?, ?)"</span><span class="pun">,</span><span class="pln">
            </span><span class="pun">(</span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Learn Flask'</span><span class="pun">)</span><span class="pln">
            </span><span class="pun">)</span><span class="pln">

cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO items (list_id, content) VALUES (?, ?)"</span><span class="pun">,</span><span class="pln">
            </span><span class="pun">(</span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Learn SQLite'</span><span class="pun">)</span><span class="pln">
            </span><span class="pun">)</span><span class="pln">

connection</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">
connection</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span></pre>

<p>
	اِحفظ الملف وأغلقه، وبمجرّد تنفيذ الشيفرة السابقة سيُنشأ ملفٌ باسم database.db ويُحقَّق الاتصال به، وبعدها يمكننا فتح ملف schema.sql وتشغيله باستخدام التابع <code>()executescript</code> الذي ينفِّذ العديد من تعليمات SQL البرمجية في وقتٍ واحد.
</p>

<p>
	سيؤدي تشغيل الملف schema.sql لإنشاء جداول القوائم والعناصر، ومن ثمّ سننفّذ عددًا من تعليمات الإدخال في SQL لإنشاء ثلاث قوائم وخمسة عناصر مهام باستخدام كائن المؤشر <code>Cursor</code>.
</p>

<p>
	سنستخدم العمود "list_id" لربط كلّ عنصرٍ بقائمة عن طريق قيمة معرّف القائمة. فعلى سبيل المثال، إذا كانت القائمة المسمّاة "العمل Work" هي الإدخال الأوّل في قاعدة البيانات، فسيكون معرّفها ذو القيمة 1، وسنتمكّن باستخدامه من ربط عنصر المهام المسمّى "الاجتماع الصباحي Morning meeting" مثلًا بقائمة مهام العمل، وينطبق المبدأ نفسه على كافّة القوائم والعناصر الأُخرى.
</p>

<p>
	ومن ثمّ سنحفظ التغييرات ونغلق الاتصال، وسنشغّل البرنامج كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7111_31" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ python init_db</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	وبعد تنفيذ هذا البرنامج سيظهر ملفٌ جديدٌ ضمن المجلد flask_todo باسم database.db، ومع انتهاء هذه الخطوة نكون قد فعّلنا البيئة البرمجية وثبتنا فلاسك، وأنشأنا قاعدة بيانات SQLite، وبذلك سنتمكن من استرجاع كل ما أُدخِل في قاعدة البيانات من قوائم وعناصر لعرضها في الصفحة الرئيسية للتطبيق الذي نحن في صدد إنشائه.
</p>

<h2>
	الخطوة الثانية - عرض عناصر المهام
</h2>

<p>
	سنعمل في هذه الخطوة على ربط قاعدة البيانات التي أنشأناها في الخطوة السابقة مع تطبيق فلاسك الذي سيعرض قوائم المهام والعناصر الموجودة في كلٍ منها، كما سنستخدم استعلامات قواعد البيانات المركّبة SQLite joins للاستعلام عن بيانات من الجدولين معًا، وسنعمل على تجميع عناصر المهام حسب القوائم التابعة لها.
</p>

<p>
	أولاً، سننشئ ملف التطبيق، لذا أنشئ ملفًا باسم app.py في المجلد flask_todo:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7111_33" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ومن ثمّ سنضيف الشيفرة التالية إلى الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7111_35" style="">
<span class="kwd">from</span><span class="pln"> itertools </span><span class="kwd">import</span><span class="pln"> groupby
</span><span class="kwd">import</span><span class="pln"> sqlite3
</span><span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> render_template</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">,</span><span class="pln"> flash</span><span class="pun">,</span><span class="pln"> redirect</span><span class="pun">,</span><span class="pln"> url_for


</span><span class="kwd">def</span><span class="pln"> get_db_connection</span><span class="pun">():</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> sqlite3</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="str">'database.db'</span><span class="pun">)</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">row_factory </span><span class="pun">=</span><span class="pln"> sqlite3</span><span class="pun">.</span><span class="typ">Row</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> conn


app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">config</span><span class="pun">[</span><span class="str">'SECRET_KEY'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'this should be a secret random string'</span><span class="pln">


</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> index</span><span class="pun">():</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">
    todos </span><span class="pun">=</span><span class="pln"> conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'SELECT i.content, l.title FROM items i JOIN lists l \
                          ON i.list_id = l.id ORDER BY l.title;'</span><span class="pun">).</span><span class="pln">fetchall</span><span class="pun">()</span><span class="pln">

    lists </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{}</span><span class="pln">

    </span><span class="kwd">for</span><span class="pln"> k</span><span class="pun">,</span><span class="pln"> g </span><span class="kwd">in</span><span class="pln"> groupby</span><span class="pun">(</span><span class="pln">todos</span><span class="pun">,</span><span class="pln"> key</span><span class="pun">=</span><span class="kwd">lambda</span><span class="pln"> t</span><span class="pun">:</span><span class="pln"> t</span><span class="pun">[</span><span class="str">'title'</span><span class="pun">]):</span><span class="pln">
        lists</span><span class="pun">[</span><span class="pln">k</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> list</span><span class="pun">(</span><span class="pln">g</span><span class="pun">)</span><span class="pln">

    conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'index.html'</span><span class="pun">,</span><span class="pln"> lists</span><span class="pun">=</span><span class="pln">lists</span><span class="pun">)</span></pre>

<p>
	اِحفظ الملف وأغلقه.
</p>

<p>
	تفتح الدالة <code>()get_db_connection</code> اتصالًا مع ملف قاعدة البيانات database.db، وتحدّد بعد ذلك قيمة السمة <code>row_factory</code> لتكون <code>sqlite3.Row</code> لنتمكّن من الوصول إلى الأعمدة باستخدام أسمائها، ما يعني أنّ اتصال قاعدة البيانات سيعيد سجلات يمكننا التعامل معها كما هو الحال مع قواميس بايثون Dictionaries الاعتيادية (وهي حاويات لتخزين القيم المنظمّة في بايثون)، ونهايةً تعيد الدالة كائن الاتصال <code>conn</code> المُستخدم للوصول إلى قاعدة البيانات.
</p>

<p>
	الآن سنفتح اتصالًا مع قاعدة البيانات باستخدام دالة عرض فلاسك <code>()index</code>، وسننفّذ استعلام SQL التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7111_38" style="">
<span class="pln">SELECT i</span><span class="pun">.</span><span class="pln">content</span><span class="pun">,</span><span class="pln"> l</span><span class="pun">.</span><span class="pln">title FROM items i JOIN lists l ON i</span><span class="pun">.</span><span class="pln">list_id </span><span class="pun">=</span><span class="pln"> l</span><span class="pun">.</span><span class="pln">id ORDER BY l</span><span class="pun">.</span><span class="pln">title</span><span class="pun">;</span></pre>

<p>
	وسنتمكن باستخدام الدالة <code>()fetchall</code> من الحصول على نتائج الاستعلام وحفظها ضمن متغيّر <code>todos</code>.
</p>

<p>
	استخدمنا في الاستعلام السابق الأمر <code>SELECT</code> لجلب محتوى العنصر مع عنوان القائمة المنتمي إليها، عبر ربط كلٍ من جدولي القوائم والعناصر (إذ يشير الرمز <code>i</code> للعناصر، والرمز <code>l</code> للقوائم)، إذ سنحصل بالنتيجة على كافّة سجلات جدول العناصر ذات القيمة <code>list_id</code> الموافقة لقيمة المعرّف <code>id</code> من جدول القوائم باستخدام شرط التجميع <code>i.list_id = l.id</code> المكتوب بعد البادئة البرمجية <code>ON</code>، ومن ثمّ استخدمنا أمر الترتيب <code>ORDER BY</code> لفرز النتائج وفقًا لعناوين القوائم.
</p>

<p>
	الآن ولفهم هذا الاستعلام بوضوح، سنفتح موجه أوامر بايثون التفاعلي Python REPL في مجلد المشروع flask_todo:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7111_40" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ python</span></pre>

<p>
	ومن ثمّ سنختبر محتويات المتغير <code>todos</code> الحاوي على نتائج الاستعلام السابق عبر تشغيل الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7111_42" style="">
<span class="kwd">from</span><span class="pln"> app </span><span class="kwd">import</span><span class="pln"> get_db_connection
conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">
todos </span><span class="pun">=</span><span class="pln"> conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'SELECT i.content, l.title FROM items i JOIN lists l \
ON i.list_id = l.id ORDER BY l.title;'</span><span class="pun">).</span><span class="pln">fetchall</span><span class="pun">()</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> todo </span><span class="kwd">in</span><span class="pln"> todos</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">todo</span><span class="pun">[</span><span class="str">'title'</span><span class="pun">],</span><span class="pln"> </span><span class="str">':'</span><span class="pun">,</span><span class="pln"> todo</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">])</span></pre>

<p>
	استوردنا في الشيفرة السابقة قاعدة البيانات get<em>db</em>connection من الملف app.py، ومن ثمّ فُتِح الاتصال مع قاعدة البيانات ونُفِّذ الاستعلام (وهو استعلام SQL ذاته الموجود في الملف app.py)، وباستخدام حلقة for التكرارية طُبِع عنوان كل قائمة ومحتويات كل عنصر مهام مرتبطٍ بها، وبذلك سيظهر الخرج في حالتنا على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7111_45" style="">
<span class="typ">Home</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> </span><span class="typ">Buy</span><span class="pln"> fruit
</span><span class="typ">Home</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> </span><span class="typ">Cook</span><span class="pln"> dinner
</span><span class="typ">Study</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> </span><span class="typ">Learn</span><span class="pln"> </span><span class="typ">Flask</span><span class="pln">
</span><span class="typ">Study</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> </span><span class="typ">Learn</span><span class="pln"> </span><span class="typ">SQLite</span><span class="pln">
</span><span class="typ">Work</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> </span><span class="typ">Morning</span><span class="pln"> meeting</span></pre>

<p>
	أغلق موجه أوامر بايثون REPL باستخدام الاختصار "CTRL+D".
</p>

<p>
	الآن، وبعدما فهمت آلية عمل ونتائج الاستعلامات المُجمّعة في SQL، سنعود إلى دالة فلاسك <code>()index</code> الموجودة في الملف app.py، إذ سنصرّح بدايةً عن المتغير <code>todos</code> ومن ثمّ سنستخدم الشيفرة التالية لتجميع النتائج:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7111_47" style="">
<span class="pln">lists </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{}</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> k</span><span class="pun">,</span><span class="pln"> g </span><span class="kwd">in</span><span class="pln"> groupby</span><span class="pun">(</span><span class="pln">todos</span><span class="pun">,</span><span class="pln"> key</span><span class="pun">=</span><span class="kwd">lambda</span><span class="pln"> t</span><span class="pun">:</span><span class="pln"> t</span><span class="pun">[</span><span class="str">'title'</span><span class="pun">]):</span><span class="pln">
    lists</span><span class="pun">[</span><span class="pln">k</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> list</span><span class="pun">(</span><span class="pln">g</span><span class="pun">)</span></pre>

<p>
	تبدأ الشيفرة المبينة أعلاه بالتصريح عن قاموس dictionary فارغ باسم lists، ثم تستخدم حلقة for التكرارية لتجميع النتائج الموجودة في المتغير <code>todos</code> وترتيبها حسب عنوان قائمة المهام باستخدام دالة الترتيب <code>()groupby</code> المستوردة من المكتبة المعيارية itertools، إذ تولّد هذه الدالة مجموعةً من النتائج من أجل كل تكرار في الحلقة عبر مروره على كل عنصرٍ مخزّنٍ في المتغير <code>todos</code>.
</p>

<p>
	يمثّل متغيّر الحلقة <code>k</code> عناوين قوائم المهام (المنزل والدراسة والعمل في حالتنا) المُستخرجة من قاعدة البيانات عبر تمرير قيمة التابع <code>['lambda t: t['title</code> وسيطًا لدالة التجميع <code>()groupby</code>، التي تُعيد عنوان القائمة التي يتبع لها هذا العنصر (بنفس مبدأ الدالة <code>['todo['title</code> المُستخدم في حلقة for السابقة)؛ بينما يمثّل المتغير <code>g</code> المجموعة الحاوية على عناصر المهام التابعة لكل عنوان قائمة. فمن أجل أول دورة في الحلقة مثلًا، سيمثّل المتغير <code>k</code> القائمة <code>Home</code>، في حين سيتكرر المتغير <code>g</code> ليحتوي بالنتيجة عناصر القائمة الموجودة في <code>k</code> وهي في حالتنا "Buy fruit" و "Cook".
</p>

<p>
	وهذا ما يمثّل علاقةً من نوع واحد-إلى-متعدّد بين القوائم والعناصر، بحيث يضم كل عنوان قائمة مجموعةً من عناصر المهام. وبذلك، ستظهر القوائم عند تشغيل الملف app.py وبعد انتهاء تنفيذ حلقة for على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7111_49" style="">
<span class="pun">{</span><span class="str">'Home'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[&lt;</span><span class="pln">sqlite3</span><span class="pun">.</span><span class="typ">Row</span><span class="pln"> object at </span><span class="lit">0x7f9f58460950</span><span class="pun">&gt;,</span><span class="pln">
          </span><span class="pun">&lt;</span><span class="pln">sqlite3</span><span class="pun">.</span><span class="typ">Row</span><span class="pln"> object at </span><span class="lit">0x7f9f58460c30</span><span class="pun">&gt;],</span><span class="pln">
 </span><span class="str">'Study'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[&lt;</span><span class="pln">sqlite3</span><span class="pun">.</span><span class="typ">Row</span><span class="pln"> object at </span><span class="lit">0x7f9f58460b70</span><span class="pun">&gt;,</span><span class="pln">
           </span><span class="pun">&lt;</span><span class="pln">sqlite3</span><span class="pun">.</span><span class="typ">Row</span><span class="pln"> object at </span><span class="lit">0x7f9f58460b50</span><span class="pun">&gt;],</span><span class="pln">
 </span><span class="str">'Work'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[&lt;</span><span class="pln">sqlite3</span><span class="pun">.</span><span class="typ">Row</span><span class="pln"> object at </span><span class="lit">0x7f9f58460890</span><span class="pun">&gt;]}</span></pre>

<p>
	وسيحتوي كل كائن <code>sqlite3.Row</code> على البيانات المُسترجعة باستخدام استعلام SQL في الدالة <code>()index</code> من جدول العناصر items، ولعرض هذه البيانات بوضوح أكثر، سنكتب برنامجًا يمر على محتويات الحاوية lists مستعرضًا كل قائمة وعناصرها.
</p>

<p>
	لكتابة هذا البرنامج، سنفتح ملفًا باسم list_example.py في المجلد flask_todo:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7111_55" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano list_example</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ثمّ سنكتب ضمنه الشيفرات التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7111_57" style="">
<span class="kwd">from</span><span class="pln"> itertools </span><span class="kwd">import</span><span class="pln"> groupby
</span><span class="kwd">from</span><span class="pln"> app </span><span class="kwd">import</span><span class="pln"> get_db_connection

conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">
todos </span><span class="pun">=</span><span class="pln"> conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'SELECT i.content, l.title FROM items i JOIN lists l \
                        ON i.list_id = l.id ORDER BY l.title;'</span><span class="pun">).</span><span class="pln">fetchall</span><span class="pun">()</span><span class="pln">

lists </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{}</span><span class="pln">

</span><span class="kwd">for</span><span class="pln"> k</span><span class="pun">,</span><span class="pln"> g </span><span class="kwd">in</span><span class="pln"> groupby</span><span class="pun">(</span><span class="pln">todos</span><span class="pun">,</span><span class="pln"> key</span><span class="pun">=</span><span class="kwd">lambda</span><span class="pln"> t</span><span class="pun">:</span><span class="pln"> t</span><span class="pun">[</span><span class="str">'title'</span><span class="pun">]):</span><span class="pln">
    lists</span><span class="pun">[</span><span class="pln">k</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> list</span><span class="pun">(</span><span class="pln">g</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">for</span><span class="pln"> list_</span><span class="pun">,</span><span class="pln"> items </span><span class="kwd">in</span><span class="pln"> lists</span><span class="pun">.</span><span class="pln">items</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">list_</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> item </span><span class="kwd">in</span><span class="pln"> items</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'    '</span><span class="pun">,</span><span class="pln"> item</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">])</span></pre>

<p>
	اِحفظ الملف وأغلقه.
</p>

<p>
	الشيفرة السابقة مشابهةٌ إلى حد كبير لتلك الموجودة في الدالة <code>()index</code>، وتبيّن حلقة for التكرارية فيها كيفيّة هيكلة حاوية القوائم، إذ تبدأ بالمرور على عناصر الحاوية، التي تمثّل القوائم لطباعة عناوين هذه القوائم والموجودة ضمن المتغير <code>list_variable</code>، ومن ثمّ المرور على كافّة عناصر المهام التابعة لكل قائمة وطباعة محتويات كل منها.
</p>

<p>
	وبتشغيل البرنامج <code>list_example.py</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7111_60" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ python list_example</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	نحصل على الخرج التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7111_62" style="">
<span class="typ">Home</span><span class="pln">
     </span><span class="typ">Buy</span><span class="pln"> fruit
     </span><span class="typ">Cook</span><span class="pln"> dinner
</span><span class="typ">Study</span><span class="pln">
     </span><span class="typ">Learn</span><span class="pln"> </span><span class="typ">Flask</span><span class="pln">
     </span><span class="typ">Learn</span><span class="pln"> </span><span class="typ">SQLite</span><span class="pln">
</span><span class="typ">Work</span><span class="pln">
     </span><span class="typ">Morning</span><span class="pln"> meeting</span></pre>

<p>
	الآن وبعد فهم كل جزئية في الدالة <code>()index</code>، سننشئ قالبًا رئيسي وسنعمل على إخراج الملف index.html باستخدام الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_7111_64" style="">
<span class="pln">return render_template('index.html', lists=lists)</span></pre>

<p>
	لذا، سننشئ مجلدًا للقوالب باسم templates ضمن المجلد flask_todo وسنفتح ضمنه ملفًا باسم base.html:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_7111_67" style="">
<span class="pln">(env)user@localhost:$ mkdir templates
(env)user@localhost:$ nano templates/base.html</span></pre>

<p>
	والآن سنكتب شيفرة <a href="https://academy.hsoub.com/programming/html/bootstrap/" rel="">بوتستراب Bootstap</a> التالية ضمن الملف base.html، وإن لم تكن قوالب HTML في فلاسك مألوفةً بالنسبة لك، يمكنك قراءة <a href="https://academy.hsoub.com/programming/python/flask/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%88%D9%8A%D8%A8-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D9%85%D9%86-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1622/" rel="">الخطوة الثالثة</a> من المقال كيفية إنشاء تطبيق ويب باستخدام إطار فلاسك في لغة بايثون 3.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_7111_69" style="">
<span class="dec">&lt;!doctype html&gt;</span><span class="pln">
</span><span class="tag">&lt;html</span><span class="pln"> </span><span class="atn">lang</span><span class="pun">=</span><span class="atv">"en"</span><span class="tag">&gt;</span><span class="pln">
  </span><span class="tag">&lt;head&gt;</span><span class="pln">
    </span><span class="com">&lt;!-- Required meta tags --&gt;</span><span class="pln">
    </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">"utf-8"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"viewport"</span><span class="pln"> </span><span class="atn">content</span><span class="pun">=</span><span class="atv">"width=device-width, initial-scale=1, shrink-to-fit=no"</span><span class="tag">&gt;</span><span class="pln">

    </span><span class="com">&lt;!-- Bootstrap CSS --&gt;</span><span class="pln">
    </span><span class="tag">&lt;link</span><span class="pln"> </span><span class="atn">rel</span><span class="pun">=</span><span class="atv">"stylesheet"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"</span><span class="pln"> </span><span class="atn">integrity</span><span class="pun">=</span><span class="atv">"sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"</span><span class="pln"> </span><span class="atn">crossorigin</span><span class="pun">=</span><span class="atv">"anonymous"</span><span class="tag">&gt;</span><span class="pln">

    </span><span class="tag">&lt;title&gt;</span><span class="pln">{% block title %} {% endblock %}</span><span class="tag">&lt;/title&gt;</span><span class="pln">
  </span><span class="tag">&lt;/head&gt;</span><span class="pln">
  </span><span class="tag">&lt;body&gt;</span><span class="pln">
    </span><span class="tag">&lt;nav</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar navbar-expand-md navbar-light bg-light"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar-brand"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('index')}}"</span><span class="tag">&gt;</span><span class="pln">FlaskTodo</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;button</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar-toggler"</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"button"</span><span class="pln"> </span><span class="atn">data-toggle</span><span class="pun">=</span><span class="atv">"collapse"</span><span class="pln"> </span><span class="atn">data-target</span><span class="pun">=</span><span class="atv">"#navbarNav"</span><span class="pln"> </span><span class="atn">aria-controls</span><span class="pun">=</span><span class="atv">"navbarNav"</span><span class="pln"> </span><span class="atn">aria-expanded</span><span class="pun">=</span><span class="atv">"false"</span><span class="pln"> </span><span class="atn">aria-label</span><span class="pun">=</span><span class="atv">"Toggle navigation"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;span</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar-toggler-icon"</span><span class="tag">&gt;&lt;/span&gt;</span><span class="pln">
        </span><span class="tag">&lt;/button&gt;</span><span class="pln">
        </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"collapse navbar-collapse"</span><span class="pln"> </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"navbarNav"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;ul</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar-nav"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;li</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"nav-item active"</span><span class="tag">&gt;</span><span class="pln">
                </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"nav-link"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"#"</span><span class="tag">&gt;</span><span class="pln">About</span><span class="tag">&lt;/a&gt;</span><span class="pln">
            </span><span class="tag">&lt;/li&gt;</span><span class="pln">
            </span><span class="tag">&lt;/ul&gt;</span><span class="pln">
        </span><span class="tag">&lt;/div&gt;</span><span class="pln">
    </span><span class="tag">&lt;/nav&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"container"</span><span class="tag">&gt;</span><span class="pln">
        {% block content %} {% endblock %}
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">

    </span><span class="com">&lt;!-- Optional JavaScript --&gt;</span><span class="pln">
    </span><span class="com">&lt;!-- jQuery first, then Popper.js, then Bootstrap JS --&gt;</span><span class="pln">
    </span><span class="tag">&lt;script</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"https://code.jquery.com/jquery-3.3.1.slim.min.js"</span><span class="pln"> </span><span class="atn">integrity</span><span class="pun">=</span><span class="atv">"sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"</span><span class="pln"> </span><span class="atn">crossorigin</span><span class="pun">=</span><span class="atv">"anonymous"</span><span class="tag">&gt;&lt;/script&gt;</span><span class="pln">
    </span><span class="tag">&lt;script</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"</span><span class="pln"> </span><span class="atn">integrity</span><span class="pun">=</span><span class="atv">"sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"</span><span class="pln"> </span><span class="atn">crossorigin</span><span class="pun">=</span><span class="atv">"anonymous"</span><span class="tag">&gt;&lt;/script&gt;</span><span class="pln">
    </span><span class="tag">&lt;script</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"</span><span class="pln"> </span><span class="atn">integrity</span><span class="pun">=</span><span class="atv">"sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"</span><span class="pln"> </span><span class="atn">crossorigin</span><span class="pun">=</span><span class="atv">"anonymous"</span><span class="tag">&gt;&lt;/script&gt;</span><span class="pln">
  </span><span class="tag">&lt;/body&gt;</span><span class="pln">
</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	اِحفظ الملف وأغلقه.
</p>

<p>
	الجزء الأكبر من الشيفرة السابقة هو تعليمات <a href="https://wiki.hsoub.com/HTML" rel="external">HTML</a> معيارية وشيفرات لازمة لعمل بوتستراب، إذ تزوّد الوسوم <code>&lt;meta&gt;</code> متصفح الويب بالمعلومات، في حين ينشئ الوسم
</p>
<link>
<p>
	ارتباطًا إلى ملفات <a href="https://wiki.hsoub.com/CSS" rel="external">CSS</a> الخاصة ببوتستراب، أما الوسم <code>&lt;script&gt;</code> فيضمِّن شيفرة جافا سكربت المسؤولة عن بعض ميزات بوتستراب الإضافية.
</p>

<p>
	الآن سننشئ الملف index.html الذي سيوسِّع extend شيفرة القالب الرئيسي base.html:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_7111_71" style="">
<span class="pln">(env)user@localhost:$ nano templates/index.html</span></pre>

<p>
	وسنكتب الشيفرة التالية في الملف index.html:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_7111_73" style="">
<span class="pln">{% extends 'base.html' %}

{% block content %}
    </span><span class="tag">&lt;h1&gt;</span><span class="pln">{% block title %} Welcome to FlaskTodo {% endblock %}</span><span class="tag">&lt;/h1&gt;</span><span class="pln">
    {% for list, items in lists.items() %}
        </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"card"</span><span class="pln"> </span><span class="atn">style</span><span class="pun">=</span><span class="atv">"</span><span class="pln">width</span><span class="pun">:</span><span class="pln"> </span><span class="lit">18rem</span><span class="pun">;</span><span class="pln"> margin</span><span class="pun">-</span><span class="pln">bottom</span><span class="pun">:</span><span class="pln"> </span><span class="lit">50px</span><span class="pun">;</span><span class="atv">"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"card-header"</span><span class="tag">&gt;</span><span class="pln">
                </span><span class="tag">&lt;h3&gt;</span><span class="pln">{{ list }}</span><span class="tag">&lt;/h3&gt;</span><span class="pln">
            </span><span class="tag">&lt;/div&gt;</span><span class="pln">
            </span><span class="tag">&lt;ul</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"list-group list-group-flush"</span><span class="tag">&gt;</span><span class="pln">
                {% for item in items %}
                    </span><span class="tag">&lt;li</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"list-group-item"</span><span class="tag">&gt;</span><span class="pln">{{ item['content'] }}</span><span class="tag">&lt;/li&gt;</span><span class="pln">
                {% endfor %}
            </span><span class="tag">&lt;/ul&gt;</span><span class="pln">
        </span><span class="tag">&lt;/div&gt;</span><span class="pln">
    {% endfor %}
{% endblock %}</span></pre>

<p>
	استخدمنا في الشيفرة السابقة، وباتبّاع نفس القواعد المشروحة في برنامج الملف <code>list_example.py</code>، حلقة for تكرارية للمرور على كل عنصر ضمن الحاوية lists، لعرض عنوان كل قائمة ضمن وسم من النوع <code>&lt;h3&gt;</code>، ومن ثمّ عرض كافّة عناصر المهام التابعة لكل قائمة ضمن وسم قوائم <code>&lt;li&gt;</code>.
</p>

<p>
	أمّا الآن فسنعيّن قيم متغيرات البيئة التي يحتاجها فلاسك، وسنشغّل البرنامج باستخدام الأوامر التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7111_76" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_APP</span><span class="pun">=</span><span class="pln">app
</span><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_ENV</span><span class="pun">=</span><span class="pln">development
</span><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ flask run</span></pre>

<p>
	وبمجرّد تشغيل خادم التطوير، يمكنك الذهاب إلى الرابط "/http://127.0.0.1:5000" باستخدام المتصفّح، فستظهر صفحة ويب تتضمّن العبارة "Welcome to FlaskTodo" إضافةً إلى عناصر قائمتك كما هو مبين في الصورة التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="102671" href="https://academy.hsoub.com/uploads/monthly_2022_07/first_image_welcome.png.7ede05f77516b63d19b83722682ede61.png" rel=""><img alt="first_image_welcome.png" class="ipsImage ipsImage_thumbnailed" data-fileid="102671" data-unique="31owm2pjn" src="https://academy.hsoub.com/uploads/monthly_2022_07/first_image_welcome.thumb.png.bac8638a9ed834784d70e4317e2c0f34.png" style="width: 600px; height: auto;"></a>
</p>

<p>
	يمكنك في هذه المرحلة إيقاف خادم التطوير باستخدام الاختصار "CTRL+C".
</p>

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

<h2>
	الخطوة الثالثة - إضافة عناصر مهام جديدة
</h2>

<p>
	سننشئ في هذه الخطوة وجهة route جديدة لإضافة عناصر المهام، كما سنُدخل البيانات في جداول قاعدة البيانات وسنعمل على ربط كل عنصر مع القائمة التي يتبع لها.
</p>

<p>
	بدايةً، سنفتح الملف app.py:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7111_79" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	وفي نهايته سننشئ وجهة جديدة ليعمل مثل دالة عرض فلاسك باسم <code>()create</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7111_83" style="">
<span class="pun">...</span><span class="pln">
</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/create/'</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=(</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">))</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> create</span><span class="pun">():</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">
    lists </span><span class="pun">=</span><span class="pln"> conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'SELECT title FROM lists;'</span><span class="pun">).</span><span class="pln">fetchall</span><span class="pun">()</span><span class="pln">

    conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'create.html'</span><span class="pun">,</span><span class="pln"> lists</span><span class="pun">=</span><span class="pln">lists</span><span class="pun">)</span></pre>

<p>
	اِحفظ الملف وأغلقه.
</p>

<p>
	وبما أنّنا سنستخدم هذه الوجهة لإدخال بيانات جديدة في قاعدة البيانات باستخدام نموذج ويب، فلا بدّ من تمكين كلا طريقتي الطلبات "GET" و "POST" باستخدام التعليمة التالية في المزخرف <code>()app.route</code>:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_7111_85" style="">
<span class="pln"> methods</span><span class="pun">=(</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">)</span></pre>

<p>
	يمكنك الآن فتح اتصال ضمن دالة فلاسك <code>()create</code> مع قاعدة البيانات وجلب كافّة عناوين القوائم المتوفّرة فيها، ليُغلق بعدها هذا الاتصال ويُخرَج قالب create.html يحتوي عناوين القوائم المُمرّرة.
</p>

<p>
	افتح إذًا ملف قالب جديد باسم create.html:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_7111_87" style="">
<span class="pln">(env)user@localhost:$ nano templates/create.html</span></pre>

<p>
	وأضِف شيفرة HTML التالية ضمنه:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_7111_90" style="">
<span class="pln">{% extends 'base.html' %}

{% block content %}
</span><span class="tag">&lt;h1&gt;</span><span class="pln">{% block title %} Create a New Item {% endblock %}</span><span class="tag">&lt;/h1&gt;</span><span class="pln">

</span><span class="tag">&lt;form</span><span class="pln"> </span><span class="atn">method</span><span class="pun">=</span><span class="atv">"post"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"form-group"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;label</span><span class="pln"> </span><span class="atn">for</span><span class="pun">=</span><span class="atv">"content"</span><span class="tag">&gt;</span><span class="pln">Content</span><span class="tag">&lt;/label&gt;</span><span class="pln">
        </span><span class="tag">&lt;input</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"text"</span><span class="pln"> </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"content"</span><span class="pln">
               </span><span class="atn">placeholder</span><span class="pun">=</span><span class="atv">"Todo content"</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"form-control"</span><span class="pln">
               </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"{{ request.form['content'] }}"</span><span class="tag">&gt;&lt;/input&gt;</span><span class="pln">
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">

    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"form-group"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;label</span><span class="pln"> </span><span class="atn">for</span><span class="pun">=</span><span class="atv">"list"</span><span class="tag">&gt;</span><span class="pln">List</span><span class="tag">&lt;/label&gt;</span><span class="pln">
        </span><span class="tag">&lt;select</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"form-control"</span><span class="pln"> </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"list"</span><span class="tag">&gt;</span><span class="pln">
            {% for list in lists %}
                {% if list['title'] == request.form['list'] %}
                    </span><span class="tag">&lt;option</span><span class="pln"> </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"{{ request.form['list'] }}"</span><span class="pln"> </span><span class="atn">selected</span><span class="tag">&gt;</span><span class="pln">
                        {{ request.form['list'] }}
                    </span><span class="tag">&lt;/option&gt;</span><span class="pln">
                {% else %}
                    </span><span class="tag">&lt;option</span><span class="pln"> </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"{{ list['title'] }}"</span><span class="tag">&gt;</span><span class="pln">
                        {{ list['title'] }}
                    </span><span class="tag">&lt;/option&gt;</span><span class="pln">
                {% endif %}
            {% endfor %}
        </span><span class="tag">&lt;/select&gt;</span><span class="pln">
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"form-group"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;button</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"submit"</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"btn btn-primary"</span><span class="tag">&gt;</span><span class="pln">Submit</span><span class="tag">&lt;/button&gt;</span><span class="pln">
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
</span><span class="tag">&lt;/form&gt;</span><span class="pln">
{% endblock %}</span></pre>

<p>
	اِحفظ الملف وأغلقه.
</p>

<p>
	استخدامنا الكائن <code>request.form</code> لتخزين البيانات المُدخلة في النموذج والوصول إليها في حال حدوث أي أخطاء لدى إرسال وتأكيد النموذج، مثل حالة ترك حقل محتوى عنصر المهام فارغًا.
</p>

<p>
	وباستخدام العنصر <code>&lt;select&gt;</code>، يحدث التنقّل ما بين القوائم المُسترجعة من قاعدة البيانات باستخدام الدالة <code>()create</code>، وصولًا إلى عنوان القائمة المطابق للعنوان المُخزّن في الكائن <code>request.form</code>؛ فإذا كان عنوان القائمة مساويًا لما هو مُخزَّن في الكائن <code>request.form</code>، فسيكون الخيار المحدد هو عنوان القائمة؛ وإلّا سيُعرض عنوان القائمة على نحوٍ اعتيادي ضمن وسم <code>&lt;option&gt;</code> دون أن يكون الخيار مُحدّدًا.
</p>

<p>
	الآن، سنشغّل تطبيق فلاسك من الطرفية terminal كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7111_92" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ flask run</span></pre>

<p>
	وبالذهاب إلى الرابط <code>http://127.0.0.1:5000/create</code> في المتصفح، سيظهر نموذج إنشاء عنصر مهام جديد، ولكن حتى هذه اللحظة لن يعمل هذا النموذج لعدم وجود أي شيفرة للتعامل مع الطلبات من النوع POST المُرسلة من المتصفح عند إرسال وتأكيد النموذج. لذا، سنوقف خادم التطوير حاليًا باستخدام الاختصار "CTRL+C"، ثمّ سنضيف للدالة <code>()create</code> الشيفرة اللازمة لعمل النموذج بصورةٍ صحيحة عبر التعامل مع الطلبات من نوع POST.
</p>

<p>
	افتح الملف app.py:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7111_94" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	وسنعدّل شيفرة الدالة <code>()create</code> لتصبح كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7111_96" style="">
<span class="pun">...</span><span class="pln">
</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/create/'</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=(</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">))</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> create</span><span class="pun">():</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">method </span><span class="pun">==</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">:</span><span class="pln">
        content </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">]</span><span class="pln">
        list_title </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'list'</span><span class="pun">]</span><span class="pln">

        </span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> content</span><span class="pun">:</span><span class="pln">
            flash</span><span class="pun">(</span><span class="str">'Content is required!'</span><span class="pun">)</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> redirect</span><span class="pun">(</span><span class="pln">url_for</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">))</span><span class="pln">

        list_id </span><span class="pun">=</span><span class="pln"> conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'SELECT id FROM lists WHERE title = (?);'</span><span class="pun">,</span><span class="pln">
                                 </span><span class="pun">(</span><span class="pln">list_title</span><span class="pun">,)).</span><span class="pln">fetchone</span><span class="pun">()[</span><span class="str">'id'</span><span class="pun">]</span><span class="pln">
        conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'INSERT INTO items (content, list_id) VALUES (?, ?)'</span><span class="pun">,</span><span class="pln">
                     </span><span class="pun">(</span><span class="pln">content</span><span class="pun">,</span><span class="pln"> list_id</span><span class="pun">))</span><span class="pln">
        conn</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">
        conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> redirect</span><span class="pun">(</span><span class="pln">url_for</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">))</span><span class="pln">

    lists </span><span class="pun">=</span><span class="pln"> conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'SELECT title FROM lists;'</span><span class="pun">).</span><span class="pln">fetchall</span><span class="pun">()</span><span class="pln">

    conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'create.html'</span><span class="pun">,</span><span class="pln"> lists</span><span class="pun">=</span><span class="pln">lists</span><span class="pun">)</span></pre>

<p>
	اِحفظ الملف وأغلقه.
</p>

<p>
	يتيح الشرط 'request.method == 'POST في الشيفرة السابقة إمكانية الحصول على كلٍ من محتوى عنصر المهام وعنوان قائمة المهام من البيانات المُدخلة في النموذج؛ ففي حال ترك حقل المحتوى فارغًا، سترسِل رسالةً للمستخدم باستخدام الدالة <code>()flash</code> ثمّ تُعيد التوجيه إلى الصفحة الرئيسية index؛ أما في حال كون حقل المحتوى غير فارغ، فستنفَّذ العبارة <code>SELECT</code> للحصول على معرّف القائمة الموافق لعنوان القائمة المُدخل في النموذج وحفظه في المتغير المُسمّى <code>list_id</code>، ثمّ يُنفّذ الأمر <code>INSERT INTO</code> لإدراج عنصر المهام الجديد في جدول العناصر items، إذ تربط قيمة المتغير <code>list_id</code> العنصر بالقائمة التي ينتمي إليها. وفي النهاية لا بُد من الالتزام بتأكيد العملية وإغلاق الاتصال مع قاعدة البيانات والعودة بالمستخدم إلى الصفحة الرئيسية index.
</p>

<p>
	سنضيف في الخطوة الأخيرة رابط إنشاء عنصر مهام جديد <code>create/</code> في شريط التصفح، وعرض الرسائل أدناه، لذا سنفتح الملف base.html:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_7111_98" style="">
<span class="pln">(env)user@localhost:$ nano templates/base.html</span></pre>

<p>
	الآن سنعدّل الملف السابق بإضافة عنصر تنقّل <code>&lt;li&gt;</code> مرتبط بدالة فلاسك <code>()create</code>، كما سنضيف حلقة for تكرارية أعلى كتلة المحتوى البرمجية لعرض الرسائل من دالة فلاسك <code>()get_flashed_messages</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_7111_100" style="">
<span class="tag">&lt;nav</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar navbar-expand-md navbar-light bg-light"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar-brand"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('index')}}"</span><span class="tag">&gt;</span><span class="pln">FlaskTodo</span><span class="tag">&lt;/a&gt;</span><span class="pln">
    </span><span class="tag">&lt;button</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar-toggler"</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"button"</span><span class="pln"> </span><span class="atn">data-toggle</span><span class="pun">=</span><span class="atv">"collapse"</span><span class="pln"> </span><span class="atn">data-target</span><span class="pun">=</span><span class="atv">"#navbarNav"</span><span class="pln"> </span><span class="atn">aria-controls</span><span class="pun">=</span><span class="atv">"navbarNav"</span><span class="pln"> </span><span class="atn">aria-expanded</span><span class="pun">=</span><span class="atv">"false"</span><span class="pln"> </span><span class="atn">aria-label</span><span class="pun">=</span><span class="atv">"Toggle navigation"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;span</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar-toggler-icon"</span><span class="tag">&gt;&lt;/span&gt;</span><span class="pln">
    </span><span class="tag">&lt;/button&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"collapse navbar-collapse"</span><span class="pln"> </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"navbarNav"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;ul</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar-nav"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;li</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"nav-item active"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"nav-link"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('create') }}"</span><span class="tag">&gt;</span><span class="pln">New</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;/li&gt;</span><span class="pln">

        </span><span class="tag">&lt;li</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"nav-item active"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"nav-link"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"#"</span><span class="tag">&gt;</span><span class="pln">About</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;/li&gt;</span><span class="pln">
        </span><span class="tag">&lt;/ul&gt;</span><span class="pln">
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
</span><span class="tag">&lt;/nav&gt;</span><span class="pln">
</span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"container"</span><span class="tag">&gt;</span><span class="pln">
    {% for message in get_flashed_messages() %}
        </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"alert alert-danger"</span><span class="tag">&gt;</span><span class="pln">{{ message }}</span><span class="tag">&lt;/div&gt;</span><span class="pln">
    {% endfor %}
    {% block content %} {% endblock %}
</span><span class="tag">&lt;/div&gt;</span></pre>

<p>
	اِحفظ الملف وأغلقه.
</p>

<p>
	الآن سيظهر الرابط <code>create/</code> في شريط التصفح لدى تشغيل تطبيق فلاسك:
</p>

<pre class="ipsCode">
(env)user@localhost:$ flask run
</pre>

<p>
	وسننتقل بالنقر عليه إلى صفحة إنشاء عنصر مهام جديد، فإذا أُرسِل النموذج دون محتوى، ستُعرض رسالةٌ مفادها بأنّ <strong>المحتوى مطلوب !Content is required</strong>، أمّا في حال ملء النموذج، فستظهر المهمّة الجديدة المُضافة في الصفحة الرئيسية index، وبذلك نكون قد أضفنا إمكانية إنشاء عناصر مهام جديدة وحفظها في قاعدة البيانات.
</p>

<p>
	يمكنك الحصول على <a href="https://github.com/do-community/flask-todo" rel="external nofollow">الشيفرة الكاملة للتطبيق</a>.
</p>

<h2>
	الخاتمة
</h2>

<p>
	ستحصل مع نهاية هذا المقال وبتطبيق الخطوات الواردة فيه على تطبيقٍ لإدارة قوائم وعناصر المهام، إذ تضم كل قائمة عدّة عناصر مهام، وكل عنصر مهام يتبع لقائمة واحدة، ما يشكّل علاقةً من النوع واحد-إلى-متعدّد.
</p>

<p>
	وبذلك تكون قد تعلمّت كيفية استخدام إطار العمل فلاسك وقواعد البيانات SQLite لإدارة عدّة جداول مترابطة في قاعدة البيانات باستخدام مفهوم المفتاح الأجنبي، كما تعلمّت كيفية جلب البيانات المترابطة من جدولين وعرضها في تطبيق ويب باستخدام استعلامات SQLite المركبّة.
</p>

<p>
	كما تعرفّت على الدالة <code>()groupby</code> المسؤولة عن فرز البيانات المستخلصة من قاعدة البيانات، وتعلمّت طريقة ربط سجلات قاعدة البيانات بالجداول التابعة لها.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-use-one-to-many-database-relationships-with-flask-and-sqlite" rel="external nofollow">How To Use One-to-Many Database Relationships with Flask and SQLite</a> لصاحبه Abdelhadi Dyouri.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%88%D9%8A%D8%A8-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D9%85%D9%86-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1622/" rel="">إنشاء تطبيق ويب باستخدام إطار عمل فلاسك Flask من لغة بايثون </a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D9%84%D8%B1%D9%91%D8%A8%D8%B7-%D8%A8%D9%8A%D9%86-%D8%AC%D8%AF%D9%88%D9%84%D9%8A-%D8%A7%D9%84%D9%85%D9%82%D8%A7%D9%84%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D9%85%D9%8F%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D9%8A%D9%86-%D8%A8%D8%B9%D9%84%D8%A7%D9%82%D8%A9-%D9%88%D8%A7%D8%AD%D8%AF-%D9%84%D9%84%D8%B9%D8%AF%D9%8A%D8%AF-one-to-many-relationship-r512/" rel="">الرّبط بين جدولي المقالات والمُستخدمين بعلاقة واحد للعديد One-to-Many Relationship</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/servers/databases/%D9%86%D9%85%D8%B0%D8%AC%D8%A9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%88%D8%A3%D9%86%D9%88%D8%A7%D8%B9%D9%87%D8%A7-%D9%81%D9%8A-%D8%B9%D9%85%D9%84%D9%8A%D8%A9-%D8%AA%D8%B5%D9%85%D9%8A%D9%85-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-r521/" rel="">نمذجة البيانات وأنواعها في عملية تصميم قواعد البيانات</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1623</guid><pubDate>Tue, 12 Jul 2022 16:07:00 +0000</pubDate></item><item><title>&#x625;&#x646;&#x634;&#x627;&#x621; &#x62A;&#x637;&#x628;&#x64A;&#x642; &#x648;&#x64A;&#x628; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x625;&#x637;&#x627;&#x631; &#x639;&#x645;&#x644; &#x641;&#x644;&#x627;&#x633;&#x643; Flask &#x645;&#x646; &#x644;&#x63A;&#x629; &#x628;&#x627;&#x64A;&#x62B;&#x648;&#x646;</title><link>https://academy.hsoub.com/programming/python/flask/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%88%D9%8A%D8%A8-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D9%85%D9%86-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1622/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_07/62c68b4523bed_-------Flask---3.png.6708ac42bc2d57c13ff145cb80f628a2.png" /></p>

<p>
	يُعد <a href="https://academy.hsoub.com/programming/python/flask/" rel="">فلاسك</a> إطار عمل للويب مبني بلغة <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D9%85%D8%B1%D8%AC%D8%B9-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%AA%D8%B9%D9%84%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r735/" rel="">بايثون</a>، ويتميز بكونه صغير الحجم وسهل المعالجة، ويوفّر أيضًا العديد من الأدوات والميزات التي من شأنها جعل إنشاء تطبيقات الويب في لغة بايثون أسهل، مانحًا المطورين المرونة في العمل، كما أنّه أبسط للاستخدام من قِبل المطورين المبتدئين نظرًا لإمكانية إنشاء تطبيق ويب كامل بسرعة باستخدام ملفٍ وحيدٍ مكتوب بلغة بايثون. إضافةً لما سبق، يتميز فلاسك بكونه قابلًا للتوسّع والوراثة دون أن يفرض أي بنية هرمية لطريقة عرض الملفات، كما أنّه لا يتطلب أي شفرات برمجية معقدّة استهلالية قبل البدء باستخدامه.
</p>

<p>
	سنتعرّف في هذا المقال أيضًا على إطار العمل <a href="https://academy.hsoub.com/programming/css/bootstrap/" rel="">بوتستراب bootstrap</a> لإضافة التنسيقات على التطبيق ليبدو أكثر جاذبية بصريًا، كما سيساعدنا على تمكين ميزة الصفحات المتوافقة مع المتصفحات في تطبيق الويب، ما يضمن عمله بصورةٍ جيدة في المتصفحات الخاصة بالجوال، دون كتابة شفرات HTML و CSS و JavaScript لتحقيق هذه الغاية، وبالتالي سيساعدك بوتستراب في صبِّ تركيزك على تعلُّم كيفية عمل فلاسك نفسه.
</p>

<p>
	يستخدم فلاسك محرّك القوالب <a href="https://academy.hsoub.com/tags/jinja2/" rel="">jinja</a> لبناء صفحات HTML ديناميكيًا باستخدام مفاهيم <a href="https://wiki.hsoub.com/Python" rel="external">بايثون </a>المألوفة، من متغيراتٍ وحلقاتٍ تكرارية وقوائم وغيرها، وبالتالي ستُستخدم هذه القوالب جزءًا من هذا المشروع.
</p>

<p>
	ستتمكّن في هذا المقال من بناء مدونة ويب صغيرة باستخدام إطار العمل فلاسك مع قاعدة بيانات <a href="https://academy.hsoub.com/devops/servers/databases/%D9%83%D9%8A%D9%81-%D9%88%D9%85%D8%AA%D9%89-%D9%86%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-sqlite-r111/" rel="">SQLite</a> مُستخدمًا الاصدار الثالث من بايثون، إذ سيتمكّن مستخدمو هذا التطبيق من عرض جميع التدوينات المنشورة في قاعدة بياناتك، والضغط على عنوان التدوينة لعرض محتوياتها، مع إمكانية إضافتهم لتدوينات جديدة إلى قاعدة البيانات، أو تعديل أو حذف تدوينةٍ موجودةٍ أصلًا.
</p>

<h2>
	مستلزمات العمل
</h2>

<p>
	قبل المتابعة في هذا المقال لابدّ من:
</p>

<ul>
<li>
		توفُّر <a href="https://academy.hsoub.com/programming/python/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%AA%D9%87%D8%A7-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r714/" rel="">بيئة برمجة بايثون 3 محلية</a>، مثبّتة على حاسوبك، وسنفترض في مقالنا أن اسم مجلد المشروع هو flask_blog.
	</li>
	<li>
		فهم مبادئ بايثون 3 مثل أنماط البيانات والجمل الشرطية وحلقة for التكرارية والدوال، وغيرها من المفاهيم المشابهة في <a href="https://academy.hsoub.com/tags/%D8%AF%D9%84%D9%8A%D9%84%20%D8%AA%D8%B9%D9%84%D9%85%20%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86/" rel="">بايثون 3</a>.
	</li>
</ul>
<h2>
	الخطوة الأولى – تثبيت فلاسك
</h2>

<p>
	في هذه الخطوة سنفعّل بيئة بايثون ونثبّت فلاسك باستخدام أمر تثبيت الحزم <code>pip</code>، وفي حال كون بيئة البرمجة غير مفعّلة بعد، سنستخدم الأمر التالي لتفعيلها بعد التأكّد من كون موجه الأوامر يشير إلى مسار مجلد المشروع flask_blog:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_6" style="">
<span class="pln">$ source env</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">activate</span></pre>

<p>
	وبمجرّد تفعيل بيئة البرمجة، سيُظهِر موجّه الأوامر البادئة <code>env</code> والتي تظهر على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_8" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$</span></pre>

<p>
	تشير هذه البادئة إلى أن بيئة العمل <code>env</code> فعّالةٌ حاليًا، والتي قد تكون باسمٍ آخر لديك وذلك حسب الاسم الذي اخترته لها خلال إنشائها.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<div class="ipsQuote_contents ipsClearfix">
		<p>
			<strong>ملاحظة:</strong> يمكنك استخدام نظام إدارة الإصدارات <a href="https://academy.hsoub.com/programming/workflow/git/" rel="">Git</a> لإدارة ومتابعة عملية تطوير المشروع بفعالية، وفي حال استخدامك له، فمن الجيد أن تستثني المجلد env الذي أنشأته للتو في ملف "gitignore." لتفادي تتبّع الملفات التي ليس لها صلةٌ بالمشروع.
		</p>
	</div>
</blockquote>

<p>
	الآن سنثبّت حزم بايثون وسنعزل شيفرة المشروع بعيدًا عن نظام بايثون المُثبت أساسًا باستخدام أوامر <code>pip</code> و <code>python</code>.
</p>

<p>
	ولتثبيت فلاسك، سنشغّل الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_10" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ pip install flask</span></pre>

<p>
	وعند انتهاء التثبيت، سنشغّل الأمر التالي للتحقُّق من إتمام العملية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_12" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ python </span><span class="pun">-</span><span class="pln">c </span><span class="str">"import flask; print(flask.__version__)"</span></pre>

<p>
	وبذلك نكون قد استخدمنا <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%A7-%D9%87%D9%88-%D8%B3%D8%B7%D8%B1-%D8%A7%D9%84%D8%A3%D9%88%D8%A7%D9%85%D8%B1-%D8%9F-r353/" rel="">واجهة سطر أوامر</a> بايثون ذات الخيار <code>c-</code> لتنفّيذ شيفرة بايثون، ومن ثم استوردنا مكتبة فلاسك باستخدام الأمر <code>import flask</code>، ثمّ طبعنا إصدار فلاسك المُثبت باستخدام المتغير <code>flask.__version__ variable</code>.
</p>

<p>
	وسيتضمّن الخرج رقم الإصدار على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_14" style="">
<span class="lit">1.1</span><span class="pun">.</span><span class="lit">2</span></pre>

<p>
	وبذلك نكون قد أنشأنا مجلد المشروع وبيئة العمل الافتراضية، كما أنّنا ثبَّتنا فلاسك، وبذلك أصبحنا مُستعدين لإنشاء تطبيق أساسي.
</p>

<h2>
	الخطوة الثانية – كيفية إنشاء تطبيق أساسي
</h2>

<p>
	الآن، وبعد الانتهاء من تثبيت بيئة العمل، سنبدأ باستخدام فلاسك، إذ سننشئ في هذه الخطوة تطبيق ويب بسيط ضمن ملف بايثون وتشغيله ليبدأ الخادم بالعمل مُظهرًا بعض المعلومات في المتصفح.
</p>

<p>
	الآن سنفتح الملف hello.py الموجود في مجلد flask_blog للتعديل عليه باستخدام محرّر النصوص نانو nano، أو أي محرّر آخر تفضِّله:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_16" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano hello</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	سيشكّل ملف hello.py مثالًا مبسّطًا لكيفية التعامل مع طلبات HTTP، إذ سنستورد في هذا الملف كائن فلاسك ونكتب تابعًا لتوليد الاستجابة لطلبات بروتوكول HTTP، ولتحقيق ذلك نكتب الشيفرة التالية في الملف hello.py:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_19" style="">
<span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pln">
app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> hello</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="str">'Hello, World!'</span></pre>

<p>
	استوردنا في الجزء السابق من الشيفرة كائن فلاسك من حزمة فلاسك، ثم استخدمناه لإنشاء نسخةٍ فعليةٍ موجودةٍ في الذاكرة لتطبيق فلاسك Flask application instance باسم <code>app</code>، وليست مجرد كائن برمجي، ومن ثمّ مررنا المتغير الخاص <code>__name__</code>، الذي سيخزّن اسم وحدة بايثون الحالية ليُعلم تطبيق فلاسك بمكان وجود هذه الوحدة، إذ لا بُدّ من إجراء هذه الخطوة كون فلاسك يهيّئ بعض المسارات اللازمة في الخلفية. وبمجرد إنشاء هذا التطبيق <code>app</code>، يمكنك استخدامه في معالجة طلبات الويب القادمة وإرسال الردود إلى المُستخدم.
</p>

<p>
	يكون المزخرف <code>app.route@</code> مسؤولًا عن تعديل دوال بايثون المألوفة لتصبح دوالًا عاملةً في فلاسك، والتي تحوّل القيمة المعادة من قِبل التابع إلى استجابةٍ من نوع HTTP تُعرض لدى عميل HTTP الذي قد يكون متصفحًا مثلًا؛ فبمجرد تمرير القيمة <code>/</code> وسيطًا للتابع <code>app.route@</code>، سينشئ الردود على طلبات الويب الواردة إلى الرابط <code>/</code>، والذي يمثّل الرابط الرئيسي في التطبيق، وبذلك سيعيد التابع <code>()hello</code> السلسلة النصية '!Hello, World' ردًا على الطلب.
</p>

<p>
	الآن، اِحفظ الملف وأغلقه. ولتشغيل تطبيق الويب الذي أنشأناه، لا بُدّ من إرشاد فلاسك إلى موقعه (في حالتنا الملف ذو الاسم hello.py) باستخدام متغير بيئة فلاسك <code>FLASK_APP</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_21" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_APP</span><span class="pun">=</span><span class="pln">hello</span></pre>

<p>
	ومن ثم تشغيله بوضع التطوير باستخدام متغير بيئة فلاسك <code>Flask_ENV</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_23" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_ENV</span><span class="pun">=</span><span class="pln">development</span></pre>

<p>
	وفي النهاية، سنشغّل التطبيق باستخدام الأمر <code>flask run</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_25" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ flask run</span></pre>

<p>
	وبمجرّد تشغيل التطبيق، سيكون الخرج مشابهًا لما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_27" style="">
<span class="pun">*</span><span class="pln"> </span><span class="typ">Serving</span><span class="pln"> </span><span class="typ">Flask</span><span class="pln"> app </span><span class="str">"hello"</span><span class="pln"> </span><span class="pun">(</span><span class="pln">lazy loading</span><span class="pun">)</span><span class="pln">
 </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Environment</span><span class="pun">:</span><span class="pln"> development
 </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Debug</span><span class="pln"> mode</span><span class="pun">:</span><span class="pln"> on
 </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Running</span><span class="pln"> on http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Press</span><span class="pln"> CTRL</span><span class="pun">+</span><span class="pln">C to quit</span><span class="pun">)</span><span class="pln">
 </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Restarting</span><span class="pln"> </span><span class="kwd">with</span><span class="pln"> stat
 </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Debugger</span><span class="pln"> </span><span class="kwd">is</span><span class="pln"> active</span><span class="pun">!</span><span class="pln">
 </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Debugger</span><span class="pln"> PIN</span><span class="pun">:</span><span class="pln"> </span><span class="lit">813</span><span class="pun">-</span><span class="lit">894</span><span class="pun">-</span><span class="lit">335</span></pre>

<p>
	يحتوي الخرج السابق على عدة معلومات، مثل:
</p>

<ul>
<li>
		اسم التطبيق المُشغَّل.
	</li>
	<li>
		بيئة التشغيل الحالية التي يعمل عليها التطبيق.
	</li>
	<li>
		عبارة <code>Debug mode:on</code> التي تشير إلى أن مصحّح أخطاء فلاسك قيد التشغيل، وهو ذو فوائد عديدة أثناء عملية التطوير كونه يقدم رسائل خطأ مفصّلة عندما يحدث أي خلل، ما يجعل عملية تنقيح الأخطاء أسهل وأيسر.
	</li>
	<li>
		التطبيق يعمل على الحاسب المحلي وذلك على الرابط <code>/http://127.0.0.1:5000</code>، إذ أن <code>127.0.0.1</code> هو عنوان IP الذي يمثِّل الخادم المحلي localhost على حاسبك، و <code>5000:</code> هو رقم المنفذ.
	</li>
</ul>
<p>
	افتح المتصفح واكتب عنوان URL التالي "/http://127.0.0.1:5000"، ستظهر عبارة <code>!Hello, World</code> استجابةً لهذا العنوان، وهذا ما يؤكد أن التطبيق يعمل بنجاح.
</p>

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

<p>
	يمكنك في هذه المرحلة الإبقاء على خادم التطوير قيد التشغيل في الطرفية terminal الخاصة به، ومن ثم افتح نافذة طرفية جديدة وغيّر المسار فيها إلى مسار مجلد المشروع حيث يتواجد ملف hello.py، ثم فعّل البيئة الافتراضية (السبب مبيّنٌ في الملاحظة أدناه)، وهيّئ متغيرات البيئة <code>FLASK_ENV</code> و <code>FLASP_APP</code>، ومن ثم تابع الخطوات التالية. (ذُكرت هذه الأوامر سابقًا في هذه الخطوة).
</p>

<p>
	<strong>ملاحظة:</strong> من الضروري تفعيل البيئة الافتراضية لدى فتح طرفية جديدة، ولا بدّ من إعداد متغيرات البيئة <code>FLASK_ENV</code> و <code>FLASK_APP</code>.
</p>

<p>
	لا يمكن تشغيل تطبيق فلاسك آخر باستخدام الأمر <code>flask run</code> نفسه خلال فترة عمل خادم تطوير تطبيقات فلاسك، كونه يستخدم المنفذ رقم 5000 افتراضيًا، وحالما يُحجَز هذا المنفذ يصبح غير متاحًا لتشغيل أي تطبيقٍ آخر، وفي حال فعلت ذلك ستظهر رسالة خطأ مشابهةٍ لما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_29" style="">
<span class="typ">OSError</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="typ">Errno</span><span class="pln"> </span><span class="lit">98</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Address</span><span class="pln"> already </span><span class="kwd">in</span><span class="pln"> use</span></pre>

<p>
	ويمكن حل لهذه المشكلة، إمّا بإيقاف الخادم العامل حاليًا عن طريق الضغط على "CTRL+C" ومن ثم تنفيذ الأمر <code>flask run</code> مجدّدًا، أو في حال رغبتك بتشغيل كلا التطبيقين في نفس الوقت، فمن الممكن تمرير رقم منفذٍ مختلف باستخدام الوسيط <code>p-</code>. سنستخدم الأمر التالي لتشغيل تطبيقٍ آخر يستخدم المنفذ <code>5001</code> مثالًا حول هذه الطريقة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_32" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ flask run </span><span class="pun">-</span><span class="pln">p </span><span class="lit">5001</span></pre>

<p>
	وبذلك أصبح لديك تطبيق ويب صغير باستخدام فلاسك، وبعد أن شغّلت وعرضت معلومات في متصفح الويب، سنستخدم فيما يلي ملفات HTML في هذا التطبيق.
</p>

<h2>
	الخطوة الثالثة – استخدام قوالب HTML
</h2>

<p>
	لا يعرض تطبيقك حاليًا سوى رسالةٍ صغيرة دون أي استخدامٍ للغة HTML، إذ تستخدم تطبيقات الويب عادةً HTML بصورةٍ رئيسية لعرض المعلومات لزوار الموقع أو التطبيق، لذا سنعمل فيما يلي على دمج ملفات HTML في التطبيق، والتي يمكن عرضها في متصفح الويب.
</p>

<p>
	يقدّم فلاسك تابعًا مساعدًا يُسمّى <code>()render_template</code>، يمكنّنا من استخدام [محرّك القوالب جينجا <a href="http://jinja.pocoo.org/" rel="external nofollow">jinja</a>، وهذا يجعل إدارة HTML أسهل عن طريق كتابة شيفرة HTML إلى جانب الشيفرة الوظيفية المعبّرة عن غرض التطبيق وذلك في ملفات ذات امتداد "html."، وستُستخدم ملفات HTML هذه (أي القوالب) لبناء كل صفحات التطبيق، مثل الصفحة الرئيسية التي تستعرض التدوينات الحالية في المدونة، والصفحة الخاصة بكل تدوينة، والصفحة التي يتمكن المستخدم من خلالها إضافة تدوينة جديدة، وغيرها.
</p>

<p>
	في هذه الخطوة، سننشئ تطبيق فلاسك رئيسي في ملفٍ جديد. بدايةً استخدم محرّر النصوص نانو nano أو أي محرِّر تفضله لإنشاء الملف app.py والتعديل عليه ضمن مجلد "flask_blog"، إذ سيحتوي هذا الملف على كل الشيفرة المُستخدمة لإنشاء تطبيق التدوينات:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_34" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	سنستورد في هذا الملف الجديد كائن فلاسك لإنشاء نسخة تطبيق فلاسك كما فعلنا سابقًا، كما سنستورد التابع المساعد <code>()render_template</code>، الذي سيمكننّا من إخراج ملفات قوالب HTML الموجودة في المجلد templates الذي سننشئه بعد قليل. سيحتوي هذا الملف الجديد على تابعٍ وحيدٍ عامل في فلاسك مسؤول عن التعامل مع الطلبات الموجهة إلى الرابط <code>/</code>. الآن، أضِف مايلي إلى الملف الجديد app.py:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_36" style="">
<span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> render_template
app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln">
</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> index</span><span class="pun">():</span><span class="pln">
 </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'index.html'</span><span class="pun">)</span></pre>

<p>
	سيعيد التابع <code>()index</code> العامل في فلاسك نتيجة استدعاء التابع <code>()render_template</code> عن طريق تمرير الوسيط <code>index.html</code>، وهذا يخبر التابع <code>()render_template</code> بالبحث عن ملف باسم <code>index.html</code> في مجلد القوالب المُسمّى templates.
</p>

<p>
	المجلد templates والملف <code>index.html</code> غير موجودين فعليًا حتى الآن، وستحصل على خطأ في حال شغّلت التطبيق في هذه اللحظة، ولكن ننصحك بتشغيل التطبيق الآن بهدف التعرُّف على هذا الخطأ الشائع، ومن ثم صحّحه عن طريق إنشاء الملف والمجلد اللازمين فعليًا.
</p>

<p>
	الآن، احفظ الملف وأغلقه، ثمّ أوقف خادم التطوير في الطرفية الأخرى التي تشغِّل التطبيق hello عن طريق الضغط على "CTRL+C"؛ ولكن قبل تشغيل التطبيق تأكد من تحديد قيمة متغير البيئة <code>FLASK_APP</code> على نحوٍ صحيح، نظرًا لكونك لم تعد تستخدم تطبيق hello، وذلك على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_38" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ export FLASK_APP</span><span class="pun">=</span><span class="pln">app
</span><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ flask run</span></pre>

<p>
	سيؤدي فتح العنوان "http://127.0.0.1:5000" في المتصفح إلى ظهور صفحة تنقيح الأخطاء التي ستُعلمك بعدم العثور على القالب "index.html"، ويُميَّز سطر الشيفرة الرئيسي المسؤول عن ظهور هذا الخطأ بلونٍ واضح، وهو في هذه الحالة السطر:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_40" style="">
<span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'index.html'</span><span class="pun">)</span></pre>

<p>
	وإذا نقرت عليه، سيكشف منقّح الأخطاء مزيدًا من الأسطر البرمجية، وبالتالي سيكون لديك مزيدٌ من المعلومات ذات الصلة المساعدة على حل المشكلة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="102663" href="https://academy.hsoub.com/uploads/monthly_2022_07/first_img_step3a.png.58aef2bf67c888ca30ff913fa56e4cf0.png" rel=""><img alt="first_img_step3a.png" class="ipsImage ipsImage_thumbnailed" data-fileid="102663" data-unique="ek3y44psp" src="https://academy.hsoub.com/uploads/monthly_2022_07/first_img_step3a.thumb.png.2f66228e96d14fce0c5244346ffe5dbc.png" style="width: 680px; height: auto;"></a>
</p>

<p>
	كي نصحّح هذا الخطأ، سننشئ مجلدًا باسم templates ضمن المجلد flask_blog، ومن ثم سنفتح ملف index.html بداخله للتحرير كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_44" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ mkdir templates
</span><span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">index</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	ثم سنضيف شيفرة HTML التالية في الملف index.html:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_46" style="">
<span class="pun">&lt;!</span><span class="pln">DOCTYPE html</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">html lang</span><span class="pun">=</span><span class="str">"en"</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">head</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">meta charset</span><span class="pun">=</span><span class="str">"UTF-8"</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">title</span><span class="pun">&gt;</span><span class="typ">FlaskBlog</span><span class="pun">&lt;/</span><span class="pln">title</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">head</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">body</span><span class="pun">&gt;</span><span class="pln">
   </span><span class="pun">&lt;</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="typ">Welcome</span><span class="pln"> to </span><span class="typ">FlaskBlog</span><span class="pun">&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">body</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">html</span><span class="pun">&gt;</span></pre>

<p>
	احفظ الملف ثم استخدم المتصفح للانتقال إلى الرابط "/http://127.0.0.1:5000" مجددًّا، أو حدِّث الصفحة، وفي هذه المرة سيعرض المتصفح النص "Welcome to FlaskBlog" وذلك في صيغة وسم <code>&lt;h1&gt;</code>.
</p>

<p>
	تمتلك تطبيقات فلاسك عادةً مجلدًا للملفات الثابتة يسمّى static، إضافةً إلى مجلد القوالب templates، إذ يستضيف هذا المجلد ملفات، مثل CSS و JavaScript والصور التي يستخدمها التطبيق.
</p>

<p>
	يمكنك إنشاء ملف تنسيقات style.css لإضافة CSS إلى التطبيق من خلال البدء بإنشاء مجلدٍ باسم static داخل مجلد flask_blog الرئيسي كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_49" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ mkdir static</span></pre>

<p>
	ثم أضِف مجلدًا آخر باسم css داخل المجلد static لتضع به الملفات ذات اللاحقة "css."، إذ أن الهدف من هذا الإجراء هو تنظيم الملفات الثابتة في مجلدات مخصصّة حسب نوعها؛ فمثلًا نضع ملفات <a href="https://wiki.hsoub.com/JavaScript" rel="external">JavaScript</a> في مجلد باسم js، والصور في مجلد باسم images أو img، وهكذا. ستنشئ التعليمة التالية مجلدًا باسم css داخل المجلد static:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_51" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ mkdir static</span><span class="pun">/</span><span class="pln">css</span></pre>

<p>
	اِفتح الآن الملف style.css الموجود داخل المجلد css بهدف تحريره:
</p>

<pre class="ipsCode">
(env)user@localhost:$ nano static/css/style.css
</pre>

<p>
	اكتب <a href="https://wiki.hsoub.com/CSS#.D9.82.D9.88.D8.A7.D8.B9.D8.AF_.40" rel="external">قاعدة CSS</a> التالية ضمن الملف style.css:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_53" style="">
<span class="pln">h1 </span><span class="pun">{</span><span class="pln">
    border</span><span class="pun">:</span><span class="pln"> </span><span class="lit">2px</span><span class="pln"> </span><span class="com">#eee solid;</span><span class="pln">
    color</span><span class="pun">:</span><span class="pln"> brown</span><span class="pun">;</span><span class="pln">
    text</span><span class="pun">-</span><span class="pln">align</span><span class="pun">:</span><span class="pln"> center</span><span class="pun">;</span><span class="pln">
    padding</span><span class="pun">:</span><span class="pln"> </span><span class="lit">10px</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	شيفرة CSS هذه مسؤولةٌ عن إضافة حدود لكافة الوسوم من نوع <code>&lt;h1&gt;</code>، كما أنّها تغيِّر لون الخط فيها إلى البني، وتجعل محاذاة النص إلى الوسط، وتضيف هوامشًا داخليةً ضيقةً إليها.
</p>

<p>
	احفظ الآن الملف وأغلقه، ثم افتح ملف القالب index.html بهدف تحريره:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_55" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">index</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	سنضيف الآن رابطًا في قسم الترويسة <a href="https://wiki.hsoub.com/HTML/head" rel="external"><code>&lt;head&gt;</code></a> من القالب index.html للوصول إلى الملف style.css على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_57" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">head</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">meta charset</span><span class="pun">=</span><span class="str">"UTF-8"</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">link rel</span><span class="pun">=</span><span class="str">"stylesheet"</span><span class="pln"> href</span><span class="pun">=</span><span class="str">"{{ url_for('static', filename= 'css/style.css') }}"</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">title</span><span class="pun">&gt;</span><span class="typ">FlaskBlog</span><span class="pun">&lt;/</span><span class="pln">title</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">head</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span></pre>

<p>
	استخدمنا التابع المساعد <code>url_for()‎</code> لإنشاء المسار المناسب للملف، إذ يحدد الوسيط الأول أنّنا ننشئ ارتباطًا إلى ملفٍ ساكن، والوسيط الثاني هو مسار الملف هذا داخل مجلد الملفات الساكنة.
</p>

<p>
	اِحفظ الملف وأغلقه، وحالما تحدّث الصفحة index في التطبيق، ستلاحظ أن النص "Welcome to FlaskBlog" أصبح بلونٍ بني، ومحاذاته إلى الوسط، ومحاطًا بحدود.
</p>

<p>
	يمكنك استخدام لغة <a href="https://wiki.hsoub.com/CSS" rel="external">CSS</a> لتنسيق مظهر التطبيق وجعله أكثر جاذبية بصريًا باستخدام التصميم الذي تريد؛ ولكن إن لم تكن مصمم ويب، أو لم يكن لديك الخبرة الكافية في التعامل مع CSS، فبإمكانك استخدام حزمة بوتستراب Bootstrap، التي تزودك بمكونات سهلة الاستخدام لتنسيق مظهر التطبيق، إذ سنستخدم في تطبيقنا هذا بوتستراب.
</p>

<p>
	وفي حال أردت إنشاء قوالب HTML أُخرى، فلن تضطر لإعادة كتابة الشيفرة التي كتبتها سابقًا في القالب index.html، فمن الممكن تفادي التكرار غير الضروري بالاستفادة من ملف القالب الرئيسي base template، والتي <a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%8A%D8%B1%D8%A7%D8%AF-%D9%81%D9%8A-%D9%85%D9%8F%D8%AD%D8%B1%D9%91%D9%83-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-jinja-r497/" rel="">سترث</a> كافّة ملفات HTML الأُخرى شيفرته.
</p>

<p>
	ولكي ننشئ قالب رئيسي، سننشئ بدايةً ملفًا باسم base.html في مجلد القوالب templates:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_59" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano templates</span><span class="pun">/</span><span class="pln">base</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	ثم سنكتب الشيفرة التالية في القالب base.html:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_61" style="">
<span class="pun">&lt;!</span><span class="pln">doctype html</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">html lang</span><span class="pun">=</span><span class="str">"en"</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="pun">&lt;</span><span class="pln">head</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;!--</span><span class="pln"> </span><span class="typ">Required</span><span class="pln"> meta tags </span><span class="pun">--&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">meta charset</span><span class="pun">=</span><span class="str">"utf-8"</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">meta name</span><span class="pun">=</span><span class="str">"viewport"</span><span class="pln"> content</span><span class="pun">=</span><span class="str">"width=device-width, initial-scale=1, shrink-to-fit=no"</span><span class="pun">&gt;</span><span class="pln">

    </span><span class="pun">&lt;!--</span><span class="pln"> </span><span class="typ">Bootstrap</span><span class="pln"> CSS </span><span class="pun">--&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">link rel</span><span class="pun">=</span><span class="str">"stylesheet"</span><span class="pln"> href</span><span class="pun">=</span><span class="str">"https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"</span><span class="pln"> integrity</span><span class="pun">=</span><span class="str">"sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"</span><span class="pln"> crossorigin</span><span class="pun">=</span><span class="str">"anonymous"</span><span class="pun">&gt;</span><span class="pln">

    </span><span class="pun">&lt;</span><span class="pln">title</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}&lt;/</span><span class="pln">title</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="pun">&lt;/</span><span class="pln">head</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="pun">&lt;</span><span class="pln">body</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">nav </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"navbar navbar-expand-md navbar-light bg-light"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">a </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"navbar-brand"</span><span class="pln"> href</span><span class="pun">=</span><span class="str">"{{ url_for('index')}}"</span><span class="pun">&gt;</span><span class="typ">FlaskBlog</span><span class="pun">&lt;/</span><span class="pln">a</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">button </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"navbar-toggler"</span><span class="pln"> type</span><span class="pun">=</span><span class="str">"button"</span><span class="pln"> data</span><span class="pun">-</span><span class="pln">toggle</span><span class="pun">=</span><span class="str">"collapse"</span><span class="pln"> data</span><span class="pun">-</span><span class="pln">target</span><span class="pun">=</span><span class="str">"#navbarNav"</span><span class="pln"> aria</span><span class="pun">-</span><span class="pln">controls</span><span class="pun">=</span><span class="str">"navbarNav"</span><span class="pln"> aria</span><span class="pun">-</span><span class="pln">expanded</span><span class="pun">=</span><span class="str">"false"</span><span class="pln"> aria</span><span class="pun">-</span><span class="pln">label</span><span class="pun">=</span><span class="str">"Toggle navigation"</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">span </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"navbar-toggler-icon"</span><span class="pun">&gt;&lt;/</span><span class="pln">span</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">button</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"collapse navbar-collapse"</span><span class="pln"> id</span><span class="pun">=</span><span class="str">"navbarNav"</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">ul </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"navbar-nav"</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;</span><span class="pln">li </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"nav-item active"</span><span class="pun">&gt;</span><span class="pln">
                </span><span class="pun">&lt;</span><span class="pln">a </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"nav-link"</span><span class="pln"> href</span><span class="pun">=</span><span class="str">"#"</span><span class="pun">&gt;</span><span class="typ">About</span><span class="pun">&lt;/</span><span class="pln">a</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">li</span><span class="pun">&gt;</span><span class="pln">
            </span><span class="pun">&lt;/</span><span class="pln">ul</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">nav</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"container"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">

    </span><span class="pun">&lt;!--</span><span class="pln"> </span><span class="typ">Optional</span><span class="pln"> </span><span class="typ">JavaScript</span><span class="pln"> </span><span class="pun">--&gt;</span><span class="pln">
    </span><span class="pun">&lt;!--</span><span class="pln"> jQuery first</span><span class="pun">,</span><span class="pln"> then </span><span class="typ">Popper</span><span class="pun">.</span><span class="pln">js</span><span class="pun">,</span><span class="pln"> then </span><span class="typ">Bootstrap</span><span class="pln"> JS </span><span class="pun">--&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">script src</span><span class="pun">=</span><span class="str">"https://code.jquery.com/jquery-3.3.1.slim.min.js"</span><span class="pln"> integrity</span><span class="pun">=</span><span class="str">"sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"</span><span class="pln"> crossorigin</span><span class="pun">=</span><span class="str">"anonymous"</span><span class="pun">&gt;&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">script src</span><span class="pun">=</span><span class="str">"https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"</span><span class="pln"> integrity</span><span class="pun">=</span><span class="str">"sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"</span><span class="pln"> crossorigin</span><span class="pun">=</span><span class="str">"anonymous"</span><span class="pun">&gt;&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">script src</span><span class="pun">=</span><span class="str">"https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"</span><span class="pln"> integrity</span><span class="pun">=</span><span class="str">"sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"</span><span class="pln"> crossorigin</span><span class="pun">=</span><span class="str">"anonymous"</span><span class="pun">&gt;&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="pun">&lt;/</span><span class="pln">body</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">html</span><span class="pun">&gt;</span></pre>

<p>
	اِحفظ الملف وأغلقه حالما تنتهي من تحريره.
</p>

<p>
	إنّ الجزء الأكبر من الشيفرة السابقة هو تعليمات <a href="https://wiki.hsoub.com/HTML" rel="external">HTML</a> معيارية وشيفرات لازمة لعمل بوتستراب، إذ تزوّد الوسوم <code>&lt;meta&gt;</code> متصفح الويب بالمعلومات، في حين ينشئ الوسم <code>&lt;link&gt;</code> ارتباطًا إلى ملفات CSS الخاصة ببوتستراب، ويضمِّن الوسم <code>&lt;script&gt;</code> شيفرة جافا سكربت مسؤولة عن بعض ميزات بوتستراب الإضافية.
</p>

<p>
	أمّا الأجزاء من الشيفرة الموضحّة فيما يلي فهي خاصةٌ بمحرك القوالب جينجا:
</p>

<ul>
<li>
		<code>{% block title %} {% endblock %}</code>: كتلة برمجية تحجز مكانًا لعنوان الصفحة، والذي سنستخدمه في تحديد العنوان الخاص بكل صفحة في التطبيق دون الحاجة إلى إعادة كتابة قسم الترويسة <code>&lt;head&gt;</code> كاملًا في كل مرةٍ من أجل كل صفحة.
	</li>
	<li>
		<code>{{ url_for('index')‎ }}</code>: استدعاءٌ لدالة تعيد عنوان URL إلى دالة عرض <code>index()‎</code>، والتي نمرر لها وسيطًا واحدًا فقط وهو اسم تابع فلاسك، لتنشئ ارتباطًا مع وجهة تابع فلاسك بدلًا من ملف ساكن كما كان الحال لدى استدعاء التابع <code>()url_for</code> السابق الذي استخدمناه لإنشاء ارتباط مع ملف CSS الساكن.
	</li>
	<li>
		<code>{% block content %} {% endblock %}</code>: كتلة برمجية أخرى ستُستبدل لاحقًا بالمحتوى الفعلي بالاعتماد على القالب الابن، أي القالب الذي يرث شيفرات القالب الرئيسي base.html.
	</li>
</ul>
<p>
	والآن بعد أن أصبح لديك قالبٌ رئيسي، يمكنك الاستفادة من مميزاته باستخدام فكرة الوراثة، لذلك افتح الملف index.html:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_63" style="">
<span class="pln">nano templates</span><span class="pun">/</span><span class="pln">index</span><span class="pun">.</span><span class="pln">html</span></pre>

<p>
	ثمّ استبدل محتوياته بما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_65" style="">
<span class="pun">{%</span><span class="pln"> extends </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">

</span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">h1</span><span class="pun">&gt;{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="typ">Welcome</span><span class="pln"> to </span><span class="typ">FlaskBlog</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	استخدمنا في هذه النسخة الجديدة من القالب template.html الوسم <code>{% extends %}</code> لترث الشيفرات من القالب الرئيسي base.html، وذلك عن طريق استبدال الشيفرة السابقة بكتلة المحتوى content في القالب الرئيسي.
</p>

<p>
	تحوي كتلة المحتوى <code>block content</code> على وسم <code>&lt;h1&gt;</code> وبداخله كتلة عنوان <code>title</code> تحتوي العبارة "Welcome to FlaskBlog"، وبذلك نتفادى تكرار نفس النص مرتين، ذلك لأنّ هذا النص سيظهر في عنوان الصفحة ونص ضمن وسم <code>&lt;h1&gt;</code> أسفل شريط التصفح الموروث من القالب الرئيسي.
</p>

<p>
	تمكنّك وراثة القوالب من إعادة استخدام شيفرة HTML الموجودة في القوالب الأخرى (القالب الرئيسي <code>base.html</code> في حالتنا) دون الحاجة لتكراره في كل مرة.
</p>

<p>
	اِحفظ الملف وأغلقه، ثم حدِّث الصفحة الرئيسية index في المتصفح، فستظهر الصفحة بشريط تصفح وعنوانٍ منسقٍ كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="102665" href="https://academy.hsoub.com/uploads/monthly_2022_07/Second_img_step3b.png.c998ce92bedf96631dc09100457ddb50.png" rel=""><img alt="Second_img_step3b.png" class="ipsImage ipsImage_thumbnailed" data-fileid="102665" data-unique="jtsc8wgpg" src="https://academy.hsoub.com/uploads/monthly_2022_07/Second_img_step3b.thumb.png.0a578e9100fb5abacd80b101ca25058e.png" style="width: 600px; height: auto;"></a>
</p>

<p>
	وبذلك نكون قد استخدمنا قوالب HTML والملفات الساكنة في فلاسك، وكذلك استخدمنا بوتستراب لتنسيق مظهر الصفحة، واستفدنا من القالب الرئيسي لتفادي تكرار الشيفرات البرمجية. سنُعدُّ في الخطوة التالية قاعدة بيانات لتخزين بيانات التطبيق.
</p>

<h2>
	الخطوة الرابعة – إعداد قاعدة البيانات
</h2>

<p>
	سيكون التركيز في هذه الخطوة على إعداد قاعدة بيانات لتخزين البيانات، والتي هي تدوينات posts في حالة تطبيقنا، كما سنملأ قاعدة البيانات ببعض المدخلات التجريبية.
</p>

<p>
	سنستخدم ملف قاعدة بيانات من نوع <a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-sqlite-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-flask-r346/" rel="">SQLite</a> لتخزين البيانات، لأن وحدة <a href="https://academy.hsoub.com/programming/php/%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-php-mysqli-%D9%88%D9%86%D8%B8%D8%A7%D9%85-%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-sqlite3-r1131/" rel=""><code>sqlite3</code></a> المُستخدمة للتعاطي مع قاعدة البيانات هذه موجودة وجاهزة افتراضيًا في مكتبة لغة بايثون.
</p>

<p>
	بدايةً، وبما أنّ تخزين البيانات في SQLite يكون ضمن جداول وأعمدة، وكون بيانات تطبيقنا هي تدوينات، سننشئ جدولًا باسم posts يحتوي على كافّة الأعمدة اللازمة، إذ سننشئ ملفًا بلاحقة <code>sql.</code> يحتوي على أوامر SQL اللازمة لإنشاء جدول posts وأعمدته اللازمة، ثم سنستخدم هذا الملف لإنشاء قاعدة البيانات.
</p>

<p>
	افتح الملف المُسمى schema.sql الموجود في المجلد flask_blog:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_69" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano schema</span><span class="pun">.</span><span class="pln">sql</span></pre>

<p>
	اكتب تعليمات SQL التالية داخل هذا الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_71" style="">
<span class="pln">DROP TABLE IF EXISTS posts</span><span class="pun">;</span><span class="pln">

CREATE TABLE posts </span><span class="pun">(</span><span class="pln">
    id INTEGER PRIMARY KEY AUTOINCREMENT</span><span class="pun">,</span><span class="pln">
    created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP</span><span class="pun">,</span><span class="pln">
    title TEXT NOT NULL</span><span class="pun">,</span><span class="pln">
    content TEXT NOT NULL
</span><span class="pun">);</span></pre>

<p>
	ثم اِحفظ الملف وأغلقه.
</p>

<p>
	يعمل أوّل أمر من أوامر SQL وهو:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_73" style="">
<span class="pln">DROP TABLE IF EXISTS posts</span><span class="pun">;</span></pre>

<p>
	على حذف أي جداول موجودةٍ مسبقًا باسم posts لتجنُّب أي تضاربٍ، أو نتائج عشوائية ناتجةٍ عن تشابه أسماء الجداول، ومن الجدير بالملاحظة أنّ هذا الأمر سيحذِف كل المحتويات في قاعدة البيانات في كل مرة تشغِّل فيها أوامر SQL هذه، لذلك لا تُدخل أي بيانات مهمّة في التطبيق حتى تنتهي من كافّة الخطوات التعليميّة في المقال وتجربة نتائجه النهائية.
</p>

<p>
	بينما ينشئ الأمر الثاني من أوامر SQL وهو:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_75" style="">
<span class="pln">CREATE TABLE posts</span></pre>

<p>
	جدولًا باسم posts له الأعمدة التالية:
</p>

<ul>
<li>
		"id": ويحتوي على بياناتٍ من نوع رقم صحيح ويمثّل مفتاحًا أساسيًا يحتوي على قيمةٍ فريدة في قاعدة البيانات من أجل كل سجل (والسجل هو التدوينة في حالتنا).
	</li>
	<li>
		"created": يحتوي على تاريخ ووقت إنشاء التدوينة، وتشير <code>NOT NULL</code> إلى أن هذا العمود لا يجب أن يحتوي على قيمٍ فارغة، أما القيمة الافتراضية فهي <code>CURRENT_TIMESTAMP</code> والتي تمثِّل تاريخ ووقت إضافة التدوينة إلى قاعدة البيانات، وكما هو الحال في عمود <code>id</code>، لا يتوجب عليك تحديد قيم لهذا العمود، إذ أنها تُملأ تلقائيًا.
	</li>
	<li>
		"title": عنوان التدوينة.
	</li>
	<li>
		"content": محتوى التدوينة.
	</li>
</ul>
<p>
	الآن، وبعد أن أصبح لدينا تخطيط قاعدة البيانات SQL المطلوب في ملف schema.sql، سنستخدمه لإنشاء قاعدة البيانات باستخدام ملف بايثون لإنشاء ملف قاعدة بيانات SQLite بلاحقة <code>db.</code>.
</p>

<p>
	افتح الملف المُسمّى init_db.py الموجود داخل المجلد flask_blog باستخدام محرِّر النصوص المفضل لديك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_77" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano init_db</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ثم اكتب ضمنه الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_79" style="">
<span class="kwd">import</span><span class="pln"> sqlite3

connection </span><span class="pun">=</span><span class="pln"> sqlite3</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="str">'database.db'</span><span class="pun">)</span><span class="pln">


</span><span class="kwd">with</span><span class="pln"> open</span><span class="pun">(</span><span class="str">'schema.sql'</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> f</span><span class="pun">:</span><span class="pln">
    connection</span><span class="pun">.</span><span class="pln">executescript</span><span class="pun">(</span><span class="pln">f</span><span class="pun">.</span><span class="pln">read</span><span class="pun">())</span><span class="pln">

cur </span><span class="pun">=</span><span class="pln"> connection</span><span class="pun">.</span><span class="pln">cursor</span><span class="pun">()</span><span class="pln">

cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO posts (title, content) VALUES (?, ?)"</span><span class="pun">,</span><span class="pln">
            </span><span class="pun">(</span><span class="str">'First Post'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Content for the first post'</span><span class="pun">)</span><span class="pln">
            </span><span class="pun">)</span><span class="pln">

cur</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">"INSERT INTO posts (title, content) VALUES (?, ?)"</span><span class="pun">,</span><span class="pln">
            </span><span class="pun">(</span><span class="str">'Second Post'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Content for the second post'</span><span class="pun">)</span><span class="pln">
            </span><span class="pun">)</span><span class="pln">

connection</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">
connection</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span></pre>

<p>
	استوردنا في الشيفرة السابقة وحدة <code>sqlite3</code>، ثم أنشأنا اتصالًا مع ملف قاعدة بيانات باسم <code>database.db</code>، والذي يُنشأ تلقائيًا فور تشغيل ملف بايثون هذا، ومن ثم استخدمنا الدالة <code>()open</code> لفتح الملف schema.sql، ثم نفّذنا باستخدام التابع <code>()executescript</code> محتويات هذا الملف، الذي ينفِّذ عدة عبارات SQL معًا، وهكذا يُنشأ الجدول posts.
</p>

<p>
	استخدمنا في الشيفرة السابقة كائن المؤشر Cursor، الذي يمكِّننا من استخدام تابعه <code>()execute</code> لتنفيذ تعليمتي إدخال <code>INSERT</code> في SQL لإضافة تدوينتين معًا إلى جدول التدوينات posts. وفي النهاية أُرسلت هذه الأوامر إلى قاعدة البيانات وأُغلِق الاتصال المفتوح معها.
</p>

<p>
	اِحفظ الملف وأغلقه، ثم شغِّله من موجّه الأوامر باستخدام أمر بايثون التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_81" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ python init_db</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	وحالما ينتهي التنفيذ، سيكون لديك ملفٌ جديدٌ باسم database.db ضمن مجلد flask_blog، وهذا يدل على نجاح عملية إعداد قاعدة البيانات. سنعمل في الخطوة التالية على قراءة التدوينات المُضافة إلى قاعدة البيانات وعرضها في الصفحة الرئيسية للتطبيق.
</p>

<h2>
	الخطوة 5 – عرض كل التدوينات
</h2>

<p>
	الآن وبعد أن أعددنا قاعدة البيانات، يمكننا تعديل دالة عرض فلاسك <code>index()‎</code> لعرض كل التدوينات الموجودة في قاعدة البيانات. لذلك، افتح الملف app.py لتنفيذ التعديلات التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_83" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	سيكون التعديل الأوّل هو استيراد وحدة <code>sqlite3</code> في بداية الملف على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_85" style="">
<span class="kwd">import</span><span class="pln"> sqlite3
</span><span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> render_template

</span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span></pre>

<p>
	ثم سنبني بعد ذلك دالةً تعمل على إنشاء اتصال مع قاعدة البيانات وتعيد نتائجه. أضِف هذه الدالة مباشرةً بعد أوامر الاستيراد:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_87" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
</span><span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> render_template

</span><span class="kwd">def</span><span class="pln"> get_db_connection</span><span class="pun">():</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> sqlite3</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="str">'database.db'</span><span class="pun">)</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">row_factory </span><span class="pun">=</span><span class="pln"> sqlite3</span><span class="pun">.</span><span class="typ">Row</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> conn
</span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span></pre>

<p>
	تفتح الدالة <code>get_db_connection()‎</code> اتصالًا مع ملف قاعدة البيانات database.db، وتحدّد بعد ذلك قيمة السمة <code>row_factory</code> لتكون <code>sqlite3.Row</code> لنتمكّن من الوصول إلى الأعمدة باستخدام أسمائها، ما يعني أنّ اتصال قاعدة البيانات سيعيد سجلات يمكننا التعامل معها كما هو الحال مع قواميس بايثون الاعتيادية (وحدات تخزين القيم المنظمّة في بايثون)، ونهايةً يعيد التابع كائن الاتصال <code>conn</code>، الذي سنستخدمه للوصول إلى قاعدة البيانات.
</p>

<p>
	بعد أن عرّفنا الدالة <code>get_db_connection()‎</code>، سنعدّل دالة <code>index()‎</code> لتصبح كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_89" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> index</span><span class="pun">():</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">
    posts </span><span class="pun">=</span><span class="pln"> conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'SELECT * FROM posts'</span><span class="pun">).</span><span class="pln">fetchall</span><span class="pun">()</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'index.html'</span><span class="pun">,</span><span class="pln"> posts</span><span class="pun">=</span><span class="pln">posts</span><span class="pun">)</span></pre>

<p>
	بعد هذا التعديل الجديد على الدالة <code>index()‎</code>، فإنّنا نفتح اتصالًا مع قاعدة البيانات باستخدام الدالة <code>get_db_connection()‎</code> التي عرفناها للتو. سننفذ بعد ذلك استعلام SQL للحصول على كل المدخلات الموجودة في الجدول posts، إذ أنّنا نستدعي التابع <code>fetchall()‎</code> لجلب كل الأسطر الناتجة عن الاستعلام، وهذا سيعيد بالنتيجة قائمةً بالتدوينات المُدخلة إلى قاعدة البيانات في الخطوة السابقة.
</p>

<p>
	يمكنك إغلاق الاتصال بقاعدة البيانات باستخدام التابع <code>close()‎</code> وإعادة نتيجة عرض القالب index.html، كما يمكنك تمرر الكائن posts وسيطًا، فهو الكائن الحاوي على النتائج المُستخلصة من قاعدة البيانات، ويساعدنا هذا التمرير على الوصول إلى التدوينات برمجيًا داخل قالب index.html.
</p>

<p>
	وبعد تنفيذ كل هذه التعديلات، احفظ الملف app.py وأغلقه.
</p>

<p>
	الآن وبعد أن مرّرنا كل التدوينات المُستخلصة من قاعدة البيانات إلى القالب index.html، سنستخدم حلقة for التكرارية لعرض معلومات عن كل تدوينة داخل صفحة index.
</p>

<p>
	افتح ملف index.html:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_6564_93" style="">
<span class="pln">(env)user@localhost:$ nano templates/index.html</span></pre>

<p>
	ثم عدِّله كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_6564_95" style="">
<span class="pln">{% extends 'base.html' %}
{% block content %}
    </span><span class="tag">&lt;h1&gt;</span><span class="pln">{% block title %} Welcome to FlaskBlog {% endblock %}</span><span class="tag">&lt;/h1&gt;</span><span class="pln">
    {% for post in posts %}
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"#"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;h2&gt;</span><span class="pln">{{ post['title'] }}</span><span class="tag">&lt;/h2&gt;</span><span class="pln">
        </span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;span</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"badge badge-primary"</span><span class="tag">&gt;</span><span class="pln">{{ post['created'] }}</span><span class="tag">&lt;/span&gt;</span><span class="pln">
        </span><span class="tag">&lt;hr&gt;</span><span class="pln">
    {% endfor %}
{% endblock %}</span></pre>

<p>
	تقابل هنا الصيغة البرمجية <code>{% for post in posts %}</code> حلقة for في محرّك جينجا jinja، والتي تشبه بناء حلقة for في لغة بايثون ما عدا ضرورة إغلاقها باستخدام الصيغة البرمجية <code>{% endfor %}</code>. الهدف من استخدام هذه الحلقة حاليًا هو المرور على كل عنصر في القائمة posts المُمررة إلى الدالة <code>index()‎</code> من خلال السطر البرمجي التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_97" style="">
<span class="pln"> </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'index.html'</span><span class="pun">,</span><span class="pln"> posts</span><span class="pun">=</span><span class="pln">posts</span><span class="pun">)</span></pre>

<p>
	وضمن هذه الحلقة نعرض عنوان التدوينة في وسمٍ عنوان <code>&lt;h2&gt;</code> داخل وسم الرابط <code>&lt;a&gt;</code> (لأنّنا سنستخدم هذا الوسم لاحقًا لإنشاء رابط مخصّص لعرض معلومات خاصة عن كل تدوينة).
</p>

<p>
	يُعرض عنوان التدوينة باستخدام محدّد المتغير المُجرد Literal variable delimiter وهو <code>{{ ... }}</code>. تذكّر أنّ كل عنصر post في القائمة سيكون مشابهًا لما هو عليه في قاموس بايثون، وبالتالي يمكنك الوصول إلى عنوان التدوينة من خلال <code>post['title']‎</code>؛ كما من الممكن عرض تاريخ إنشاء التدوينة بنفس الآلية.
</p>

<p>
	وحالما تنتهي من تعديل الملف، احفظه وأغلقه، ثم انتقل في المتصفح إلى صفحة index، وعندها ستظهر لك المشاركتان posts المُضافتان سابقًا إلى قاعدة البيانات.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="102666" href="https://academy.hsoub.com/uploads/monthly_2022_07/third_img_step5.png.3788a2f81d795b2a18f14fd94ac790e4.png" rel=""><img alt="third_img_step5.png" class="ipsImage ipsImage_thumbnailed" data-fileid="102666" data-unique="clb1h7ut7" src="https://academy.hsoub.com/uploads/monthly_2022_07/third_img_step5.thumb.png.0698094ffc5d3d9fe1ea15473d33977d.png" style="width: 600px; height: auto;"></a>
</p>

<p>
	والآن، وبعد تعديل دالة <code>index()‎</code> المسؤولة عن عرض كل التدوينات الموجودة في قاعدة البيانات داخل الصفحة الرئيسية للتطبيق، سننتقل إلى خطوة عرض كل تدوينة في صفحة خاصة بها وذلك من خلال تمكين المستخدمين من الانتقال إلى أي تدوينة -كلٌ على حدى-.
</p>

<h2>
	الخطوة السادسة – عرض تدوينة بحد ذاتها
</h2>

<p>
	تهدف هذه الخطوة إلى عرض تدوينة واحدة بناءً على معرّفها ID، سننشئ وجهة route فلاسك ودالة عرض جديدة، إضافةً لقالب HTML جديد.
</p>

<p>
	في نهاية هذه الخطوة، سيكون الرابط "http://127.0.0.1:5000/1" مثلًا هو الصفحة التي تعرض التدوينة الأولى، لأن معرف التدوينة الأولى هو الرقم 1؛ أي سيعرض الرابط "http://127.0.0.1:5000/ID" التدوينة ذات المعرّف ID في حال وجودها في قاعدة البيانات.
</p>

<p>
	الآن افتح الملف app.py لتحريره:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_102" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	وطالما أنّنا سنجلب التدوينات من قاعدة البيانات بناءً على معرّفها ID من مسارات متعدّدة لاحقًا عند استخدام التطبيق، سننشئ دالةً منفردة باسم <code>get_post()‎</code> تعيد معلومات التدوينة ذات المعرّف ID هذا بمجرد تمرير القيمة ID وسيطًا لها، أما في حال عدم وجود تدوينة تحمل هذا المعرّف في قاعدة البيانات، ستكون الاستجابة برسالة "غير موجود 404 Not Found"؛ ولنتمكَّن من الاستجابة برسالة الخطأ رقم 404، لا بدّ من تضمين الدالة <code>abort()‎</code> من مكتبة فلاسك <code>Werkzeug</code> التي يجري تنزيلها أثناء تثبيت فلاسك.
</p>

<p>
	ننجز الاستدعاء السابق أعلى الملف على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_104" style="">
<span class="kwd">import</span><span class="pln"> sqlite3
</span><span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> render_template
</span><span class="kwd">from</span><span class="pln"> werkzeug</span><span class="pun">.</span><span class="pln">exceptions </span><span class="kwd">import</span><span class="pln"> abort
</span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span></pre>

<p>
	ثم نضيف الدالة <code>get_post()‎</code> تمامًا بعد الدالة <code>get_db_connection()‎</code> المُعرّفة في الخطوة السابقة.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_106" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> get_db_connection</span><span class="pun">():</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> sqlite3</span><span class="pun">.</span><span class="pln">connect</span><span class="pun">(</span><span class="str">'database.db'</span><span class="pun">)</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">row_factory </span><span class="pun">=</span><span class="pln"> sqlite3</span><span class="pun">.</span><span class="typ">Row</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> conn
</span><span class="kwd">def</span><span class="pln"> get_post</span><span class="pun">(</span><span class="pln">post_id</span><span class="pun">):</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">
    post </span><span class="pun">=</span><span class="pln"> conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'SELECT * FROM posts WHERE id = ?'</span><span class="pun">,</span><span class="pln">
                        </span><span class="pun">(</span><span class="pln">post_id</span><span class="pun">,)).</span><span class="pln">fetchone</span><span class="pun">()</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> post </span><span class="kwd">is</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">:</span><span class="pln">
        abort</span><span class="pun">(</span><span class="lit">404</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> post
</span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span></pre>

<p>
	تتضمّن الدالة الجديدة الوسيط <code>post_id</code>، الذي يحدّد التدوينة المُعادة نتيجةً لاستدعاء هذه الدالة.
</p>

<p>
	تُستخدم خلال تنفيذ هذه الدالة دالةٌ أخرى، هي <code>get_db_connection()‎</code> لفتح اتصال مع قاعدة البيانات وتنفيذ استعلام SQL لجلب التدوينة الموافقة لقيمة <code>post_id</code> الممرّرة، كما يُستخدم التابع <code>fetchone()‎</code> لجلب نتيجة الاستعلام وتخزينها في المتغير <code>post</code>، ومن ثمّ يُغلق الاتصال مع قاعدة البيانات؛ فإذا كانت قيمة المتغير <code>post</code> فارغة <code>None</code>، فهذا يدل على عدم العثور على نتائج في قاعدة البيانات، وعندها نستخدم الدالة <code>abort()‎</code> التي ضمّناها توًّا للرد برقم الخطأ 404 وبالتالي التوقُّف عن متابعة تنفيذ شيفرة الدالة؛ أما في حال العثور على التدوينة، تُعاد قيمة المتغير <code>post</code>.
</p>

<p>
	الآن، أضف دالة عرض فلاسك التالية في نهاية الملف app.py:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_117" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/&lt;int:post_id&gt;'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> post</span><span class="pun">(</span><span class="pln">post_id</span><span class="pun">):</span><span class="pln">
    post </span><span class="pun">=</span><span class="pln"> get_post</span><span class="pun">(</span><span class="pln">post_id</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'post.html'</span><span class="pun">,</span><span class="pln"> post</span><span class="pun">=</span><span class="pln">post</span><span class="pun">)</span></pre>

<p>
	سنضيف في دالة عرض فلاسك الجديدة قاعدة متغيرة Variable Rule هي <code>&lt;int:post_id&gt;</code> لنحدّد أنّ الجزء بعد العلامة (<code>/</code>) هو رقمٌ صحيحٌ موجب، وذلك بتحويل قيمة هذا الجزء إلى النوع <code>int</code>، وهو ما نحتاجه للوصول إلى دالة العرض. يتعرف فلاسك على هذا الشرط ويمرّر القيمة إلى الوسيط <code>post_id</code> في دالة فلاسك <code>post()‎</code>، وبعد ذلك يمكننا استخدام الدالة <code>()get_post</code> للحصول على التدوينة المرتبطة بالمعرّف ID المحدّد، ومن ثمّ تخزين النتيجة في المتغير <code>post</code>، الذي سيُمرّر في النهاية إلى القالب post.html، الذي سننشئه فيما يلي.
</p>

<p>
	الآن احفظ الملف app.py وافتح ملف قالب جديد post.html لتحريره:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_6564_112" style="">
<span class="pln">(env)user@localhost:$ nano templates/post.html</span></pre>

<p>
	اكتب الشيفرة التالية في الملف الجديد post.html، إذ تتشابه هذه الشيفرة مع تلك المُستخدمة في ملف index.html، إلّا أنّنا سنعرض معلومات عن تدوينةٍ واحدةٍ فقط، إضافةً إلى عرض محتويات هذه التدوينة:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_6564_119" style="">
<span class="pln">{% extends 'base.html' %}
{% block content %}
    </span><span class="tag">&lt;h2&gt;</span><span class="pln">{% block title %} {{ post['title'] }} {% endblock %}</span><span class="tag">&lt;/h2&gt;</span><span class="pln">
    </span><span class="tag">&lt;span</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"badge badge-primary"</span><span class="tag">&gt;</span><span class="pln">{{ post['created'] }}</span><span class="tag">&lt;/span&gt;</span><span class="pln">
    </span><span class="tag">&lt;p&gt;</span><span class="pln">{{ post['content'] }}</span><span class="tag">&lt;/p&gt;</span><span class="pln">
{% endblock %}</span></pre>

<p>
	اضفنا كتلة العنوان <code>title</code> المُعرفة سابقًا في القالب base.html لجعل عنوان التدوينة معروضًا باستخدام وسم من نوع عنوان <code>&lt;h2&gt;</code>، وجعله أيضًا عنوانًا للصفحة.
</p>

<p>
	اِحفظ الملف وأغلقه.
</p>

<p>
	يمكنك الآن الانتقال في المتصفح إلى الروابط التالية لاستعراض التدوينتين الموجودتين حاليًا في قاعدة البيانات، وكذلك الصفحة التي تخبر المستخدم بعدم العثور على التدوينة المطلوبة (لأنّه لا يوجد حتى الآن تدوينة برقم معرف 3):
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_123" style="">
<span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="lit">1</span><span class="pln">
http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="lit">2</span><span class="pln">
http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="lit">3</span></pre>

<p>
	سنعود إلى الصفحة الرئيسية لإنشاء رابط لكل عنوان تدوينة لينقلنا إلى صفحة التدوينة الخاصة به باستخدام الدالة <code>url_for()‎</code>، لذا افتح قالب index.html أولًا لتحريره:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_6564_125" style="">
<span class="pln">(env)user@localhost:$ nano templates/index.html</span></pre>

<p>
	ثم عدّل قيمة السمة <code>href</code> لتصبح <code>{{ url_for('post', post_id=post['id'])‎ }}</code> بدلًا من <code>#</code>، بحيث تبدو حلقة for على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_6564_127" style="">
<span class="pln">{% for post in posts %}
    </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('post', post_id=post['id']) }}"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;h2&gt;</span><span class="pln">{{ post['title'] }}</span><span class="tag">&lt;/h2&gt;</span><span class="pln">
    </span><span class="tag">&lt;/a&gt;</span><span class="pln">
    </span><span class="tag">&lt;span</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"badge badge-primary"</span><span class="tag">&gt;</span><span class="pln">{{ post['created'] }}</span><span class="tag">&lt;/span&gt;</span><span class="pln">
    </span><span class="tag">&lt;hr&gt;</span><span class="pln">
{% endfor %}</span></pre>

<p>
	تُمرَّر القيمة <code>post </code> إلى الدالة <code>url_for()‎</code> التي تحتاج رقم معرّف التدوينة وسيطًا لها، وبما أنها اسم دالة العرض <code>()post </code>فتُستدعى على النحو التالي<code>post['id' ]‎ </code>لتُستخدم وسيطًا للدالة<code>url_for()‎</code> التي ستعيد بالنتيجة الرابط المناسب لكل تدوينة بناءً على معرّفها ID.
</p>

<p>
	اِحفظ الملف وأغلقه.
</p>

<p>
	ستعمل الروابط الآن في الصفحة الرئيسية على النحو المطلوب، وبذلك نكون قد انتهينا من بناء الجزء المسؤول عن عرض التدوينات الموجودة في قاعدة البيانات من تطبيق الويب، وسنضيف في الخطوات التالية إمكانية إضافة، أو تعديل، أو حذف التدوينات لهذا التطبيق.
</p>

<h2>
	الخطوة 7 – التعديل على جدول التدوينات
</h2>

<p>
	بعد أن أنهينا عملية استعراض التدوينات الموجودة في قاعدة البيانات داخل صفحات تطبيق الويب، سنعمل على توفير إمكانية كتابة تدوينات جديدة وحفظها في قاعدة البيانات لمستخدمي التطبيق، وكذلك إمكانية تعديل التدوينات الموجودة أصلًا أو حذف التدوينات غير اللازمة.
</p>

<h3>
	إنشاء تدوينة جديدة
</h3>

<p>
	لدينا حتى الآن تطبيقٌ يعرض التدوينات من قاعدة البيانات، ولكنه لا يوفر أي طريقةٍ لإضافة تدوينات جديدة، إلّا عن طريق إضافة تدوينة يدويًا عبر الاتصال بقاعدة البيانات <code>SQLite</code>. فيما يلي سننشئ صفحةً تمكنّنا من إضافة تدوينة جديدة عن طريق إدخال عنوانها ومحتواها مباشرةً.
</p>

<p>
	افتح ملف app.py لتحريره:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_131" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	لا بُد أولًا من استيراد التالي من إطار العمل فلاسك:
</p>

<ul>
<li>
		الكائن <code>request</code> العام المسؤول عن الوصول إلى بيانات الطلب والتي ستُرسل من خلال نموذج HTML.
	</li>
	<li>
		الدالة <code>url_for()‎</code> لتوليد عناوين الروابط.
	</li>
	<li>
		الدالة <code>flash()‎</code> لعرض رسالةٍ وامضة عند انتهاء معالجة الطلب.
	</li>
	<li>
		الدالة <code>redirect()‎</code> لإعادة توجيه المستخدم إلى موقعٍ آخر في المتصفح.
	</li>
</ul>
<p>
	أضِف هذه الاستيرادات إلى الملف كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_133" style="">
<span class="kwd">import</span><span class="pln"> sqlite3
</span><span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">,</span><span class="pln"> render_template</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">,</span><span class="pln"> url_for</span><span class="pun">,</span><span class="pln"> flash</span><span class="pun">,</span><span class="pln"> redirect
</span><span class="kwd">from</span><span class="pln"> werkzeug</span><span class="pun">.</span><span class="pln">exceptions </span><span class="kwd">import</span><span class="pln"> abort
</span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span></pre>

<p>
	تخزّن الدالة <code>flash()‎</code> الرسائل في جلسة المتصفح لدى المستخدم، وهذا ما يتطلّب إعداد مفتاح أمان Secret Key، إذ سيُستخدم هذا المفتاح لجعل الجلسات آمنة، وبذلك يتمكّن فلاسك من الحفاظ على المعلومات عند الانتقال من طلبٍ إلى آخر، مثل حالة الانتقال من صفحة إنشاء تدوينة جديدة إلى صفحة عرض كل التدوينات، مع ملاحظة أن المستخدم يستطيع الوصول إلى المعلومات المُخزّنة في الجلسة، ولكنه لا يستطيع تعديلها إلّا إذا كان لديه مفتاح الأمان، وبالتالي لا يجب أن تسمح لأي أحدٍ بالوصول إلى مفتاح الأمان الخاص بك.
</p>

<p>
	ولإعداد مفتاح أمان، سنضيف ضبط <code>SECRET_KEY</code> إلى التطبيق من خلال الكائن <code>app.config</code>، الذي سنضيفه مباشرةً بعد تعريف الكائن <code>app</code> وقبل دالة عرض فلاسك <code>index()‎</code>، على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_135" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">config</span><span class="pun">[</span><span class="str">'SECRET_KEY'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'your secret key'</span><span class="pln">


</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> index</span><span class="pun">():</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">
    posts </span><span class="pun">=</span><span class="pln"> conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'SELECT * FROM posts'</span><span class="pun">).</span><span class="pln">fetchall</span><span class="pun">()</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'index.html'</span><span class="pun">,</span><span class="pln"> posts</span><span class="pun">=</span><span class="pln">posts</span><span class="pun">)</span><span class="pln">

</span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span></pre>

<p>
	تذكّر أنّ مفتاح الأمان يجب أن يكون سلسلةً نصيةً عشوائية بطولٍ مناسب.
</p>

<p>
	الآن وبعد إعداد مفتاح الأمان، سننشئ دالة عرض فلاسك مسؤولة عن إخراج قالب يحتوي على نموذج إدخال يمكن للمستخدم ملؤه لإنشاء تدوينة جديدة. أضِف دالة فلاسك هذه في نهاية الملف على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_137" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/create'</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=(</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">))</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> create</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'create.html'</span><span class="pun">)</span></pre>

<p>
	ستنشئ هذه الشيفرة وجهة route مهمتها قبول الطلبات سواءً بطريقة GET، أو POST، إذ أن طريقة GET هي الطريقة الافتراضية؛ ولتمكين قبول طريقة POST أيضًا المُستخدمة من قبل المتصفح عند إرسال نماذج الإدخال، سنمرر متغيرًا من نوع <code>tuple</code> يحتوي على طرق الطلبات المقبولة إلى وسيط الطرائق <code>methods</code> في المزخرف <code>‎@app.route()‎</code>.
</p>

<p>
	اِحفظ الملف وأغلقه، ثمّ لإنشاء القالب، افتح ملفًا باسم create.html داخل مجلد القوالب templates على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_6564_139" style="">
<span class="pln">(env)user@localhost:$ nano templates/create.html</span></pre>

<p>
	أضف الشيفرة التالية داخل هذا الملف الجديد:
</p>

<pre class="ipsCode prettyprint lang-css prettyprinted" id="ips_uid_6564_141" style="">
<span class="pun">{%</span><span class="pln"> </span><span class="kwd">extends</span><span class="pln"> </span><span class="str">'base.html'</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> block content </span><span class="pun">%}</span><span class="pln">
</span><span class="str">&lt;h1&gt;</span><span class="pun">{%</span><span class="pln"> block title </span><span class="pun">%}</span><span class="pln"> </span><span class="typ">Create</span><span class="pln"> a </span><span class="typ">New</span><span class="pln"> </span><span class="typ">Post</span><span class="pln"> </span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}&lt;/</span><span class="pln">h1</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">form method</span><span class="pun">=</span><span class="str">"post"</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"form-group"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">label </span><span class="kwd">for</span><span class="pun">=</span><span class="str">"title"</span><span class="pun">&gt;</span><span class="typ">Title</span><span class="pun">&lt;/</span><span class="pln">label</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"text"</span><span class="pln"> name</span><span class="pun">=</span><span class="str">"title"</span><span class="pln">
               placeholder</span><span class="pun">=</span><span class="str">"Post title"</span><span class="pln"> </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"form-control"</span><span class="pln">
               value</span><span class="pun">=</span><span class="str">"{{ request.form['title'] }}"</span><span class="pun">&gt;&lt;/</span><span class="pln">input</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"form-group"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">label </span><span class="kwd">for</span><span class="pun">=</span><span class="str">"content"</span><span class="pun">&gt;</span><span class="typ">Content</span><span class="pun">&lt;/</span><span class="pln">label</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">textarea name</span><span class="pun">=</span><span class="str">"content"</span><span class="pln"> placeholder</span><span class="pun">=</span><span class="str">"Post content"</span><span class="pln">
                  </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"form-control"</span><span class="pun">&gt;{{</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">}}&lt;/</span><span class="pln">textarea</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"form-group"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">button type</span><span class="pun">=</span><span class="str">"submit"</span><span class="pln"> </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"btn btn-primary"</span><span class="pun">&gt;</span><span class="typ">Submit</span><span class="pun">&lt;/</span><span class="pln">button</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">form</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	الجزء الأكبر من هذه الشيفرة ما هو إلّا تعليمات HTML معيارية لإظهار صندوق إدخال لاستقبال عنوان التدوينة، وصندوق كتابة لتحرير محتواها، وزرٌ لتأكيد إرسال النموذج.
</p>

<p>
	القيمة داخل أداة عنوان التدوينة هي <code>{{ request.form['title']‎ }}</code> والقيمة داخل أداة صندوق الكتابة هي <code>{{ request.form['content']‎ }}</code>، وقد فعلنا هذا للحفاظ على البيانات المدخلة في كل من الأداتين في حال حدوث خطأ ما؛ فمثلًا إذا كتب المستخدم محتوًى كبير للتدوينة وأرسل النموذج دون إدخال عنوان، فستظهر رسالةٌ تعلمه أنّ العنوان مطلوب دون فقدان المحتوى الذي كتبه في حقل المحتوى، لأنّ المعلومات المدخلة سيُحتفظ بها في الكائن العام <code>request</code>.
</p>

<p>
	الآن وفي حين خادم التطوير يعمل، استخدم المتصفح للانتقال إلى الوجهة <code>‎/create</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_143" style="">
<span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln">create</span></pre>

<p>
	فستظهر لك صفحة إنشاء تدوينة جديدة مع أدوات لإدخال كلٍ من العنوان والمحتوى.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="102664" href="https://academy.hsoub.com/uploads/monthly_2022_07/fourth_img_step7a.png.1e21146f4c8f5ebcd61a51c013d53385.png" rel=""><img alt="fourth_img_step7a.png" class="ipsImage ipsImage_thumbnailed" data-fileid="102664" data-unique="i3jl772qi" src="https://academy.hsoub.com/uploads/monthly_2022_07/fourth_img_step7a.thumb.png.5fec14e898478bd95048956d1a3de0aa.png" style="width: 600px; height: auto;"></a>
</p>

<p>
	يرسل نموذج الإدخال هذا الطلب بطريقة POST إلى دالة عرض فلاسك <code>create()‎</code>، ولكن حتى هذه اللحظة لا يوجد شيفرة مسؤولة عن معالجة هذا الطلب في الدالة، وبالتالي لن يحدث شيء في حال ملء النموذج الآن وإرساله.
</p>

<p>
	لذا فيما يلي ستعالج الدالة <code>create()‎</code> الطلبات الواردة بطريقة POST عند إرسال محتويات نموذج الإدخال وذلك بعد التحقق من قيمة تابع الطلب <code>request.method</code>؛ فإذا كانت قيمته 'POST'، تستمر بمتابعة قراءة البيانات المرسلة والتحقق منها وإدخالها في قاعدة البيانات.
</p>

<p>
	الآن، افتح الملف app.py لتحريره:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_146" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	عدِّل شيفرة دالة عرض فلاسك <code>create()‎</code> لتصبح كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_148" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/create'</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=(</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">))</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> create</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">method </span><span class="pun">==</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">:</span><span class="pln">
        title </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'title'</span><span class="pun">]</span><span class="pln">
        content </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">]</span><span class="pln">

        </span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> title</span><span class="pun">:</span><span class="pln">
            flash</span><span class="pun">(</span><span class="str">'Title is required!'</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
            conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">
            conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'INSERT INTO posts (title, content) VALUES (?, ?)'</span><span class="pun">,</span><span class="pln">
                         </span><span class="pun">(</span><span class="pln">title</span><span class="pun">,</span><span class="pln"> content</span><span class="pun">))</span><span class="pln">
            conn</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">
            conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> redirect</span><span class="pun">(</span><span class="pln">url_for</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">))</span><span class="pln">

    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'create.html'</span><span class="pun">)</span></pre>

<p>
	تَمكَّنا باستخدام العبارة الشرطية التي توازن قيمة <code>request.method</code> مع القيمة <code>POST</code> من التحقُّق بأنّ التعليمات التالية لها لن تُنفّذ إلّا إذا كان الطلب الحالي هو فعلًا بطريقة <code>POST</code>، ومن ثمّ قرأنا قيم العنوان والمحتوى المرسلين من الكائن <code>request.form</code> الذي يمكِّننا من الوصول إلى بيانات نموذج الإدخال المُضمّنة في الطلب؛ ففي حال عدم إدخال قيمةٍ للعنوان، فسيتحقق الشرط <code>if not title</code> وبالتالي ستظهر رسالة للمستخدم نعلمه من خلالها بأن العنوان مطلوب؛ أمّا في حال وجود العنوان سيُفتَح الاتصال مع قاعدة البيانات باستخدام الدالة <code>get_db_connection()‎</code> لإدخال العنوان والمحتوى المرسلين إلى الجدول posts فيها.
</p>

<p>
	بعد تنفيذ هذه التعديلات في قاعدة البيانات وإضافة التدوينة الجديدة إليها، يُغلق الاتصال معها، ومن ثمّ نعيد توجيه الطلب إلى الصفحة الرئيسية باستخدام الدالة <code>redirect()‎</code> بتمرير الرابط الناتج عن التابع <code>url_for()‎</code> الذي قد مررنا له القيمة <code>index</code> وسيطًا.
</p>

<p>
	اِحفظ الملف وأغلقه، ثم انتقل في المتصفح إلى الوجهة <code>‎/create</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_150" style="">
<span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln">create</span></pre>

<p>
	املأ النموذج بالعنوان والمحتوى الذي تريد، وسترى التدوينة الجديدة في الصفحة الرئيسية حال إرسال النموذج.
</p>

<p>
	الآن سنعمل على إظهار الرسائل للمستخدم وإضافة رابط في شريط التصفح في القالب base.html لتسهيل الوصول إلى هذه الصفحة الجديدة، من خلال فتح ملف القالب:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_6564_152" style="">
<span class="pln">(env)user@localhost:$ nano templates/base.html</span></pre>

<p>
	الآن، عدِّل الملف من خلال إضافة وسم القائمة <code>&lt;li&gt;</code> بعد الرابط <code>About</code> داخل وسم شريط التصفُّح <code>&lt;nav&gt;</code>، ثمّ أضف حلقة تكرارية أعلى كتلة المحتوى مباشرةً لعرض الرسائل أسفل شريط التصفح، إذ يوفّر فلاسك هذه الرسائل من خلال دالة فلاسك الخاصة <code>get_flashed_messages()‎</code>:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_6564_154" style="">
<span class="tag">&lt;nav</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar navbar-expand-md navbar-light bg-light"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar-brand"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('index')}}"</span><span class="tag">&gt;</span><span class="pln">FlaskBlog</span><span class="tag">&lt;/a&gt;</span><span class="pln">
    </span><span class="tag">&lt;button</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar-toggler"</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"button"</span><span class="pln"> </span><span class="atn">data-toggle</span><span class="pun">=</span><span class="atv">"collapse"</span><span class="pln"> </span><span class="atn">data-target</span><span class="pun">=</span><span class="atv">"#navbarNav"</span><span class="pln"> </span><span class="atn">aria-controls</span><span class="pun">=</span><span class="atv">"navbarNav"</span><span class="pln"> </span><span class="atn">aria-expanded</span><span class="pun">=</span><span class="atv">"false"</span><span class="pln"> </span><span class="atn">aria-label</span><span class="pun">=</span><span class="atv">"Toggle navigation"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;span</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar-toggler-icon"</span><span class="tag">&gt;&lt;/span&gt;</span><span class="pln">
    </span><span class="tag">&lt;/button&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"collapse navbar-collapse"</span><span class="pln"> </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"navbarNav"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;ul</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"navbar-nav"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;li</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"nav-item"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"nav-link"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"#"</span><span class="tag">&gt;</span><span class="pln">About</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;/li&gt;</span><span class="pln">
        </span><span class="tag">&lt;li</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"nav-item"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"nav-link"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{url_for('create')}}"</span><span class="tag">&gt;</span><span class="pln">New Post</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;/li&gt;</span><span class="pln">
        </span><span class="tag">&lt;/ul&gt;</span><span class="pln">
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
</span><span class="tag">&lt;/nav&gt;</span><span class="pln">
</span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"container"</span><span class="tag">&gt;</span><span class="pln">
    {% for message in get_flashed_messages() %}
        </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"alert alert-danger"</span><span class="tag">&gt;</span><span class="pln">{{ message }}</span><span class="tag">&lt;/div&gt;</span><span class="pln">
    {% endfor %}
    {% block content %} {% endblock %}
</span><span class="tag">&lt;/div&gt;</span></pre>

<p>
	اِحفظ الملف وأغلقه. سيتضمن الآن شريط التصفح عنصرًا جديدًا باسم "New Post"، الذي ينقلنا إلى الوجهة <code>‎/create</code>.
</p>

<h2>
	تعديل تدوينة منشورة
</h2>

<p>
	لا بدّ من توفّر إمكانية التعديل على التدوينات المنشورة أصلًا في التطبيق بما يضمن إمكانية تحديثها دائمًا، لذا سنوضّح في هذا القسم من المقال كيفية إنشاء صفحة جديدة في التطبيق خاصةٍ بتعديل التدوينات.
</p>

<p>
	بدايةً، أضِف وجهة route جديدة إلى الملف app.py، إذ ستستقبل دالة هذه الوجهة رقم التدوينة ID المُراد تعديلها على هيئة وسيط، بحيث يكون الرابط بالشّكل <code>‎/post_id/edit</code> إذ يمثّل المتغير <code>post_id</code> رقم التدوينة. ولإنجاز ذلك، ابدأ بفتح الملف app.py في وضع التحرير على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_156" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ومن ثمّ أضف في نهاية الملف دالة <code>edit()‎</code> المشابهة للدالة <code>create()‎</code> التي أنشأناها سابقًا، لأنّ تعديل تدوينة منشورة أصلًا أمرٌ مشابهٌ من حيث المبدأ البرمجي لإنشاء تدوينة جديدة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_158" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/&lt;int:id&gt;/edit'</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=(</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">))</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> edit</span><span class="pun">(</span><span class="pln">id</span><span class="pun">):</span><span class="pln">
    post </span><span class="pun">=</span><span class="pln"> get_post</span><span class="pun">(</span><span class="pln">id</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">method </span><span class="pun">==</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">:</span><span class="pln">
        title </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'title'</span><span class="pun">]</span><span class="pln">
        content </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">]</span><span class="pln">

        </span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> title</span><span class="pun">:</span><span class="pln">
            flash</span><span class="pun">(</span><span class="str">'Title is required!'</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
            conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">
            conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'UPDATE posts SET title = ?, content = ?'</span><span class="pln">
                         </span><span class="str">' WHERE id = ?'</span><span class="pun">,</span><span class="pln">
                         </span><span class="pun">(</span><span class="pln">title</span><span class="pun">,</span><span class="pln"> content</span><span class="pun">,</span><span class="pln"> id</span><span class="pun">))</span><span class="pln">
            conn</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">
            conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> redirect</span><span class="pun">(</span><span class="pln">url_for</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">))</span><span class="pln">

    </span><span class="kwd">return</span><span class="pln"> render_template</span><span class="pun">(</span><span class="str">'edit.html'</span><span class="pun">,</span><span class="pln"> post</span><span class="pun">=</span><span class="pln">post</span><span class="pun">)</span></pre>

<p>
	نلاحظ من بناء الدالة السابقة تحديد التدوينة المراد تعديلها من خلال معرفة الرابط الخاص بها URL، وسيمرّر فلاسك رقم ID الخاص بها إلى دالة <code>()edit</code> وسيطًا، ومن ثم نضيف هذه القيمة إلى دالة استدعاء التدوينات <code>get_post()‎</code> لاستدعاء التدوينة ذات رقم ID المحدّد من قاعدة البيانات، ومن ثم تُضاف البيانات الجديدة إليها على هيئة طلب تدوينة جديدة POST وذلك ضمن الجملة الشرطية <code>`if request.method == 'POST</code>.
</p>

<p>
	وعلى نحوٍ مشابه لحالة إنشاء تدوينة جديدة، سنبدأ باستخلاص البيانات من الكائن <code>request.form</code>، بحيث تُعرض رسالة تنبيهية في حال كانت قيمة العنوان فارغة، وإلّا سنؤسس الاتصال مع قاعدة البيانات، ثم نحدَّث جدول التدوينات <code>posts</code> بالعنوان والمحتويات الجديدة للتدوينة ذات الرقم ID الموافق للرقم الذي حصلنا عليه من رابط التدوينة المُراد تعديلها.
</p>

<p>
	أمّا بالنسبة للطلب GET، فسيحدث إخراجٌ لقالب edit.html المُمرر إلى المتغير <code>post</code>، الذي يحتوي القيمة المُعادة من الدالة <code>get_post()‎</code>، والهدف من هذا الطلب استعراض محتوى وعنوان التدوينة الحالية ضمن صفحة التعديل.
</p>

<p>
	احفظ الملف وأغلقه، ثمّ أنشئ قالب edit.html جديد على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_6564_160" style="">
<span class="pln">(env)user@localhost:$ nano templates/edit.html</span></pre>

<p>
	اكتب الشيفرة البرمجية التالية في الملف الجديد:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_6564_162" style="">
<span class="pln">{% extends 'base.html' %}

{% block content %}
</span><span class="tag">&lt;h1&gt;</span><span class="pln">{% block title %} Edit "{{ post['title'] }}" {% endblock %}</span><span class="tag">&lt;/h1&gt;</span><span class="pln">

</span><span class="tag">&lt;form</span><span class="pln"> </span><span class="atn">method</span><span class="pun">=</span><span class="atv">"post"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"form-group"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;label</span><span class="pln"> </span><span class="atn">for</span><span class="pun">=</span><span class="atv">"title"</span><span class="tag">&gt;</span><span class="pln">Title</span><span class="tag">&lt;/label&gt;</span><span class="pln">
        </span><span class="tag">&lt;input</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"text"</span><span class="pln"> </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"title"</span><span class="pln"> </span><span class="atn">placeholder</span><span class="pun">=</span><span class="atv">"Post title"</span><span class="pln">
               </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"form-control"</span><span class="pln">
               </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"{{ request.form['title'] or post['title'] }}"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;/input&gt;</span><span class="pln">
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">

    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"form-group"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;label</span><span class="pln"> </span><span class="atn">for</span><span class="pun">=</span><span class="atv">"content"</span><span class="tag">&gt;</span><span class="pln">Content</span><span class="tag">&lt;/label&gt;</span><span class="pln">
        </span><span class="tag">&lt;textarea</span><span class="pln"> </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"content"</span><span class="pln"> </span><span class="atn">placeholder</span><span class="pun">=</span><span class="atv">"Post content"</span><span class="pln">
                  </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"form-control"</span><span class="tag">&gt;</span><span class="pln">{{ request.form['content'] or post['content'] }}</span><span class="tag">&lt;/textarea&gt;</span><span class="pln">
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"form-group"</span><span class="tag">&gt;</span><span class="pln">
        </span><span class="tag">&lt;button</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"submit"</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"btn btn-primary"</span><span class="tag">&gt;</span><span class="pln">Submit</span><span class="tag">&lt;/button&gt;</span><span class="pln">
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">
</span><span class="tag">&lt;/form&gt;</span><span class="pln">
</span><span class="tag">&lt;hr&gt;</span><span class="pln">
{% endblock %}</span></pre>

<p>
	اِحفظ الملف وأغلقه.
</p>

<p>
	تتبِّع هذه الشيفرة البرمجية نفس النمط السابق ما عدا التعليمتان:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_164" style="">
<span class="pun">{{</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'title'</span><span class="pun">]</span><span class="pln"> </span><span class="kwd">or</span><span class="pln"> post</span><span class="pun">[</span><span class="str">'title'</span><span class="pun">]‎</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">

</span><span class="pun">{{</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">form</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">]</span><span class="pln"> </span><span class="kwd">or</span><span class="pln"> post</span><span class="pun">[</span><span class="str">'content'</span><span class="pun">]‎</span><span class="pln"> </span><span class="pun">}}</span></pre>

<p>
	اللتان تعرضان البيانات المُخزّنة ضمن طلب التعديل في حال وجودها، وإلّا ستعرضان البيانات الموجودة في المتغير <code>post</code> المُمرّر إلى القالب الذي يحتوي على البيانات الحالية من قاعدة البيانات.
</p>

<p>
	باستخدام المتصفح، اذهب الآن إلى الرابط التالي، الذي سيمكنّك من تعديل التدوينة الأولى ذات الرقم 1:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_166" style="">
<span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="lit">1</span><span class="pun">/</span><span class="pln">edit</span></pre>

<p>
	فستظهر لك صفحة <strong>تعديل "التدوينة الأولى First Post"</strong> على النحو التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="102662" href="https://academy.hsoub.com/uploads/monthly_2022_07/fifth_img_step7b.png.68c55d1b3dc928ad5f3000ae90d2d5f3.png" rel=""><img alt="fifth_img_step7b.png" class="ipsImage ipsImage_thumbnailed" data-fileid="102662" data-unique="zzsadjtz9" src="https://academy.hsoub.com/uploads/monthly_2022_07/fifth_img_step7b.thumb.png.462b823181c5531112848b8b4982b52c.png" style="width: 600px; height: auto;"></a>
</p>

<p>
	عدّل التدوينة ثمّ أكّد النموذج وأرسله، وتأكّد من تحديث بيانات التدوينة.
</p>

<p>
	الآن، يجب إضافة رابط يشير إلى صفحة التعديل لكل تدوينة وذلك في صفحة المؤشر (الفهرس)، لذلك افتح ملف القالب index.html:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_6564_169" style="">
<span class="pln">(env)user@localhost:$ nano templates/index.html</span></pre>

<p>
	عدّل الملف ليصبح على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_6564_171" style="">
<span class="pln">{% extends 'base.html' %}

{% block content %}
    </span><span class="tag">&lt;h1&gt;</span><span class="pln">{% block title %} Welcome to FlaskBlog {% endblock %}</span><span class="tag">&lt;/h1&gt;</span><span class="pln">
    {% for post in posts %}
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('post', post_id=post['id']) }}"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;h2&gt;</span><span class="pln">{{ post['title'] }}</span><span class="tag">&lt;/h2&gt;</span><span class="pln">
        </span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;span</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"badge badge-primary"</span><span class="tag">&gt;</span><span class="pln">{{ post['created'] }}</span><span class="tag">&lt;/span&gt;</span><span class="pln">
        </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ url_for('edit', id=post['id']) }}"</span><span class="tag">&gt;</span><span class="pln">
            </span><span class="tag">&lt;span</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"badge badge-warning"</span><span class="tag">&gt;</span><span class="pln">Edit</span><span class="tag">&lt;/span&gt;</span><span class="pln">
        </span><span class="tag">&lt;/a&gt;</span><span class="pln">
        </span><span class="tag">&lt;hr&gt;</span><span class="pln">
    {% endfor %}
{% endblock %}</span></pre>

<p>
	ويمكنك إضافة وسم <code>&lt;a&gt;</code> للربط مع الدالة <code>()edit</code>، مرورًا بالقيمة <code>post['id']‎</code> لربط صفحة التعديل لكلِّ تدوينة مع الرابط <code>Edit</code>.
</p>

<h2>
	حذف تدوينة
</h2>

<p>
	أحيانًا، يصبح بقاء بعض التدوينات منشورةً للعموم أمرًا غير مطلوب، ومن هنا تأتي أهمية إمكانية حذف التدوينات المنشورة أصلًا، وفيما يلي سنتعلّم كيفيّة إضافة آلية لحذف هذه التدوينات من التطبيق.
</p>

<p>
	بدايةً، سنضيف وجهة جديدة للحذف، هو <code>‎/ID/delete</code> يتعامل مع الطلبات من النوع POST بما يشبه الدالة <code>edit()‎</code> الذي أنشأناها سابقًا، إذ ستستقبل دالة الحذف <code>delete()‎</code> رقم معرّف التدوينة ID لحذفها من الرابط URL.
</p>

<p>
	الآن، افتح الملف app.py:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_173" style="">
<span class="pun">(</span><span class="pln">env</span><span class="pun">)</span><span class="pln">user@localhost</span><span class="pun">:</span><span class="pln">$ nano app</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	أضِف الدالة التالية إلى نهاية الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_175" style="">
<span class="com"># ....</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/&lt;int:id&gt;/delete'</span><span class="pun">,</span><span class="pln"> methods</span><span class="pun">=(</span><span class="str">'POST'</span><span class="pun">,))</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> delete</span><span class="pun">(</span><span class="pln">id</span><span class="pun">):</span><span class="pln">
    post </span><span class="pun">=</span><span class="pln"> get_post</span><span class="pun">(</span><span class="pln">id</span><span class="pun">)</span><span class="pln">
    conn </span><span class="pun">=</span><span class="pln"> get_db_connection</span><span class="pun">()</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">execute</span><span class="pun">(</span><span class="str">'DELETE FROM posts WHERE id = ?'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">id</span><span class="pun">,))</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">commit</span><span class="pun">()</span><span class="pln">
    conn</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
    flash</span><span class="pun">(</span><span class="str">'"{}" was successfully deleted!'</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="pln">post</span><span class="pun">[</span><span class="str">'title'</span><span class="pun">]))</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> redirect</span><span class="pun">(</span><span class="pln">url_for</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">))</span></pre>

<p>
	تتعامل هذه الدالة فقط مع الطلبات الواردة بطريقة POST؛ وهذا يعني أنك إذا انتقلت إلى الوجهة <code>‎/ID/delete</code> في المتصفح، ستحصل على خطأ، لأن المتصفحات تستخدم طريقة GET افتراضيًا للطلبات؛ ولكن يمكنك الوصول إلى هذه الوجهة من خلال نموذج الإدخال الذي يرسل طلبًا بطريقة POST يتضمن قيمة معرّف التدوينة المُراد حذفها، لتستقبل دالة فلاسك هذه قيمة المعرّف ID وتستخدمها لجلب التدوينة من قاعدة البيانات باستخدام دالة <code>get_post()‎</code>.
</p>

<p>
	افتح بعد ذلك اتصالًا مع قاعدة البيانات ونفِّذ تعليمة SQL التالية لحذف التدوينة:
</p>

<pre class="ipsCode">
DELETE FROM
</pre>

<p>
	يمكنك تأكيد وإرسال التعديلات إلى قاعدة البيانات وإغلاق الاتصال أثناء إظهار رسالة وامضة تُعلم المستخدم بإتمام عملية حذف التدوينة بنجاح، ثم يُعاد توجيه المستخدم إلى الصفحة الرئيسية.
</p>

<p>
	لاحظ أننا هنا لا نخرج render ملف قالب، وإنمّا نضيف فقط زر أوامر "Delete" إلى صفحة تعديل التدوينة.
</p>

<p>
	الآن افتح ملف القالب edit.html:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_6564_177" style="">
<span class="pln">(env)user@localhost:$ nano templates/edit.html</span></pre>

<p>
	ثم أضف وسم النموذج <code>&lt;form&gt;</code> بعد وسم إظهار الفاصل الأفقي <code>&lt;hr&gt;</code> تماماً بعد السطر البرمجي <code>{% endblock %}</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_6564_179" style="">
<span class="tag">&lt;hr&gt;</span><span class="pln">
</span><span class="tag">&lt;form</span><span class="pln"> </span><span class="atn">action</span><span class="pun">=</span><span class="atv">"{{ url_for('delete', id=post['id']) }}"</span><span class="pln"> </span><span class="atn">method</span><span class="pun">=</span><span class="atv">"POST"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;input</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"submit"</span><span class="pln"> </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"Delete Post"</span><span class="pln">
            </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"btn btn-danger btn-sm"</span><span class="pln">
            </span><span class="atn">onclick</span><span class="pun">=</span><span class="atv">"</span><span class="kwd">return</span><span class="pln"> confirm</span><span class="pun">(</span><span class="str">'Are you sure you want to delete this post?'</span><span class="pun">)</span><span class="atv">"</span><span class="tag">&gt;</span><span class="pln">
</span><span class="tag">&lt;/form&gt;</span><span class="pln">
{% endblock %}</span></pre>

<p>
	إذ استخدمنا التابع <code>confirm()‎</code> لعرض رسالة تأكيد قبل إرسال الطلب.
</p>

<p>
	الآن انتقل إلى صفحة تعديل تدوينة في المتصفح مجددًا وجرّب حذفها:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6564_181" style="">
<span class="pln">http</span><span class="pun">://</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="lit">1</span><span class="pun">/</span><span class="pln">edit</span></pre>

<p>
	ومع الانتهاء من هذه الخطوة، ستكون شيفرة المشروع مشابهةً لشيفرة <a href="https://github.com/do-community/flask_blog" rel="external nofollow">تدوينة فلاسك على غيت هب</a>، وبهذا سيتمكّن مستخدمو التطبيق من إنشاء تدوينات جديدة وإضافتها إلى قاعدة البيانات، وكذلك تعديل أو حذف التدوينات الموجودة أصلًا.
</p>

<h2>
	الخاتمة
</h2>

<p>
	قدّمنا في هذا المقال المفاهيم الأساسية لإطار عمل فلاسك في بايثون، وتعلمّت كيفيّة إنشاء تطبيق ويب بسيط وتشغيله في خادم تطوير، وآلية تمكين المستخدم من التواصل مع الخادم وتزويده ببياناتٍ مخصصة باستخدام محدِّدات الرابط URL ونماذج الادخال؛ كما اسُتخدم محرك قوالب جينجا jinja لتوفير ملفات HTML ذات المحتوى القابل لإعادة الاستخدام لكتابة منطق التطبيق فيها. ومع نهاية هذا المقال سيكون لديك مدونة ويب بكامل المميزات، والتي تتعامل مع قاعدة بيانات SQLite لإنشاء وعرض أو تعديل أو حذف التدوينات باستخدام لغة بايثون واستعلامات SQL.
</p>

<p>
	يمكنك أيضًا تطوير هذا التطبيق بإضافة ميزات تصريح وصول المستخدمين لتقييد عملية إنشاء وتعديل التدوينات لتكون متاحةً فقط للمستخدمين الذين لديهم حسابات، كما يمكنك مثلًا إضافة ميزة التعليقات وتضمين الكلمات المفتاحية لكل تدوينة، أو ميزة رفع الملفات التي تمكّن المستخدمين من إضافة الصور إلى محتوى التدوينة.
</p>

<p>
	يوجد العديد من الإضافات المطوَّرة من قبل المبرمجين لفلاسك والتي يمكنك استخدامها لتسهيل عملية التطوير، ومنها:
</p>

<ul>
<li>
		Flask-Login: لإدارة جلسة المستخدم ومعالجة عمليات تسجيل الدخول وتسجيل الخروج وتذكّر معلومات المستخدمين الذين سجلوا الدخول سابقًا.
	</li>
	<li>
		Flask-SQLAlchemy: لتسهيل استخدام فلاسك مع مكتبة <a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%AC%D9%87%D9%8A%D8%B2-%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-flask-sqlalchemy-r508/" rel="">SQLAlchemy</a>، والتي هي حزمة SQL في بايثون للتعامل مع قواعد بيانات SQL.
	</li>
	<li>
		Flask-Mail: لتنفيذ مهام إرسال رسائل البريد الإلكتروني من خلال تطبيق فلاسك.
	</li>
</ul>
<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-make-a-web-application-using-flask-in-python-3" rel="external nofollow">How To Make a Web Application Using Flask in Python 3</a> لصاحبه Abdelhadi Dyouri.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A3%D8%B7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%81%D9%8A-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D9%81%D9%84%D8%A7%D8%B3%D9%83-%D9%86%D9%85%D9%88%D8%B0%D8%AC%D8%A7-r1547/" rel="">استخدام أطر العمل في برمجة تطبيقات الويب: فلاسك نموذجا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-flask-r333/" rel="">مدخل إلى تطوير تطبيقات الويب باستخدام إطار العمل Flask</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1622</guid><pubDate>Mon, 04 Jul 2022 16:00:00 +0000</pubDate></item></channel></rss>
