<?xml version="1.0"?>
<rss version="2.0"><channel><title>&#x627;&#x644;&#x628;&#x631;&#x645;&#x62C;&#x629;: Django</title><link>https://academy.hsoub.com/programming/python/django/?d=2</link><description>&#x627;&#x644;&#x628;&#x631;&#x645;&#x62C;&#x629;: Django</description><language>ar</language><item><title>&#x625;&#x636;&#x627;&#x641;&#x629; &#x628;&#x639;&#x636; &#x627;&#x644;&#x645;&#x64A;&#x632;&#x627;&#x62A; &#x627;&#x644;&#x645;&#x62A;&#x642;&#x62F;&#x645;&#x629; &#x625;&#x644;&#x649; &#x645;&#x62F;&#x648;&#x646;&#x629; &#x62C;&#x627;&#x646;&#x63A;&#x648;</title><link>https://academy.hsoub.com/programming/python/django/%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-%D8%A8%D8%B9%D8%B6-%D8%A7%D9%84%D9%85%D9%8A%D8%B2%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%AA%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D9%85%D8%AF%D9%88%D9%86%D8%A9-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2482/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2025_01/--.png.55fdc7005992617f4d96958d70676ad6.png" /></p>
<p>
	سنضيف في هذا المقال بعض الميزات المتقدمة الاختيارية لموقع مدونة جانغو Django الخاص بنا، والذي أنشأناه في المقالات السابقة من <a href="https://academy.hsoub.com/tags/%D8%AC%D8%A7%D9%86%D8%BA%D9%88%20%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/" rel="">هذه السلسلة</a>، بما في ذلك ميزة ترقيم الصفحات Pagination، والمنشورات ذات الصلة Related Posts، وميزة البحث Search.
</p>

<h2 id="pagination">
	إنشاء ترقيم الصفحات Pagination في جانغو
</h2>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="165033" href="https://academy.hsoub.com/uploads/monthly_2025_01/01---.png.487c61bc44cd1a06de4e63db4a11e53b.png" rel=""><img alt="01 التنقل بين الصفحات" class="ipsImage ipsImage_thumbnailed" data-fileid="165033" data-unique="v5ixl8yrw" style="width: 300px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2025_01/01---.thumb.png.f6bca7bc1053c307b11e77adaa3707ef.png"> </a>
</p>

<p>
	قد يكون إنشاء مرقّم صفحات فكرة جيدة عند إضافة الكثير من المنشورات إلى مدونتنا، فنحن لا نريد عرض عدد كبير جدًا من المنشورات في صفحة واحدة، لذا يجب إضافة شيفرة برمجية إضافية إلى دوال العرض view functions. لنأخذ على سبيل المثال عرض الصفحة الرئيسية <code>home</code>، حيث يجب أولًا أن نستورد بعض الحزم الضرورية لتفعيل الترقيم كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1683_11" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">core</span><span class="pun">.</span><span class="pln">paginator </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Paginator</span><span class="pun">,</span><span class="pln"> </span><span class="typ">EmptyPage</span><span class="pun">,</span><span class="pln"> </span><span class="typ">PageNotAnInteger</span></pre>

<p>
	نحدّث بعد ذلك العرض <code>home</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1683_17" style=""><span class="kwd">def</span><span class="pln"> home</span><span class="pun">(</span><span class="pln">request</span><span class="pun">):</span><span class="pln">
   site </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Site</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">first</span><span class="pun">()</span><span class="pln">
   categories </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Category</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
   tags </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Tag</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
   featured_post </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">is_featured</span><span class="pun">=</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="com"># إضافة مرقم الصفحات</span><span class="pln">
   page </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">GET</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="str">""</span><span class="pun">)</span><span class="pln">  </span><span class="com"># الحصول على رقم الصفحة الحالية</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">objects</span><span class="pun">.</span><span class="pln">all</span><span class="pun">().</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">is_published</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
   paginator </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Paginator</span><span class="pun">(</span><span class="pln">posts</span><span class="pun">,</span><span class="pln"> n</span><span class="pun">)</span><span class="pln">  </span><span class="com"># ‫عرض n منشور لكل صفحة</span><span class="pln">

   </span><span class="kwd">try</span><span class="pun">:</span><span class="pln">
       posts </span><span class="pun">=</span><span class="pln"> paginator</span><span class="pun">.</span><span class="pln">page</span><span class="pun">(</span><span class="pln">page</span><span class="pun">)</span><span class="pln">
   </span><span class="kwd">except</span><span class="pln"> </span><span class="typ">PageNotAnInteger</span><span class="pun">:</span><span class="pln">
       posts </span><span class="pun">=</span><span class="pln"> paginator</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">
   </span><span class="kwd">except</span><span class="pln"> </span><span class="typ">EmptyPage</span><span class="pun">:</span><span class="pln">
       posts </span><span class="pun">=</span><span class="pln"> paginator</span><span class="pun">.</span><span class="pln">page</span><span class="pun">(</span><span class="pln">paginator</span><span class="pun">.</span><span class="pln">num_pages</span><span class="pun">)</span><span class="pln">

   </span><span class="kwd">return</span><span class="pln"> render</span><span class="pun">(</span><span class="pln">
       request</span><span class="pun">,</span><span class="pln">
       </span><span class="str">"home.html"</span><span class="pun">,</span><span class="pln">
       </span><span class="pun">{</span><span class="pln">
           </span><span class="str">"site"</span><span class="pun">:</span><span class="pln"> site</span><span class="pun">,</span><span class="pln">
           </span><span class="str">"posts"</span><span class="pun">:</span><span class="pln"> posts</span><span class="pun">,</span><span class="pln">
           </span><span class="str">"categories"</span><span class="pun">:</span><span class="pln"> categories</span><span class="pun">,</span><span class="pln">
           </span><span class="str">"tags"</span><span class="pun">:</span><span class="pln"> tags</span><span class="pun">,</span><span class="pln">
           </span><span class="str">"featured_post"</span><span class="pun">:</span><span class="pln">featured_post
       </span><span class="pun">},</span><span class="pln">
   </span><span class="pun">)</span></pre>

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

<p>
	بعد أن انتهينا من إعداد الترقيم في الكود البرمجي للعرض، يتوجب علينا إضافة مرقّم الصفحات في القالب Template لكي يظهر في صفحة قائمة المنشورات، أي سنضيفه في الملف <code>templates/vendor/list.html</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1683_19" style=""><span class="com">&lt;!-- مرقم الصفحات --&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">"isolate inline-flex -space-x-px rounded-md mx-auto my-5 max-h-10"</span><span class="pln">
 </span><span class="atn">aria-label</span><span class="pun">=</span><span class="atv">"Pagination"</span><span class="pln">
</span><span class="tag">&gt;</span><span class="pln">
 {% if posts.has_previous %}
 </span><span class="tag">&lt;a</span><span class="pln">
   </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"?page={{ posts.previous_page_number }}"</span><span class="pln">
   </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"relative inline-flex items-center rounded-l-md border border-gray-300 bg-white px-2 py-2 text-sm font-medium text-gray-500 hover:bg-gray-50 focus:z-20"</span><span class="pln">
 </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">"sr-only"</span><span class="tag">&gt;</span><span class="pln">Previous</span><span class="tag">&lt;/span&gt;</span><span class="pln">
   </span><span class="com">&lt;!-- Heroicon name: mini/chevron-left --&gt;</span><span class="pln">
   </span><span class="tag">&lt;svg</span><span class="pln">
     </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"h-5 w-5"</span><span class="pln">
     </span><span class="atn">xmlns</span><span class="pun">=</span><span class="atv">"http://www.w3.org/2000/svg"</span><span class="pln">
     </span><span class="atn">viewBox</span><span class="pun">=</span><span class="atv">"0 0 20 20"</span><span class="pln">
     </span><span class="atn">fill</span><span class="pun">=</span><span class="atv">"currentColor"</span><span class="pln">
     </span><span class="atn">aria-hidden</span><span class="pun">=</span><span class="atv">"true"</span><span class="pln">
   </span><span class="tag">&gt;</span><span class="pln">
     </span><span class="tag">&lt;path</span><span class="pln">
       </span><span class="atn">fill-rule</span><span class="pun">=</span><span class="atv">"evenodd"</span><span class="pln">
       </span><span class="atn">d</span><span class="pun">=</span><span class="atv">"M12.79 5.23a.75.75 0 01-.02 1.06L8.832 10l3.938 3.71a.75.75 0 11-1.04 1.08l-4.5-4.25a.75.75 0 010-1.08l4.5-4.25a.75.75 0 011.06.02z"</span><span class="pln">
       </span><span class="atn">clip-rule</span><span class="pun">=</span><span class="atv">"evenodd"</span><span class="pln">
     </span><span class="tag">/&gt;</span><span class="pln">
   </span><span class="tag">&lt;/svg&gt;</span><span class="pln">
 </span><span class="tag">&lt;/a&gt;</span><span class="pln">
 {% endif %}

 {% for i in posts.paginator.page_range %}
 {% if posts.number == i %}
 </span><span class="tag">&lt;a</span><span class="pln">
   </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"?page={{ i }}"</span><span class="pln">
   </span><span class="atn">aria-current</span><span class="pun">=</span><span class="atv">"page"</span><span class="pln">
   </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"relative z-10 inline-flex items-center border border-blue-500 bg-blue-50 px-4 py-2 text-sm font-medium text-blue-600 focus:z-20"</span><span class="pln">
   </span><span class="tag">&gt;</span><span class="pln">{{ i }}</span><span class="tag">&lt;/a</span><span class="pln">
 </span><span class="tag">&gt;</span><span class="pln">
 {% else %}
 </span><span class="tag">&lt;a</span><span class="pln">
   </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"?page={{ i }}"</span><span class="pln">
   </span><span class="atn">aria-current</span><span class="pun">=</span><span class="atv">"page"</span><span class="pln">
   </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"relative inline-flex items-center border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-500 hover:bg-gray-50 focus:z-20"</span><span class="pln">
   </span><span class="tag">&gt;</span><span class="pln">{{ i }}</span><span class="tag">&lt;/a</span><span class="pln">
 </span><span class="tag">&gt;</span><span class="pln">
 {% endif %}
 {% endfor %}

 {% if posts.has_next %}
 </span><span class="tag">&lt;a</span><span class="pln">
   </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"?page={{ posts.next_page_number }}"</span><span class="pln">
   </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"relative inline-flex items-center rounded-r-md border border-gray-300 bg-white px-2 py-2 text-sm font-medium text-gray-500 hover:bg-gray-50 focus:z-20"</span><span class="pln">
 </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">"sr-only"</span><span class="tag">&gt;</span><span class="pln">Next</span><span class="tag">&lt;/span&gt;</span><span class="pln">
   </span><span class="com">&lt;!-- Heroicon name: mini/chevron-right --&gt;</span><span class="pln">
   </span><span class="tag">&lt;svg</span><span class="pln">
     </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"h-5 w-5"</span><span class="pln">
     </span><span class="atn">xmlns</span><span class="pun">=</span><span class="atv">"http://www.w3.org/2000/svg"</span><span class="pln">
     </span><span class="atn">viewBox</span><span class="pun">=</span><span class="atv">"0 0 20 20"</span><span class="pln">
     </span><span class="atn">fill</span><span class="pun">=</span><span class="atv">"currentColor"</span><span class="pln">
     </span><span class="atn">aria-hidden</span><span class="pun">=</span><span class="atv">"true"</span><span class="pln">
   </span><span class="tag">&gt;</span><span class="pln">
     </span><span class="tag">&lt;path</span><span class="pln">
       </span><span class="atn">fill-rule</span><span class="pun">=</span><span class="atv">"evenodd"</span><span class="pln">
       </span><span class="atn">d</span><span class="pun">=</span><span class="atv">"M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z"</span><span class="pln">
       </span><span class="atn">clip-rule</span><span class="pun">=</span><span class="atv">"evenodd"</span><span class="pln">
     </span><span class="tag">/&gt;</span><span class="pln">
   </span><span class="tag">&lt;/svg&gt;</span><span class="pln">
 </span><span class="tag">&lt;/a&gt;</span><span class="pln">
 {% endif %}
</span><span class="tag">&lt;/nav&gt;</span></pre>

<p>
	علينا تطبيق الشيء نفسه لجميع الصفحات التي تعرض قائمة منشورات مثل صفحة الوسوم والفئات وصفحة البحث.
</p>

<h2 id="relatedposts">
	عرض المنشورات ذات الصلة Related Posts في جانغو
</h2>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="165034" href="https://academy.hsoub.com/uploads/monthly_2025_01/02___.png.fdc76cb4a3a39e48e728a32c0b92a56e.png" rel=""><img alt="02 المشاركات ذات الصلة" class="ipsImage ipsImage_thumbnailed" data-fileid="165034" data-unique="d1r3sr5ff" src="https://academy.hsoub.com/uploads/monthly_2025_01/02___.thumb.png.85e48e21778a6c96ecce2caee4402312.png"> </a>
</p>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1683_21" style=""><span class="kwd">def</span><span class="pln"> post</span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> slug</span><span class="pun">):</span><span class="pln">
   site </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Site</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">first</span><span class="pun">()</span><span class="pln">
   requested_post </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">slug</span><span class="pun">=</span><span class="pln">slug</span><span class="pun">)</span><span class="pln">
   categories </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Category</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
   tags </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Tag</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">all</span><span class="pun">()</span><span class="pln">

   </span><span class="com"># المنشورات ذات الصلة</span><span class="pln">
   </span><span class="com">## الحصول على جميع الوسوم المتعلقة بهذا المقال</span><span class="pln">
   post_tags </span><span class="pun">=</span><span class="pln"> requested_post</span><span class="pun">.</span><span class="pln">tag</span><span class="pun">.</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
   </span><span class="com">## ترشيح جميع المنشورات التي تحتوي على وسوم متعلقة بالمنشور الحالي، واستبعاد المنشور الحالي</span><span class="pln">
   related_posts_ids </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
       </span><span class="typ">Post</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
       </span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">tag__in</span><span class="pun">=</span><span class="pln">post_tags</span><span class="pun">)</span><span class="pln">
       </span><span class="pun">.</span><span class="pln">exclude</span><span class="pun">(</span><span class="pln">id</span><span class="pun">=</span><span class="pln">requested_post</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">values_list</span><span class="pun">(</span><span class="str">"id"</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">)</span><span class="pln">

   related_posts </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">pk__in</span><span class="pun">=</span><span class="pln">related_posts_ids</span><span class="pun">)</span><span class="pln">

   </span><span class="kwd">return</span><span class="pln"> render</span><span class="pun">(</span><span class="pln">
       request</span><span class="pun">,</span><span class="pln">
       </span><span class="str">"post.html"</span><span class="pun">,</span><span class="pln">
       </span><span class="pun">{</span><span class="pln">
           </span><span class="str">"site"</span><span class="pun">:</span><span class="pln"> site</span><span class="pun">,</span><span class="pln">
           </span><span class="str">"post"</span><span class="pun">:</span><span class="pln"> requested_post</span><span class="pun">,</span><span class="pln">
           </span><span class="str">"categories"</span><span class="pun">:</span><span class="pln"> categories</span><span class="pun">,</span><span class="pln">
           </span><span class="str">"tags"</span><span class="pun">:</span><span class="pln"> tags</span><span class="pun">,</span><span class="pln">
           </span><span class="str">"related_posts"</span><span class="pun">:</span><span class="pln"> related_posts</span><span class="pun">,</span><span class="pln">
       </span><span class="pun">},</span><span class="pln">
   </span><span class="pun">)</span></pre>

<p>
	قد تكون الشيفرة البرمجية السابقة صعبة الفهم بعض الشيء، دعنا نحللها ونشرحها بالتفصيل، حيث يمثل السطر 3 الحصول على المنشور المطلوب باستخدام المتغير <code>slug</code>، ويمثل السطر 9 الحصول على جميع الوسوم Tags التي تعود إلى المنشور المطلوب.
</p>

<p>
	تصبح الأمور أعقد في الأسطر من 11 إلى 16، حيث يسترد التابع <code>Post.objects.all()‎</code> جميع المنشورات من قاعدة البيانات، ثم يسترد التابع <code>filter(tag__in=post_tags)‎</code> جميع المنشورات التي تحتوي على وسوم مرتبطة بالمنشور الحالي، ولكن لدينا مشكلتان، إذ سيُضمَّن المنشور الحالي في مجموعة الاستعلام، لذا سنستخدم التابع <code>exclude(id=requested_post.id)‎</code> لاستبعاد المنشور الحالي.
</p>

<p>
	لنبسّط الآن المشكلة الثانية، ولنفترض أن لدينا السيناريو التالي مع وجود ثلاث منشورات وثلاثة وسوم:
</p>

<table>
	<thead>
		<tr>
			<th>
				معرّف الوسم<br>
				Tag ID
			</th>
			<th>
				اسم الوسم<br>
				Tag Name
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				1
			</td>
			<td>
				Tag 1
			</td>
		</tr>
		<tr>
			<td>
				2
			</td>
			<td>
				Tag 2
			</td>
		</tr>
		<tr>
			<td>
				3
			</td>
			<td>
				Tag 3
			</td>
		</tr>
	</tbody>
</table>

<table>
	<thead>
		<tr>
			<th>
				معرّف المنشور<br>
				Post ID
			</th>
			<th>
				اسم المنشور<br>
				Post Name
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				1
			</td>
			<td>
				Post 1
			</td>
		</tr>
		<tr>
			<td>
				2
			</td>
			<td>
				Post 2
			</td>
		</tr>
		<tr>
			<td>
				3
			</td>
			<td>
				Post 3
			</td>
		</tr>
	</tbody>
</table>

<p>
	وتكون العلاقة بين المنشورات والوسوم علاقة متعدد إلى متعدد Many-to-Many.
</p>

<table>
	<thead>
		<tr>
			<th>
				معرّف الوسم<br>
				Tag ID
			</th>
			<th>
				معرّف المنشور<br>
				Post ID
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				1
			</td>
			<td>
				2
			</td>
		</tr>
		<tr>
			<td>
				1
			</td>
			<td>
				3
			</td>
		</tr>
		<tr>
			<td>
				1
			</td>
			<td>
				1
			</td>
		</tr>
		<tr>
			<td>
				2
			</td>
			<td>
				1
			</td>
		</tr>
		<tr>
			<td>
				2
			</td>
			<td>
				2
			</td>
		</tr>
		<tr>
			<td>
				2
			</td>
			<td>
				3
			</td>
		</tr>
		<tr>
			<td>
				3
			</td>
			<td>
				2
			</td>
		</tr>
	</tbody>
</table>

<table>
	<thead>
		<tr>
			<th>
				معرّف المنشور<br>
				Post ID
			</th>
			<th>
				معرّف الوسم<br>
				Tag ID
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				1
			</td>
			<td>
				1
			</td>
		</tr>
		<tr>
			<td>
				1
			</td>
			<td>
				2
			</td>
		</tr>
		<tr>
			<td>
				2
			</td>
			<td>
				1
			</td>
		</tr>
		<tr>
			<td>
				2
			</td>
			<td>
				2
			</td>
		</tr>
		<tr>
			<td>
				2
			</td>
			<td>
				3
			</td>
		</tr>
		<tr>
			<td>
				3
			</td>
			<td>
				1
			</td>
		</tr>
		<tr>
			<td>
				3
			</td>
			<td>
				2
			</td>
		</tr>
	</tbody>
</table>

<p>
	لنفترض أن المنشور الحالي هو المنشور 2، وبالتالي ستكون الوسوم المتعلقة به هي 1 و 2 و 3، حيث سيذهب جانغو أولًا إلى الوسم 1 عند استخدام التابع <code>filter(tag__in=post_tags)‎</code>، ثم سيجد المنشورات المتعلقة بالوسم 1، والتي هي المنشورات 2 و 3 و 1، ثم يذهب إلى الوسم 2، ويجد المنشورات المتعلقة بالوسم 2، وينتقل أخيرًا إلى الوسم 3.
</p>

<p>
	يعيد التابع <code>filter(tag__in=post_tags)‎</code> بعد ذلك في النهاية القائمة <code>[2,3,1,1,2,3,2]</code>، وستُعاد القائمة <code>[3,1,1,3]</code> بعد تنفيذ التابع <code>exclude()‎</code>، ولا نريد ذلك أيضًا، حيث نحتاج لإيجاد طريقة للتخلص من التكرارات، لذا يجب استخدام التابع <code>values_list('id')‎</code> لتمرير معرّفات<code> </code>المنشورات إلى المتغير <code>related_posts_ids</code>، ثم نستخدم هذا المتغير لاسترداد المنشورات ذات الصلة، وبذلك نتخلص من التكرار.
</p>

<p>
	يمكننا عرض المنشورات ذات الصلة في القالب المقابل كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1683_23" style=""><span class="pln">   </span><span class="pun">&lt;!--</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"> </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">"grid grid-cols-3 gap-4 my-5"</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"> related_posts </span><span class="pun">%}</span><span class="pln">
     </span><span class="pun">&lt;!--</span><span class="pln"> </span><span class="pun">المنشور</span><span class="pln"> </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">"mb-4 ring-1 ring-slate-200 rounded-md h-fit hover:shadow-md"</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 'post' post.slug %}"</span><span class="pln">
         </span><span class="pun">&gt;&lt;</span><span class="pln">img
           </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"rounded-t-md object-cover h-60 w-full"</span><span class="pln">
           src</span><span class="pun">=</span><span class="str">"{{ post.featured_image.url }}"</span><span class="pln">
           alt</span><span class="pun">=</span><span class="str">"..."</span><span class="pln">
       </span><span class="pun">/&gt;&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="kwd">class</span><span class="pun">=</span><span class="str">"m-4 grid gap-2"</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">"text-sm text-gray-500"</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">created_at</span><span class="pun">|</span><span class="pln">date</span><span class="pun">:</span><span class="str">"F j, o"</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="pun">&gt;</span><span class="pln">
         </span><span class="pun">&lt;</span><span class="pln">h2 </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"text-lg font-bold"</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">h2</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">"text-base"</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">content</span><span class="pun">|</span><span class="pln">striptags</span><span class="pun">|</span><span class="pln">truncatewords</span><span class="pun">:</span><span class="lit">30</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">a
           </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"bg-blue-500 hover:bg-blue-700 rounded-md p-2 text-white uppercase text-sm font-semibold font-sans w-fit focus:ring"</span><span class="pln">
           href</span><span class="pun">=</span><span class="str">"{% url 'post' post.slug %}"</span><span class="pln">
           </span><span class="pun">&gt;</span><span class="typ">Read</span><span class="pln"> more </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="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></pre>

<div class="banner-container ipsBox ipsPadding">
	<div class="inner-banner-container">
		<p class="banner-heading">
			دورة تطوير التطبيقات باستخدام لغة Python
		</p>

		<p class="banner-subtitle">
			احترف تطوير التطبيقات مع أكاديمية حسوب والتحق بسوق العمل فور انتهائك من الدورة
		</p>

		<div>
			<a class="ipsButton ipsButton_large ipsButton_primary ipsButton_important" href="https://academy.hsoub.com/learn/python-application-development" rel="">اشترك الآن</a>
		</div>
	</div>

	<div class="banner-img">
		<a href="https://academy.hsoub.com/learn/python-application-development" rel=""><img alt="دورة تطوير التطبيقات باستخدام لغة Python" src="https://academy.hsoub.com/learn/assets/images/courses/python-application-development.png"></a>
	</div>
</div>

<h2 id="">
	تطبيق عملية البحث في جانغو
</h2>

<p>
	سنعمل أخيرًا على إضافة ميزة البحث لمدونتنا، حيث نحتاج إلى استمارة بحث Search Form في الواجهة الأمامية، والتي ترسل استعلام بحث إلى العرض، تسترد دالة العرض السجلات الملائمة من قاعدة البيانات، وتعيد صفحة بحث تعرض النتيجة.
</p>

<h3 id="searchform">
	استمارة البحث Search Form
</h3>

<p>
	لنضيف أولًا استمارة بحث إلى الشريط الجانبي Sidebar في الملف <code>templates/vendor/sidebar.html</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1683_27" style=""><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"col-span-1"</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">"border rounded-md mb-4"</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">"bg-slate-200 p-4"</span><span class="tag">&gt;</span><span class="pln">Search</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">"p-4"</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 'search' %}"</span><span class="pln"> </span><span class="atn">method</span><span class="pun">=</span><span class="atv">"POST"</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"grid grid-cols-4 gap-2"</span><span class="tag">&gt;</span><span class="pln">
       {% csrf_token %}
       </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">"q"</span><span class="pln">
         </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"search"</span><span class="pln">
         </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"border rounded-md w-full focus:ring p-2 col-span-3"</span><span class="pln">
         </span><span class="atn">placeholder</span><span class="pun">=</span><span class="atv">"Search something..."</span><span class="pln">
       </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">"bg-blue-500 hover:bg-blue-700 rounded-md p-2 text-white uppercase font-semibold font-sans w-full focus:ring col-span-1"</span><span class="pln">
       </span><span class="tag">&gt;</span><span class="pln">
         Search
       </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;/div&gt;</span><span class="pln">
 . . .
</span><span class="tag">&lt;/div&gt;</span></pre>

<p>
	لاحظ في الأسطر من 7 إلى 13 السمة <code>name</code> لحقل الإدخال <code><a href="https://wiki.hsoub.com/HTML/input" rel="external">input</a></code>، حيث سنسميها <code>q</code>، سيُربط دخل المستخدم بالمتغير  <code>q</code> ويُرسَل للواجهة الخلفية.
</p>

<p>
	إذا نقرنا على الزر في السطر 5، فسيُوجَّه المستخدم إلى عنوان URL بالاسم <code>search</code>، لذا يجب تسجيل نمط عنوان URL المقابل كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1683_33" style=""><span class="pln">path</span><span class="pun">(</span><span class="str">'search'</span><span class="pun">,</span><span class="pln"> views</span><span class="pun">.</span><span class="pln">search</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">=</span><span class="str">'search'</span><span class="pun">),</span></pre>

<h3 id="searchview">
	عرض البحث Search View
</h3>

<p>
	سيكون عرض البحث Search View كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1683_35" style=""><span class="kwd">def</span><span class="pln"> search</span><span class="pun">(</span><span class="pln">request</span><span class="pun">):</span><span class="pln">
   site </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Site</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">first</span><span class="pun">()</span><span class="pln">
   categories </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Category</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
   tags </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Tag</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">all</span><span class="pun">()</span><span class="pln">

   query </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">POST</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="str">"q"</span><span class="pun">,</span><span class="pln"> </span><span class="str">""</span><span class="pun">)</span><span class="pln">
   </span><span class="kwd">if</span><span class="pln"> query</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">objects</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">is_published</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">).</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">title__icontains</span><span class="pun">=</span><span class="pln">query</span><span class="pun">)</span><span class="pln">
   </span><span class="kwd">else</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="kwd">return</span><span class="pln"> render</span><span class="pun">(</span><span class="pln">
       request</span><span class="pun">,</span><span class="pln">
       </span><span class="str">"search.html"</span><span class="pun">,</span><span class="pln">
       </span><span class="pun">{</span><span class="pln">
           </span><span class="str">"site"</span><span class="pun">:</span><span class="pln"> site</span><span class="pun">,</span><span class="pln">
           </span><span class="str">"categories"</span><span class="pun">:</span><span class="pln"> categories</span><span class="pun">,</span><span class="pln">
           </span><span class="str">"tags"</span><span class="pun">:</span><span class="pln"> tags</span><span class="pun">,</span><span class="pln">
           </span><span class="str">"posts"</span><span class="pun">:</span><span class="pln"> posts</span><span class="pun">,</span><span class="pln">
           </span><span class="str">"query"</span><span class="pun">:</span><span class="pln"> query</span><span class="pun">,</span><span class="pln">
       </span><span class="pun">},</span><span class="pln">
   </span><span class="pun">)</span></pre>

<h3 id="searchtemplate">
	قالب البحث Search Template
</h3>

<p>
	لإنشاء قالب البحث لنكتب الكود التالي في الملف <code>templates/search.html</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1683_37" style=""><span class="pun">{%</span><span class="pln"> extends </span><span class="str">'layout.html'</span><span class="pln"> </span><span class="pun">%}</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="pun">&lt;</span><span class="pln">title</span><span class="pun">&gt;</span><span class="typ">Page</span><span class="pln"> </span><span class="typ">Title</span><span class="pun">&lt;/</span><span class="pln">title</span><span class="pun">&gt;</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"> block content </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">"grid grid-cols-4 gap-4 py-10"</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-span-3 grid grid-cols-1"</span><span class="pun">&gt;</span><span class="pln">

   </span><span class="pun">{%</span><span class="pln"> include </span><span class="str">"vendor/list.html"</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="pun">&gt;</span><span class="pln">
 </span><span class="pun">{%</span><span class="pln"> include </span><span class="str">"vendor/sidebar.html"</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="pun">&gt;</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endblock </span><span class="pun">%}</span></pre>

<p>
	بعدها نحاول البحث عن شيء ما في استمارة البحث، ويجب إعادة المنشورات التي نطلبها فقط.
</p>

<h2>
	الخلاصة
</h2>

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

<p>
	ترجمة -وبتصرّف- للمقال <a href="https://www.ericsdevblog.com/posts/django-for-beginners-5/" rel="external nofollow">Django for Beginners #5 - Some Advanced Features</a> لصاحبه Eric Hu.
</p>

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

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%85%D8%AF%D9%88%D9%86%D8%A9-%D9%83%D8%A7%D9%85%D9%84-%D9%81%D9%8A-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2458/" rel="">إنشاء تطبيق مدونة كامل في جانغو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B9%D9%85%D9%84%D9%8A%D8%A7%D8%AA-crud-%D9%84%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D9%85%D8%AF%D9%88%D9%86%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2457/" rel="">استخدام عمليات CRUD لإدارة مدونة في جانغو</a>
	</li>
	<li>
		<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>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D9%84%D8%A8%D8%AF%D8%A1-%D9%81%D9%8A-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D8%AF%D9%88%D9%86%D8%A9-%D8%A8%D8%B3%D9%8A%D8%B7%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2446/" rel="">البدء في إنشاء مدونة بسيطة في جانغو</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2482</guid><pubDate>Fri, 03 Jan 2025 15:03:01 +0000</pubDate></item><item><title>&#x62F;&#x645;&#x62C; &#x646;&#x645;&#x627;&#x630;&#x62C; GPT OpenAI &#x641;&#x64A; &#x62A;&#x637;&#x628;&#x64A;&#x642; &#x62C;&#x627;&#x646;&#x63A;&#x648; Django</title><link>https://academy.hsoub.com/programming/python/django/%D8%AF%D9%85%D8%AC-%D9%86%D9%85%D8%A7%D8%B0%D8%AC-gpt-openai-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-r2460/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_12/GPTOpenAIDjango_.png.8be4a490ba1166b720815026fb0bf3e0.png" /></p>
<p>
	نشرح في مقال اليوم الخطوات الأساسية لدمج نماذج الذكاء الاصطناعي التي توفرها شركة OpenAI في تطبيق جانغو Django، ففي الآونة الأخيرة ازادت شعبية <span ipsnoautolink="true">نماذج  OpenAI</span> أو ما يعرف بنماذج  GPT OpenAI بشكل كبير بفضل قدرتها على توليد محتوى نصي عالي الجودة في مختلف المجالات سواء كتابة رسائل البريد الإلكتروني والقصص، أو الإجابة على استفسارات العملاء، أو ترجمة المحتوى من لغة لأخرى.
</p>

<p>
	تُستخدم نماذج جي بي تي <a href="https://platform.openai.com/docs/models" rel="external nofollow">GPT models</a> من  قبل المستخدمين من خلال <a href="https://chatgpt.com/" rel="external nofollow">روبوت الدردشة تشات جي بي تي ChatGPT</a>، وهو نظام محادثة ذكي أطلقته OpenAI، لكن يمكن للمطورين الاستفادة من هذه النماذج في تطوير تطبيقاتهم الخاصة باستعمال واجهة برمجة التطبيقات <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> التي وفرتها OpenAI لتوفير مرونة أكبر في التعامل مع هذه النماذج. وسنوضح في الفقرات التالية خطوات إنشاء تطبيق جانغو يستخدم هذه الواجهة البرمجية، وبالتحديد الواجهة البرمجية لنموذج إكمال المحادثة <a href="http://api.openai.com/v1/chat/completions" rel="external nofollow">ChatCompletion <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr></a> من أجل توليد قصة قصيرة ونتعرف على طريقة تخصص معاملات النموذج المختلفة، وتنسيق ردوده واستجاباته.
</p>

<h2 id="">
	متطلبات العمل
</h2>

<p>
	كي نتمكن من إكمال هذه المقالة، سوف تحتاج الآتي:
</p>

<ol>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D9%84%D8%A8%D8%AF%D8%A1-%D9%85%D8%B9-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%84%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-r1625/" rel="">إطار جانغو Django</a> مثبت على بيئة افتراضية <code>env</code>  ضمن حاسوبنا
	</li>
	<li>
		إنشاء حساب على <a href="https://platform.openai.com/" rel="external nofollow">منصة OpenAI</a>
	</li>
	<li>
		توليد مفتاح الواجهة البرمجية OpenAI <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> key من لوحة تحكم الحساب في منصة OpenAI
	</li>
	<li>
		تثبيت حزمة OpenAI Package الخاصة بلغة بايثون ضمن البيئة الافتراضية كما سنشرح في الخطوة التالية
	</li>
</ol>

<h2>
	تثبيت مكتبة OpenAI في جانغو
</h2>

<p>
	لنفترض أننا ثبَّتنا جانغو في البيئة افتراضية ضمن مجلد باسم <code>django-apps</code>. علينا التأكد من تفعيل البيئة الافتراضية وظهور اسمها داخل قوسين<code>()</code> في <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="">سطر الأوامر Terminal</a>.<br>
	إذا لم تكن البيئة الافتراضية مفعّلة فيمكننا تفعيلها يدويًا بالانتقال للمجلد <code>django-apps</code>  في سطر الأوامر وكتابة الأمر التالي:
</p>

<pre class="ipsCode">hasoub-academy@ubuntu:$ .env/bin/activate 
</pre>

<p>
	بمجرد تفعيل البيئة الافتراضية، نشغل الأمر التالي لتنزيل حزمة OpenAI Package الخاصة بلغة بايثون:
</p>

<pre class="ipsCode">(env)hasoub-academy@ubuntu:$ pip install openai
</pre>

<p>
	نحن الآن جاهزون للبدء بتطوير تطبيق جانغو الخاص بنا كما سنوضح في الخطوات التالية.
</p>

<h2 id="openaiapi">
	 إرسال الطلبات للواجهة البرمجية OpenAI <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>
</h2>

<p>
	نحتاج بداية لإضافة مفتاح الواجهة البرمجية OpenAI <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> key لتطبيقنا كي نتمكن من إرسال الطلبات للواجهة البرمجية ChatCompletion <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>. واختبار الرد الذي نحصل عليه منها.
</p>

<p>
	لنكتب الأمر التالي لتشغيل بايثون داخل البيئة الافتراضية:
</p>

<pre class="ipsCode">(env)hasoub-academy@ubuntu:$ python
</pre>

<p>
	نستورد بعدها المكتبة OpenAI ونعرًف العميل client المخصص للتفاعل مع الواجهة البرمجية كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5426_11" style=""><span class="kwd">import</span><span class="pln"> openai
client </span><span class="pun">=</span><span class="pln"> </span><span class="typ">OpenAI</span><span class="pun">(</span><span class="pln">api_key</span><span class="pun">=</span><span class="str">"your-api-key"</span><span class="pun">)</span></pre>

<p>
	<strong>ملاحظة</strong>: نحتاج لاستبدال<code>your-api-key</code> بمفتاح الواجهة البرمجية الخاص بنا وهو شبيه للتالي  <code>sk-abdfhghlisciodfop</code>.
</p>

<p>
	بعدها نرسل طلب للواجهة البرمجية ChatCompletion <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> باستخدم الدالة <code>chat.completions.create</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7314_9" style=""><span class="pln">response </span><span class="pun">=</span><span class="pln"> client</span><span class="pun">.</span><span class="pln">chat</span><span class="pun">.</span><span class="pln">completions</span><span class="pun">.</span><span class="pln">create</span><span class="pun">(</span><span class="pln">
    model</span><span class="pun">=</span><span class="str">"gpt-3.5-turbo"</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">"role"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"user"</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">"عد من 1 إلى 10"</span><span class="pun">}</span><span class="pln">
    </span><span class="pun">]</span><span class="pln">
</span><span class="pun">)</span></pre>

<p>
	حددنا في الكود السابق <a href="https://platform.openai.com/docs/models" rel="external nofollow">النموذج</a> الذي نحتاج لاستخدامه في تطبيقنا ليكون <code>gpt-3.5-turbo</code>، وأضفنا كائن رسالة واحد يحتوي على الدور مستخدم <code>user</code>، ومرّرنا مُوجّه prompt بسيط سنرسل لاختبار الواجهة البرمجية وهو في حالتنا طلب العد من واحد إلى عشرة.
</p>

<p>
	<strong>ملاحظة1</strong>: عند التفاعل مع نموذج GPT  سنتعامل مع ثلاثة أدوار رئيسية وهي: دور المستخدم <code>user</code> الذي يطرح الأسئلة أو يطلب المساعدة من النموذج، ودور النظام <code>system</code> الذي يتضمن القواعد والتعليمات التي توجّه للنموذج، ودور المساعد <code>assistance</code> الذي يمثل نموذج الذكاء الاصطناعي نفسه والذي سنستخدمة للإجابة على أسئلة المستخدم أو تنفيذ الأوامر التي يطلبها منه.
</p>

<p>
	<strong>ملاحظة2</strong>: من المهم دائمًا الرجوع إلى التوثيق الرسمي لمنصة OpenAI للحصول على تعليمات دقيقة وشاملة حول كيفية استخدام نماذج GPT في تطبيقاتك، فهذه النماذج تتعدل وتتغير مع مرور الوقت.
</p>

<p>
	لنطبع الآن الرد المستلم من الواجهة البرمجية <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-api-r1314/" rel=""><abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr></a> والذي يتضمن عرض الأعداد من واحد إلى عشرة على شكل قائمة من خلال الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3055_15" style=""><span class="kwd">print</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">choices</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">message</span><span class="pun">.</span><span class="pln">content</span><span class="pun">)</span></pre>

<p>
	سنحصل على النتيجة التالية:
</p>

<pre class="ipsCode">1, 2, 3, 4, 5, 6, 7, 8, 9, 10
</pre>

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

<h2 id="parameters">
	ضبط معاملات النموذج Parameters
</h2>

<p>
	بعد أن نجنا في إرسال <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-api-r1314/" rel="">طلب <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr></a> بسيط إلى الواجهة البرمجية لإكمال المحادثة ChatCompletion <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>، لنتعرف على طريقة ضبط معاملات النموذج للتحكم بسلوك هذا النموذج. فهنالك العديد من المعاملات المتاحة للتحكم في النص المولد، سنوضح ثلاثة منها.
</p>

<h3>
	1. درجة الحرارة Temperature
</h3>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7314_12" style=""><span class="pln">response </span><span class="pun">=</span><span class="pln"> client</span><span class="pun">.</span><span class="pln">chat</span><span class="pun">.</span><span class="pln">completions</span><span class="pun">.</span><span class="pln">create</span><span class="pun">(</span><span class="pln">
    model</span><span class="pun">=</span><span class="str">"gpt-3.5-turbo"</span><span class="pun">,</span><span class="pln"> 
    messages</span><span class="pun">=[{</span><span class="str">"role"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"user"</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">"اذكر خمس كلمات"</span><span class="pun">}],</span><span class="pln"> 
    temperature</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="kwd">print</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">choices</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">message</span><span class="pun">.</span><span class="pln">content</span><span class="pun">)</span></pre>

<pre class="ipsCode" id="ips_uid_7314_21">المخرجــــات
1. تفاح
2. سيارة
3. كتاب
4. شمس
5. شجرة</pre>

<p>
	لنجرب منحه قيمة قريبة من الصفر مثل<code> 0.1</code> ونرى كيف ستولد الكلمات:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7314_16" style=""><span class="pln">response </span><span class="pun">=</span><span class="pln"> client</span><span class="pun">.</span><span class="pln">chat</span><span class="pun">.</span><span class="pln">completions</span><span class="pun">.</span><span class="pln">create</span><span class="pun">(</span><span class="pln">
    model</span><span class="pun">=</span><span class="str">"gpt-3.5-turbo"</span><span class="pun">,</span><span class="pln"> 
    messages</span><span class="pun">=[{</span><span class="str">"role"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"user"</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">"اذكر خمس كلمات"</span><span class="pun">}],</span><span class="pln"> 
    temperature</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="kwd">print</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">choices</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">message</span><span class="pun">.</span><span class="pln">content</span><span class="pun">)</span></pre>

<pre class="ipsCode" id="ips_uid_7314_19">المخرجــــات
1. تفاح
2. فيل
3. ضوء الشمس
4. مغامرة
5. سكون
</pre>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7314_25" style=""><span class="pln">response </span><span class="pun">=</span><span class="pln"> client</span><span class="pun">.</span><span class="pln">chat</span><span class="pun">.</span><span class="pln">completions</span><span class="pun">.</span><span class="pln">create</span><span class="pun">(</span><span class="pln">
    model</span><span class="pun">=</span><span class="str">"gpt-3.5-turbo"</span><span class="pun">,</span><span class="pln"> 
    messages</span><span class="pun">=[{</span><span class="str">"role"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"user"</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">"اذكر خمس كلمات باللغة العربية"</span><span class="pun">}],</span><span class="pln"> 
    temperature</span><span class="pun">=</span><span class="lit">0.8</span><span class="pln">
</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">choices</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">message</span><span class="pun">.</span><span class="pln">content</span><span class="pun">)</span></pre>

<pre class="ipsCode" id="ips_uid_8779_7">المخرجــــات
1. تفاح
2. ضوء الشمس
3. سعادة
4. صداقة
5. تقنية
</pre>

<h3>
	<strong>2. العدد الأقصى للوحدات النصية Max Token</strong>
</h3>

<p>
	يسمح لنا هذا المعامل بتحديد طول النص المولد، فعند ضبطع بقيمة معينة سيضمن لنا أن الردود لن تتجاوز الرقم الذي حددناه في الوحدات النصية tokens، على سبيل المثال عندما نضبط قيمة <code>max-token </code>إلى 10:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7314_27" style=""><span class="pln">response </span><span class="pun">=</span><span class="pln"> client</span><span class="pun">.</span><span class="pln">chat</span><span class="pun">.</span><span class="pln">completions</span><span class="pun">.</span><span class="pln">create</span><span class="pun">(</span><span class="pln">
    model</span><span class="pun">=</span><span class="str">"gpt-3.5-turbo"</span><span class="pun">,</span><span class="pln"> 
    messages</span><span class="pun">=[{</span><span class="str">"role"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"user"</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">"اذكر خمس كلمات"</span><span class="pun">}],</span><span class="pln"> 
    max_tokens</span><span class="pun">=</span><span class="lit">10</span><span class="pln">
</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">choices</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">message</span><span class="pun">.</span><span class="pln">content</span><span class="pun">)</span></pre>

<p>
	سنحصل على النتائج التالية:
</p>

<pre class="ipsCode" id="ips_uid_7314_29">المخرجــــات
1. تفاحة
2. سيارة
</pre>

<p>
	وعندما نضبط قيمة <code>max-token </code>إلى 20:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7314_31" style=""><span class="pln">response </span><span class="pun">=</span><span class="pln"> client</span><span class="pun">.</span><span class="pln">chat</span><span class="pun">.</span><span class="pln">completions</span><span class="pun">.</span><span class="pln">create</span><span class="pun">(</span><span class="pln">
    model</span><span class="pun">=</span><span class="str">"gpt-3.5-turbo"</span><span class="pun">,</span><span class="pln"> 
    messages</span><span class="pun">=[{</span><span class="str">"role"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"user"</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">"اذكر خمس كلمات"</span><span class="pun">}],</span><span class="pln"> 
    max_tokens</span><span class="pun">=</span><span class="lit">20</span><span class="pln">
</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">choices</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">message</span><span class="pun">.</span><span class="pln">content</span><span class="pun">)</span></pre>

<p>
	سنحصل على النتائج التالية:
</p>

<pre class="ipsCode" id="ips_uid_7314_33">المخرجــــات
1. تفاح
2. سيارة
3. موسيقى
4. محيط
5. صداقة
</pre>

<h3>
	<strong>3. التدفق Stream</strong>
</h3>

<p>
	يحدد هذا المعامل هل نريد أن تتدفق الردود على دفعات streams أو تعود لنا دفعة واحدة، فعند ضبطه بالقيمة <code>True</code>، سنستلم الرد بشكل متدفق، أي على دفعات خلال عملية توليدها. هذا يفيدنا في المحادثات الطويلة وفي تطبيقات الزمن الحقيقي التفاعلية.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7314_36" style=""><span class="pln">response </span><span class="pun">=</span><span class="pln"> client</span><span class="pun">.</span><span class="pln">chat</span><span class="pun">.</span><span class="pln">completions</span><span class="pun">.</span><span class="pln">create</span><span class="pun">(</span><span class="pln">
    model</span><span class="pun">=</span><span class="str">"gpt-3.5-turbo"</span><span class="pun">,</span><span class="pln"> 
    messages</span><span class="pun">=[{</span><span class="str">"role"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"user"</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">"اذكر خمس كلمات"</span><span class="pun">}],</span><span class="pln"> 
    stream</span><span class="pun">=</span><span class="kwd">True</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

collected_messages </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"> chunk </span><span class="kwd">in</span><span class="pln"> response</span><span class="pun">:</span><span class="pln">
    chunk_message </span><span class="pun">=</span><span class="pln"> chunk</span><span class="pun">.</span><span class="pln">choices</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">delta</span><span class="pun">.</span><span class="pln">content
    </span><span class="kwd">if</span><span class="pln"> chunk_message </span><span class="kwd">is</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">:</span><span class="pln">
        collected_messages</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">chunk_message</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">print</span><span class="pun">(</span><span class="pln">collected_messages</span><span class="pun">)</span></pre>

<pre class="ipsCode" id="ips_uid_7314_38">المخرجــــات
['', 'قطة', '\n', 'كتاب', '\n', 'حاسوب', '\n', 'شمس', '\n', 'ماء']
</pre>

<p>
	سيحتفظ المتغير <code>chunk_message</code> بكل جزء من الرسالة المتدفقة بشكل مؤقت أثناء استلامها من الواجهة البرمجية، بعد ذلك، ستضاف هذه الأجزاء إلى القائمة <code>collected_messages</code> التي تُجمع فيها كل الرسائل. يجب علينا التأكد من أن الجزء المتدفق ليس <code>None</code>، لأن هذه القيمة تدل على انتهاء تدفق الردود وتستخدم كشرط للخروج من الحلقة التكرارية.
</p>

<h2 id="-1">
	صياغة مُوجِّه نظام System Prompt مخصص
</h2>

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

<p>
	ننشئ بداية <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D9%88%D8%AD%D8%AF%D8%A7%D8%AA-modules-%D9%88%D8%A7%D9%84%D8%AD%D8%B2%D9%85-packages-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r329/" rel="">وحدة بايثون</a> تحتوي على دالة تتولى المهمة المطلوبة. لذا ننشئ ملفًا جديدًا باسم <code>story_generator.py</code> داخل مجلد مشروع جانغو بكتابة الأمر التالي:
</p>

<pre class="ipsCode" id="ips_uid_3055_31">(env)hasoub-academy@ubuntu:$ touch ~/my_blog_app/blog/blogsite/story_generator.py
</pre>

<p>
	ثم نضيف مفتاح الواجهة البرمجية OpenAI <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> key إلى متغيرات البيئة environmental variables، فلا نضيفها في ملف بايثون مباشرة لحماية المفتاح:
</p>

<pre class="ipsCode">(env)hasoub-academy@ubuntu:$ export OPENAI_KEY="your-api-key"
</pre>

<p>
	نفتح الآن الملف <code>story_generator.py</code> وننشئ بداخله عميل OpenAI client ونعرف دالة باسم <code>generate_story</code> وظيفتها توليد محتوى قصة بناءً على مجموعة من الكلمات التي يحددها المستخدم:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7314_40" style=""><span class="kwd">import</span><span class="pln"> os
</span><span class="kwd">from</span><span class="pln"> openai </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">OpenAI</span><span class="pln">
client </span><span class="pun">=</span><span class="pln"> </span><span class="typ">OpenAI</span><span class="pun">(</span><span class="pln">api_key</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">"OPENAI_KEY"</span><span class="pun">])</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> generate_story</span><span class="pun">(</span><span class="pln">words</span><span class="pun">):</span><span class="pln">
    </span><span class="com"># استدعاء واجهة برمجة التطبيقات من OpenAI لتوليد القصة</span><span class="pln">
    response </span><span class="pun">=</span><span class="pln"> get_short_story</span><span class="pun">(</span><span class="pln">words</span><span class="pun">)</span><span class="pln">
    
    </span><span class="com"># تنسيق الاستجابة وإرجاعها</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> format_response</span><span class="pun">(</span><span class="pln">response</span><span class="pun">)</span></pre>

<p>
	لتنظيم الكود البرمجي، سنستدعي ضمن هذه الدالة البرمجية دالة منفصلة باسم <code>get_short_story</code> تطلب توليد القصة من الواجهة البرمجية OpenAI <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>، ودالة أخرى باسم <code>format_response</code> تنسِّق الرد المستلم من الواجهة البرمجية.
</p>

<p>
	لنركز الآن على دالة توليد القصة <code>get_short_story</code> حيث سنضيف الكود الخاص بها في نهاية الملف <code>story_generator.py </code>كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7314_45" style=""><span class="kwd">def</span><span class="pln"> get_short_story</span><span class="pun">(</span><span class="pln">words</span><span class="pun">):</span><span class="pln">
    </span><span class="com"># إنشاء موجه النظام</span><span class="pln">
    system_prompt </span><span class="pun">=</span><span class="pln"> f</span><span class="str">"""أنت مولد قصص قصيرة.
    اكتب قصة قصيرة باستخدام الكلمات التالية: {words}.
    لا تتجاوز فقرة واحدة."""</span><span class="pln">

    </span><span class="com">#  الاتصال بواجهة برمجة التطبيقات</span><span class="pln">
    response </span><span class="pun">=</span><span class="pln"> client</span><span class="pun">.</span><span class="pln">chat</span><span class="pun">.</span><span class="pln">completions</span><span class="pun">.</span><span class="pln">create</span><span class="pun">(</span><span class="pln">
        model</span><span class="pun">=</span><span class="str">"gpt-3.5-turbo"</span><span class="pun">,</span><span class="pln">
        messages</span><span class="pun">=[{</span><span class="pln">
            </span><span class="str">"role"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"user"</span><span class="pun">,</span><span class="pln">
            </span><span class="str">"content"</span><span class="pun">:</span><span class="pln"> system_prompt
        </span><span class="pun">}],</span><span class="pln">
        temperature</span><span class="pun">=</span><span class="lit">0.8</span><span class="pun">,</span><span class="pln">
        max_tokens</span><span class="pun">=</span><span class="lit">1000</span><span class="pln">
    </span><span class="pun">)</span><span class="pln">

    </span><span class="com"># إرجاع استجابة الواجهة البرمجية</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> response</span></pre>

<p>
	كما نلاحظ فقد ضبطنا هنا مُوجِّه النظام system prompt كي يخبر النموذج ما هو، وما هي المهمة التي عليه تأديتها، وحددنا في الموجّه حجم القصة المطلوبة، ثم مررنا هذا الموجّه للواجهة البرمجية ChatCompletion <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>.
</p>

<p>
	أخيرًا نكتب كود دالة التنسيق <code>format_response</code>  في نهاية الملف <code>story_generator.py</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7314_47" style=""><span class="kwd">def</span><span class="pln"> format_response</span><span class="pun">(</span><span class="pln">response</span><span class="pun">):</span><span class="pln">
    </span><span class="com"># استخرج القصة المولدة من الاستجابة</span><span class="pln">
    story </span><span class="pun">=</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">choices</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">message</span><span class="pun">.</span><span class="pln">content
    
    </span><span class="com"># إزالة أي نص أو تنسيقات غير مرغوبة   </span><span class="pln">
    story </span><span class="pun">=</span><span class="pln"> story</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">
    </span><span class="kwd">return</span><span class="pln"> story</span></pre>

<p>
	لاختبار هذه الدوال سنستدعي الدالة <code>generate_story</code> ونمرر لها مجموعة كلمات كمعاملات، ثم نطبع الرد الذي تعيده لنا من خلال إضافة سطر الكود التالي للملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7314_49" style=""><span class="kwd">print</span><span class="pun">(</span><span class="pln">generate_story</span><span class="pun">(</span><span class="str">"قطة، كتاب، حاسوب شمس، ماء"</span><span class="pun">))</span></pre>

<p>
	لنحفظ الملف ونغلقه، ونشغًله من داخل سطر الأوامر كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3055_41" style=""><span class="pun">
(env) sammy@ubuntu:$ python ~/my_blog_app/blog/blogsite/story_generator.py
</span></pre>

<p>
	يجب أن نحصل على قصة تشابه القصة التالية:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_8779_11" style=""><span class="pln">في زاوية مريحة من غرفة مشمسة، كانت هناك قطة ذات فرو ناعم تُدعى "مشمش"، تتمدد بكسل بجانب رف كتب شاهق. بين صفوف الكتب، كان حاسوب فضولي يهمس بهدوء. بينما كانت أشعة الشمس تتسلل عبر النافذة، مُلَقيةً ضوءًا دافئًا، لاحظت "مشمش" بقعة ماء صغيرة على الرف. مدفوعةً بالفضول، دفعت القطة الكتاب الأقرب إلى البقعة، ليُفتح الكتاب ويكشف عن مكان مخفي يحتوي على عقد ماسي متلألئ. مع اكتشاف السر، بدأت "مشمش" مغامرة غير متوقعة، حيث امتزجت أشعة الشمس، والماء، وقوة المعرفة لتنسج قصة مثيرة من الغموض والاكتشاف.</span></pre>

<p>
	بعد أن تأكدنا من عمل الدالة <code>generate_story</code> وتوليد القصة بشكل صحيح، لنغير طريقة تنفيذ الكود وبدلاً من طباعة القصة مباشرة في سطر الأوامر، سنستدعي الدالة من خلال عرض جانغو <a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A8%D9%86%D9%8A%D8%A9-mtv-%D9%84%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D8%AF%D9%88%D9%86%D8%A9-%D8%A8%D8%B3%D9%8A%D8%B7%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2452/" rel="">Django view</a> لعرض القصة على واجهة المستخدم.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7314_55" style=""><span class="kwd">print</span><span class="pun">(</span><span class="pln">generate_story</span><span class="pun">(</span><span class="str">"قطة، كتاب، حاسوب شمس، ماء"</span><span class="pun">))</span></pre>

<p>
	يمكن تجربة تعديل مُوجّه النظام system prompt بما يناسبنا لتوليد محتوى أفضل. سنلاحظ بالتجربة أن بإمكاننا دائمًا تحسين النتائج بما يتناسب مع احتياجاتنا.
</p>

<p>
	لننتقل إلى الخطوة التالية، حيث سندمج الوحدة التي عرفناها في ملف <code>story_generator.py </code>مع  مشروع جانغو الخاص بنا.
</p>

<h2 id="-2">
	دمج وحدة بايثون مع جانغو في الواجهة الخلفية
</h2>

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

<p>
	نفتح ملف <code>views.py</code> داخل مجلد مشروع جانغو، ونستورد الوحدات والحزم البرمجية اللازمة، ثم نضيف دالة عرض view باسم <code>generate_story_from_words</code> ونكتب ضمنها ما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7314_57" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">http </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">JsonResponse</span><span class="pln">
</span><span class="kwd">from</span><span class="pln"> </span><span class="pun">.</span><span class="pln">story_generator </span><span class="kwd">import</span><span class="pln"> generate_story

</span><span class="kwd">def</span><span class="pln"> generate_story_from_words</span><span class="pun">(</span><span class="pln">request</span><span class="pun">):</span><span class="pln">
    words </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">GET</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="str">'words'</span><span class="pun">)</span><span class="pln">  </span><span class="com"># استخراج الكلمات المتوقعة من الطلب</span><span class="pln">
    story </span><span class="pun">=</span><span class="pln"> generate_story</span><span class="pun">(</span><span class="pln">words</span><span class="pun">)</span><span class="pln">  </span><span class="com"># استدعاء دالة generate_story باستخدام الكلمات المستخرجة</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="typ">JsonResponse</span><span class="pun">({</span><span class="str">'story'</span><span class="pun">:</span><span class="pln"> story</span><span class="pun">})</span><span class="pln">  </span><span class="com"># إرجاع القصة في استجابة JSON</span></pre>

<p>
	بعدها نحتاج لربط الدالة بمسار URL في مشروع جانغو لتمكين المستخدمين من الوصول إليها عبر المتصفح. لنفتح الملف <code>urls.py</code> ونضيف نمط الرابط URL pattern للدالة <code>generate_story_from_words</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7314_59" style=""><span class="pln">urlpatterns </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    </span><span class="com"># أنماط URL الأخرى...</span><span class="pln">
    path</span><span class="pun">(</span><span class="str">'generate-story/'</span><span class="pun">,</span><span class="pln"> views</span><span class="pun">.</span><span class="pln">generate_story_from_words</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">=</span><span class="str">'generate-story'</span><span class="pun">),</span><span class="pln">
</span><span class="pun">]</span></pre>

<p>
	الآن يمكننا إرسال الطلبات من خلال نقطة الوصول التالية<code>/generate-story/</code> باستخدام المتصفح، وإرسال طلب من النوع GET لها وتمرير الكلمات المتوقعة كمعاملات للطلب، نفتح <a href="https://academy.hsoub.com/devops/linux/%D8%B4%D8%B1%D8%AD-%D8%AA%D9%85%D9%87%D9%8A%D8%AF%D9%8A-%D9%84%D8%B3%D8%B7%D8%B1-%D8%A3%D9%88%D8%A7%D9%85%D8%B1-%D9%84%D9%8A%D9%86%D9%83%D8%B3-r810/" rel="">سطر الأوامر</a> ونكتب الأمر curl بالشكل التالي:
</p>

<pre class="ipsCode" id="ips_uid_7314_61">(env)hasoub-academy@ubuntu:$ curl "http://your_domain/generate-story/?words=قطة,كتاب,حاسوب,شمس,ماء"
</pre>

<p>
	علينا استبدال <code>http://your_domain </code>بالعنوان الفعلي الذي يستضيف مشروعنا. تمثّل الكلمات الممررة عبر هذا الرابط مثل كتاب، وماء، وحاسوب ما هي الكلمات التي نريد استخدامها لتوليد محتوى القصة. يمكن بالطبع تغيير هذه الكلمات واستخدام كلمات أخرى حسب الحاجة.
</p>

<p>
	بعد تشغيل أمر curl يجب أن نرى ردًا من الخادم يحتوي على القصة المولدة استنادًا إلى الكلمات التي قدمناها.
</p>

<pre class="ipsCode" id="ips_uid_7314_63">(env)hasoub-academy@ubuntu:$ curl "http://your_domain/generate-story/?words="قطة,كتاب,حاسوب,شمس,ماء"
</pre>

<p>
	وسنحصل على مخرجات قريبة للتالي:
</p>

<pre class="ipsCode" id="ips_uid_7011_7">{
  "story": "كان يا مكان، في كوخ صغير مريح يقع وسط غابة كثيفة، قطة فضولية تُدعى 'مشمش' تجلس بجانب النافذة، تستمتع بأشعة الشمس الدافئة. بينما كانت 'مشمش' تحرك ذيلها بكسل، لفت نظرها كتاب مغبر ملقى على رف قريب. بدافع الفضول، قفزت بعناية إلى الرف، مما أدى إلى سقوط مجموعة من الكتب، فتح أحدها ليكشف عن مكان مخفي. داخل هذا المكان، اكتشفت 'مشمش' حاسوبًا قديمًا، بدأ شاشته يومض عندما لمست زر الطاقة. مفتونةً بالشاشة المتوهجة، انطلقت 'مشمش' في عالم من المناظر الافتراضية، حيث تجولت بحرية، تطارد الأسماك الرقمية وتوقف للإعجاب بشلالات رائعة. ضائعة في هذه المغامرة الجديدة، اكتشفت 'مشمش' عجائب العوالم الملموسة والافتراضية معًا، مدركةً أن الاستكشاف الحقيقي لا يعرف حدودًا."
}
</pre>

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

<p>
	إلى هنا نكون قد وصلنا لنهاية هذا المقال الذي وضحنا فيه الخطوات الأساسية التي نحتاجها لدمج  OpenAI modes داخل تطبيق جانغو Django باستخدام الواجهة البرمجية OpenAI <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>، وتعلمنا طريقة إرسال الطلبات للواجهة البرمجية ChatCompletion <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> والتحكم بسلوك النموذج عن طريق ضبط معاملاته المختلفة. لتحسين هذا المشروع وزيادة ميزاته، يمكننا استكشاف المزيد من مميزات الواجهة البرمجية OpenAI <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> وتجريب مُوجِّهات نظام system prompt مختلفة، وقيم معاملات متنوعة حتى نحصل على قصة مميزة وإبداعية.
</p>

<p>
	ترجمة-وبتصرٌّف-للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-integrate-openai-gpt-models-in-your-django-project" rel="external nofollow">How to Integrate OpenAI GPT Models in Your Django Project</a>
</p>

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

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%88%D8%AA%D9%88%D8%B5%D9%8A%D9%84%D9%87-%D8%A8%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-r1626/" rel="">إنشاء تطبيق جانغو وتوصيله بقاعدة بيانات</a>
	</li>
	<li>
		<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>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D9%84%D8%B1%D8%A8%D8%B7-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-openai-api-%D9%85%D8%B9-nodejs-r2233/" rel="">دليلك لربط واجهة OpenAI <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> مع Node.js</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/artificial-intelligence/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%88%D9%83%D9%84%D8%A7%D8%A1-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%A7%D9%84%D9%85%D8%AD%D9%88%D9%84%D8%A7%D8%AA-transformers-agents-%D9%81%D9%8A-%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-%D8%A7%D9%84%D8%AA%D9%88%D9%84%D9%8A%D8%AF%D9%8A-r2379/" rel="">استخدام وكلاء مكتبة المحولات Transformers Agents في الذكاء الاصطناعي التوليدي</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2460</guid><pubDate>Tue, 03 Dec 2024 12:00:00 +0000</pubDate></item><item><title>&#x625;&#x646;&#x634;&#x627;&#x621; &#x62A;&#x637;&#x628;&#x64A;&#x642; &#x645;&#x62F;&#x648;&#x646;&#x629; &#x643;&#x627;&#x645;&#x644; &#x641;&#x64A; &#x62C;&#x627;&#x646;&#x63A;&#x648;</title><link>https://academy.hsoub.com/programming/python/django/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%85%D8%AF%D9%88%D9%86%D8%A9-%D9%83%D8%A7%D9%85%D9%84-%D9%81%D9%8A-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2458/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_12/--_.png.5bf8a9b9d1cb86273b2c127ccca9ce16.png" /></p>
<p>
	حان الوقت الآن لإنشاء تطبيق مدونة متكامل باستخدام إطار عمل جانغو Django، فقد تعلمنا في <a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B9%D9%85%D9%84%D9%8A%D8%A7%D8%AA-crud-%D9%84%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D9%85%D8%AF%D9%88%D9%86%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2457/" rel="">المقال السابق</a> كيف يمكن للنماذج Model والعروض View والقوالب Template أن تعمل معًا لإنشاء تطبيق جانغو لكن هذه العملية صعبة نوعًا ما، إذ يتوجب علينا كتابة 5 إجراءات على الأقل لكل ميزة نريد تحقيقها، وستكون الشيفرة البرمجية مكررةً في معظمها.
</p>

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

<h2 id="model">
	إنشاء طبقة النموذج Model
</h2>

<p>
	سنبدأ أولًا بتصميم بنية قاعدة البيانات.
</p>

<h3 id="-1">
	تصميم بنية قاعدة البيانات
</h3>

<p>
	بالنسبة لأيّ نظام تدوين أساسي، ستحتاج إلى 4 نماذج على الأقل هي <code>User</code> و <code>Category</code> و <code>Tag</code> و <code>Post</code>، وسنضيف في المقال التالي بعض الميزات المتقدمة لاحقًا، ولكن سنكتفي حاليًا بهذه النماذج الأربعة فقط.
</p>

<h4 id="user">
	نموذج المستخدم User
</h4>

<table>
	<thead>
		<tr>
			<th>
				المفتاح
			</th>
			<th>
				نوعه
			</th>
			<th>
				معلومات عنه
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				المعرّف id
			</td>
			<td>
				عدد صحيح Integer
			</td>
			<td>
				يتزايد تلقائيًا
			</td>
		</tr>
		<tr>
			<td>
				الاسم name
			</td>
			<td>
				سلسلة نصية String
			</td>
			<td>
				-
			</td>
		</tr>
		<tr>
			<td>
				البريد الإلكتروني email
			</td>
			<td>
				سلسلة نصية
			</td>
			<td>
				فريد
			</td>
		</tr>
		<tr>
			<td>
				كلمة السر password
			</td>
			<td>
				سلسلة نصية
			</td>
			<td>
				-
			</td>
		</tr>
	</tbody>
</table>

<p>
	إن النموذج <code>User</code> مُضمَّن مسبقًا في جانغو، إذ يوفّر هذا النموذج المُضمَّن بعض الميزات الأساسية مثل تشفير كلمات المرور Password Hashing واستثياق المستخدمين User Authentication، بالإضافة إلى نظام الأذونات المُضمَّن مع مدير جانغو Django Admin كما سنوضّح لاحقًا.
</p>

<h4 id="category">
	نموذج الفئة Category
</h4>

<table>
	<thead>
		<tr>
			<th>
				المفتاح
			</th>
			<th>
				نوعه
			</th>
			<th>
				معلومات عنه
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				المعرّف id
			</td>
			<td>
				عدد صحيح Integer
			</td>
			<td>
				يتزايد تلقائيًا
			</td>
		</tr>
		<tr>
			<td>
				الاسم name
			</td>
			<td>
				سلسلة نصية String
			</td>
			<td>
				-
			</td>
		</tr>
		<tr>
			<td>
				الاسم المختصر slug
			</td>
			<td>
				سلسلة نصية
			</td>
			<td>
				فريد
			</td>
		</tr>
		<tr>
			<td>
				الوصف description
			</td>
			<td>
				نص
			</td>
			<td>
				-
			</td>
		</tr>
	</tbody>
</table>

<h4 id="tag">
	نموذج الوسم Tag
</h4>

<table>
	<thead>
		<tr>
			<th>
				المفتاح
			</th>
			<th>
				نوعه
			</th>
			<th>
				معلومات عنه
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				المعرّف id
			</td>
			<td>
				عدد صحيح Integer
			</td>
			<td>
				يتزايد تلقائيًا
			</td>
		</tr>
		<tr>
			<td>
				الاسم name
			</td>
			<td>
				سلسلة نصية String
			</td>
			<td>
				-
			</td>
		</tr>
		<tr>
			<td>
				الاسم المختصر slug
			</td>
			<td>
				سلسلة نصية
			</td>
			<td>
				فريد
			</td>
		</tr>
		<tr>
			<td>
				الوصف description
			</td>
			<td>
				نص
			</td>
			<td>
				-
			</td>
		</tr>
	</tbody>
</table>

<h4 id="post">
	نموذج المنشور Post
</h4>

<table>
	<thead>
		<tr>
			<th>
				المفتاح
			</th>
			<th>
				نوعه
			</th>
			<th>
				معلومات عنه
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				المعرّف id
			</td>
			<td>
				عدد صحيح Integer
			</td>
			<td>
				يتزايد تلقائيًا
			</td>
		</tr>
		<tr>
			<td>
				العنوان title
			</td>
			<td>
				سلسلة نصية String
			</td>
			<td>
				-
			</td>
		</tr>
		<tr>
			<td>
				الاسم المختصر slug
			</td>
			<td>
				سلسلة نصية
			</td>
			<td>
				فريد
			</td>
		</tr>
		<tr>
			<td>
				المحتوى content
			</td>
			<td>
				نص
			</td>
			<td>
				-
			</td>
		</tr>
		<tr>
			<td>
				featured_image
			</td>
			<td>
				سلسلة نصية String
			</td>
			<td>
				-
			</td>
		</tr>
		<tr>
			<td>
				is_published
			</td>
			<td>
				قيمة منطقية Boolean
			</td>
			<td>
				-
			</td>
		</tr>
		<tr>
			<td>
				is_featured
			</td>
			<td>
				قيمة منطقية
			</td>
			<td>
				-
			</td>
		</tr>
		<tr>
			<td>
				created_at
			</td>
			<td>
				تاريخ Date
			</td>
			<td>
				-
			</td>
		</tr>
	</tbody>
</table>

<h4 id="site">
	نموذج الموقع Site
</h4>

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

<table>
	<thead>
		<tr>
			<th>
				المفتاح
			</th>
			<th>
				نوعه
			</th>
			<th>
				معلومات عنه
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				الاسم name
			</td>
			<td>
				سلسلة نصية String
			</td>
			<td>
				-
			</td>
		</tr>
		<tr>
			<td>
				الوصف description
			</td>
			<td>
				نص text
			</td>
			<td>
				-
			</td>
		</tr>
		<tr>
			<td>
				الشعار logo
			</td>
			<td>
				سلسلة نصية
			</td>
			<td>
				-
			</td>
		</tr>
	</tbody>
</table>

<h4 id="-2">
	العلاقات
</h4>

<p>
	توجد ست علاقات بالنسبة لهذا التطبيق وهي:
</p>

<ul>
	<li>
		لكل مستخدم عدة منشورات
	</li>
	<li>
		لكل فئة عدة منشورات
	</li>
	<li>
		ينتمي كل وسم إلى منشورات متعددة
	</li>
	<li>
		ينتمي كل منشور إلى مستخدم واحد
	</li>
	<li>
		ينتمي كل منشور إلى فئة واحدة
	</li>
	<li>
		ينتمي كل منشور إلى وسوم متعددة
	</li>
</ul>

<h3 id="-3">
	تطبيق التصميم باستخدام جانغو
</h3>

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

<h4 id="site-1">
	نموذج الموقع Site
</h4>

<p>
	لنبدأ بنموذج الموقع <code>Site</code> مع توفير خصائص لحفظ بيانات الموقع مثل اسم الموقع ووصفه وصورة الشعار كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4651_20" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Site</span><span class="pun">(</span><span class="pln">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
   name </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">200</span><span class="pun">)</span><span class="pln">
   description </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">TextField</span><span class="pun">()</span><span class="pln">
   logo </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">ImageField</span><span class="pun">(</span><span class="pln">upload_to</span><span class="pun">=</span><span class="str">"logo/"</span><span class="pun">)</span><span class="pln">

   </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Meta</span><span class="pun">:</span><span class="pln">
       verbose_name_plural </span><span class="pun">=</span><span class="pln"> </span><span class="str">"site"</span><span class="pln">

   </span><span class="kwd">def</span><span class="pln"> __str__</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"> self</span><span class="pun">.</span><span class="pln">name</span></pre>

<p>
	لاحظ أن نوع حقل الصورة <code>ImageField()‎</code> هو سلسلة نصية <code>string</code>، إذ لا يمكن لقواعد البيانات تخزين الصور، لذا تُخزَّن الصور في نظام ملفات خادمك، وسيحتفظ هذا الحقل بالمسار الذي يشير لموقع الصورة. سنرفع الصور في هذا المثال إلى المجلد ‎<code>mediafiles/logo/</code>‎، وتذكّر إضافة مجلد ملفات الوسائط ‎<code>MEDIA_ROOT = "mediafiles/"</code>‎ في الملف <code>settings.py</code> الذي أعددناه <a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D9%84%D8%A8%D8%AF%D8%A1-%D9%81%D9%8A-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D8%AF%D9%88%D9%86%D8%A9-%D8%A8%D8%B3%D9%8A%D8%B7%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2446/" rel="">سابقًا</a>.
</p>

<p>
	<strong>ملاحظة</strong>: تحتاج لأن تثبّت مكتبة <a href="https://pillow.readthedocs.io/en/stable/" rel="external nofollow">Pillow</a> على جهازك ليعمل الحقل <code>ImageField()‎</code> كما يلي:
</p>

<pre class="ipsCode">pip install Pillow
</pre>

<h4 id="category-1">
	نموذج الفئة Category
</h4>

<p>
	بعدها سنصميم نموذج الفئة Category الذي سيُستخدم لتنظيم المحتويات ضمن المدونة ضمن فئات ويحدد الحقول التي سنحتاج إليها لتمثيل الفئات بشكل صحيح في قاعدة البيانات. يتضمن نموذج الفئة <code>Category</code> المحتويات التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4651_23" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Category</span><span class="pun">(</span><span class="pln">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
   name </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">200</span><span class="pun">)</span><span class="pln">
   slug </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">SlugField</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">
   description </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">TextField</span><span class="pun">()</span><span class="pln">

   </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Meta</span><span class="pun">:</span><span class="pln">
       verbose_name_plural </span><span class="pun">=</span><span class="pln"> </span><span class="str">"categories"</span><span class="pln">

   </span><span class="kwd">def</span><span class="pln"> __str__</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"> self</span><span class="pun">.</span><span class="pln">name</span></pre>

<p>
	ينبغي أن يكون النموذج <code>Category</code> مفهومًا بالنسبة لك، لكن دعنا نوضّح الصنف <code>Meta</code>، الذي سنستخدمه لإضافة البيانات الوصفية Metadata إلى النماذج. تمثّل البيانات الوصفية للنموذج أيّ شيء ليس حقلًا مثل خيارات الترتيب واسم جدول قاعدة البيانات وما إلى ذلك، حيث استخدمنا في مثالنا الخيار <code>verbose_name_plural</code> لتحديد صيغة الجمع لكلمة "category"، فجانغو ليس ذكيًا مثل <a href="https://academy.hsoub.com/programming/php/laravel/" rel="">لارافيل Laravel</a> في هذا الجانب، فإن لم نمنح جانغو صيغة الجمع الصحيحة، فسوف يستخدم الجمع "categorys"، وهذا خاطئ.
</p>

<p>
	تحدد الدالة ‎<code>__str__(self)</code>‎ الحقل الذي سيستخدمه جانغو عند الإشارة إلى فئة معينة، حيث استخدمنا في مثالنا الحقل <code>name</code>، وسنوضّح أهمية ذلك عندما نصل إلى قسم مدير جانغو.
</p>

<h4 id="tag-1">
	نموذج الوسم Tag
</h4>

<p>
	يتضمن نموذج الوسم <code>Tag</code> لتخزين الوسوم التي سترتبط بالمنشورات وهو يتضمن اسم الوسم والرابط اللطيف له ووصفه كما توضح الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4651_25" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Tag</span><span class="pun">(</span><span class="pln">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
   name </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">200</span><span class="pun">)</span><span class="pln">
   slug </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">SlugField</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">
   description </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">TextField</span><span class="pun">()</span><span class="pln">

   </span><span class="kwd">def</span><span class="pln"> __str__</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"> self</span><span class="pun">.</span><span class="pln">name</span></pre>

<h4 id="post-1">
	نموذج المنشور Post
</h4>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4651_27" style=""><span class="kwd">from</span><span class="pln"> ckeditor</span><span class="pun">.</span><span class="pln">fields </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">RichTextField</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">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">(</span><span class="pln">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
   title </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">200</span><span class="pun">)</span><span class="pln">
   slug </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">SlugField</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">
   content </span><span class="pun">=</span><span class="pln"> </span><span class="typ">RichTextField</span><span class="pun">()</span><span class="pln">
   featured_image </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">ImageField</span><span class="pun">(</span><span class="pln">upload_to</span><span class="pun">=</span><span class="str">"images/"</span><span class="pun">)</span><span class="pln">
   is_published </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">BooleanField</span><span class="pun">(</span><span class="pln">default</span><span class="pun">=</span><span class="kwd">False</span><span class="pun">)</span><span class="pln">
   is_featured </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">BooleanField</span><span class="pun">(</span><span class="pln">default</span><span class="pun">=</span><span class="kwd">False</span><span class="pun">)</span><span class="pln">
   created_at </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">DateField</span><span class="pun">(</span><span class="pln">auto_now</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">

   </span><span class="com"># تعريف العلاقات</span><span class="pln">
   category </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">ForeignKey</span><span class="pun">(</span><span class="typ">Category</span><span class="pun">,</span><span class="pln"> on_delete</span><span class="pun">=</span><span class="pln">models</span><span class="pun">.</span><span class="pln">CASCADE</span><span class="pun">)</span><span class="pln">
   tag </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">ManyToManyField</span><span class="pun">(</span><span class="typ">Tag</span><span class="pun">)</span><span class="pln">
   user </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">ForeignKey</span><span class="pun">(</span><span class="pln">settings</span><span class="pun">.</span><span class="pln">AUTH_USER_MODEL</span><span class="pun">,</span><span class="pln"> on_delete</span><span class="pun">=</span><span class="pln">models</span><span class="pun">.</span><span class="pln">CASCADE</span><span class="pun">)</span><span class="pln">

   </span><span class="kwd">def</span><span class="pln"> __str__</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"> self</span><span class="pun">.</span><span class="pln">title</span></pre>

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

<p>
	تذكّر أنه يمكنك فقط إضافة نص عادي عند إنشاء منشور في المقال السابق، وهذا ليس مثاليًا لمقال في مدونة. إذ تمنحك محرّرات النصوص الغنية أو محرّرات WYSIWYG القدرة على تحرير صفحات HTML مباشرةً دون كتابة الشيفرة البرمجية، حيث استخدمنا المحرّر <a href="https://ckeditor.com/" rel="external nofollow">CKEditor</a> في هذا المقال.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163260" href="https://academy.hsoub.com/uploads/monthly_2024_11/01_ckeditor.png.d472c6114951a3bc5b8e0ba0953f2491.png" rel=""><img alt="CKEditor" class="ipsImage ipsImage_thumbnailed" data-fileid="163260" data-ratio="44.83" data-unique="wd1apxv80" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2024_11/01_ckeditor.thumb.png.c84df8f3f2843d994d6bdfde51430420.png"></a>
</p>

<p>
	لذا يمكنك تثبيت المحرّر CKEditor من خلال تشغيل الأمر التالي:
</p>

<pre class="ipsCode">pip install django-ckeditor
</pre>

<p>
	سجّل بعد ذلك <code>ckeditor</code> في ملف الإعدادات <code>settings.py</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4651_34" style=""><span class="pln">INSTALLED_APPS </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
   </span><span class="str">"blog"</span><span class="pun">,</span><span class="pln">
   </span><span class="str">"ckeditor"</span><span class="pun">,</span><span class="pln">
   </span><span class="str">"django.contrib.admin"</span><span class="pun">,</span><span class="pln">
   </span><span class="str">"django.contrib.auth"</span><span class="pun">,</span><span class="pln">
   </span><span class="str">"django.contrib.contenttypes"</span><span class="pun">,</span><span class="pln">
   </span><span class="str">"django.contrib.sessions"</span><span class="pun">,</span><span class="pln">
   </span><span class="str">"django.contrib.messages"</span><span class="pun">,</span><span class="pln">
   </span><span class="str">"django.contrib.staticfiles"</span><span class="pun">,</span><span class="pln">
</span><span class="pun">]</span></pre>

<h4 id="-4">
	تعريف العلاقات
</h4>

<p>
	أخيرًا، يمكنك إضافة علاقات إلى النماذج، حيث سنضيف الأسطر الثلاثة التالية في النموذج <code>Post</code> للقيام بذلك كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4651_36" style=""><span class="pln">category </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">ForeignKey</span><span class="pun">(</span><span class="typ">Category</span><span class="pun">,</span><span class="pln"> on_delete</span><span class="pun">=</span><span class="pln">models</span><span class="pun">.</span><span class="pln">CASCADE</span><span class="pun">)</span><span class="pln">
tag </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">ManyToManyField</span><span class="pun">(</span><span class="typ">Tag</span><span class="pun">)</span><span class="pln">
user </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">ForeignKey</span><span class="pun">(</span><span class="pln">settings</span><span class="pun">.</span><span class="pln">AUTH_USER_MODEL</span><span class="pun">,</span><span class="pln"> on_delete</span><span class="pun">=</span><span class="pln">models</span><span class="pun">.</span><span class="pln">CASCADE</span><span class="pun">)</span></pre>

<p>
	بما أننا نستخدم نموذج المستخدم <code>User</code> المُضمَّن (<code>settings.AUTH_USER_MODEL</code>)، فتذكر استيراد الوحدة <code>settings</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4651_40" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">conf </span><span class="kwd">import</span><span class="pln"> settings</span></pre>

<p>
	ولِّد بعد ذلك ملفات التهجير Migration Files وطبّقها على قاعدة البيانات باستخدام الأمرين التاليين:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4651_42" style=""><span class="pln">python manage</span><span class="pun">.</span><span class="pln">py makemigrations</span></pre>

<pre class="ipsCode">python manage.py migrate
</pre>

<h2 id="-5">
	إعداد لوحة مدير المدونة
</h2>

<p>
	الخطوة التالية هي إعداد لوحة المدير، حيث يأتي جانغو مع نظام إدارة مضمَّن معه، ويمكنك استخدامه من خلال التسجيل بمستخدم Superuser باستخدام الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4651_44" style=""><span class="pln">python manage</span><span class="pun">.</span><span class="pln">py createsuperuser</span></pre>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163206" href="https://academy.hsoub.com/uploads/monthly_2024_11/02_superuser.png.1ba4d3a6f27e12c5877eb3da30027f8a.png" rel=""><img alt="02 superuser" class="ipsImage ipsImage_thumbnailed" data-fileid="163206" data-unique="edd2rkrks" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/02_superuser.thumb.png.170f99fc00926f245d83d21016ca5cf2.png"> </a>
</p>

<p>
	يمكنك بعد ذلك الوصول إلى لوحة المدير من خلال الانتقال إلى العنوان ‎<code>http://127.0.0.1:8000/admin/</code>‎.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163207" href="https://academy.hsoub.com/uploads/monthly_2024_11/03_django-login.png.3a36f5f148be5965952fc04466a9ecf1.png" rel=""><img alt="03 django login" class="ipsImage ipsImage_thumbnailed" data-fileid="163207" data-unique="vlxymzv1y" style="width: 400px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/03_django-login.thumb.png.277856e13290694b7001414ddfd26feb.png"> </a>
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163261" href="https://academy.hsoub.com/uploads/monthly_2024_11/04_admin.png.63b960edc003def53afc0dff2acf09cf.png" rel=""><img alt="django Admin" class="ipsImage ipsImage_thumbnailed" data-fileid="163261" data-ratio="46.83" data-unique="yfwvn3xgj" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2024_11/04_admin.thumb.png.2d1525801fbded24891520ba0081a0e0.png"></a>
</p>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4651_51" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">contrib </span><span class="kwd">import</span><span class="pln"> admin
</span><span class="kwd">from</span><span class="pln"> </span><span class="pun">.</span><span class="pln">models </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Site</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Category</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Tag</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Post</span><span class="pln">

</span><span class="com"># سجّل نماذجك هنا</span><span class="pln">
</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">CategoryAdmin</span><span class="pun">(</span><span class="pln">admin</span><span class="pun">.</span><span class="typ">ModelAdmin</span><span class="pun">):</span><span class="pln">
   prepopulated_fields </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="str">"slug"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">(</span><span class="str">"name"</span><span class="pun">,)}</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">TagAdmin</span><span class="pun">(</span><span class="pln">admin</span><span class="pun">.</span><span class="typ">ModelAdmin</span><span class="pun">):</span><span class="pln">
   prepopulated_fields </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="str">"slug"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">(</span><span class="str">"name"</span><span class="pun">,)}</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">PostAdmin</span><span class="pun">(</span><span class="pln">admin</span><span class="pun">.</span><span class="typ">ModelAdmin</span><span class="pun">):</span><span class="pln">
   prepopulated_fields </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="str">"slug"</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">

admin</span><span class="pun">.</span><span class="pln">site</span><span class="pun">.</span><span class="pln">register</span><span class="pun">(</span><span class="typ">Site</span><span class="pun">)</span><span class="pln">
admin</span><span class="pun">.</span><span class="pln">site</span><span class="pun">.</span><span class="pln">register</span><span class="pun">(</span><span class="typ">Category</span><span class="pun">,</span><span class="pln"> </span><span class="typ">CategoryAdmin</span><span class="pun">)</span><span class="pln">
admin</span><span class="pun">.</span><span class="pln">site</span><span class="pun">.</span><span class="pln">register</span><span class="pun">(</span><span class="typ">Tag</span><span class="pun">,</span><span class="pln"> </span><span class="typ">TagAdmin</span><span class="pun">)</span><span class="pln">
admin</span><span class="pun">.</span><span class="pln">site</span><span class="pun">.</span><span class="pln">register</span><span class="pun">(</span><span class="typ">Post</span><span class="pun">,</span><span class="pln"> </span><span class="typ">PostAdmin</span><span class="pun">)</span></pre>

<p>
	نستورد في السطر 2 النماذج التي أنشأناها، ثم نسجل النماذج المستوردة باستخدام التابع <code>admin.site.register()‎</code>. لاحظ بعد ذلك وجود شيء إضافي عند تسجيل النموذج <code>Category</code>، وهو الصنف <code>CategoryAdmin</code> الذي عرّفناه في السطر 6، وبالتالي يمكنك تمرير بعض المعلومات الإضافية إلى نظام الإدارة في جانغو. يمكنك استخدام الميزة <code>prepopulated_fields</code> لتوليد أسماء لطيفة Slugs لجميع الفئات والوسوم والمنشورات، حيث تعتمد قيمة <code>slug</code> على الاسم <code>name</code>، ولنختبر ذلك من خلال إنشاء فئة جديدة.
</p>

<p>
	انتقل إلى العنوان ‎<code>http://127.0.0.1:8000/admin/</code>‎، وانقر على الفئات Categories، وأضف فئة جديدة. تذكّر أننا حددنا صيغة الجمع للكلمة Category في نموذجنا، فإن لم نفعل ذلك، فسيستخدم جانغو الجمع Categorys بدلًا من ذلك كما شرحنا سابقًا.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163262" href="https://academy.hsoub.com/uploads/monthly_2024_11/05_admin-home.png.f6f20828c82f9f726237be360a531a49.png" rel=""><img alt="admin-home" class="ipsImage ipsImage_thumbnailed" data-fileid="163262" data-ratio="56.50" data-unique="0hc95habc" style="width: 500px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2024_11/05_admin-home.thumb.png.68afc0a609e4a3f51bcfa2306082b40e.png"></a>
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163287" href="https://academy.hsoub.com/uploads/monthly_2024_12/06_category.png.33d0259ef2e7017b591d5119729ee0da.png" rel=""><img alt="06_category.png" class="ipsImage ipsImage_thumbnailed" data-fileid="163287" data-ratio="38.83" data-unique="dmv54k1sh" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2024_12/06_category.thumb.png.8910d93ecc13de6fd0aac05db5ccc33b.png"></a>
</p>

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

<h3 id="optionalconfigurations">
	عمليات ضبط اختيارية Optional Configurations
</h3>

<p>
	لم ينتهي عملنا بعد، لذا افتح لوحة الفئات، ستلاحظ أن بإمكانك الوصول إلى الفئات من صفحة المنشور، ولكن لا توجد طريقة للوصول إلى المنشورات المقابلة من صفحة الفئة. لحل هذه المشكلة، يمكنك استخدام الصنف <code>InlineModelAdmin</code> في الملف <code>blog/admin.py</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4651_57" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">PostInlineCategory</span><span class="pun">(</span><span class="pln">admin</span><span class="pun">.</span><span class="typ">StackedInline</span><span class="pun">):</span><span class="pln">
   model </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Post</span><span class="pln">
   max_num </span><span class="pun">=</span><span class="pln"> </span><span class="lit">2</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">CategoryAdmin</span><span class="pun">(</span><span class="pln">admin</span><span class="pun">.</span><span class="typ">ModelAdmin</span><span class="pun">):</span><span class="pln">
   prepopulated_fields </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="str">"slug"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">(</span><span class="str">"name"</span><span class="pun">,)}</span><span class="pln">
   inlines </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
       </span><span class="typ">PostInlineCategory</span><span class="pln">
   </span><span class="pun">]</span></pre>

<p>
	أنشأنا أولًا الصنف <code>PostInlineCategory</code>، ثم استخدمناه في <code>CategoryAdmin</code>، وتعني عبارة <code>max_num = 2</code> عرض منشورين فقط في صفحة الفئة التي ستبدو كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163263" href="https://academy.hsoub.com/uploads/monthly_2024_11/07_category-inline.png.4f3060128cf2d54f2996def40025ecba.png" rel=""><img alt="change-category" class="ipsImage ipsImage_thumbnailed" data-fileid="163263" data-ratio="56.50" data-unique="0sptzrwq7" style="width: 700px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2024_11/07_category-inline.thumb.png.bfdefffa01a7273bd1c4d5ab87ccc2af.png"></a>
</p>

<p>
	يمكنك بعد ذلك تطبيق الشيء نفسه مع <code>TagAdmin</code> في الملف <code>blog/admin.py</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4651_60" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">PostInlineTag</span><span class="pun">(</span><span class="pln">admin</span><span class="pun">.</span><span class="typ">TabularInline</span><span class="pun">):</span><span class="pln">
   model </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">.</span><span class="pln">tag</span><span class="pun">.</span><span class="pln">through
   max_num </span><span class="pun">=</span><span class="pln"> </span><span class="lit">5</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">TagAdmin</span><span class="pun">(</span><span class="pln">admin</span><span class="pun">.</span><span class="typ">ModelAdmin</span><span class="pun">):</span><span class="pln">
   prepopulated_fields </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="str">"slug"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">(</span><span class="str">"name"</span><span class="pun">,)}</span><span class="pln">
   inlines </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
       </span><span class="typ">PostInlineTag</span><span class="pln">
   </span><span class="pun">]</span></pre>

<p>
	لاحظ أن هذه الشيفرة البرمجية مشابهة جدًا لما سبق، ولكن المتغير <code>model</code> ليس <code>Post</code> فقط، بل هو <code>Post.tag.through</code>، لأن العلاقة بين المنشور <code>Post</code> والوسم <code>Tag</code> هي علاقة متعدد إلى متعدد Many-to-Many، وستكون النتيجة النهائية كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163286" href="https://academy.hsoub.com/uploads/monthly_2024_12/08_tag-inline.png.f923c265b14caff8a5ce2f32141abb5a.png" rel=""><img alt="08_tag-inline.png" class="ipsImage ipsImage_thumbnailed" data-fileid="163286" data-ratio="46.00" data-unique="e8s58zgzr" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2024_12/08_tag-inline.thumb.png.784cc83184930313c01838877b346966.png"></a>
</p>

<h2 id="view">
	بناء طبقة العروض View
</h2>

<p>
	ركزنا في الأقسام السابقة على الواجهة الخلفية ولوحة مدير المدونة، وسنركز الآن على جزء الواجهة الأمامية، وهو الجزء الذي يمكن للمستخدمين رؤيته، حيث سنبدأ بدوال العرض View Functions.
</p>

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

<h3 id="home">
	عرض الصفحة الرئيسية home
</h3>

<p>
	ضع المحتويات التالية في الملف <code>blog/views.py</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4651_62" style=""><span class="kwd">from</span><span class="pln"> </span><span class="pun">.</span><span class="pln">models </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Site</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Category</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Tag</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Post</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> home</span><span class="pun">(</span><span class="pln">request</span><span class="pun">):</span><span class="pln">
   site </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Site</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">first</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">objects</span><span class="pun">.</span><span class="pln">all</span><span class="pun">().</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">is_published</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
   categories </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Category</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
   tags </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Tag</span><span class="pun">.</span><span class="pln">objects</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</span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> </span><span class="str">'home.html'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
       </span><span class="str">'site'</span><span class="pun">:</span><span class="pln"> site</span><span class="pun">,</span><span class="pln">
       </span><span class="str">'posts'</span><span class="pun">:</span><span class="pln"> posts</span><span class="pun">,</span><span class="pln">
       </span><span class="str">'categories'</span><span class="pun">:</span><span class="pln"> categories</span><span class="pun">,</span><span class="pln">
       </span><span class="str">'tags'</span><span class="pun">:</span><span class="pln"> tags</span><span class="pun">,</span><span class="pln">
   </span><span class="pun">})</span></pre>

<p>
	نستورد في السطر 1 النماذج التي أنشأناها في <a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B9%D9%85%D9%84%D9%8A%D8%A7%D8%AA-crud-%D9%84%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D9%85%D8%AF%D9%88%D9%86%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2457/" rel="">المقال السابق</a>. يحتوي الموقع في السطر 4 على المعلومات الأساسية لموقعنا، حيث نسترد دائمًا السجل الأول من قاعدة البيانات. يضمن التابع <code>filter(is_published=True)‎</code> في السطر 5 عرض المقالات المنشورة فقط.
</p>

<p>
	بعد ذلك علينا تجهيز موجّه إرسال Dispatcher لعنوان URL المقابل لهذه الصفحة في الملف <code>djangoBlog/urls.py</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4651_66" style=""><span class="pln">path</span><span class="pun">(</span><span class="str">''</span><span class="pun">,</span><span class="pln"> views</span><span class="pun">.</span><span class="pln">home</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">=</span><span class="str">'home'</span><span class="pun">),</span></pre>

<h3 id="category-2">
	عرض الفئة category
</h3>

<p>
	ضع أيضًا المحتويات التالية في الملف <code>blog/views.py</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4651_64" style=""><span class="kwd">def</span><span class="pln"> category</span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> slug</span><span class="pun">):</span><span class="pln">
   site </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Site</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">first</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">objects</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">category__slug</span><span class="pun">=</span><span class="pln">slug</span><span class="pun">).</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">is_published</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
   requested_category </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Category</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">slug</span><span class="pun">=</span><span class="pln">slug</span><span class="pun">)</span><span class="pln">
   categories </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Category</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
   tags </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Tag</span><span class="pun">.</span><span class="pln">objects</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</span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> </span><span class="str">'category.html'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
       </span><span class="str">'site'</span><span class="pun">:</span><span class="pln"> site</span><span class="pun">,</span><span class="pln">
       </span><span class="str">'posts'</span><span class="pun">:</span><span class="pln"> posts</span><span class="pun">,</span><span class="pln">
       </span><span class="str">'category'</span><span class="pun">:</span><span class="pln"> requested_category</span><span class="pun">,</span><span class="pln">
       </span><span class="str">'categories'</span><span class="pun">:</span><span class="pln"> categories</span><span class="pun">,</span><span class="pln">
       </span><span class="str">'tags'</span><span class="pun">:</span><span class="pln"> tags</span><span class="pun">,</span><span class="pln">
   </span><span class="pun">})</span></pre>

<p>
	وتذكر تجهيز موجّه إرسال عنوان URL المقابل لهذه الصفحة في الملف <code>djangoBlog/urls.py</code> كما يلي:
</p>

<pre class="ipsCode">path('category/&lt;slug:slug&gt;', views.category, name='category'),
</pre>

<p>
	مرّرنا متغيرًا إضافيًا هو <code>slug</code> من عنوان URL إلى دالة العرض، واستخدمنا هذا المتغير في السطرين 3 و 4 من الشيفرة البرمجية السابقة للعثور على الفئة والمنشورات الصحيحة.
</p>

<h3 id="tag-2">
	عرض الوسم tag
</h3>

<p>
	ضع أيضًا المحتويات التالية في الملف <code>blog/views.py</code>:
</p>

<pre class="ipsCode">def tag(request, slug):
   site = Site.objects.first()
   posts = Post.objects.filter(tag__slug=slug).filter(is_published=True)
   requested_tag = Tag.objects.get(slug=slug)
   categories = Category.objects.all()
   tags = Tag.objects.all()

   return render(request, 'tag.html', {
       'site': site,
       'posts': posts,
       'tag': requested_tag,
       'categories': categories,
       'tags': tags,
   })
</pre>

<p>
	تذكر تجهيز موجّه إرسال عنوان URL المقابل لهذه الصفحة في الملف <code>djangoBlog/urls.py</code> كما يلي:
</p>

<pre class="ipsCode">path('tag/&lt;slug:slug&gt;', views.tag, name='tag'),
</pre>

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

<p>
	ضع أيضًا المحتويات التالية في الملف <code>blog/views.py</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1200_26" style=""><span class="kwd">def</span><span class="pln"> post</span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> slug</span><span class="pun">):</span><span class="pln">
   site </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Site</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">first</span><span class="pun">()</span><span class="pln">
   requested_post </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">slug</span><span class="pun">=</span><span class="pln">slug</span><span class="pun">)</span><span class="pln">
   categories </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Category</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
   tags </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Tag</span><span class="pun">.</span><span class="pln">objects</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</span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> </span><span class="str">'post.html'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
       </span><span class="str">'site'</span><span class="pun">:</span><span class="pln"> site</span><span class="pun">,</span><span class="pln">
       </span><span class="str">'post'</span><span class="pun">:</span><span class="pln"> requested_post</span><span class="pun">,</span><span class="pln">
       </span><span class="str">'categories'</span><span class="pun">:</span><span class="pln"> categories</span><span class="pun">,</span><span class="pln">
       </span><span class="str">'tags'</span><span class="pun">:</span><span class="pln"> tags</span><span class="pun">,</span><span class="pln">
   </span><span class="pun">})</span></pre>

<p>
	وتذكر تجهيز موجّه إرسال عنوان URL المقابل لهذه الصفحة في الملف <code>djangoBlog/urls.py</code> كما يلي:
</p>

<pre class="ipsCode">path('post/&lt;slug:slug&gt;', views.post, name='post'),
</pre>

<h2 id="template">
	إنشاء طبقة القوالب Template
</h2>

<p>
	يمكنك استخدام <a href="https://github.com/ericsdevblog/blog-template" rel="external nofollow">قالب جاهز</a>، فنحن <a href="https://academy.hsoub.com/tags/%D8%AC%D8%A7%D9%86%D8%BA%D9%88%20%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/" rel="">في هذه السلسة</a> لا نركز على لغتي HTML و CSS.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163285" href="https://academy.hsoub.com/uploads/monthly_2024_12/09_blog-template.png.6044e097b2d5fcc66231e88391b4ecf8.png" rel=""><img alt="09_blog-template.png" class="ipsImage ipsImage_thumbnailed" data-fileid="163285" data-ratio="183.49" data-unique="dm5uirufd" width="327" src="https://academy.hsoub.com/uploads/monthly_2024_12/09_blog-template.thumb.png.ec6c7c1b1dabdd5550bc4e8eaf9a9019.png"></a>
</p>

<p>
	وستكون بنية القالب الذي سنستخدمه كما يلي:
</p>

<pre class="ipsCode">templates
├── category.html
├── home.html
├── layout.html
├── post.html
├── search.html
├── tag.html
└── vendor
   ├── list.html
   └── sidebar.html
</pre>

<p>
	يحتوي الملف <code>layout.html</code> على ترويسة وتذييل الصفحة، وهو المكان الذي نستورد فيه عادة أكواد التنسيقات CSS وأكواد جافا سكريبت JavaScript. وتوجهنا دوال العرض إلى القوالب <code>home</code> و <code>category</code> و <code>tag</code> و <code>post</code>، وتتوسّع جميعها إلى القالب <code>layout</code> الذي يعتبر كأساس لها جميعًا. وتتواجد المكونات التي ستظهر عدة مرات في قوالب مختلفة ضمن المجلد <code>vendor</code>، ويمكنك استيرادها باستخدام الوسم <code>include</code>.
</p>

<h3 id="layout">
	قالب التخطيط Layout
</h3>

<p>
	ضع المحتويات التالية في الملف <code>layout.html</code>:
</p>

<pre class="ipsCode">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
 &lt;head&gt;
   &lt;meta charset="UTF-8" /&gt;
   &lt;meta http-equiv="X-UA-Compatible" content="IE=edge" /&gt;
   &lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&gt;
   {% load static %}
   &lt;link rel="stylesheet" href="{% static 'style.css' %}" /&gt;
   {% block title %}{% endblock %}
 &lt;/head&gt;

 &lt;body class="container mx-auto font-serif"&gt;
   &lt;nav class="flex flex-row justify-between h-16 items-center border-b-2"&gt;
     &lt;div class="px-5 text-2xl"&gt;
       &lt;a href="/"&gt; My Blog &lt;/a&gt;
     &lt;/div&gt;
     &lt;div class="hidden lg:flex content-between space-x-10 px-10 text-lg"&gt;
       &lt;a
         href="https://github.com/ericnanhu"
         class="hover:underline hover:underline-offset-1"
         &gt;GitHub&lt;/a
       &gt;
       &lt;a href="#" class="hover:underline hover:underline-offset-1"&gt;Link&lt;/a&gt;
       &lt;a href="#" class="hover:underline hover:underline-offset-1"&gt;Link&lt;/a&gt;
     &lt;/div&gt;
   &lt;/nav&gt;

   {% block content %}{% endblock %}

   &lt;footer class="bg-gray-700 text-white"&gt;
     &lt;div
       class="flex justify-center items-center sm:justify-between flex-wrap lg:max-w-screen-2xl mx-auto px-4 sm:px-8 py-10"
     &gt;
       &lt;p class="font-serif text-center mb-3 sm:mb-0"&gt;
         Copyright ©
         &lt;a href="https://www.ericsdevblog.com/" class="hover:underline"
           &gt;Eric Hu&lt;/a
         &gt;
       &lt;/p&gt;

       &lt;div class="flex justify-center space-x-4"&gt;
         . . .
       &lt;/div&gt;
     &lt;/div&gt;
   &lt;/footer&gt;
 &lt;/body&gt;
&lt;/html&gt;
</pre>

<p>
	لاحظ في السطرين 7 و 8 الطريقة التي يمكنك بها استيراد الملفات الساكنة ملفات CSS و جافا سكريبت في جانغو، فكما وضحنا لن نتحدث في هذا المقال عن لغة CSS، ولكن ما يهمنا هو معرفة طريقة استيراد ملفات CSS إضافية لتطبيق جانغو. سيبحث جانغو عن الملفات الساكنة Static Files في مجلدات التطبيق الفردية افتراضيًا، حيث سيذهب إلى المجلد ‎<code>/blog</code>‎ في تطبيقنا <code>blog</code>، ويبحث عن المجلد <code>static</code>، ثم يبحث عن الملف <code>style.css</code> ضمن المجلد <code>static</code> كما هو محدد في القالب.
</p>

<pre class="ipsCode">blog
├── admin.py
├── apps.py
├── __init__.py
├── migrations
├── models.py
├── static
│   ├── input.css
│   └── style.css
├── tests.py
└── views.py
</pre>

<h3 id="home-1">
	قالب الصفحة الرئيسية Home
</h3>

<p>
	ضع المحتويات التالية في الملف <code>home.html</code>:
</p>

<pre class="ipsCode">{% extends 'layout.html' %} {% block title %}
&lt;title&gt;Page Title&lt;/title&gt;
{% endblock %} {% block content %}
&lt;div class="grid grid-cols-4 gap-4 py-10"&gt;
 &lt;div class="col-span-3 grid grid-cols-1"&gt;
   &lt;!-- المنشور البارز --&gt;
   &lt;div class="mb-4 ring-1 ring-slate-200 rounded-md hover:shadow-md"&gt;
     &lt;a href="{% url 'post' featured_post.slug %}"
       &gt;&lt;img
         class="float-left mr-4 rounded-l-md object-cover h-full w-1/3"
         src="{{ featured_post.featured_image.url }}"
         alt="..."
     /&gt;&lt;/a&gt;
     &lt;div class="my-4 mr-4 grid gap-2"&gt;
       &lt;div class="text-sm text-gray-500"&gt;
         {{ featured_post.created_at|date:"F j, o" }}
       &lt;/div&gt;
       &lt;h2 class="text-lg font-bold"&gt;{{ featured_post.title }}&lt;/h2&gt;
       &lt;p class="text-base"&gt;
         {{ featured_post.content|striptags|truncatewords:80 }}
       &lt;/p&gt;
       &lt;a
         class="bg-blue-500 hover:bg-blue-700 rounded-md p-2 text-white uppercase text-sm font-semibold font-sans w-fit focus:ring"
         href="{% url 'post' featured_post.slug %}"
         &gt;Read more →&lt;/a
       &gt;
     &lt;/div&gt;
   &lt;/div&gt;

   {% include "vendor/list.html" %}
 &lt;/div&gt;
 {% include "vendor/sidebar.html" %}
&lt;/div&gt;
{% endblock %}
</pre>

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

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163214" href="https://academy.hsoub.com/uploads/monthly_2024_11/10_home.png.fc88e215fc42d106cc1085ef02b8fd7e.png" rel=""><img alt="10 home" class="ipsImage ipsImage_thumbnailed" data-fileid="163214" data-unique="zuzgw6nsg" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/10_home.thumb.png.829586f8dfd01fba96841a7932d02a34.png"> </a>
</p>

<h3 id="-6">
	قائمة المنشورات
</h3>

<p>
	ضع أيضًا المحتويات التالية في الملف <code>vendor/list.html</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1200_8" style=""><span class="pun">&lt;!--</span><span class="pln"> </span><span class="pun">قائمة</span><span class="pln"> </span><span class="pun">المنشورات</span><span class="pln"> </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">"grid grid-cols-3 gap-4"</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"> </span><span class="pun">المنشور</span><span class="pln"> </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">"mb-4 ring-1 ring-slate-200 rounded-md h-fit hover:shadow-md"</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 'post' post.slug %}"</span><span class="pln">
     </span><span class="pun">&gt;&lt;</span><span class="pln">img
       </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"rounded-t-md object-cover h-60 w-full"</span><span class="pln">
       src</span><span class="pun">=</span><span class="str">"{{ post.featured_image.url }}"</span><span class="pln">
       alt</span><span class="pun">=</span><span class="str">"..."</span><span class="pln">
   </span><span class="pun">/&gt;&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="kwd">class</span><span class="pun">=</span><span class="str">"m-4 grid gap-2"</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">"text-sm text-gray-500"</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">created_at</span><span class="pun">|</span><span class="pln">date</span><span class="pun">:</span><span class="str">"F j, o"</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="pun">&gt;</span><span class="pln">
     </span><span class="pun">&lt;</span><span class="pln">h2 </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"text-lg font-bold"</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">h2</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">"text-base"</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">content</span><span class="pun">|</span><span class="pln">striptags</span><span class="pun">|</span><span class="pln">truncatewords</span><span class="pun">:</span><span class="lit">30</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">a
       </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"bg-blue-500 hover:bg-blue-700 rounded-md p-2 text-white uppercase text-sm font-semibold font-sans w-fit focus:ring"</span><span class="pln">
       href</span><span class="pun">=</span><span class="str">"{% url 'post' post.slug %}"</span><span class="pln">
       </span><span class="pun">&gt;</span><span class="typ">Read</span><span class="pln"> more </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="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></pre>

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

<p>
	تذكّر في السطر 6 أننا أنشأنا موجّه إرسال عنوان URL كما يلي:
</p>

<pre class="ipsCode">path('post/&lt;slug:slug&gt;', views.post, name='post'),
</pre>

<p>
	ستجد التعليمة ‎<code>{% url 'post' post.slug %}</code>‎ في القالب موجّهَ إرسال عنوان URL بالاسم <code>'posts'</code>، وتسند القيمة <code>post.slug</code> إلى المتغير ‎<code>&lt;slug:slug&gt;</code>‎، والذي سيُمرَّر بعد ذلك إلى دالة العرض المقابلة. ينسّق المرشّح Filter الذي هو <code>date</code> في السطر 14 بيانات التاريخ المُمرَّرة إلى القالب لأن القيمة الافتراضية ليست سهلة الاستخدام، ويمكنك العثور على <a href="https://docs.djangoproject.com/en/5.1/ref/templates/builtins/#date" rel="external nofollow">تنسيقات تواريخ أخرى</a> في توثيق جانغو الرسمي.
</p>

<p>
	وضعنا مرشحَين بعد <code>post.content</code> في السطر 18، حيث يزيل المرشّح الأول وسوم HTML، ويأخذ المرشّح الثاني أول 30 كلمة ويقتطع الباقي.
</p>

<h3 id="sidebar">
	قالب الشريط الجانبي Sidebar
</h3>

<p>
	ضع أيضًا المحتويات التالية في الملف <code>vendor/sidebar.html</code>:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1200_10" style=""><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"col-span-1"</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">"border rounded-md mb-4"</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">"bg-slate-200 p-4"</span><span class="tag">&gt;</span><span class="pln">Search</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">"p-4"</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">""</span><span class="pln"> </span><span class="atn">method</span><span class="pun">=</span><span class="atv">"get"</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">"text"</span><span class="pln"> </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"search"</span><span class="pln"> </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"search"</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"border rounded-md w-44 focus:ring p-2"</span><span class="pln"> </span><span class="atn">placeholder</span><span class="pun">=</span><span class="atv">"Search something..."</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">"bg-blue-500 hover:bg-blue-700 rounded-md p-2 text-white uppercase font-semibold font-sans w-fit focus:ring"</span><span class="tag">&gt;</span><span class="pln">Search</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;/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">"border rounded-md mb-4"</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">"bg-slate-200 p-4"</span><span class="tag">&gt;</span><span class="pln">Categories</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">"p-4"</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">"list-none list-inside"</span><span class="tag">&gt;</span><span class="pln">
       {% for category in categories %}
       </span><span class="tag">&lt;li&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 'category' category.slug %}"</span><span class="pln">
           </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"text-blue-500 hover:underline"</span><span class="pln">
           </span><span class="tag">&gt;</span><span class="pln">{{ category.name }}</span><span class="tag">&lt;/a</span><span class="pln">
         </span><span class="tag">&gt;</span><span class="pln">
       </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">
 </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">"border rounded-md mb-4"</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">"bg-slate-200 p-4"</span><span class="tag">&gt;</span><span class="pln">Tags</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">"p-4"</span><span class="tag">&gt;</span><span class="pln">
     {% for tag in tags %}
     </span><span class="tag">&lt;span</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"mr-2"</span><span class="pln">
       </span><span class="tag">&gt;&lt;a</span><span class="pln">
         </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{% url 'tag' tag.slug %}"</span><span class="pln">
         </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"text-blue-500 hover:underline"</span><span class="pln">
         </span><span class="tag">&gt;</span><span class="pln">{{ tag.name }}</span><span class="tag">&lt;/a</span><span class="pln">
       </span><span class="tag">&gt;&lt;/span</span><span class="pln">
     </span><span class="tag">&gt;</span><span class="pln">
     {% endfor %}
   </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;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"border rounded-md mb-4"</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">"bg-slate-200 p-4"</span><span class="tag">&gt;</span><span class="pln">More Card</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">"p-4"</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;/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;/div&gt;</span></pre>

<h3 id="category-3">
	قالب الفئة Category
</h3>

<p>
	ضع المحتويات التالية في الملف <code>category.html</code>:
</p>

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

{% block title %}
</span><span class="tag">&lt;title&gt;</span><span class="pln">Page Title</span><span class="tag">&lt;/title&gt;</span><span class="pln">
{% endblock %}

{% block content %}
</span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"grid grid-cols-4 gap-4 py-10"</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-span-3 grid grid-cols-1"</span><span class="tag">&gt;</span><span class="pln">

   {% include "vendor/list.html" %}

 </span><span class="tag">&lt;/div&gt;</span><span class="pln">
 {% include "vendor/sidebar.html" %}
</span><span class="tag">&lt;/div&gt;</span><span class="pln">
{% endblock %}</span></pre>

<h3 id="tag-3">
	قالب الوسم Tag
</h3>

<p>
	ضع المحتويات التالية في الملف <code>tag.html</code>:
</p>

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

{% block title %}
</span><span class="tag">&lt;title&gt;</span><span class="pln">Page Title</span><span class="tag">&lt;/title&gt;</span><span class="pln">
{% endblock %}

{% block content %}
</span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"grid grid-cols-4 gap-4 py-10"</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-span-3 grid grid-cols-1"</span><span class="tag">&gt;</span><span class="pln">

   {% include "vendor/list.html" %}

 </span><span class="tag">&lt;/div&gt;</span><span class="pln">
 {% include "vendor/sidebar.html" %}
</span><span class="tag">&lt;/div&gt;</span><span class="pln">
{% endblock %}</span></pre>

<h3 id="post-3">
	قالب المنشور Post
</h3>

<p>
	أخيرًا، يتضمن قالب المنشور المحتويات التالية:
</p>

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

{% block title %}
</span><span class="tag">&lt;title&gt;</span><span class="pln">Page Title</span><span class="tag">&lt;/title&gt;</span><span class="pln">
{% endblock %}

{% block content %}
</span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"grid grid-cols-4 gap-4 py-10"</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-span-3"</span><span class="tag">&gt;</span><span class="pln">

   </span><span class="tag">&lt;img</span><span class="pln">
       </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"rounded-md object-cover h-96 w-full"</span><span class="pln">
       </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"{{ post.featured_image.url }}"</span><span class="pln">
       </span><span class="atn">alt</span><span class="pun">=</span><span class="atv">"..."</span><span class="pln">
   </span><span class="tag">/&gt;</span><span class="pln">
   </span><span class="tag">&lt;h2</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"mt-5 mb-2 text-center text-2xl font-bold"</span><span class="tag">&gt;</span><span class="pln">{{ post.title }}</span><span class="tag">&lt;/h2&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">"mb-5 text-center text-sm text-slate-500 italic"</span><span class="tag">&gt;</span><span class="pln">By {{ post.user|capfirst }} | {{ post.created_at }}</span><span class="tag">&lt;/p&gt;</span><span class="pln">

   </span><span class="tag">&lt;div&gt;</span><span class="pln">{{ post.content|safe }}</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">"my-5"</span><span class="tag">&gt;</span><span class="pln">
       {% for tag in post.tag.all %}
       </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{% url 'tag' tag.slug %}"</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"text-blue-500 hover:underline"</span><span class="pln"> </span><span class="atn">mr-3</span><span class="atv">"&gt;</span><span class="pln">#{{ tag.name }}</span><span class="tag">&lt;/a&gt;</span><span class="pln">
       {% endfor %}
   </span><span class="tag">&lt;/div&gt;</span><span class="pln">

 </span><span class="tag">&lt;/div&gt;</span><span class="pln">
 {% include "vendor/sidebar.html" %}
</span><span class="tag">&lt;/div&gt;</span><span class="pln">
{% endblock %}</span></pre>

<p>
	لاحظ في السطر 19 أننا أضفنا المرشح <code>safe</code>، لأن جانغو سيعرض شيفرة HTML كنص عادي افتراضيًا لأسباب أمنية، لذا يجب ان نخبر جانغو بأنه يمكن عرض شيفرات HTML كما هي.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163215" href="https://academy.hsoub.com/uploads/monthly_2024_11/11_post.png.28446cef4a0421e70b4e3b5065c0d549.png" rel=""><img alt="11 post" class="ipsImage ipsImage_thumbnailed" data-fileid="163215" data-unique="atwejose1" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/11_post.thumb.png.348dfcee95d5c930ad038b5ced0ba26e.png"> </a>
</p>

<p>
	أصبح كل شيءجاهزًا، كل ما عليك الأن هو تشغيل خادم التطوير باستخدام الأمر التالي، وعرض تطبيق جانغو الأول الخاص بك:
</p>

<pre class="ipsCode">python manage.py runserver
</pre>

<p>
	ترجمة -وبتصرّف- للمقال <a href="https://www.ericsdevblog.com/posts/django-for-beginners-4/" rel="external nofollow">Django for Beginners #4 - The Blog App</a> لصاحبه Eric Hu.
</p>

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

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B9%D9%85%D9%84%D9%8A%D8%A7%D8%AA-crud-%D9%84%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D9%85%D8%AF%D9%88%D9%86%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2457/" rel="">استخدام عمليات CRUD لإدارة مدونة في جانغو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%88%D8%AA%D9%88%D8%B5%D9%8A%D9%84%D9%87-%D8%A8%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-r1626/" rel="">إنشاء تطبيق جانغو وتوصيله بقاعدة بيانات</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2090/" rel="">إنشاء موقع ويب هيكلي بجانغو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%B1%D9%81%D8%B9-%D9%85%D8%B3%D8%AA%D9%88%D9%89-%D8%A3%D9%85%D8%A7%D9%86-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%81%D9%8A-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%A7%D8%AC-r1717/" rel="">رفع مستوى أمان تطبيقات جانغو في بيئة الإنتاج</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D9%84%D8%A8%D8%AF%D8%A1-%D9%85%D8%B9-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%84%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-r1625/" rel="">البدء مع إطار العمل جانغو لإنشاء تطبيق ويب</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2458</guid><pubDate>Sat, 30 Nov 2024 15:00:00 +0000</pubDate></item><item><title>&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x639;&#x645;&#x644;&#x64A;&#x627;&#x62A; CRUD &#x644;&#x625;&#x62F;&#x627;&#x631;&#x629; &#x645;&#x62F;&#x648;&#x646;&#x629; &#x641;&#x64A; &#x62C;&#x627;&#x646;&#x63A;&#x648;</title><link>https://academy.hsoub.com/programming/python/django/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B9%D9%85%D9%84%D9%8A%D8%A7%D8%AA-crud-%D9%84%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D9%85%D8%AF%D9%88%D9%86%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2457/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_11/--CRUD_.png.d0e15da023e82888880eaff705f6d6ef.png" /></p>
<p>
	قدمنا في المقالات السابقة من <a href="https://academy.hsoub.com/tags/%D8%AC%D8%A7%D9%86%D8%BA%D9%88%20%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/" rel="">هذه السلسلة</a> العديد من المفاهيم الجديدة في جانغو، وسنوضح في هذا المقال كيفية تفاعل الموجه URL Dispatcher والنماذج Models والعروض Views والقوالب Templates معًا في تطبيق مدونة في جانغو.
</p>

<p>
	لن ننشئ في هذا المقال مدونة كاملة الميزات مع الفئات Categories والوسوم Tags وما إلى ذلك، بل سنكتفي فقط بإنشاء صفحة تعرض مقالًا منشورًا، وصفحة رئيسية تعرض قائمةً بجميع المقالات، وصفحة لإنشاء وتعديل وحذف المنشورات.
</p>

<h2 id="">
	تصميم بنية قاعدة البيانات
</h2>

<p>
	لنبدأ أولًا بطبقة النموذج Model التي سنصمم فيها بنية قاعدة البيانات، لذا انتقل إلى الملف <code>blog/models.py</code> وأنشئ نموذج <code>Post</code> جديد، وضع فيه الشيفرة البرمجية التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8711_9" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">db </span><span class="kwd">import</span><span class="pln"> models

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">(</span><span class="pln">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
   title </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</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"> models</span><span class="pun">.</span><span class="typ">TextField</span><span class="pun">()</span></pre>

<p>
	يحتوي نموذج المنشور <code>Post</code> على حقلين فقط هما، العنوان <code>title</code> من النوع <code>CharField</code> بحد أقصى 100 محرف، والمحتوى <code>content</code> من النوع <code>TextField</code>.
</p>

<p>
	لتطبيق هذه التغييرات على قاعدة البيانات علينا توليد ملفات التهجير Migration المقابلة باستخدام الأمر التالي:
</p>

<pre class="ipsCode">python manage.py makemigrations
</pre>

<p>
	ثم تطبيق عمليات التهجير باستخدام الأمر التالي:
</p>

<pre class="ipsCode">python manage.py migrate
</pre>

<h2 id="crud-1">
	عمليات CRUD
</h2>

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

<ul>
	<li>
		الإنشاء Create لإدراج بيانات جديدة في قاعدة البيانات
	</li>
	<li>
		القراءة Read لاسترداد البيانات من قاعدة البيانات
	</li>
	<li>
		التحديث Update لتعديل البيانات الموجودة مسبقًا في قاعدة البيانات
	</li>
	<li>
		الحذف Delete لإزالة البيانات من قاعدة البيانات
	</li>
</ul>

<p>
	ويشار إلى هذه العمليات مع بعضها البعض باسم عمليات CRUD.
</p>

<h2 id="create">
	إجراء الإنشاء Create
</h2>

<p>
	لنبدأ أولًا بعملية الإنشاء، فلا تزال قاعدة البيانات فارغة، لذا يجب على المستخدم إنشاء منشور جديد، ولكنك تحتاج إلى موجّه إرسال عناوين URL لتوجيه نمط عنوان URL الذي هو ‎<code>/post/create/</code>‎ إلى دالة العرض View Function وهي <code>post_create()‎</code> لإكمال هذا الإجراء.
</p>

<p>
	تحتاج دالة العرض <code>post_create()‎ </code>إلى التمييز بين نوع الطلب الوارد إلى السيرفر لذا يجب أن تحتوي على عنصر تحكم في التدفق كتعليمة <code>if</code> لتميز فيما إذا كان تابع الطلب هو <code>GET</code>، عندها ستعيد دالة العرض قالبًا يحتوي على استمارة HTML، لتسمح للمستخدم بتمرير المعلومات إلى الواجهة الخلفية، أما إرسال الاستمارة Form فيجب أن يكون ضمن طلب <code>POST</code>. لذا إذا كان تابع الطلب هو <code>POST</code>، فيجب إنشاء مورد <code>Post</code> جديد وحفظه.
</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-%D8%B4%D8%B1%D8%AD-%D8%A7%D9%84%D8%AA%D8%AE%D8%A7%D8%B7%D8%A8-%D8%A8%D9%8A%D9%86-%D8%A7%D9%84%D8%B9%D9%85%D9%8A%D9%84-%D9%88%D8%A7%D9%84%D8%AE%D8%A7%D8%AF%D9%85-r74/" rel="">HTTP</a> في حال احتياجك إلى تجديد بعض المعلومات:
</p>

<ul>
	<li>
		تابع <code>GET</code> هو تابع طلبات HTTP الأكثر استخدامًا، ويُستخدم لطلب البيانات والموارد من الخادم
	</li>
	<li>
		يُستخدم تابع <code>POST</code> لإرسال البيانات إلى الخادم، ويستعمل عادة لإنشاء أو تحديث المورد
	</li>
	<li>
		يعمل تابع <code>HEAD</code> مثل تابع <code>GET</code> تمامًا، باستثناء أن استجابة HTTP تحتوي على الترويسة فقط دون الجسم، ويستخدم المطورون هذا التابع لأغراض تنقيح الأخطاء Debugging
	</li>
	<li>
		تابع <code>PUT</code> مشابه لتابع <code>POST</code>، مع اختلاف واحد بسيط، فإذا أرسلت مورد باستخدام التابع <code>POST</code> وكان المورد موجودًا مسبقًا على الخادم، فلن يسبب هذا الإجراء أي فرق على الخادم، أما التابع<code> PUT</code> فسيكرر تحديث هذا المورد بالبيانات المرسلة في كل مرة تجري فيها الطلب.
	</li>
	<li>
		يزيل تابع <code>DELETE</code> موردًا من الخادم.
	</li>
</ul>

<p>
	لنبدأ بموجّه إرسال عناوين URL، لذا انتقل إلى الملف <code>djangoBlog/urls.py</code> وضع فيه ما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8711_13" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">urls </span><span class="kwd">import</span><span class="pln"> path
</span><span class="kwd">from</span><span class="pln"> blog </span><span class="kwd">import</span><span class="pln"> views

urlpatterns </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
   path</span><span class="pun">(</span><span class="str">"post/create/"</span><span class="pun">,</span><span class="pln"> views</span><span class="pun">.</span><span class="pln">post_create</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">=</span><span class="str">"create"</span><span class="pun">),</span><span class="pln">
</span><span class="pun">]</span></pre>

<p>
	ستحتاج بعد ذلك إلى دالة العرض <code>post_create()‎، </code>لذا انتقل إلى الملف blog/views.py وضع فيه الشيفرة البرمجية التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8711_15" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">shortcuts </span><span class="kwd">import</span><span class="pln"> redirect</span><span class="pun">,</span><span class="pln"> render
</span><span class="kwd">from</span><span class="pln"> </span><span class="pun">.</span><span class="pln">models </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Post</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> post_create</span><span class="pun">(</span><span class="pln">request</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">"GET"</span><span class="pun">:</span><span class="pln">
       </span><span class="kwd">return</span><span class="pln"> render</span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> </span><span class="str">"post/create.html"</span><span class="pun">)</span><span class="pln">
   </span><span class="kwd">elif</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">
       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="pln">request</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"> content</span><span class="pun">=</span><span class="pln">request</span><span class="pun">.</span><span class="pln">POST</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">save</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="str">"home"</span><span class="pun">)</span></pre>

<p>
	تفحص الدالة <code>post_create()‎</code> أولًا تابع طلب HTTP، فإذا كان تابع <code>GET</code>، فيجب إعادة القالب <code>create.html</code>، وإذا كان <code>POST</code> فيجب استخدام المعلومات التي يمرّرها طلب POST لإنشاء نسخة <code>POST</code> جديدة، ثم إعادة التوجيه إلى الصفحة الرئيسية التي سننشئها في الخطوة التالية.
</p>

<p>
	سننشئ القالب <code>create.html</code>، ولكن يجب إنشاء القالب <code>templates/layout.html</code> أولًا كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_8711_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;meta</span><span class="pln"> </span><span class="atn">http-equiv</span><span class="pun">=</span><span class="atv">"X-UA-Compatible"</span><span class="pln"> </span><span class="atn">content</span><span class="pun">=</span><span class="atv">"IE=edge"</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.0"</span><span class="tag">&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://cdn.tailwindcss.com"</span><span class="tag">&gt;&lt;/script&gt;</span><span class="pln">
   {% block title %}{% endblock %}
</span><span class="tag">&lt;/head&gt;</span><span class="pln">

</span><span class="tag">&lt;body</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"container mx-auto font-serif"</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">"bg-white text-black font-serif"</span><span class="tag">&gt;</span><span class="pln">
       </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"nav"</span><span class="tag">&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">"flex flex-row justify-between h-16 items-center shadow-md"</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">"px-5 text-2xl"</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">"/"</span><span class="tag">&gt;</span><span class="pln">
                       My Blog
                   </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">"hidden lg:flex content-between space-x-10 px-10 text-lg"</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 'create' %}"</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"hover:underline hover:underline-offset-1"</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;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"https://github.com/ericnanhu"</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"hover:underline hover:underline-offset-1"</span><span class="tag">&gt;</span><span class="pln">GitHub</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;/nav&gt;</span><span class="pln">
       </span><span class="tag">&lt;/div&gt;</span><span class="pln">

       {% block content %}{% endblock %}

       </span><span class="tag">&lt;footer</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"bg-gray-700 text-white"</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">"flex justify-center items-center sm:justify-between flex-wrap lg:max-w-screen-2xl mx-auto px-4 sm:px-8 py-10"</span><span class="tag">&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">"font-serif text-center mb-3 sm:mb-0"</span><span class="tag">&gt;</span><span class="pln">Copyright © </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"https://www.ericsdevblog.com/"</span><span class="pln">
                       </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"hover:underline"</span><span class="tag">&gt;</span><span class="pln">Eric Hu</span><span class="tag">&lt;/a&gt;&lt;/p&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">"flex justify-center space-x-4"</span><span class="tag">&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;/footer&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>
	لاحظ ‎<code>{% url 'create' %}</code>‎ في السطر 22، فهذه هي الطريقة التي يمكنك بها عكس عناوين URL اعتمادًا على أسمائها، حيث يتطابق الاسم <code>create</code> مع الاسم الذي أعطيته لموجّه الإرسال ‎<code>post/create/</code>‎. أضفنا أيضًا إطار عمل <a href="https://academy.hsoub.com/programming/css/bootstrap/%D9%85%D9%82%D8%A7%D8%B1%D9%86%D8%A9-%D8%A8%D9%8A%D9%86-bootstrap-%D9%88-tailwind-css-r1595/" rel="">TailwindCSS</a> عبر شبكة CDN في السطر 8 لجعل هذه الصفحة تبدو أفضل، ولكن يجب ألّا تفعل ذلك في بيئة الإنتاج.
</p>

<p>
	لننشئ بعد ذلك القالب <code>templates/post/create.html</code>، إذ يتوجب علينا إنشاء المجلد <code>post</code> له لتوضيح أن هذا القالب مخصص لإنشاء منشور:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8711_20" style=""><span class="pun">{%</span><span class="pln"> extends </span><span class="str">'layout.html'</span><span class="pln"> </span><span class="pun">%}</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="pun">&lt;</span><span class="pln">title</span><span class="pun">&gt;</span><span class="typ">Create</span><span class="pun">&lt;/</span><span class="pln">title</span><span class="pun">&gt;</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"> block content </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">"w-96 mx-auto my-8"</span><span class="pun">&gt;</span><span class="pln">
   </span><span class="pun">&lt;</span><span class="pln">h2 </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"text-2xl font-semibold underline mb-4"</span><span class="pun">&gt;</span><span class="typ">Create</span><span class="pln"> new post</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">form action</span><span class="pun">=</span><span class="str">"{% url 'create' %}"</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">{%</span><span class="pln"> csrf_token </span><span class="pun">%}</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;&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"> id</span><span class="pun">=</span><span class="str">"title"</span><span class="pln"> name</span><span class="pun">=</span><span class="str">"title"</span><span class="pln">
           </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"p-2 w-full bg-gray-50 rounded border border-gray-300 focus:ring-3 focus:ring-blue-300"</span><span class="pun">&gt;&lt;</span><span class="pln">br</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">Content</span><span class="pun">:&lt;/</span><span class="pln">label</span><span class="pun">&gt;&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 type</span><span class="pun">=</span><span class="str">"text"</span><span class="pln"> id</span><span class="pun">=</span><span class="str">"content"</span><span class="pln"> name</span><span class="pun">=</span><span class="str">"content"</span><span class="pln"> rows</span><span class="pun">=</span><span class="str">"15"</span><span class="pln">
           </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"p-2 w-full bg-gray-50 rounded border border-gray-300 focus:ring-3 focus:ring-blue-300"</span><span class="pun">&gt;&lt;/</span><span class="pln">textarea</span><span class="pun">&gt;&lt;</span><span class="pln">br</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="pln">
           </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"font-sans text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-md text-sm w-full px-5 py-2.5 text-center"</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">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>
	يحدّد السطر 10 الإجراء الذي ستتخذه هذه الاستمارة عند إرسالها، وتابع الطلب الذي ستستخدمه، ويضيف السطر 11 حماية من هجمات CSRF إلى الاستمارة لأغراض أمنية. لاحظ أيضًا الحقل <code><a href="https://wiki.hsoub.com/HTML/input" rel="external">&lt;input&gt;</a></code> في السطرين 13 و 14، فالسمة Attribute التي هي <code>name</code> في هذا الحقل مهمة جدًا، حيث سيُربَط إدخال المستخدم بهذه السمة عند إرسال الاستمارة، ويمكنك بعد ذلك استرداد هذا الإدخال في دالة العرض كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8711_23" style=""><span class="pln">title</span><span class="pun">=</span><span class="pln">request</span><span class="pun">.</span><span class="pln">POST</span><span class="pun">[</span><span class="str">"title"</span><span class="pun">]</span></pre>

<p>
	وينطبق الأمر نفسه على العنصر <code><a href="https://wiki.hsoub.com/HTML/textarea" rel="external">&lt;textarea&gt;</a></code> في السطرين 17 و 18، ويجب أن يكون الزر من النوع <code>type="submit"‎</code> ليعمل بنجاح.
</p>

<h2 id="list">
	إجراء القائمة List
</h2>

<p>
	لننشئ الآن صفحة رئيسية لعرض قائمة بجميع المنشورات، حيث سنبدأ بعنوان URL لهذه الصفحة في الملف <code>djangoBlog/urls.py</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8711_26" style=""><span class="pln">path</span><span class="pun">(</span><span class="str">""</span><span class="pun">,</span><span class="pln"> views</span><span class="pun">.</span><span class="pln">post_list</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">=</span><span class="str">"home"</span><span class="pun">),</span></pre>

<p>
	ثم ننتقل إلى دالة العرض في الملف <code>blog/views.py</code> ونضع فيه ما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8711_28" style=""><span class="kwd">def</span><span class="pln"> post_list</span><span class="pun">(</span><span class="pln">request</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">objects</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</span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> </span><span class="str">"post/list.html"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="str">"posts"</span><span class="pun">:</span><span class="pln"> posts</span><span class="pun">})</span></pre>

<p>
	وسيتضمن القالب <code>list.html</code> المحتويات التالية:
</p>

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

{% block title %}
</span><span class="tag">&lt;title&gt;</span><span class="pln">My Blog</span><span class="tag">&lt;/title&gt;</span><span class="pln">
{% endblock %}

{% block content %}
</span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"max-w-screen-lg mx-auto my-8"</span><span class="tag">&gt;</span><span class="pln">
   {% for post in posts %}
   </span><span class="tag">&lt;h2</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"text-2xl font-semibold underline mb-2"</span><span class="tag">&gt;&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{% url 'show' post.pk %}"</span><span class="tag">&gt;</span><span class="pln">{{ post.title }}</span><span class="tag">&lt;/a&gt;&lt;/h2&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">"mb-4"</span><span class="tag">&gt;</span><span class="pln">{{ post.content | truncatewords:50 }}</span><span class="tag">&lt;/p&gt;</span><span class="pln">
   {% endfor %}
</span><span class="tag">&lt;/div&gt;</span><span class="pln">
{% endblock %}</span></pre>

<p>
	تتكرر التعليمة ‎<code>{% for post in posts %}</code>‎ على جميع المنشورات <code>posts</code>، ويُسنَد كل عنصر إلى المتغير <code>post</code>، وتمرّر التعليمة ‎<code>{% url 'show' post.pk %}</code>‎ المفتاح الرئيسي Primary Key للمنشور <code>post</code> إلى موجّه إرسال عنوان URL للصفحة <code>show</code> التي سننشئها لاحقًا. تستخدم التعليمة ‎<code>{{ post.content | truncatewords:50 }}</code>‎ المرشّح <code>truncatewords</code> لاقتطاع المحتوى بحيث يحتوي على أول 50 كلمة.
</p>

<h2 id="show">
	إجراء العرض Show
</h2>

<p>
	يجب أن يعرض إجراء العرض محتوى منشور معين، مما يعني أن عنوان URL الخاص به يجب أن يحتوي على شيء فريد يسمح لجانغو بتحديد نسخة واحدة من <code>Post</code> فقط، ويكون هذا الشيء الفريد هو المفتاح الرئيسي Primary Key، لذا ضع ما يلي في الملف <code>djangoBlog/urls.py</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8711_32" style=""><span class="pln">path</span><span class="pun">(</span><span class="str">"post/&lt;int:id&gt;"</span><span class="pun">,</span><span class="pln"> views</span><span class="pun">.</span><span class="pln">post_show</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">=</span><span class="str">"show"</span><span class="pun">),</span></pre>

<p>
	سيُسنَد العدد الصحيح الذي يلي ‎<code>post/</code>‎ إلى المتغير <code>id</code>، ويُمرّر إلى دالة العرض في الملف <code>blog/views.py</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8711_34" style=""><span class="kwd">def</span><span class="pln"> post_show</span><span class="pun">(</span><span class="pln">request</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"> </span><span class="typ">Post</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">pk</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</span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> </span><span class="str">"post/show.html"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="str">"post"</span><span class="pun">:</span><span class="pln"> post</span><span class="pun">})</span></pre>

<p>
	وسيتضمن القالب المقابل <code>templates/post/show.html</code> المحتويات التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8711_36" style=""><span class="pun">{%</span><span class="pln"> extends </span><span class="str">'layout.html'</span><span class="pln"> </span><span class="pun">%}</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="pun">&lt;</span><span class="pln">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">title</span><span class="pun">&gt;</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"> block content </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">"max-w-screen-lg mx-auto my-8"</span><span class="pun">&gt;</span><span class="pln">

   </span><span class="pun">&lt;</span><span class="pln">h2 </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"text-2xl font-semibold underline mb-2"</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">h2</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">"mb-4"</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">a href</span><span class="pun">=</span><span class="str">"{% url 'update' post.pk %}"</span><span class="pln"> </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"font-sans text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-md text-sm w-full px-5 py-2.5 text-center"</span><span class="pun">&gt;</span><span class="typ">Update</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"> endblock </span><span class="pun">%}</span></pre>

<h2 id="update">
	إجراء التحديث Update
</h2>

<p>
	ضع ما يلي في الملف <code>djangoBlog/urls.py</code> لتحديد موجّه إرسال عنوان URL لإجراء التحديث:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8711_38" style=""><span class="pln">path</span><span class="pun">(</span><span class="str">"post/update/&lt;int:id&gt;"</span><span class="pun">,</span><span class="pln"> views</span><span class="pun">.</span><span class="pln">post_update</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">=</span><span class="str">"update"</span><span class="pun">),</span></pre>

<p>
	وتكون دالة العرض كما يلي في الملف <code>blog/views.py</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8711_40" style=""><span class="kwd">def</span><span class="pln"> post_update</span><span class="pun">(</span><span class="pln">request</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">"GET"</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">objects</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">pk</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</span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> </span><span class="str">"post/update.html"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="str">"post"</span><span class="pun">:</span><span class="pln"> post</span><span class="pun">})</span><span class="pln">
   </span><span class="kwd">elif</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">
       post </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">update_or_create</span><span class="pun">(</span><span class="pln">
           pk</span><span class="pun">=</span><span class="pln">id</span><span class="pun">,</span><span class="pln">
           defaults</span><span class="pun">={</span><span class="pln">
               </span><span class="str">"title"</span><span class="pun">:</span><span class="pln"> request</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="str">"content"</span><span class="pun">:</span><span class="pln"> request</span><span class="pun">.</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><span class="pln">
       </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="str">"home"</span><span class="pun">)</span></pre>

<p>
	<strong>ملاحظة</strong>: أضيف التابع <code>update_or_create()‎</code> حديثًا إلى الإصدار 4.1 من جانغو.
</p>

<p>
	وسيتضمن القالب المقابل <code>templates/post/update.html</code> المحتويات التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8711_42" style=""><span class="pun">{%</span><span class="pln"> extends </span><span class="str">'layout.html'</span><span class="pln"> </span><span class="pun">%}</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="pun">&lt;</span><span class="pln">title</span><span class="pun">&gt;</span><span class="typ">Update</span><span class="pun">&lt;/</span><span class="pln">title</span><span class="pun">&gt;</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"> block content </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">"w-96 mx-auto my-8"</span><span class="pun">&gt;</span><span class="pln">
   </span><span class="pun">&lt;</span><span class="pln">h2 </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"text-2xl font-semibold underline mb-4"</span><span class="pun">&gt;</span><span class="typ">Update</span><span class="pln"> post</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">form action</span><span class="pun">=</span><span class="str">"{% url 'update' post.pk %}"</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">{%</span><span class="pln"> csrf_token </span><span class="pun">%}</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;&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"> id</span><span class="pun">=</span><span class="str">"title"</span><span class="pln"> name</span><span class="pun">=</span><span class="str">"title"</span><span class="pln"> value</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">"p-2 w-full bg-gray-50 rounded border border-gray-300 focus:ring-3 focus:ring-blue-300"</span><span class="pun">&gt;&lt;</span><span class="pln">br</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">Content</span><span class="pun">:&lt;/</span><span class="pln">label</span><span class="pun">&gt;&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 type</span><span class="pun">=</span><span class="str">"text"</span><span class="pln"> id</span><span class="pun">=</span><span class="str">"content"</span><span class="pln"> name</span><span class="pun">=</span><span class="str">"content"</span><span class="pln"> rows</span><span class="pun">=</span><span class="str">"15"</span><span class="pln">
           </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"p-2 w-full bg-gray-50 rounded border border-gray-300 focus:ring-3 focus:ring-blue-300"</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">textarea</span><span class="pun">&gt;&lt;</span><span class="pln">br</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">div </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"grid grid-cols-2 gap-x-2"</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">"font-sans text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-md text-sm w-full px-5 py-2.5 text-center"</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">a href</span><span class="pun">=</span><span class="str">"{% url 'delete' post.pk %}"</span><span class="pln">
           </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"font-sans text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-md text-sm w-full px-5 py-2.5 text-center"</span><span class="pun">&gt;</span><span class="typ">Delete</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">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"> endblock </span><span class="pun">%}</span></pre>

<h2 id="delete">
	إجراء الحذف Delete
</h2>

<p>
	ضع ما يلي في الملف <code>djangoBlog/urls.py</code> لتحديد موجّه إرسال عنوان URL لإجراء الحذف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8711_44" style=""><span class="pln">path</span><span class="pun">(</span><span class="str">"post/delete/&lt;int:id&gt;"</span><span class="pun">,</span><span class="pln"> views</span><span class="pun">.</span><span class="pln">post_delete</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">=</span><span class="str">"delete"</span><span class="pun">),</span></pre>

<p>
	وتكون دالة العرض كما يلي في الملف <code>blog/views.py</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8711_46" style=""><span class="kwd">def</span><span class="pln"> post_delete</span><span class="pun">(</span><span class="pln">request</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"> </span><span class="typ">Post</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">pk</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">delete</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="str">"home"</span><span class="pun">)</span></pre>

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

<h2 id="-1">
	بدء تشغيل الخادم
</h2>

<p>
	لنبدأ الآن بتشغيل خادم التطوير ونرى النتيجة كما يلي:
</p>

<pre class="ipsCode">python manage.py runserver
</pre>

<p>
	ستكون الصفحة الرئيسية للمدونة كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163165" href="https://academy.hsoub.com/uploads/monthly_2024_11/01_home.png.1dfa126a321036dc81cd049542bf0763.png" rel=""><img alt="الصفحة الرئيسية للمدونة" class="ipsImage ipsImage_thumbnailed" data-fileid="163165" data-ratio="42.50" data-unique="huowqsbfq" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2024_11/01_home.thumb.png.0aa71da4c9e4e1e414d300eb50d70606.png"></a>
</p>

<p>
	وتكون صفحة إنشاء منشور جديد كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163159" href="https://academy.hsoub.com/uploads/monthly_2024_11/02_create.png.ad4b22e5c9592c057a13d58b6c1818dd.png" rel=""><img alt="02 create" class="ipsImage ipsImage_thumbnailed" data-fileid="163159" data-unique="99xwg3ozu" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/02_create.thumb.png.59896412b3fb7c597e465df77088fa06.png"> </a>
</p>

<p>
	وتكون صفحة عرض المنشور كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163166" href="https://academy.hsoub.com/uploads/monthly_2024_11/03_show.png.bf9c9f02a55fc213622c5179b0cc79a6.png" rel=""><img alt="صفحة عرض المنشور" class="ipsImage ipsImage_thumbnailed" data-fileid="163166" data-ratio="40.50" data-unique="rs6uh50xi" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2024_11/03_show.thumb.png.0351ef87a78aec1bdabd742d3e6853a7.png"></a>
</p>

<p>
	وتكون صفحة تحديث المنشور كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="163167" href="https://academy.hsoub.com/uploads/monthly_2024_11/04_update.png.5afd7541fa95fa18b1b9558b3c8ac8db.png" rel=""><img alt="04_update.png" class="ipsImage ipsImage_thumbnailed" data-fileid="163167" data-ratio="70.00" data-unique="zv42nipz0" style="width: 600px; height: auto;" width="857" src="https://academy.hsoub.com/uploads/monthly_2024_11/04_update.thumb.png.3b7ed1a094986c928df7d42542f395ce.png"></a>
</p>

<h2>
	الخلاصة
</h2>

<p>
	بهذا نكون قد أنهينا العمل على تطبيق مدونتنا البسيطة باستخدام Django وأنشأنا نموذج لتمثيل المنشورات في قاعدة البيانات، وتعلمنا كيف ننفيذ عمليات عبر توابع HTTP مثل GET و POST و PUT و DELETE. كما شرحنا خطوات تصميم واجهة المستخدم باستخدام القوالب لعرض المنشورات وإنشاء منشورات جديدة وتحديثها وحذفها. تابع المقال التالي من السلسلة للتعرف على خطوات إكمال المدونة.
</p>

<p>
	ترجمة -وبتصرّف- للمقال <a href="https://www.ericsdevblog.com/posts/django-for-beginners-3/" rel="external nofollow">Django for Beginners #3 - The CRUD Operations</a> لصاحبه Eric Hu.
</p>

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

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A8%D9%86%D9%8A%D8%A9-mtv-%D9%84%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D8%AF%D9%88%D9%86%D8%A9-%D8%A8%D8%B3%D9%8A%D8%B7%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2452/" rel="">استخدام بنية MTV لإنشاء مدونة بسيطة في جانغو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%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%AC%D8%A7%D9%86%D8%BA%D9%88-django-r2049/" rel="">إعداد بيئة تطوير تطبيقات جانغو Django</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%85%D9%87%D8%A7%D9%85-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-%D9%88%D8%B1%D9%8A%D8%A2%D9%83%D8%AA-react-r1773/" rel="">بناء تطبيق مهام باستخدام جانغو Django وريآكت React</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%B1%D9%81%D8%B9-%D9%85%D8%B3%D8%AA%D9%88%D9%89-%D8%A3%D9%85%D8%A7%D9%86-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%81%D9%8A-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%A7%D8%AC-r1717/" rel="">رفع مستوى أمان تطبيقات جانغو في بيئة الإنتاج</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2457</guid><pubDate>Thu, 28 Nov 2024 15:03:01 +0000</pubDate></item><item><title>&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x628;&#x646;&#x64A;&#x629; MTV &#x644;&#x625;&#x646;&#x634;&#x627;&#x621; &#x645;&#x62F;&#x648;&#x646;&#x629; &#x628;&#x633;&#x64A;&#x637;&#x629; &#x641;&#x64A; &#x62C;&#x627;&#x646;&#x63A;&#x648;</title><link>https://academy.hsoub.com/programming/python/django/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A8%D9%86%D9%8A%D8%A9-mtv-%D9%84%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D8%AF%D9%88%D9%86%D8%A9-%D8%A8%D8%B3%D9%8A%D8%B7%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2452/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_11/--MTV_.png.730847a4db79e057f80dd5dc66625f9a.png" /></p>
<p>
	نشرح في مقال اليوم بنية نموذج-قالب-عرض Model-Template-View أو MTV اختصارًا التي يعتمد عليها إطار عمل جانغو Django لتطوير الويب حيث يكون النموذج Model مسؤولًا عن التفاعل مع قاعدة البيانات، ويجب أن يقابل كل نموذج جدولًا منها، ويعبر القالب Template عن جزء الواجهة الأمامية من التطبيق، وهو الشيء الذي سيراه المستخدم، أما العرض View فهو يتضمن المنطق البرمجي لواجهة التطبيق الخلفية، ومسؤولً عن استرداد البيانات من قاعدة البيانات عبر النماذج ووضعها في العرض المقابل وإعادة القالب المعروض إلى المستخدم في النهاية.
</p>

<p>
	هذه المقالة جزء من سلسلة من المقالات تشرح جانغو للمبتدئين على النحو التالي:
</p>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A7%D9%84%D8%A8%D8%AF%D8%A1-%D9%81%D9%8A-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D8%AF%D9%88%D9%86%D8%A9-%D8%A8%D8%B3%D9%8A%D8%B7%D8%A9-r2446/" rel="">الجزء الأول: البدء في إنشاء مدونة بسيطة</a>
	</li>
	<li>
		<strong>الجزء الثاني: استخدام بنية MTV لإنشاء مدونة بسيطة</strong>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B9%D9%85%D9%84%D9%8A%D8%A7%D8%AA-crud-%D9%84%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D9%85%D8%AF%D9%88%D9%86%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2457/" rel="">الجزء الثالث: استخدام عمليات CRUD لإدارة المدونة</a>
	</li>
	<li>
		الجزء الرابع: تطبيق المدونة الكامل
	</li>
	<li>
		الجزء الخامس: إضافة بعض الميزات المتقدمة إلى تطبيق المدونة
	</li>
</ul>

<h2 id="models">
	مفهوم النماذج Models
</h2>

<p>
	النموذج من أفضل ميزات <a href="https://academy.hsoub.com/programming/python/django/" rel="">جانغو Django</a>، ففي أطر عمل الويب الأخرى ستحتاج إلى إنشاء نموذج وملف تهجير Migration، وملف التهجير هو مخطط Schema لقاعدة البيانات، يصف بنية قاعدة البيانات كأسماء الأعمدة وأنواعها، ويوفر النموذج واجهةً تتعامل مع معالجة البيانات بناءً على هذا المخطط، ولكنك ستحتاج إلى نموذج فقط في جانغو، ويمكن توليد ملفات التهجير المقابلة باستخدام أمر بسيط هو <code>python manage.py makemigrations</code>، مما يوفر عليك كثيرًا من الوقت.
</p>

<p>
	يحتوي كل تطبيق جانغو على ملف <code>models.py</code> واحد، ويجب تعريف جميع النماذج المرتبطة بالتطبيق بداخله، ويقابل كل نموذج ملف تهجير، والذي يقابل بدوره جدولًا في قاعدة البيانات. يمكنك التمييز بين الجداول الخاصة بالتطبيقات المختلفة من خلال إسناد بادئة لكل جدول تلقائيًا، حيث سيكون لجدول قاعدة البيانات المقابل لتطبيق <code>blog</code> الخاص بنا البادئة <code>blog_‎</code>.
</p>

<p>
	يوضح المثال التالي نموذجًا في الملف <code>blog/models.py</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_7" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">db </span><span class="kwd">import</span><span class="pln"> models

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Person</span><span class="pun">(</span><span class="pln">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
   first_name </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">30</span><span class="pun">)</span><span class="pln">
   last_name </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</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_9974_9" style=""><span class="pln">python manage</span><span class="pun">.</span><span class="pln">py makemigrations</span></pre>

<p>
	ويجب أن يبدو ملف التهجير الناتج <code>blog/migrations/0001_initial.py</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_11" style=""><span class="com"># Generated by Django 4.1.2 on 2022-10-19 23:13</span><span class="pln">

</span><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">db </span><span class="kwd">import</span><span class="pln"> migrations</span><span class="pun">,</span><span class="pln"> models

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Migration</span><span class="pun">(</span><span class="pln">migrations</span><span class="pun">.</span><span class="typ">Migration</span><span class="pun">):</span><span class="pln">

   initial </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">True</span><span class="pln">

   dependencies </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">

   operations </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
       migrations</span><span class="pun">.</span><span class="typ">CreateModel</span><span class="pun">(</span><span class="pln">
           name</span><span class="pun">=</span><span class="str">"Person"</span><span class="pun">,</span><span class="pln">
           fields</span><span class="pun">=[</span><span class="pln">
               </span><span class="pun">(</span><span class="pln">
                   </span><span class="str">"id"</span><span class="pun">,</span><span class="pln">
                   models</span><span class="pun">.</span><span class="typ">BigAutoField</span><span class="pun">(</span><span class="pln">
                       auto_created</span><span class="pun">=</span><span class="kwd">True</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">
                       serialize</span><span class="pun">=</span><span class="kwd">False</span><span class="pun">,</span><span class="pln">
                       verbose_name</span><span class="pun">=</span><span class="str">"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="pun">(</span><span class="str">"first_name"</span><span class="pun">,</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">30</span><span class="pun">)),</span><span class="pln">
               </span><span class="pun">(</span><span class="str">"last_name"</span><span class="pun">,</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">30</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>

<pre class="ipsCode">python manage.py migrate
</pre>

<p>
	يجب أن تبدو قاعدة بياناتك <code>db.sqlite3</code> كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162670" href="https://academy.hsoub.com/uploads/monthly_2024_11/01_db-blog_person.png.1609d233adaee2c64b0b728ea5401d7a.png" rel=""><img alt="01 db blog person" class="ipsImage ipsImage_thumbnailed" data-fileid="162670" data-unique="32mrid364" style="width: 500px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/01_db-blog_person.thumb.png.66cb9b11297c76a1add533f79111e11d.png"> </a>
</p>

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

<p>
	سينشئ النموذج في مثالنا جدول قاعدة بيانات <code>person</code>، وسيحتوي هذا الجدول على ثلاثة أعمدة اسمها <code>id</code> و <code>first_name</code> و <code>last_name</code>، حيث يُنشأ العمود <code>id</code> تلقائيًا كما هو موضح في ملف التهجير، ويُستخدم هذا العمود كمفتاح رئيسي Primary Key للفهرسة Indexing افتراضيًا.
</p>

<p>
	يسمَّى<code>CharField()‎</code> بنوع الحقل ويعرِّف نوع العمود، ويسمَّى <code>max_length</code> بخيار الحقل ويحدّد معلومات إضافية حول هذا العمود.
</p>

<h3 id="">
	أهم نواع حقول النموذج وخياراتها
</h3>

<p>
	يوضّح الجدول التالي بعض أنواع الحقول الأكثر استخدامًا وننصحك بالاطلاع على جميع <a href="https://docs.djangoproject.com/en/5.1/ref/models/fields/" rel="external nofollow">أنواع الحقول وخياراتها</a> من توثيق جانغو الرسمي.
</p>

<table>
	<thead>
		<tr>
			<th>
				نوع الحقل
			</th>
			<th>
				وصف عنه
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				<code>BigAutoField</code>
			</td>
			<td>
				ينشئ عمودًا نوعه عدد صحيح يتزايد تلقائيًا، يُستخدم عادةً مع العمود <code>id</code>.
			</td>
		</tr>
		<tr>
			<td>
				<code>BooleanField</code>
			</td>
			<td>
				ينشئ عمودًا قيمه منطقية <code>True</code> أو <code>False</code>.
			</td>
		</tr>
		<tr>
			<td>
				<code>DateField</code> و <code>DateTimeField</code>
			</td>
			<td>
				يُستخدم لإضافة تواريخ وأوقات كما يوحي اسمه.
			</td>
		</tr>
		<tr>
			<td>
				<code>FileField</code> و <code>ImageField</code>
			</td>
			<td>
				ينشئ عمودًا لتخزين المسار الذي يؤشّر إلى الملف أو الصورة المرفوعة.
			</td>
		</tr>
		<tr>
			<td>
				<code>IntegerField</code> و <code>BigIntegerField</code>
			</td>
			<td>
				تتراوح قيم الأعداد الصحيحة Integer من ‎-2147483648 إلى 2147483647، وتتراوح قيم الأعداد الصحيحة الكبيرة Big Integer من ‎-9223372036854775808 إلى 9223372036854775807
			</td>
		</tr>
		<tr>
			<td>
				<code>SlugField</code>
			</td>
			<td>
				الاسم المختصر Slug هو نسخة بسيطة من عنوان URL للاسم أو العنوان.
			</td>
		</tr>
		<tr>
			<td>
				<code>CharField</code> و <code>TextField</code>
			</td>
			<td>
				ينشئ كل من <code>CharField</code> و <code>TextField</code> عمودًا لتخزين السلاسل النصية Strings، ولكن يقابل <code>TextField</code> مربع نص أكبر في صفحة مدير جانغو Django Admin التي سنتحدث عنها لاحقًا في هذه السلسلة من المقالات.
			</td>
		</tr>
	</tbody>
</table>

<p>
	يوضح الجدول التالي بعض خيارات الحقول الأكثر استخدامًا:
</p>

<table>
	<thead>
		<tr>
			<th>
				خيار الحقل
			</th>
			<th>
				وصف عنه
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				<code>blank</code>
			</td>
			<td>
				يسمح للحقل بأن يحتوي على إدخال فارغ.
			</td>
		</tr>
		<tr>
			<td>
				<code>choices</code>
			</td>
			<td>
				يمنح الحقل خيارات متعددة، حيث سنوضّح ذلك لاحقًا عندما نصل إلى مدير جانغو.
			</td>
		</tr>
		<tr>
			<td>
				<code>default</code>
			</td>
			<td>
				يعطي الحقل قيمةً افتراضية.
			</td>
		</tr>
		<tr>
			<td>
				<code>unique</code>
			</td>
			<td>
				يتأكد من أن كلّ عنصر في العمود فريد، ويُستخدَم عادةً لتحديد الاسم المختصر Slug والحقول الأخرى التي يُفترَض أن تحتوي على قيم فريدة.
			</td>
		</tr>
	</tbody>
</table>

<h3 id="meta">
	خيارات الصنف Meta
</h3>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_13" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Category</span><span class="pun">(</span><span class="pln">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
   priority </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">IntegerField</span><span class="pun">()</span><span class="pln">

   </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Meta</span><span class="pun">:</span><span class="pln">
       ordering </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">"priority"</span><span class="pun">]</span><span class="pln">
       verbose_name_plural </span><span class="pun">=</span><span class="pln"> </span><span class="str">"categories"</span></pre>

<h3 id="-1">
	توابع النموذج
</h3>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_15" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Person</span><span class="pun">(</span><span class="pln">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
   first_name </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">50</span><span class="pun">)</span><span class="pln">
   last_name </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">50</span><span class="pun">)</span><span class="pln">
   birth_date </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">DateField</span><span class="pun">()</span><span class="pln">

   </span><span class="kwd">def</span><span class="pln"> baby_boomer_status</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
       </span><span class="str">"Returns the person's baby-boomer status."</span><span class="pln">
       </span><span class="kwd">import</span><span class="pln"> datetime
       </span><span class="kwd">if</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">birth_date </span><span class="pun">&lt;</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">date</span><span class="pun">(</span><span class="lit">1945</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">1</span><span class="pun">):</span><span class="pln">
           </span><span class="kwd">return</span><span class="pln"> </span><span class="str">"Pre-boomer"</span><span class="pln">
       </span><span class="kwd">elif</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">birth_date </span><span class="pun">&lt;</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">date</span><span class="pun">(</span><span class="lit">1965</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">1</span><span class="pun">):</span><span class="pln">
           </span><span class="kwd">return</span><span class="pln"> </span><span class="str">"Baby boomer"</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">"Post-boomer"</span></pre>

<p>
	في الكود السابق، يفحص جانغو تاريخ ميلاد الشخص ويعيد حالة تمثل إن كانت فترة ولادته قبل زيادة المواليد التي حدثت بعد الحرب العالمية الثانية Pre-boomer، أم خلالها Baby-Boomer أم بعدها Post-boomer للشخص عند استدعاء التابع <code>baby_boomer_status()‎</code>.
</p>

<p>
	<strong>ملاحظة</strong>: الكائنات والتوابع والخاصيات مفاهيم مهمة جدًا في لغات البرمجة، لذا ننصح بمطالعة مقال <a href="https://www.w3schools.com/python/python_classes.asp" rel="external nofollow">كائنات وأصناف بايثون</a> لمزيد من المعلومات.
</p>

<h3 id="-2">
	وراثة النماذج
</h3>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_17" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">CommonInfo</span><span class="pun">(</span><span class="pln">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
   name </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">100</span><span class="pun">)</span><span class="pln">
   age </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">PositiveIntegerField</span><span class="pun">()</span><span class="pln">

   </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Meta</span><span class="pun">:</span><span class="pln">
       abstract </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">True</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="typ">CommonInfo</span><span class="pun">):</span><span class="pln">
   home_group </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">5</span><span class="pun">)</span></pre>

<p>
	لاحظ أن النموذج <code>CommonInfo</code> نموذج مجرد Abstract، مما يعني أنه لا يقابل نموذجًا فرديًا فعليًا، بل يستخدم كأب لنماذج أخرى.
</p>

<p>
	لنولّد الآن ملف تهجير جديد <code>blog/migrations/0002_student.py</code> باستخدام الأمر التالي للتحقق من ذلك:
</p>

<pre class="ipsCode">python manage.py makemigrations
</pre>

<p>
	سيتولّد ملف التهجير التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_21" style=""><span class="com"># Generated by Django 4.1.2 on 2022-10-19 23:28</span><span class="pln">

</span><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">db </span><span class="kwd">import</span><span class="pln"> migrations</span><span class="pun">,</span><span class="pln"> models

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Migration</span><span class="pun">(</span><span class="pln">migrations</span><span class="pun">.</span><span class="typ">Migration</span><span class="pun">):</span><span class="pln">

   dependencies </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
       </span><span class="pun">(</span><span class="str">"blog"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"0001_initial"</span><span class="pun">),</span><span class="pln">
   </span><span class="pun">]</span><span class="pln">

   operations </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
       migrations</span><span class="pun">.</span><span class="typ">CreateModel</span><span class="pun">(</span><span class="pln">
           name</span><span class="pun">=</span><span class="str">"Student"</span><span class="pun">,</span><span class="pln">
           fields</span><span class="pun">=[</span><span class="pln">
               </span><span class="pun">(</span><span class="pln">
                   </span><span class="str">"id"</span><span class="pun">,</span><span class="pln">
                   models</span><span class="pun">.</span><span class="typ">BigAutoField</span><span class="pun">(</span><span class="pln">
                       auto_created</span><span class="pun">=</span><span class="kwd">True</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">
                       serialize</span><span class="pun">=</span><span class="kwd">False</span><span class="pun">,</span><span class="pln">
                       verbose_name</span><span class="pun">=</span><span class="str">"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="pun">(</span><span class="str">"name"</span><span class="pun">,</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">100</span><span class="pun">)),</span><span class="pln">
               </span><span class="pun">(</span><span class="str">"age"</span><span class="pun">,</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">PositiveIntegerField</span><span class="pun">()),</span><span class="pln">
               </span><span class="pun">(</span><span class="str">"home_group"</span><span class="pun">,</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">5</span><span class="pun">)),</span><span class="pln">
           </span><span class="pun">],</span><span class="pln">
           options</span><span class="pun">={</span><span class="pln">
               </span><span class="str">"abstract"</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">False</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>
	لاحظ إنشاء الجدول <code>Student</code> فقط.
</p>

<h3 id="-3">
	علاقات قاعدة البيانات
</h3>

<p>
	تحدثنا عن كيفية إنشاء جداول فردية، ولكن لا تكون هذه الجداول مستقلة تمامًا في معظم التطبيقات، بل توجد علاقات بينها، فمثلًا قد يكون لديك فئة Category تنتمي إليها منشورات متعددة، ويكون كل منشور في المدونة تابع لمستخدم معين وما إلى ذلك، لذا توجد ثلاثة أنواع أساسية للتعبير عن <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="">العلاقات بين جداول قاعدة البيانات</a> وهي: علاقة واحد إلى واحد One-to-one وعلاقة متعدد إلى واحد Many-to-one وعلاقة متعدد إلى متعدد Many-to-many وسنشرح تاليًا المزيد حول هذه العلاقات وطريقة وصفها ضمن نماذج جانغو.
</p>

<h4 id="-4">
	علاقة واحد إلى واحد
</h4>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_24" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Person</span><span class="pun">(</span><span class="pln">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
   name </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">100</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Phone</span><span class="pun">(</span><span class="pln">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
   person </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">OneToOneField</span><span class="pun">(</span><span class="str">'Person'</span><span class="pun">,</span><span class="pln"> on_delete</span><span class="pun">=</span><span class="pln">models</span><span class="pun">.</span><span class="pln">CASCADE</span><span class="pun">)</span></pre>

<p>
	يعمل نوع الحقل <code>OneToOneField</code> مثل أي نوع حقل آخر، ولكنه يتطلب وسيطين على الأقل هما: الوسيط الأول هو اسم النموذج الآخر الذي يرتبط به هذا النموذج بعلاقة، والوسيط الثاني هو <code>on_delete</code> الذي يعرّف الإجراء الذي سيتخذه جانغو عند حذف البيانات، ويتعلق هذا المعامل بلغة SQL أكثر من جانغو، لذا لن نتحدث عنه بالتفصيل، ولكن إن كنت مهتمًا، فاطلع على <a href="https://docs.djangoproject.com/en/5.1/ref/models/fields/#django.db.models.ForeignKey.on_delete" rel="external nofollow">بعض القيم المتاحة للمعامل <code>on_delete</code></a>.
</p>

<p>
	يمكنك الآن إنشاء عمليات تهجير وتطبيقها لهذه النماذج ومعرفة ما يحدث، وإذا واجهتك مشاكل أثناء تشغيل الأوامر التالية، فاحذف الملف <code>db.sqlite3</code> وملفات التهجير للبدء من جديد:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_26" style=""><span class="pln">python manage</span><span class="pun">.</span><span class="pln">py makemigrations</span></pre>

<pre class="ipsCode">python manage.py migrate
</pre>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162671" href="https://academy.hsoub.com/uploads/monthly_2024_11/02_one-to-one.png.26f0c88b29978677ee1099dd9c2fbdfd.png" rel=""><img alt="02 one to one" class="ipsImage ipsImage_thumbnailed" data-fileid="162671" data-unique="i09t8v7v8" style="width: 300px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/02_one-to-one.thumb.png.6cdf053c7fadac7b0ba0ddfc60bbda52.png"> </a>
</p>

<p>
	لاحظ أن نوع الحقل <code>OneToOneField</code> أنشأ العمود <code>person_id</code> في الجدول <code>blog_phone</code>، وسيخزّن هذا العمود معرّف <code>id</code> الشخص الذي يمتلك هذا الهاتف.
</p>

<h4 id="-5">
	علاقة متعدد إلى واحد
</h4>

<p>
	يمكن أن تحتوي كل فئة على منشورات متعددة وينتمي كل منشور إلى فئة واحدة مثلًا، حيث يشار إلى هذه العلاقة باسم علاقة متعدد إلى واحد التي نعرّفها كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_28" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Category</span><span class="pun">(</span><span class="pln">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
   name </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">100</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">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
   category </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">ForeignKey</span><span class="pun">(</span><span class="str">'Category'</span><span class="pun">,</span><span class="pln"> on_delete</span><span class="pun">=</span><span class="pln">models</span><span class="pun">.</span><span class="pln">CASCADE</span><span class="pun">)</span></pre>

<p>
	ينشئ نوع الحقل <code>ForeignKey</code> العمود <code>category_id</code> في الجدول <code>blog_post</code>، والذي يخزن معرّف <code>id</code> الفئة التي ينتمي إليها هذا المنشور.
</p>

<h4 id="-6">
	علاقة متعدد إلى متعدد
</h4>

<p>
	تكون علاقة متعدد إلى متعدد أكثر تعقيدًا بعض الشيء، فمثلًا يمكن أن يكون لكل مقال وسوم Tags متعددة، ويمكن أن يكون لكل وسم مقالات متعددة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_30" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Tag</span><span class="pun">(</span><span class="pln">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
   name </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">100</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">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
   tags </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">ManyToManyField</span><span class="pun">(</span><span class="str">'Tag'</span><span class="pun">)</span></pre>

<p>
	ستنشئ الشيفرة البرمجية السابقة جدولًا جديدًا بالاسم <code>post_tags</code> بدلًا من إنشاء عمود جديد، وسيحتوي هذا الجدول الجديد على عمودين هما <code>post_id</code> و <code>tag_id</code>، مما يتيح لك تحديد جميع الوسوم المرتبطة بمنشور معين والعكس صحيح.
</p>

<p>
	لنفترض أن لدينا الجدول التالي مثلًا لتوضيح الأمور:
</p>

<table>
	<thead>
		<tr>
			<th>
				post_id
			</th>
			<th>
				tag_id
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				1
			</td>
			<td>
				1
			</td>
		</tr>
		<tr>
			<td>
				2
			</td>
			<td>
				1
			</td>
		</tr>
		<tr>
			<td>
				3
			</td>
			<td>
				2
			</td>
		</tr>
		<tr>
			<td>
				1
			</td>
			<td>
				2
			</td>
		</tr>
		<tr>
			<td>
				2
			</td>
			<td>
				3
			</td>
		</tr>
	</tbody>
</table>

<p>
	يمتلك المنشور الذي له المعرّف <code>id=1</code> وسمين tags لهما المعرّف <code>id=1</code> والمعرّف <code>id=2</code>. إذا أردنا عكس الأمور للعثور على المنشورات باستخدام الوسم، فيمكننا أن نرى منشورين لهما المعرّف <code>id=3</code> والمعرّف <code>id=1</code> بالنسبة للوسم ذي المعرّف <code>id=2</code>.
</p>

<h2 id="view">
	طبقة العرض View
</h2>

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

<p>
	لا تعمل دالة العرض View Function على استرداد البيانات فقط، إذ توجد أربع عمليات أساسية يمكن تطبيقها على البيانات في معظم تطبيقات الويب، وهي الإنشاء Create والقراءة Read والتحديث Update والحذف Delete، ويشار إلى هذه العمليات مجتمعة بالاسم CRUD، والتي سنوضّحها في مقال لاحق.
</p>

<p>
	تحدّثنا عن النماذج في القسم السابق، ولكننا ما زلنا لا نعرف كيفية استرداد البيانات أو تخزينها باستخدام النموذج، حيث يقدم جانغو واجهة برمجة تطبيقات <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-api-r1314/" rel=""><abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr></a> بسيطة لمساعدتنا في ذلك، تسمى <a href="https://docs.djangoproject.com/en/5.1/topics/db/queries/" rel="external nofollow"><code>QuerySet</code></a>.
</p>

<p>
	لنفترض أن لدينا النموذج <code>blog/models.py</code> التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_33" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Category</span><span class="pun">(</span><span class="pln">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
   name </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">100</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">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
   name </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">200</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">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
   title </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">255</span><span class="pun">)</span><span class="pln">
   content </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">TextField</span><span class="pun">()</span><span class="pln">
   pub_date </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">DateField</span><span class="pun">()</span><span class="pln">
   category </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">ForeignKey</span><span class="pun">(</span><span class="typ">Category</span><span class="pun">,</span><span class="pln"> on_delete</span><span class="pun">=</span><span class="pln">models</span><span class="pun">.</span><span class="pln">CASCADE</span><span class="pun">)</span><span class="pln">
   tags </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">ManyToManyField</span><span class="pun">(</span><span class="typ">Tag</span><span class="pun">)</span></pre>

<p>
	يمكنك معالجة البيانات بمساعدة <code>QuerySet</code> من خلال هذا النموذج ضمن دوال العرض التي توجد في الملف <code>blog/views.py</code>.
</p>

<h3 id="-7">
	إنشاء البيانات وحفظها
</h3>

<p>
	لنفترض أنك تريد إنشاء فئة جديدة كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_35" style=""><span class="com"># استيراد نموذج الفئة‫ Category</span><span class="pln">
</span><span class="kwd">from</span><span class="pln"> blog</span><span class="pun">.</span><span class="pln">models </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Category</span><span class="pln">

</span><span class="com"># إنشاء نسخة جديدة من النموذج‫ Category</span><span class="pln">
category </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Category</span><span class="pun">(</span><span class="pln">name</span><span class="pun">=</span><span class="str">"New Category"</span><span class="pun">)</span><span class="pln">

</span><span class="com"># حفظ الفئة التي أنشأناها في قاعدة البيانات</span><span class="pln">
category</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span></pre>

<p>
	يجب أن يكون ما سبق سهلًا إذا كنت على دراية بمفهوم <a href="https://academy.hsoub.com/programming/general/%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-oop/" rel="">البرمجة كائنية التوجه</a>، حيث أنشأنا في المثال السابق نسخة جديدة من الكائن <code>Category</code> واستخدمنا التابع <code>save()‎</code> الذي ينتمي إلى هذا الكائن لحفظ المعلومات في قاعدة البيانات.
</p>

<p>
	توجد علاقة متعدد إلى واحد بين الفئة والمنشور، وقد عرفناها باستخدام الحقل <code>ForeignKey()‎</code>، مع افتراض أن لدينا عدد كافي من السجلات في قاعدة البيانات.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_37" style=""><span class="kwd">from</span><span class="pln"> blog</span><span class="pun">.</span><span class="pln">models </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Category</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Post</span><span class="pln">

</span><span class="com"># ‫Post.objects.get(pk=1)‎ هي الطريقة التي نسترجع بها المنشور الذي له المفتاح الرئيسي pk=1، </span><span class="pln">
</span><span class="com"># حيث يرمز‫ pk إلى المفتاح الرئيسي Primary Key الذي يمثّل المعرّف id عادةً إن لم نحدّد خلاف ذلك.</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">objects</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">pk</span><span class="pun">=</span><span class="lit">1</span><span class="pun">)</span><span class="pln">

</span><span class="com"># استرداد الفئة التي اسمها‫ "New Category"</span><span class="pln">
new_category </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Category</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">name</span><span class="pun">=</span><span class="str">"New Category"</span><span class="pun">)</span><span class="pln">

</span><span class="com"># إسناد المتغير‫ new_category إلى حقل فئة المنشور وحفظه</span><span class="pln">
post</span><span class="pun">.</span><span class="pln">category </span><span class="pun">=</span><span class="pln"> new_category
post</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span></pre>

<p>
	توجد أيضًا علاقة متعدد إلى متعدد بين المنشورات والوسوم كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_39" style=""><span class="kwd">from</span><span class="pln"> blog</span><span class="pun">.</span><span class="pln">models </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Tag</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Post</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">objects</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">pk</span><span class="pun">=</span><span class="lit">1</span><span class="pun">)</span><span class="pln"> </span><span class="com"># استرداد المنشور 1</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">objects</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">pk</span><span class="pun">=</span><span class="lit">1</span><span class="pun">)</span><span class="pln"> </span><span class="com"># استرداد الوسم 1</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">objects</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">pk</span><span class="pun">=</span><span class="lit">2</span><span class="pun">)</span><span class="pln"> </span><span class="com"># استرداد الوسم 2</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">objects</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">pk</span><span class="pun">=</span><span class="lit">3</span><span class="pun">)</span><span class="pln"> </span><span class="com"># استرداد الوسم 3</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">objects</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">pk</span><span class="pun">=</span><span class="lit">4</span><span class="pun">)</span><span class="pln"> </span><span class="com"># استرداد الوسم 4</span><span class="pln">
tag5 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Tag</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">pk</span><span class="pun">=</span><span class="lit">5</span><span class="pun">)</span><span class="pln"> </span><span class="com"># استرداد الوسم 5</span><span class="pln">

post</span><span class="pun">.</span><span class="pln">tags</span><span class="pun">.</span><span class="pln">add</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"> tag5</span><span class="pun">)</span><span class="pln"> </span><span class="com"># إضافة الوسوم من 1 إلى 5 إلى المنشور 1</span></pre>

<h3 id="-8">
	استرداد البيانات
</h3>

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

<h4 id="queryset">
	توابع QuerySet
</h4>

<p>
	تتيح توابع <code>QuerySet</code> استرداد البيانات بناءً على معايير معينة، ويمكن الوصول إليها باستخدام السمة Attribute التي هي <code>objects</code>، حيث يُستخدَم التابع <code>get()‎</code> الذي رأيناه سابقًا لاسترداد سجل معين كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_41" style=""><span class="pln">first_tag </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Tag</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">pk</span><span class="pun">=</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
new_category </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Category</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">name</span><span class="pun">=</span><span class="str">"New Category"</span><span class="pun">)</span></pre>

<p>
	ويمكن أيضًا استرداد جميع السجلات باستخدام التابع <code>all()‎</code>:
</p>

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

<p>
	يعيد التابع <code>all()‎</code> مجموعة من السجلات والتي نسميها مجموعة الاستعلام QuerySet، ويمكنك تحسين هذه المجموعة من خلال سَلسَلة التابع <code>filter()‎</code> أو التابع <code>exclude()‎</code> مع التابع <code>all()‎</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_47" style=""><span class="typ">Post</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">all</span><span class="pun">().</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">pub_date__year</span><span class="pun">=</span><span class="lit">2024</span><span class="pun">)</span></pre>

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

<p>
	يمكننا أيضًا استبعاد المنشورات المنشورة عام 2024 كما يلي:
</p>

<pre class="ipsCode" id="ips_uid_9974_49">Post.objects.all().exclude(pub_date__year=2024)
</pre>

<p>
	هناك أيضًا العديد من توابع QuerySet الأخرى بالإضافة إلى <code>get()‎</code> و <code>all()‎</code> و <code>filter()‎</code> و <code>exclude()‎</code>، ولكن لن نتحدث عنها جميعًا، لذا اطّلع على القائمة الكاملة لجميع <a href="https://docs.djangoproject.com/en/5.1/ref/models/querysets/#methods-that-return-new-querysets" rel="external nofollow">توابع QuerySet</a> من توثيق جانغو الرسمي.
</p>

<h4 id="fieldlookups">
	وسطاء عمليات البحث في الحقول Field Lookups
</h4>

<p>
	وسطاء عمليات البحث في الحقول هي وسطاء الكلمات المفتاحية للتوابع <code>get()‎</code> و <code>filter()‎</code> و <code>exclude()‎</code>، والتي تعمل بطريقة مشابهة لتعليمة <code><a href="https://wiki.hsoub.com/SQL/where" rel="external">WHERE</a></code>  في لغة SQL، وتأخذ الصيغة <code>fieldname__lookuptype=value</code> مع وجود شرطة سفلية مزدوجة.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_54" style=""><span class="typ">Post</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">all</span><span class="pun">().</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">pub_date__lte</span><span class="pun">=</span><span class="str">'2024-01-01'</span><span class="pun">)</span></pre>

<p>
	<code>pub_date</code> هو اسم الحقل و <code>lte</code> هو نوع البحث الذي يرمز إلى أقل من أو يساوي، وستُعاد جميع المنشورات التي يكون فيها تاريخ النشر <code>pub_date</code> أقل من أو يساوي ‎<code>2024-01-01</code>‎.
</p>

<p>
	يمكن أيضًا استخدام وسطاء عمليات البحث في الحقول للعثور على السجلات التي لها علاقة بالسجل الحالي كما في المثال التالي، وستُعاد جميع المنشورات التي تنتمي إلى الفئة التي اسمها <code>"Django"</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_56" style=""><span class="typ">Post</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">category__name</span><span class="pun">=</span><span class="str">'Django'</span><span class="pun">)</span></pre>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_58" style=""><span class="typ">Category</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">post__title__contains</span><span class="pun">=</span><span class="str">'Django'</span><span class="pun">)</span></pre>

<p>
	يمكننا أيضًا المرور عبر علاقات متعددة كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_62" style=""><span class="typ">Category</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">post__author__name</span><span class="pun">=</span><span class="str">'Admin'</span><span class="pun">)</span></pre>

<p>
	يعيد الاستعلام هنا جميع الفئات التي تحتوي على منشورات ينشرها المستخدم <code>Admin</code>، إذ يمكن وضع سلسلة من العلاقات بالعدد الذي نريده<strong>. </strong>ويمكنك مطالعة <a href="https://docs.djangoproject.com/en/5.1/ref/models/querysets/#field-lookups" rel="external nofollow">جميع وسطاء عمليات البحث في الحقول</a> التي يمكنك استخدامها من توثيق جانغو الرسمي.
</p>

<h3 id="-9">
	حذف الكائنات
</h3>

<p>
	نستخدم التابع <code>delete()‎</code> لحذف سجلٍ ما، حيث ستحذف الشيفرة البرمجية التالية المنشور الذي له المفتاح الرئيسي <code>pk=1</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_64" style=""><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">objects</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">pk</span><span class="pun">=</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
post</span><span class="pun">.</span><span class="pln">delete</span><span class="pun">()</span></pre>

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

<pre class="ipsCode" id="ips_uid_9974_66">Post.objects.filter(pub_date__year=2022).delete()
</pre>

<p>
	سيؤدي ذلك إلى حذف جميع المنشورات في عام 2022، ولكن قد يتعلّق السجل الذي نحذفه بسجل آخر مثل محاولة حذف فئة تحتوي على منشورات متعددة كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_68" style=""><span class="pln">category </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Category</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">pk</span><span class="pun">=</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
category</span><span class="pun">.</span><span class="pln">delete</span><span class="pun">()</span></pre>

<p>
	يحاكي جانغو سلوك قيد SQL التالي <code>ON DELETE CASCADE</code>، مما يعني حذف جميع المنشورات التي تنتمي إلى هذه الفئة أيضًا، ولكن إذا أردتَ تغيير ذلك، فيمكنك تغيير خيار <code>on_delete</code> إلى شيء آخر، لذا اطّلع على <a href="https://docs.djangoproject.com/en/5.1/ref/models/fields/#django.db.models.ForeignKey.on_delete" rel="external nofollow">جميع خيارات <code>on_delete</code></a> المتاحة في توثيق جانغو الرسمي.
</p>

<h3 id="viewfunction">
	دالة العرض View Function
</h3>

<p>
	وضّحنا ما يمكن فعله داخل دالة العرض، وسنوضّح كيف تبدو دالة العرض الكاملة كما في المثال التالي، حيث تُعرَّف جميع العروض ضمن الملف <code>views.py</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_70" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">shortcuts </span><span class="kwd">import</span><span class="pln"> render
</span><span class="kwd">from</span><span class="pln"> blog</span><span class="pun">.</span><span class="pln">models </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Post</span><span class="pln">

</span><span class="com"># أنشئ عروضك الخاصة هنا</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> my_view</span><span class="pun">(</span><span class="pln">request</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">objects</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</span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> </span><span class="str">'blog/index.html'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
       </span><span class="str">'posts'</span><span class="pun">:</span><span class="pln"> posts</span><span class="pun">,</span><span class="pln">
   </span><span class="pun">})</span></pre>

<p>
	هناك شيئان يجب الانتباه إليهما في هذا المثال، أولهما أن دالة العرض تأخذ المتغير <code>request</code> كدخل، وهذا المتغير هو كائن <code>HttpRequest</code> يُمرَّر تلقائيًا إلى العرض من موجّه إرسال Dispatcher عناوين URL. اطّلع على مقال <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> لمزيد من المعلومات حول التواصل بين عميل وخادم التطبيق.
</p>

<p>
	يحتوي <code>request</code> على الكثير من المعلومات حول طلب HTTP الحالي، فمثلًا يمكننا الوصول إلى تابع طلب HTTP وكتابة شيفرات برمجية مختلفة لتوابع مختلفة كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_72" 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">'GET'</span><span class="pun">:</span><span class="pln">
   do_something</span><span class="pun">()</span><span class="pln">
</span><span class="kwd">elif</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">
   do_something_else</span><span class="pun">()</span></pre>

<p>
	<strong>ملاحظة</strong>: اطّلع على جميع المعلومات التي يمكنك الوصول إليها من كائن <code>request</code> في <a href="https://docs.djangoproject.com/en/5.1/ref/request-response/#httprequest-objects" rel="external nofollow">توثيق جانغو الرسمي</a>.
</p>

<p>
	والشيء الآخر الذي يجب الانتباه إليه هو استيراد اختصار Shortcut اسمه <code>render()‎</code>، ثم استخدامه لتمرير المتغير <code>posts</code> إلى القالب <code>blog/index.html</code>. يُطلق عليه اختصار لأنه من المفترض تحميل القالب افتراضيًا باستخدام التابع <code>loader()‎</code> وعرض هذا القالب مع البيانات المُسترَدة وإعادة كائن <code>HttpResponse</code>، ولكن بسّط جانغو هذه العملية باستخدام الاختصار <code>render()‎</code>. لن نتحدث عن الطريقة المعقدة لذلك، لأننا لن نستخدمها في هذا المقال ويمكنك مطالعة جميع <a href="https://docs.djangoproject.com/en/5.0/topics/http/shortcuts/" rel="external nofollow">دوال الاختصار Shortcut Functions</a> في توثيق جانغو الرسمي.
</p>

<h2 id="-10">
	نظام قوالب جانغو
</h2>

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

<h3 id="configurations">
	عمليات الضبط Configurations
</h3>

<p>
	يجب أولًا تغيير شيء ما في الملف <code>settings.py</code>، إذ يجب أن تخبر جانغو بمكان وضع ملفات القوالب، لذا لننشئ المجلد <code>templates</code>، حيث اخترنا وضعه ضمن المجلد الجذر للمشروع، ولكن يمكنك نقله لمكان آخر.
</p>

<pre class="ipsCode">.
├── blog
├── db.sqlite3
├── djangoBlog
├── env
├── manage.py
├── mediafiles
├── staticfiles
└── templates
</pre>

<p>
	انتقل إلى الملف <code>settings.py</code> وابحث عن <code>TEMPLATES</code>.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_74" style=""><span class="pln">TEMPLATES </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="str">'BACKEND'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'django.template.backends.django.DjangoTemplates'</span><span class="pun">,</span><span class="pln">
       </span><span class="str">'DIRS'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
           </span><span class="str">'templates'</span><span class="pun">,</span><span class="pln">
       </span><span class="pun">],</span><span class="pln">
       </span><span class="str">'APP_DIRS'</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">'OPTIONS'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
           </span><span class="str">'context_processors'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
               </span><span class="str">'django.template.context_processors.debug'</span><span class="pun">,</span><span class="pln">
               </span><span class="str">'django.template.context_processors.request'</span><span class="pun">,</span><span class="pln">
               </span><span class="str">'django.contrib.auth.context_processors.auth'</span><span class="pun">,</span><span class="pln">
               </span><span class="str">'django.contrib.messages.context_processors.messages'</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">
</span><span class="pun">]</span></pre>

<p>
	لنعدّل الخيار <code>DIRS</code> الذي يؤشر إلى المجلد <code>templates</code>، ولنتحقق الآن من عمل هذا الإعداد، لذا ننشئ نمط عنوان URL جديد يؤشّر إلى العرض <code>test()‎</code> في الملف <code>djangoBlog/urls.py</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_76" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">urls </span><span class="kwd">import</span><span class="pln"> path
</span><span class="kwd">from</span><span class="pln"> blog </span><span class="kwd">import</span><span class="pln"> views

urlpatterns </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
   path</span><span class="pun">(</span><span class="str">'test/'</span><span class="pun">,</span><span class="pln"> views</span><span class="pun">.</span><span class="pln">test</span><span class="pun">),</span><span class="pln">
</span><span class="pun">]</span></pre>

<p>
	ولننشئ الآن العرض <code>test()‎</code> في الملف <code>blog/views.py</code> كما يلي:
</p>

<pre class="ipsCode">def test(request):
   return render(request, 'test.html')
</pre>

<p>
	انتقل إلى المجلد <code>templates</code> وأنشئ القالب <code>test.html</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_78" 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="pln"> </span><span class="pun">/&gt;</span><span class="pln">
   </span><span class="pun">&lt;</span><span class="pln">meta http</span><span class="pun">-</span><span class="pln">equiv</span><span class="pun">=</span><span class="str">"X-UA-Compatible"</span><span class="pln"> content</span><span class="pun">=</span><span class="str">"IE=edge"</span><span class="pln"> </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.0"</span><span class="pln"> </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">Test</span><span class="pln"> </span><span class="typ">Page</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">p</span><span class="pun">&gt;</span><span class="typ">This</span><span class="pln"> </span><span class="kwd">is</span><span class="pln"> a test page</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">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>http://127.0.0.1:8000/‎</code>، وستظهر الصفحة التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162672" href="https://academy.hsoub.com/uploads/monthly_2024_11/03_django-template.png.9328595353be8acf6ee87078345d3c4c.png" rel=""><img alt="03 django template" class="ipsImage ipsImage_thumbnailed" data-fileid="162672" data-unique="fwpnha1my" style="width: 400px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/03_django-template.thumb.png.d33ed90a11099316ee346648e700c49a.png"> </a>
</p>

<h3 id="-11">
	لغة قوالب جانغو
</h3>

<p>
	لنناقش الآن محرّك قوالب جانغو بالتفصيل، وتذكّر أنه يمكننا إرسال البيانات من العرض إلى القالب كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_80" style=""><span class="kwd">def</span><span class="pln"> test</span><span class="pun">(</span><span class="pln">request</span><span class="pun">):</span><span class="pln">
   </span><span class="kwd">return</span><span class="pln"> render</span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> </span><span class="str">'test.html'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
       </span><span class="str">'name'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Jack'</span><span class="pln">
   </span><span class="pun">})</span></pre>

<p>
	تُسنَد السلسلة النصية <code>'Jack'</code> إلى المتغير <code>name</code> وتمرَّر إلى القالب، ويمكننا عرض المتغير <code>name</code> ضمن القالب باستخدام أقواس معقوصة مزدوجة <code>{{ }}</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_82" style=""><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="typ">Hello</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{{</span><span class="pln"> name </span><span class="pun">}}&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span></pre>

<p>
	حدّث المتصفح، وسترى النتيجة التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162673" href="https://academy.hsoub.com/uploads/monthly_2024_11/04_display-data.png.c9bd526daa4ccec7b01a782e7904a70e.png" rel=""><img alt="04 display data" class="ipsImage ipsImage_thumbnailed" data-fileid="162673" data-unique="68380qape" style="width: 400px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/04_display-data.thumb.png.830f1403abd8d2800e35d80b31c0eaad.png"> </a>
</p>

<p>
	ولكن لا تكون البيانات المُمرَّرة إلى القالب سلسلة نصية بسيطة في أغلب الحالات كما في المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_84" style=""><span class="kwd">def</span><span class="pln"> test</span><span class="pun">(</span><span class="pln">request</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">objects</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">pk</span><span class="pun">=</span><span class="lit">1</span><span class="pun">)</span><span class="pln">

   </span><span class="kwd">return</span><span class="pln"> render</span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> </span><span class="str">'test.html'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
       </span><span class="str">'post'</span><span class="pun">:</span><span class="pln"> post
   </span><span class="pun">})</span></pre>

<p>
	المتغير <code>post</code> في المثال السابق هو قاموس Dictionary، حيث يمكنك الوصول إلى العناصر الموجودة في هذا القاموس في القالب كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_86" style=""><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"> post</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"> post</span><span class="pun">.</span><span class="pln">pub_date </span><span class="pun">}}</span></pre>

<h4 id="filters">
	المرشحات Filters
</h4>

<p>
	تحوّل المرشحات قيم المتغيرات، فمثلًا إذا كان لدينا المتغير <code>django</code> الذي قيمته <code>'the web framework for perfectionists with deadlines'</code> ووضعنا المرشح <code>title</code> مع هذا المتغير كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_88" style=""><span class="pun">{{</span><span class="pln"> django</span><span class="pun">|</span><span class="pln">title </span><span class="pun">}}</span></pre>

<p>
	فسيُحوَّل القالب إلى ما يلي:
</p>

<pre class="ipsCode">The Web Framework For Perfectionists With Deadlines
</pre>

<p>
	اطّلع على جميع <a href="https://docs.djangoproject.com/en/5.1/ref/templates/builtins/#built-in-filter-reference" rel="external nofollow">المرشحات المُضمَّنة في جانغو</a> من توثيق جانغو الرسمي.
</p>

<h4 id="tags">
	الوسوم Tags
</h4>

<p>
	تضيف الوسوم ميزات لغات البرمجة مثل التحكم في التدفق والحلقات إلى شيفرة HTML، مما يوفر الكثير من الوقت والموارد، إذ لن نضطر إلى كتابة الشيفرة البرمجية نفسها مرارًا وتكرارًا. تُعرَّف جميع الوسوم باستخدام <code>{% %}</code> مثل حلقة <code>for</code> التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_90" style=""><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"> </span><span class="kwd">for</span><span class="pln"> athlete </span><span class="kwd">in</span><span class="pln"> athlete_list </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"> athlete</span><span class="pun">.</span><span class="pln">name </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></pre>

<p>
	وتكون تعليمة <code>if</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_92" style=""><span class="pun">{%</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> somevar </span><span class="pun">==</span><span class="pln"> </span><span class="str">"x"</span><span class="pln"> </span><span class="pun">%}</span><span class="pln">
 </span><span class="typ">This</span><span class="pln"> appears </span><span class="kwd">if</span><span class="pln"> variable somevar equals the string </span><span class="str">"x"</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endif </span><span class="pun">%}</span></pre>

<p>
	وتكون تعليمة <code>if-else</code> كما في المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_94" style=""><span class="pun">{%</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> athlete_list </span><span class="pun">%}</span><span class="pln">
   </span><span class="typ">Number</span><span class="pln"> of athletes</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{{</span><span class="pln"> athlete_list</span><span class="pun">|</span><span class="pln">length </span><span class="pun">}}</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> </span><span class="kwd">elif</span><span class="pln"> athlete_in_locker_room_list </span><span class="pun">%}</span><span class="pln">
   </span><span class="typ">Athletes</span><span class="pln"> should be out of the locker room soon</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="typ">No</span><span class="pln"> athletes</span><span class="pun">.</span><span class="pln">
</span><span class="pun">{%</span><span class="pln"> endif </span><span class="pun">%}</span></pre>

<p>
	اطّلع على جميع <a href="https://docs.djangoproject.com/en/5.1/ref/templates/builtins/#built-in-tag-reference" rel="external nofollow">الوسوم المُضمَّنة في جانغو</a> من توثيق جانغو الرسمي، حيث توجد الكثير من المرشحات والوسوم المفيدة الأخرى في نظام قوالب جانغو، والتي سنتحدث عنها لاحقًا.
</p>

<h3 id="inheritance">
	نظام الوراثة Inheritance
</h3>

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

<p>
	لننشئ الملف <code>layout.html</code> في المجلد <code>templates</code>، ويمثّل هذا الملف المكان الذي نعرّف فيه تخطيط القالب الخاص بنا كما يلي، ولكننا لم نضع الشيفرة البرمجية الخاصة بالتذييل وشريط التنقل لتسهيل القراءة:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_9974_96" style=""><span class="dec">&lt;!DOCTYPE html&gt;</span><span class="pln">
</span><span class="tag">&lt;html&gt;</span><span class="pln">
</span><span class="tag">&lt;head&gt;</span><span class="pln">
   {% block meta %} {% endblock %}
   ‏&lt;-- ‫استورد شيفرة CSS هنا –!&gt;
</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;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">
   </span><span class="com">&lt;!-- ضع شيفرة شريط التنقل هنا --&gt;</span><span class="pln">

   {% block content %} {% endblock %}

   </span><span class="com">&lt;!-- ضع شيفرة تذييل الصفحة هنا --&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>
	لاحظ أننا عرّفنا كتلتين في هذا الملف هما <code>meta</code> و <code>content</code> باستخدام الوسم <code>‎{% block ... %}‎</code>، وعرّفنا القالب <code>home.html</code> التالي لاستخدام هذا التخطيط:
</p>

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

{% block meta %}
   </span><span class="tag">&lt;title&gt;</span><span class="pln">Page Title</span><span class="tag">&lt;/title&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">"description"</span><span class="pln"> </span><span class="atn">content</span><span class="pun">=</span><span class="atv">"Free Web tutorials"</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">"keywords"</span><span class="pln"> </span><span class="atn">content</span><span class="pun">=</span><span class="atv">"HTML, CSS, JavaScript"</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">"author"</span><span class="pln"> </span><span class="atn">content</span><span class="pun">=</span><span class="atv">"John Doe"</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.0"</span><span class="tag">&gt;</span><span class="pln">
{% endblock %}

{% block content %}
</span><span class="tag">&lt;p&gt;</span><span class="pln">This is the content section.</span><span class="tag">&lt;/p&gt;</span><span class="pln">
   {% include 'vendor/sidebar.html' %}
{% endblock %}</span></pre>

<p>
	سيجد جانغو أولًا الملف <code>layout.html</code> عند استدعاء القالب السابق، ويملأ الكتلتين <code>meta</code> و <code>content</code> بالمعلومات الموجودة في صفحة <code>home.html</code>.
</p>

<p>
	لاحظ وجود شيء آخر في هذا القالب، حيث تخبر التعليمة ‎<code>{% include 'vendor/sidebar.html' %}</code>‎ جانغو بالبحث عن القالب <code>templates/vendor/sidebar.html</code> ووضعه في الصفحة، حيث يتضمن القالب <code>sidebar.html</code> المحتويات التالية مثلًا:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_9974_102" style=""><span class="tag">&lt;p&gt;</span><span class="pln">This is the sidebar.</span><span class="tag">&lt;/p&gt;</span></pre>

<p>
	لا يُعَد ذلك شريطًا جانبيًا، ولكن يمكننا استخدامه لتوضيح عمل نظام الوراثة، وتأكد أيضًا من صحة العرض كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_104" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">shortcuts </span><span class="kwd">import</span><span class="pln"> render

</span><span class="kwd">def</span><span class="pln"> home</span><span class="pun">(</span><span class="pln">request</span><span class="pun">):</span><span class="pln">
   </span><span class="kwd">return</span><span class="pln"> render</span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> </span><span class="str">'home.html'</span><span class="pun">)</span></pre>

<p>
	وتأكّد من أن موجّه إرسال عنوان URL الخاص بك يؤشّر إلى هذا العرض كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9974_106" style=""><span class="pln">path</span><span class="pun">(</span><span class="str">'home/'</span><span class="pun">,</span><span class="pln"> views</span><span class="pun">.</span><span class="pln">home</span><span class="pun">),</span></pre>

<p>
	افتح متصفحك وانتقل إلى العنوان <code>http://127.0.0.1:8000/home</code>، ويجب أن ترى الصفحة التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162674" href="https://academy.hsoub.com/uploads/monthly_2024_11/05_template-layout.png.1ed116dd356e6ebeecfa3005e15f0979.png" rel=""><img alt="05 template layout" class="ipsImage ipsImage_thumbnailed" data-fileid="162674" data-unique="g65uab3an" style="width: 400px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/05_template-layout.thumb.png.825168fabd4b82282662d7e5babfe796.png"> </a>
</p>

<p>
	ترجمة -وبتصرّف- للمقال <a href="https://www.ericsdevblog.com/posts/django-for-beginners-2/" rel="external nofollow">Django for Beginners #2 - The MTV Structure</a> لصاحبه Eric Hu.
</p>

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

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/python/django/%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A7%D9%84%D8%A8%D8%AF%D8%A1-%D9%81%D9%8A-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D8%AF%D9%88%D9%86%D8%A9-%D8%A8%D8%B3%D9%8A%D8%B7%D8%A9-r2446/" rel="">جانغو للمبتدئين الجزء الأول: البدء في إنشاء مدونة بسيطة</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AD%D8%AF%D9%8A%D8%AB-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-django-%D9%88-vue-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A7%D9%84%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-r2421/" rel="">إنشاء تطبيق حديث باستخدام Django و Vue | الجزء الأول: الأساسيات</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D9%84%D8%B9%D8%B1%D9%88%D8%B6-%D9%88%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-%D9%81%D9%8A-django-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-r436/" rel="">العروض والقوالب في Django - الجزء الأول</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D9%84%D8%B9%D8%B1%D9%88%D8%B6-%D9%88%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-%D9%81%D9%8A-django-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-r439/" rel="">العروض والقوالب في Django - الجزء الثاني</a>
	</li>
	<li>
		<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>
	</li>
</ul>
]]></description><guid isPermaLink="false">2452</guid><pubDate>Fri, 22 Nov 2024 15:00:00 +0000</pubDate></item><item><title>&#x627;&#x644;&#x628;&#x62F;&#x621; &#x641;&#x64A; &#x625;&#x646;&#x634;&#x627;&#x621; &#x645;&#x62F;&#x648;&#x646;&#x629; &#x628;&#x633;&#x64A;&#x637;&#x629; &#x641;&#x64A; &#x62C;&#x627;&#x646;&#x63A;&#x648;</title><link>https://academy.hsoub.com/programming/python/django/%D8%A7%D9%84%D8%A8%D8%AF%D8%A1-%D9%81%D9%8A-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D8%AF%D9%88%D9%86%D8%A9-%D8%A8%D8%B3%D9%8A%D8%B7%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2446/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_11/--_.png.1bbfa5f78d5eb7b767c12d4a94e3c285.png" /></p>
<p>
	جانغو Django هو إطار عمل ويب عالي المستوى مجاني ومفتوح المصدر ومكتوب بلغة البرمجة بايثون Python، ويُستخدَم على نطاق واسع لبناء تطبيقات ويب معقدة، وهو معروف بقدرته على التعامل مع حركة المرور العالية وميزات الأمان الخاصة به ومكوناته القابلة لإعادة الاستخدام.
</p>

<p>
	يتبع جانغو نمط معمارية نموذج-عرض-متحكم Model-View-Controller أو MVC اختصارًا ويأتي مع واجهة مُدمَجة لإدارة الموقع مع دعم الاستيثاق Authentication وربط الكائنات بالعلاقات Object-Relational Mapping أو ORM اختصارًا الذي يسمح بتعريف مخطط قاعدة بياناتك باستخدام أصناف Classes بايثون.
</p>

<p>
	يتمتع جانغو بمجتمع كبير ونشط يوفر مجموعة كبيرة من البرامج التعليمية والحزم والموارد الأخرى التي يمكن للمطورين استخدامها، إذ تُبنَى العديد من المواقع الإلكترونية المعروفة وذات حركة المرور العالية -مثل إنستغرام Instagram وبنترست Pinterest وموقع واشنطن تايمز The Washington Times- باستخدام إطار عمل جانغو. يُعَد جانغو أحد أكثر أطر عمل الويب شعبيةً وقوة للغة بايثون، وتستخدمه كلٌّ من الشركات الناشئة والكبيرة على نطاق واسع، مما يجعله خيارًا رائعًا لبناء تطبيقات ويب قوية وقابلة للتوسّع، وتؤدي مرونته وقابليته للتوسع إلى جعله الخيار الأفضل للمطورين والمؤسسات التي تريد إنشاء تطبيقات ويب معقدة وصغيرة الحجم.
</p>

<p>
	هذه المقالة جزء من سلسلة من المقالات تشرح جانغو للمبتدئين على النحو التالي:
</p>

<ul>
	<li>
		<strong><span ipsnoautolink="true">البدء في إنشاء مدونة بسيطة</span></strong>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A8%D9%86%D9%8A%D8%A9-mtv-%D9%84%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D8%AF%D9%88%D9%86%D8%A9-%D8%A8%D8%B3%D9%8A%D8%B7%D8%A9-r2452/" rel="">استخدام بنية MTV لإنشاء مدونة بسيطة</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B9%D9%85%D9%84%D9%8A%D8%A7%D8%AA-crud-%D9%84%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D9%85%D8%AF%D9%88%D9%86%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2457/" rel="">استخدام عمليات CRUD لإدارة المدونة</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%85%D8%AF%D9%88%D9%86%D8%A9-%D9%83%D8%A7%D9%85%D9%84-%D9%81%D9%8A-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2458/" rel="">تطبيق المدونة الكامل</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-%D8%A8%D8%B9%D8%B6-%D8%A7%D9%84%D9%85%D9%8A%D8%B2%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%AA%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D9%85%D8%AF%D9%88%D9%86%D8%A9-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2482/" rel="">إضافة بعض الميزات المتقدمة للمدونة</a>
	</li>
</ul>

<h2 id="">
	تثبيت الأدوات اللازمة
</h2>

<p>
	توجد بعض الأدوات التي يجب تثبيتها على جهازك قبل البدء، حيث تحتاج مبدئيًا لغة برمجة (<a href="https://academy.hsoub.com/programming/python/" 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>)، وخادم (سنستخدم خادم التطوير المُدمَج مع جانغو) لتشغيل التطبيق بنجاح. تذكّر أن هذه الأدوات مُخصَّصة لبيئة التطوير Dev Environment المخصصة لإنشاء واختبار التطبيق فقط، لذا لا يجب استخدام هذه الأدوات في بيئة الإنتاج Production Environment.
</p>

<p>
	تحتاج أيضًا إلى بيئة تطوير متكاملة <a href="https://academy.hsoub.com/programming/workflow/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%A7%D9%84%D9%85%D8%AA%D9%83%D8%A7%D9%85%D9%84%D8%A9-ide-r1513/" rel="">IDE</a> مثل بيئة باي تشارم PyCharm أو <a href="https://academy.hsoub.com/programming/python/%D9%85%D8%AD%D8%B1%D8%B1-%D8%A3%D9%83%D9%88%D8%A7%D8%AF-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86/" rel="">محرّر شيفرات برمجية</a> على الأقل، حيث سنستخدم في هذا المقال المحرّر VS Code لأنه برنامج مجاني.
</p>

<p>
	اتبّع الآن الخطوات التالية للحصول على هذه الأدوات:
</p>

<ul>
	<li>
		<a href="https://www.python.org/downloads/" rel="external nofollow">تنزيل بايثون</a>.
	</li>
	<li>
		<a href="https://www.sqlite.org/index.html" rel="external nofollow">تنزيل SQLite</a>.
	</li>
	<li>
		<a href="https://code.visualstudio.com/download" rel="external nofollow">تنزيل VS Code</a>.
	</li>
	<li>
		<a href="https://www.jetbrains.com/pycharm/download/?section=windows" rel="external nofollow">تنزيل PyCharm</a>.
	</li>
</ul>

<h2 id="-1">
	إنشاء مشروع جانغو جديد
</h2>

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

<pre class="ipsCode">mkdir django-demo
</pre>

<p>
	ثم انتقل إلى هذا المجلد باستخدام الأمر التالي:
</p>

<pre class="ipsCode">cd django-demo
</pre>

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

<pre class="ipsCode">python -m venv env
</pre>

<p>
	إذا استخدمتَ نظام لينكس Linux أو ماك macOS، فقد تضطر إلى تشغيل أمر <code>python3</code> بدلًا من <code>python</code>، ولكننا سنستخدم <code>python</code> للتبسيط. سيعمل الأمر السابق على إنشاء المجلد <code>env</code> الذي يحتوي على البيئة الافتراضية التي أنشأتها، ويمكنك تنشيط هذه البيئة الافتراضية باستخدام الأمر التالي:
</p>

<pre class="ipsCode">source env/bin/activate
</pre>

<p>
	وإذا استخدمتَ نظام ويندوز Windows، فاستخدم الأمر التالي بدلًا من ذلك:
</p>

<pre class="ipsCode">env/Scripts/activate
</pre>

<p>
	إذا نشطت البيئة الافتراضية بنجاح، فسيكون موجّه <a href="https://academy.hsoub.com/programming/workflow/%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B3%D8%B7%D8%B1-%D8%A7%D9%84%D8%A3%D9%88%D8%A7%D9%85%D8%B1-%D9%81%D9%8A-%D8%B9%D9%85%D9%84%D9%8A%D8%A9-%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%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%B9%D9%85%D9%8A%D9%84-r1471/" rel="">سطر الأوامر</a> الخاص بك كما يلي:
</p>

<pre class="ipsCode">(env) eric@djangoDemo:~/django-demo$
</pre>

<p>
	<strong>ملاحظة</strong>: يعني <code>(env)</code> هنا أنك تعمل حاليًا في بيئة افتراضية اسمها <code>env</code>.
</p>

<p>
	حان الوقت الآن لتهيئة مشروع <a href="https://academy.hsoub.com/programming/python/django/" rel="">جانغو</a> جديد، حيث تحتاج إلى تثبيت حزمة <code>Django</code> من خلال تشغيل الأمر التالي:
</p>

<pre class="ipsCode">python -m pip install Django
</pre>

<p>
	ثم يمكنك استخدام أمر <code>django-admin</code> لإنشاء مشروع جانغو جديد كما يلي:
</p>

<pre class="ipsCode">django-admin startproject djangoBlog
</pre>

<p>
	وسينشَأ المجلد الجديد <code>djangoBlog</code> التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162045" href="https://academy.hsoub.com/uploads/monthly_2024_11/01_django-new-project.png.fcc20db1d584d91353f794f407e240a6.png" rel=""><img alt="01 django new project" class="ipsImage ipsImage_thumbnailed" data-fileid="162045" data-ratio="81.33" data-unique="95shm3l3t" style="width: 300px; height: auto;" width="400" src="https://academy.hsoub.com/uploads/monthly_2024_11/01_django-new-project.thumb.png.905e75fd23091e19dcd64952c31cebde.png"> </a>
</p>

<p>
	يُفضَّل إعادة هيكلة المشروع بعض الشيء بحيث نبدأ من المجلد الجذر للمشروع كما يلي، ولكن لا حاجة لذلك إن لم ترغب في ذلك.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162046" href="https://academy.hsoub.com/uploads/monthly_2024_11/02_django-new-project-restructure.png.92beb7ab922c4d3cc61f14e764ab37eb.png" rel=""><img alt="02 django new project restructure" class="ipsImage ipsImage_thumbnailed" data-fileid="162046" data-ratio="72.33" data-unique="yki5oryku" style="width: 300px; height: auto;" width="300" src="https://academy.hsoub.com/uploads/monthly_2024_11/02_django-new-project-restructure.thumb.png.a1b0c2bfc5a35f8c74b7a56e5aca089a.png"> </a>
</p>

<h3 id="-2">
	إنشاء تطبيق المدونة
</h3>

<p>
	لا يزال المشروع فارغًا حاليًا، ولاحظ أن بنية هذا المشروع بسيطة مقارنةً ببنية <a href="https://academy.hsoub.com/programming/php/laravel/%D9%84%D8%A7%D8%B1%D8%A7%D9%81%D9%8A%D9%84-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A7%D9%84%D8%A8%D8%AF%D8%A1-%D9%81%D9%8A-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D8%AF%D9%88%D9%86%D8%A9-%D8%A8%D8%B3%D9%8A%D8%B7%D8%A9-r2425/" rel="">مشروع لارافيل Laravel</a>، وسنناقش كل ملف في مجلد المشروع بالتفصيل لاحقًا.
</p>

<p>
	يتيح جانغو إنشاء تطبيقات متعددة في مشروع واحد، فمثلًا يمكن أن يكون هناك تطبيق <code>blog</code> وتطبيق <code>gallery</code> وتطبيق <code>forum</code> ضمن مشروع واحد. يمكن أن تتشارك هذه التطبيقات في نفس الملفات الثابتة (ملفات CSS وجافاسكربت JavaScript) والصور ومقاطع الفيديو أو يمكن أن تكون مستقلة تمامًا عن بعضها البعض، إذ يعتمد ذلك على احتياجاتك الخاصة.
</p>

<p>
	سننشئ في هذا المقال تطبيق مدونة <code>blog</code> فقط، لذا نفّذ الأمر التالي في الطرفية:
</p>

<pre class="ipsCode">python manage.py startapp blog
</pre>

<p>
	يجب أن ترى مجلد <code>blog</code> جديد ضمن مجلد جذر المشروع، ويمكنك سرد محتوى المجلد باستخدام الأمر التالي:
</p>

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

<p>
	وإذا أردتَ تضمين الملفات المخفية أيضًا في النتيجة، فاستخدم الأمر التالي:
</p>

<pre class="ipsCode">ls -a
</pre>

<p>
	وإذا أردتَ رؤية بنية الملفات فاستخدم الأمر التالي:
</p>

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

<p>
	ولكن قد تحتاج إلى تثبيت برنامج <code>tree</code> ليعمل الأمر السابق بنجاح اعتمادًا على نظام تشغيلك. سنستخدم الأمر السابق لعرض بنية الملفات، حيث يجب أن يكون لمشروعك البنية التالية حاليًا:
</p>

<pre class="ipsCode">.
├── blog
│   ├── admin.py
│   ├── apps.py
│   ├── __init__.py
│   ├── migrations
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── djangoBlog
│   ├── asgi.py
│   ├── __init__.py
│   ├── __pycache__
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── env
│   ├── bin
│   ├── include
│   ├── lib
│   ├── lib64 -&gt; lib
│   └── pyvenv.cfg
└── manage.py
</pre>

<p>
	يجب بعد ذلك تسجيل تطبيق <code>blog</code> الجديد في جانغو، لذا انتقل إلى الملف <code>settings.py</code> وابحث عن القائمة <code>INSTALLED_APPS</code>:
</p>

<pre class="ipsCode prettyprint lang-ruby prettyprinted" id="ips_uid_1617_11" style=""><span class="pln">INSTALLED_APPS </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
   </span><span class="str">'blog'</span><span class="pun">,</span><span class="pln">
   </span><span class="str">'django.contrib.admin'</span><span class="pun">,</span><span class="pln">
   </span><span class="str">'django.contrib.auth'</span><span class="pun">,</span><span class="pln">
   </span><span class="str">'django.contrib.contenttypes'</span><span class="pun">,</span><span class="pln">
   </span><span class="str">'django.contrib.sessions'</span><span class="pun">,</span><span class="pln">
   </span><span class="str">'django.contrib.messages'</span><span class="pun">,</span><span class="pln">
   </span><span class="str">'django.contrib.staticfiles'</span><span class="pun">,</span><span class="pln">
</span><span class="pun">]</span></pre>

<h3 id="-3">
	بدء تشغيل الخادم
</h3>

<p>
	يمكنك الآن بدء تشغيل خادم التطوير لاختبار نجاح عمل كل شيء، لذا افتح الطرفية وشغّل الأمر التالي:
</p>

<pre class="ipsCode">python manage.py runserver
</pre>

<p>
	وسيظهر الخرج التالي في واجهة سطر الأوامر:
</p>

<pre class="ipsCode">Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).

You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
October 19, 2022 - 01:39:33
Django version 4.1.2, using settings 'djangoBlog.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
</pre>

<p>
	افتح المتصفح وانتقل إلى العنوان <code>http://127.0.0.1:8000/‎</code>، وستظهر الصفحة التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162047" href="https://academy.hsoub.com/uploads/monthly_2024_11/03_django-welcome.png.3bfc0558a8003dbd5d96eb07a8e8fd25.png" rel=""><img alt="03 django welcome" class="ipsImage ipsImage_thumbnailed" data-fileid="162047" data-unique="z5hduu2pb" style="width: 500px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_11/03_django-welcome.thumb.png.e3c71a853b7b9141e24cb15172a2ce46.png"> </a>
</p>

<h2 id="-4">
	بنية التطبيق
</h2>

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

<pre class="ipsCode">.
├── blog
│   ├── admin.py
│   ├── apps.py
│   ├── __init__.py
│   ├── migrations
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── djangoBlog
│   ├── asgi.py
│   ├── __init__.py
│   ├── __pycache__
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── env
│   ├── bin
│   ├── include
│   ├── lib
│   ├── lib64 -&gt; lib
│   └── pyvenv.cfg
└── manage.py
</pre>

<h3 id="-5">
	المجلد الجذر
</h3>

<p>
	يحتوي المجلد الجذر على الملفات والمجلدات التالية:
</p>

<ul>
	<li>
		<strong><code>manage.py</code></strong>: يمثل أداة سطر الأوامر التي تتيح لك التفاعل مع مشروع جانغو بطرق مختلفة.
	</li>
	<li>
		<strong><code>djangoBlog</code></strong>: مجلد المشروع الرئيسي الذي يحتوي على ملف الإعدادات ونقطة الدخول إلى المشروع.
	</li>
	<li>
		<strong><code>blog</code></strong>: يمثل تطبيق المدونة <code>blog</code>.
	</li>
</ul>

<h3 id="-6">
	مجلد المشروع
</h3>

<p>
	يحتوي هذا المجلد على الملفات التالية:
</p>

<ul>
	<li>
		<strong><code>djangoBlog/__init__.py</code></strong>: ملف فارغ يخبر بايثون بأن هذا المجلد يجب عَدّه حزمة بايثون.
	</li>
	<li>
		<strong><code>djangoBlog/settings.py</code></strong>: ملف الإعدادات أو الضبط Configuration لمشروع جانغو.
	</li>
	<li>
		<strong><code>djangoBlog/urls.py</code></strong>: يحتوي على التصريحات عن عناوين URL لمشروع جانغو، ويمثل جدول محتويات تطبيق جانغو الخاص بك، حيث سنتحدث عنه أكثر لاحقًا.
	</li>
	<li>
		<strong><code>djangoBlog/asgi.py</code></strong>: يُعَد نقطة الدخول لخوادم الويب المتوافقة مع واجهة ASGI لتخديم مشروعك.
	</li>
	<li>
		<strong><code>djangoBlog/wsgi.py</code></strong>: نقطة الدخول لخوادم الويب المتوافقة مع واجهة WSGI لتخديم مشروعك.
	</li>
</ul>

<h3 id="-7">
	مجلد التطبيق
</h3>

<p>
	يحتوي هذا المجلد على المجلدات والملفات التالية:
</p>

<ul>
	<li>
		<strong><code>blog/migrations</code></strong>: يحتوي هذا المجلد على جميع ملفات التهجير Migration لتطبيق المدونة، حيث ينشئ جانغو هذه الملفات تلقائيًا بناءً على نماذجك Models على عكس إطار عمل لارافيل.
	</li>
	<li>
		<strong><code>blog/admin.py</code></strong>: يأتي جانغو أيضًا مع لوحة مُدمَجة لإدارة الموقع، حيث يحتوي هذا الملف على جميع عمليات الضبط الخاصة بهذه اللوحة.
	</li>
	<li>
		<strong><code>blog/models.py</code></strong>: تصف النماذج Models بنية وعلاقة قاعدة البيانات، وتُنشَأ ملفات التهجير بناءً على هذا الملف.
	</li>
	<li>
		<strong><code>blog/views.py</code></strong>: يكافئ المتحكمات Controllers في <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="">لارافيل</a>، ويحتوي على المنطق البرمجي الأساسي لهذا التطبيق.
	</li>
</ul>

<h2 id="configuring">
	ضبط Configuring مشروع جانغو
</h2>

<p>
	توجد بعض التغييرات التي يجب إجراؤها على الملف <code>settings.py</code> قبل البدء بالشروع كما سنوضح فيما يلي.
</p>

<h3 id="-8">
	المضيفون المسموح بهم
</h3>

<p>
	تُعد القائمة <code>ALLOWED_HOSTS</code> قائمة بالنطاقات Domains التي يُسمَح لموقع جانغو بتخديمها، وهي إجراء أمني لمنع <a href="https://academy.hsoub.com/programming/advanced/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A3%D9%85%D8%A7%D9%86-%D9%85%D9%88%D8%A7%D9%82%D8%B9-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r2020/" rel="">هجمات ترويسات مضيف HTTP</a> أو HTTP Host Header Attacks، والتي يمكن حدوثها حتى عند إجراء العديد من عمليات ضبط خادم الويب الآمنة ظاهريًا.
</p>

<p>
	لاحظ أنه ما زال بإمكاننا الوصول إلى موقعنا باستخدام المضيف <code>127.0.0.1</code> حتى وإن كانت القائمة <code>ALLOWED_HOSTS</code> فارغة حاليًا، والسبب هو التحقق من صحة المضيف مقابل ‎<code>['.localhost', '127.0.0.1', '[::1]']</code>‎ عندما تكون قيمة <code>DEBUG</code> هي <code>True</code> وتكون القائمة <code>ALLOWED_HOSTS</code> فارغة.
</p>

<h3 id="-9">
	قاعدة البيانات
</h3>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1617_14" style=""><span class="pln">DATABASES </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="str">"default"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
       </span><span class="str">"ENGINE"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"django.db.backends.postgresql"</span><span class="pun">,</span><span class="pln">
       </span><span class="str">"NAME"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"&lt;database_name&gt;"</span><span class="pun">,</span><span class="pln">
       </span><span class="str">"USER"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"&lt;database_user_name&gt;"</span><span class="pun">,</span><span class="pln">
       </span><span class="str">"PASSWORD"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"&lt;database_user_password&gt;"</span><span class="pun">,</span><span class="pln">
       </span><span class="str">"HOST"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"127.0.0.1"</span><span class="pun">,</span><span class="pln">
       </span><span class="str">"PORT"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"5432"</span><span class="pun">,</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	<strong>ملاحظة</strong>: يوصي جانغو باستخدام قاعدة بيانات <a href="https://academy.hsoub.com/devops/servers/databases/postgresql/" rel="">PostgreSQL</a> في بيئة الإنتاج. توجد العديد من البرامج التعليمية التي تعلمك كيفية استخدام جانغو مع قاعدة بيانات MongoDB، ولكنها تُعَد فكرة سيئة، إذ قد تكون قاعدة بيانات MongoDB حلًا رائعًا، ولكنها لا تعمل بطريقة جيدة مع إطار عمل جانغو.
</p>

<h3 id="staticfilesmediafiles">
	الملفات الساكنة Static Files وملفات الوسائط Media Files
</h3>

<p>
	يجب أن نهتم أيضًا بالملفات الساكنة وملفات الوسائط، فالملفات الساكنة هي ملفات <a href="https://academy.hsoub.com/programming/css/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D9%84%D8%BA%D8%A9-css/" rel="">CSS</a> وملفات <a href="https://academy.hsoub.com/javascript/" rel="">جافاسكربت</a>، وملفات الوسائط هي الصور ومقاطع الفيديو والأمور الأخرى التي قد يرفعها المستخدم.
</p>

<h4 id="staticfiles">
	الملفات الساكنة Static Files
</h4>

<p>
	يجب أولًا تحديد مكان تخزين هذه الملفات، حيث سننشئ المجلد <code>static</code> ضمن تطبيق المدونة لموقع جانغو الخاص بنا، ويُعَد هذا المجلد هو المكان الذي نخزّن فيه الملفات الساكنة لتطبيق <code>blog</code> أثناء التطوير.
</p>

<pre class="ipsCode">blog
├── admin.py
├── apps.py
├── __init__.py
├── migrations
├── models.py
├── static
├── tests.py
└── views.py
</pre>

<p>
	تختلف الأمور بعض الشيء في بيئة الإنتاج، إذ يجب أن ننشئ مجلدًا مختلفًا ضمن المجلد الجذر للمشروع، ولنسمّيه بالاسم <code>staticfiles</code>.
</p>

<pre class="ipsCode">.
├── blog
├── db.sqlite3
├── djangoBlog
├── env
├── manage.py
└── staticfiles
</pre>

<p>
	ثم يجب تحديد هذا المجلد في الملف <code>settings.py</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1617_19" style=""><span class="pln">STATIC_ROOT </span><span class="pun">=</span><span class="pln"> </span><span class="str">"staticfiles/"</span></pre>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1617_21" style=""><span class="pln">STATIC_URL </span><span class="pun">=</span><span class="pln"> </span><span class="str">"static/"</span></pre>

<h4 id="mediafiles">
	ملفات الوسائط Media Files
</h4>

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

<pre class="ipsCode">.
├── blog
├── db.sqlite3
├── djangoBlog
├── env
├── manage.py
├── mediafiles
└── staticfiles
</pre>

<p>
	ثم نحدّد الموقع وعنوان URL في الملف <code>settings.py</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1617_23" style=""><span class="com"># ملفات الوسائط</span><span class="pln">

MEDIA_ROOT </span><span class="pun">=</span><span class="pln"> </span><span class="str">"mediafiles/"</span><span class="pln">

MEDIA_URL </span><span class="pun">=</span><span class="pln"> </span><span class="str">"media/"</span></pre>

<h2 id="dispatcherurl">
	موجه إرسال Dispatcher عناوين URL في جانغو
</h2>

<p>
	يحتوي مجال تطوير الويب على بنية نموذج-عرض-متحكم Model-View-Controller (أو MVC اختصارًا)، حيث يكون النموذج Model في هذه البنية مسؤولًا عن التفاعل مع قاعدة بياناتنا، ويجب أن يقابل كلُّ نموذج جدولَ قاعدة بيانات واحد. العرض View هو جزء الواجهة الأمامية Frontend من التطبيق، وهو ما يمكن للمستخدمين رؤيته، والمتحكم Controller هو المنطق البرمجي للواجهة الخلفية للتطبيق مثل استرداد البيانات من قاعدة البيانات عبر النماذج ووضعها في العرض المقابل وإعادة القالب المعروض إلى المستخدم في النهاية.
</p>

<p>
	جانغو هو إطار عمل ويب مُصمَّم بناءً على بنية MVC مع مصطلحات مختلفة، فبنية جانغو هي بنية نموذج-قالب-عرض Model-Template-View (أو MTV اختصارًا)، فالقالب Template هو الواجهة الأمامية، والعرض هو المنطق البرمجي للواجهة الخلفية. سنركّز في هذا المقال على فهم هذه الطبقات، ولكن يجب أولًا البدء بنقطة الدخول لكل تطبيق ويب، والتي هي موجّه إرسال Dispatcher عناوين URL، حيث يقرأ هذا الموجّه عنوان URL ويوجّه المستخدم إلى الصفحة الصحيحة عندما يكتب المستخدم عنوان URL ويضغط على مفتاح <code>Enter</code>.
</p>

<h3 id="url">
	عمليات ضبط عناوين URL الأساسية
</h3>

<p>
	يُخزَّن ضبط عناوين URL في الملف <code>example/urls.py</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1617_25" style=""><span class="pln">djangoBlog
</span><span class="pun">├──</span><span class="pln"> asgi</span><span class="pun">.</span><span class="pln">py
</span><span class="pun">├──</span><span class="pln"> __init__</span><span class="pun">.</span><span class="pln">py
</span><span class="pun">├──</span><span class="pln"> __pycache__
</span><span class="pun">├──</span><span class="pln"> settings</span><span class="pun">.</span><span class="pln">py
</span><span class="pun">├──</span><span class="pln"> urls</span><span class="pun">.</span><span class="pln">py
</span><span class="pun">└──</span><span class="pln"> wsgi</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	يوضّح المثال التالي ما يجب أن يبدو عليه موجّه إرسال عناوين URL:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1617_27" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">contrib </span><span class="kwd">import</span><span class="pln"> admin
</span><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">urls </span><span class="kwd">import</span><span class="pln"> path

urlpatterns </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
   path</span><span class="pun">(</span><span class="str">'admin/'</span><span class="pun">,</span><span class="pln"> admin</span><span class="pun">.</span><span class="pln">site</span><span class="pun">.</span><span class="pln">urls</span><span class="pun">),</span><span class="pln">
</span><span class="pun">]</span></pre>

<p>
	يقرأ موجّه الإرسال المعلومات من عنوان URL ويعيد عرضًا، ولكن لا تخلط بين هذا العرض وعرض <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="">إطار عمل لارافيل</a>، فالعرض في جانغو يمثّل متحكّمًا في لارافيل. إذا تبع النطاقُ <code>admin/‎</code> في هذا المثال، فسيوجّه موجّه الإرسال المستخدم إلى صفحة المدير Admin.
</p>

<h3 id="url-1">
	تمرير المعاملات باستخدام موجه إرسال عناوين URL في جانغو
</h3>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1617_29" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">urls </span><span class="kwd">import</span><span class="pln"> path
</span><span class="kwd">from</span><span class="pln"> blog </span><span class="kwd">import</span><span class="pln"> views

urlpatterns </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
   path</span><span class="pun">(</span><span class="str">'post/&lt;int:id&gt;'</span><span class="pun">,</span><span class="pln"> views</span><span class="pun">.</span><span class="pln">post</span><span class="pun">),</span><span class="pln">
</span><span class="pun">]</span></pre>

<p>
	ستلتقط الأقواس الزاويّة جزءًا من عنوان URL كمعامل، حيث يُسمَّى <code>int</code> على الجانب الأيسر بمحوِّل المسار Path Converter الذي يلتقط معاملًا من النوع الصحيح، ويمكن مطابقة أيّ سلسلة نصية باستثناء المحرف <code>/</code> عند عدم تضمين المحوّل. لاحظ وجود اسم المعامل على الجانب الأيمن، وهو ما سنحتاج إلى استخدامه في العرض.
</p>

<p>
	تتوفّر محوّلات المسارات التالية افتراضيًا:
</p>

<ul>
	<li>
		<strong><code>str</code></strong>: يطابق أيّ سلسلة نصية غير فارغة باستثناء فاصل المسار <code>'/'</code> افتراضيًا عند عدم تضمين محوّلٍ في التعبير.
	</li>
	<li>
		<strong><code>int</code></strong>: يطابق الصفر أو أيّ عدد صحيح موجب، ويعيد قيمة من النوع <code>int</code>.
	</li>
	<li>
		<strong><code>slug</code></strong>: يطابق أيّ سلسلة نصية للاسم المختصر Slug، والذي يتكون من حروف أو أرقام ASCII، بالإضافة إلى محارف الشرطة والشرطة السفلية مثل <code>building-your-1st-django-site</code>.
	</li>
	<li>
		<strong><code>uuid</code></strong>: يطابق معرّف UUID المنسَّق، حيث يجب تضمين شرطات، ويجب أن تكون الحروف صغيرة لمنع ربط عناوين URL متعددة مع الصفحة نفسها مثل المعرّف <code>075194d3-6885-417e-a8a8-6c931e272f00</code>، ويعيد نسخةً من <code>UUID</code>.
	</li>
	<li>
		<strong><code>path</code></strong>: يطابق أي سلسلة نصية غير فارغة مع فاصل المسار <code>'/'</code>، مما يتيح لك المطابقة مع مسار URL كامل بدلًا من المطابقة مع مقطعٍ من مسار URL كما هو الحال مع المحوِّل <code>str</code>.
	</li>
</ul>

<h3 id="regularexpressionsurl">
	استخدام التعابير النمطية Regular Expressions لمطابقة أنماط عناوين URL
</h3>

<p>
	قد يكون النمط Pattern الذي تريد مطابقته أكثر تعقيدًا في بعض الحالات، وبالتالي يمكنك استخدام التعابير النمطية Regular Expressions لمطابقة أنماط عناوين URL.
</p>

<p>
	يجب استخدام التابع <code>re_path()‎</code> بدلًا من التابع <code>path()‎</code> لاستخدام التعابير النمطية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1617_31" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">urls </span><span class="kwd">import</span><span class="pln"> path</span><span class="pun">,</span><span class="pln"> re_path
</span><span class="kwd">from</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> views

urlpatterns </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
   path</span><span class="pun">(</span><span class="str">'articles/2003/'</span><span class="pun">,</span><span class="pln"> views</span><span class="pun">.</span><span class="pln">special_case_2003</span><span class="pun">),</span><span class="pln">
   re_path</span><span class="pun">(</span><span class="pln">r</span><span class="str">'^articles/(?P&lt;year&gt;[0-9]{4})/$'</span><span class="pun">,</span><span class="pln"> views</span><span class="pun">.</span><span class="pln">year_archive</span><span class="pun">),</span><span class="pln">
   re_path</span><span class="pun">(</span><span class="pln">r</span><span class="str">'^articles/(?P&lt;year&gt;[0-9]{4})/(?P&lt;month&gt;[0-9]{2})/$'</span><span class="pun">,</span><span class="pln"> views</span><span class="pun">.</span><span class="pln">month_archive</span><span class="pun">),</span><span class="pln">
   re_path</span><span class="pun">(</span><span class="pln">r</span><span class="str">'^articles/(?P&lt;year&gt;[0-9]{4})/(?P&lt;month&gt;[0-9]{2})/(?P&lt;slug&gt;[\w-]+)/$'</span><span class="pun">,</span><span class="pln"> views</span><span class="pun">.</span><span class="pln">article_detail</span><span class="pun">),</span><span class="pln">
</span><span class="pun">]</span></pre>

<h3 id="url-2">
	استيراد عمليات ضبط عناوين URL الأخرى
</h3>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1617_33" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">urls </span><span class="kwd">import</span><span class="pln"> path</span><span class="pun">,</span><span class="pln"> include

urlpatterns </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
   path</span><span class="pun">(</span><span class="str">'blog/'</span><span class="pun">,</span><span class="pln"> include</span><span class="pun">(</span><span class="str">'blog.urls'</span><span class="pun">))</span><span class="pln">
</span><span class="pun">]</span></pre>

<p>
	وهذا يعني أنه إذا احتوى عنوان URL على النمط <code><a href="http://www.mydomain.com/blog/xxxx" ipsnoembed="false" rel="external nofollow">http://www.mydomain.com/blog/xxxx</a></code>، فسينتقل جانغو إلى الملف <code>blog/urls.py</code> ويطابق باقي عنوان URL.
</p>

<p>
	لنعرّف الآن أنماط عناوين URL لتطبيق <code>blog</code> باستخدام الطريقة نفسها كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1617_35" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">urls </span><span class="kwd">import</span><span class="pln"> path
</span><span class="kwd">from</span><span class="pln"> blog </span><span class="kwd">import</span><span class="pln"> views

urlpatterns </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
   path</span><span class="pun">(</span><span class="str">'post/&lt;int:id&gt;'</span><span class="pun">,</span><span class="pln"> views</span><span class="pun">.</span><span class="pln">post</span><span class="pun">),</span><span class="pln">
</span><span class="pun">]</span></pre>

<p>
	سيتطابق هذا النمط مع نمط عنوان URL الذي هو <code><a href="http://www.mydomain.com/blog/post/123" ipsnoembed="false" rel="external nofollow">http://www.mydomain.com/blog/post/123</a></code>.
</p>

<h3 id="url-3">
	تسمية أنماط عناوين URL
</h3>

<p>
	يمكنك تسمية نمط عنوان URL من خلال إعطائه معاملًا ثالثًا كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1617_37" style=""><span class="pln">urlpatterns </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
   path</span><span class="pun">(</span><span class="str">'articles/&lt;int:year&gt;/'</span><span class="pun">,</span><span class="pln"> views</span><span class="pun">.</span><span class="pln">year_archive</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">=</span><span class="str">'news-year-archive'</span><span class="pun">),</span><span class="pln">
</span><span class="pun">]</span></pre>

<p>
	يؤدي ذلك إلى القدرة على عكس عناوين URL المطلقة من القالب، حيث سنتحدث عن ذلك لاحقًا عندما نصل إلى طبقة القوالب.
</p>

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

<p>
	ترجمة -وبتصرّف- للمقال <a href="https://www.ericsdevblog.com/posts/django-for-beginners-1/" rel="external nofollow">Django for Beginners #1 - Getting Started</a> لصاحبه Eric Hu.
</p>

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

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AD%D8%B2%D9%85-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D8%A7%D9%84%D8%AB%D9%85%D8%A7%D9%86%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D9%8A-%D8%AA%D8%B3%D9%87%D9%84-%D8%AA%D8%B9%D8%A7%D9%85%D9%84%D9%83-%D9%85%D8%B9-django-r656/" rel="">حزم بايثون الثمانية التي تسهل تعاملك مع Django</a>
	</li>
	<li>
		<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>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A3%D9%85%D8%A7%D9%86-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2123/" rel="">تعرف على أمان تطبيقات جانغو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%85%D9%87%D8%A7%D9%85-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-%D9%88%D8%B1%D9%8A%D8%A2%D9%83%D8%AA-react-r1773/" rel="">بناء تطبيق مهام باستخدام جانغو Django وريآكت React</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2446</guid><pubDate>Tue, 12 Nov 2024 15:06:00 +0000</pubDate></item><item><title>&#x625;&#x646;&#x634;&#x627;&#x621; &#x62A;&#x637;&#x628;&#x64A;&#x642; &#x62D;&#x62F;&#x64A;&#x62B; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; Django &#x648; Vue | &#x627;&#x644;&#x62C;&#x632;&#x621; &#x627;&#x644;&#x62B;&#x627;&#x644;&#x62B;: &#x625;&#x636;&#x627;&#x641;&#x629; &#x627;&#x644;&#x62A;&#x639;&#x644;&#x64A;&#x642;&#x627;&#x62A; &#x648;&#x627;&#x644;&#x625;&#x639;&#x62C;&#x627;&#x628;&#x627;&#x62A;</title><link>https://academy.hsoub.com/programming/python/django/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AD%D8%AF%D9%8A%D8%AB-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-django-%D9%88-vue-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-%D8%A7%D9%84%D8%AA%D8%B9%D9%84%D9%8A%D9%82%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D8%A5%D8%B9%D8%AC%D8%A7%D8%A8%D8%A7%D8%AA-r2436/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_10/-----Django--Vue----.png.6e91cefb7628e1f6b1c2a2ac7a378826.png" /></p>
<p>
	والآن بعد أن تعرفنا على كيفية استرجاع البيانات باستخدام الاستعلامات queries وكيفية إرسال البيانات باستخدام الطفرات mutations، أصبح بإمكاننا تجربة شيء أكثر صعوبة. سنتعلم في هذا المقال كيفية إنشاء نظام لإضافة التعليقات comments والإعجابات likes.
</p>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AD%D8%AF%D9%8A%D8%AB-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-django-%D9%88-vue-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A7%D9%84%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-r2421/" rel="">إنشاء تطبيق حديث باستخدام جانغو Django وفيو Vue - الجزء الأول</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AD%D8%AF%D9%8A%D8%AB-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-django-%D9%88-vue-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A7%D9%84%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-r2421/" rel="">إنشاء تطبيق حديث باستخدام جانغو Django وفيو Vue - الجزء الثاني</a>
	</li>
	<li>
		<strong>إنشاء تطبيق حديث باستخدام جانغو Django وفيو Vue - الجزء الثالث</strong>
	</li>
</ul>

<h2 id="comments">
	إنشاء نظام التعليقات Comments
</h2>

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

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

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

<p>
	ما الذي يظهر للمستخدم في حال عدم تسجيل الدخول:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="160454" href="https://academy.hsoub.com/uploads/monthly_2024_10/01----.jpg.b3ed432d5a51c73e11c76389117ed05b.jpg" rel=""><img alt="01 حالة عدم تسجيل الدخول" class="ipsImage ipsImage_thumbnailed" data-fileid="160454" data-unique="tzhpk88li" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_10/01----.jpg.b3ed432d5a51c73e11c76389117ed05b.jpg"> </a>
</p>

<p>
	والآن بعد تسجيل الدخول:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="160452" href="https://academy.hsoub.com/uploads/monthly_2024_10/02---.jpg.4d3e627e913b6cdd4ee79a0e59e01699.jpg" rel=""><img alt="02 بعد تسجيل الدخول" class="ipsImage ipsImage_thumbnailed" data-fileid="160452" data-unique="96pjewq9j" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_10/02---.thumb.jpg.2c901d757c718313821cf9f316e73e3a.jpg"> </a>
</p>

<h3 id="">
	إعداد الواجهة الخلفية
</h3>

<p>
	مع أخذ الملاحظات السابقة في الحسبان، لنبدأ بإنشاء نموذج للتعليقات. يُفترض أن تكون الشيفرات البرمجية التالية واضحةً بالنسبة لك إذا كان لديك معرفة جيدة بكيفية <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="">العمل مع إطار العمل جانغو</a>.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_536_6" style=""><span class="com"># Comment model</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">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
    content </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">TextField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">1000</span><span class="pun">)</span><span class="pln">
    created_at </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">DateField</span><span class="pun">(</span><span class="pln">auto_now_add</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
    is_approved </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">BooleanField</span><span class="pun">(</span><span class="pln">default</span><span class="pun">=</span><span class="kwd">False</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># Each comment belongs to one user and one post</span><span class="pln">
    user </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">ForeignKey</span><span class="pun">(</span><span class="typ">User</span><span class="pun">,</span><span class="pln"> on_delete</span><span class="pun">=</span><span class="pln">models</span><span class="pun">.</span><span class="pln">SET_NULL</span><span class="pun">,</span><span class="pln"> null</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
    post </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">ForeignKey</span><span class="pun">(</span><span class="typ">Post</span><span class="pun">,</span><span class="pln"> on_delete</span><span class="pun">=</span><span class="pln">models</span><span class="pun">.</span><span class="pln">SET_NULL</span><span class="pun">,</span><span class="pln"> null</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span></pre>

<p>
	والآن تابع وطبِّق التغييرات التي أجريتها على النماذج. انتقل إلى الطرفية terminal وفعِّل الأوامر التالية:
</p>

<pre class="ipsCode">python manage.py makemigrations
</pre>

<pre class="ipsCode">python manage.py migrate
</pre>

<p>
	ستحتاج أيضًا إلى إعداد <a href="https://academy.hsoub.com/programming/general/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-graphql-r2208/" rel="">GraphQL</a> في الواجهة الخلفية. يمكنك إضافة نمط بيانات جديد يسمى CommentType لتمثيل التعليقات والتعامل معها ضمن واجهة GraphQL على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_536_9" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">CommentType</span><span class="pun">(</span><span class="typ">DjangoObjectType</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Meta</span><span class="pun">:</span><span class="pln">
        model </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">Comment</span></pre>

<p>
	الآن بالنسبة للطفرة <a href="https://academy.hsoub.com/programming/javascript/%D9%81%D9%87%D9%85-%D9%86%D8%B8%D8%A7%D9%85-%D8%A7%D9%84%D9%86%D9%88%D8%B9-%D9%81%D9%8A-graphql-r2195/" rel="">mutation</a>، لاحظ أن هناك ثلاثة أشياء يحتاج جانغو إلى معرفتها لإضافة تعليق، وهي محتوى التعليق، والمستخدم الذي يريد إضافة هذا التعليق، والمقال الذي يريد ترك التعليق عليه. سنكتب الكود التالي لإضافة تعليق جديد باستخدام GraphQL:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_536_13" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">CreateComment</span><span class="pun">(</span><span class="pln">graphene</span><span class="pun">.</span><span class="typ">Mutation</span><span class="pun">):</span><span class="pln">
    comment </span><span class="pun">=</span><span class="pln"> graphene</span><span class="pun">.</span><span class="typ">Field</span><span class="pun">(</span><span class="pln">types</span><span class="pun">.</span><span class="typ">CommentType</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Arguments</span><span class="pun">:</span><span class="pln">
        content </span><span class="pun">=</span><span class="pln"> graphene</span><span class="pun">.</span><span class="typ">String</span><span class="pun">(</span><span class="pln">required</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
        user_id </span><span class="pun">=</span><span class="pln"> graphene</span><span class="pun">.</span><span class="pln">ID</span><span class="pun">(</span><span class="pln">required</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
        post_id </span><span class="pun">=</span><span class="pln"> graphene</span><span class="pun">.</span><span class="pln">ID</span><span class="pun">(</span><span class="pln">required</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> mutate</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> info</span><span class="pun">,</span><span class="pln"> content</span><span class="pun">,</span><span class="pln"> user_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"> models</span><span class="pun">.</span><span class="typ">Comment</span><span class="pun">(</span><span class="pln">
            content</span><span class="pun">=</span><span class="pln">content</span><span class="pun">,</span><span class="pln">
            user_id</span><span class="pun">=</span><span class="pln">user_id</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><span class="pln">
        </span><span class="pun">)</span><span class="pln">
        comment</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">

        </span><span class="kwd">return</span><span class="pln"> </span><span class="typ">CreateComment</span><span class="pun">(</span><span class="pln">comment</span><span class="pun">=</span><span class="pln">comment</span><span class="pun">)</span></pre>

<pre class="ipsCode">class Mutation(graphene.ObjectType):
    . . .
    create_comment = CreateComment.Field()
</pre>

<p>
	تذكر إضافة صنف <code>CreateComment</code> داخل صنف <a href="https://academy.hsoub.com/programming/javascript/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-graphql-%D9%88%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84%D8%A7%D8%AA%D9%87%D8%A7-%D9%81%D9%8A-%D8%A8%D9%86%D8%A7%D8%A1-%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%A7%D9%84%D8%AD%D8%AF%D9%8A%D8%AB%D8%A9-r1208/" rel=""><code>Mutation</code></a>.
</p>

<h3 id="-1">
	إعداد الواجهة الأمامية
</h3>

<p>
	لإعداد الواجهة الأمامية، لننتقل إلى الملف <code>Post.vue</code> حيث المكان الذي تظهر فيه التعليقات. يُرجى ملاحظة أن هناك بعض التعليمات البرمجية محذوفة في الأمثلة التالية، بهدف ألا تكون الشيفرات طويلة جدًا، لكن إذا كنت تريد الحصول على التعليمات البرمجية الكاملة، فيمكنك تنزيل التعليمات المصدرية <a href="https://github.com/ericsdevblog/django-vue-starter-blog" rel="external nofollow">من هنا</a>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_536_15" style=""><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> POST_BY_SLUG </span><span class="pun">}</span><span class="pln"> from </span><span class="str">"@/queries"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="typ">CommentSectionComponent</span><span class="pln"> from </span><span class="str">"@/components/CommentSection.vue"</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">export</span><span class="pln"> </span><span class="kwd">default</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"PostView"</span><span class="pun">,</span><span class="pln">

  components</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="typ">CommentSectionComponent</span><span class="pln"> </span><span class="pun">},</span><span class="pln">

  data</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      postBySlug</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">,</span><span class="pln">
      comments</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">,</span><span class="pln">
      userID</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">};</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">

  computed</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// Filters out the unapproved comments</span><span class="pln">
    approvedComments</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">comments</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">((</span><span class="pln">comment</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> comment</span><span class="pun">.</span><span class="pln">isApproved</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">async</span><span class="pln"> created</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// Get the post before the instance is mounted</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> post </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">$apollo</span><span class="pun">.</span><span class="pln">query</span><span class="pun">({</span><span class="pln">
      query</span><span class="pun">:</span><span class="pln"> POST_BY_SLUG</span><span class="pun">,</span><span class="pln">
      variables</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        slug</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">$route</span><span class="pun">.</span><span class="pln">params</span><span class="pun">.</span><span class="pln">slug</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">this</span><span class="pun">.</span><span class="pln">postBySlug </span><span class="pun">=</span><span class="pln"> post</span><span class="pun">.</span><span class="pln">data</span><span class="pun">.</span><span class="pln">postBySlug</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">comments </span><span class="pun">=</span><span class="pln"> post</span><span class="pun">.</span><span class="pln">data</span><span class="pun">.</span><span class="pln">postBySlug</span><span class="pun">.</span><span class="pln">commentSet</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">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	الملف <code>query.js:</code>
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_536_17" style=""><span class="kwd">export</span><span class="pln"> </span><span class="kwd">const</span><span class="pln"> POST_BY_SLUG </span><span class="pun">=</span><span class="pln"> gql</span><span class="pun">`</span><span class="pln">
  query </span><span class="pun">(</span><span class="pln">$slug</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">!)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    postBySlug</span><span class="pun">(</span><span class="pln">slug</span><span class="pun">:</span><span class="pln"> $slug</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"> </span><span class="pun">.</span><span class="pln">
      commentSet </span><span class="pun">{</span><span class="pln">
        id
        content
        createdAt
        isApproved
        user </span><span class="pun">{</span><span class="pln">
          username
          avatar
        </span><span class="pun">}</span><span class="pln">
        numberOfLikes
        likes </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">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">`;</span></pre>

<p>
	بدايةً، يمكنك في الخطاف <code>create()‎</code> استرجاع المقال المطلوب، إضافةً إلى التعليقات باستخدام الاستعلام <code>POST_BY_SLUG</code> الذي يظهر في الأعلى.
</p>

<p>
	ستحتاج بعد ذلك في خاصية <code>computed</code> إلى تصفية التعليقات التي لم يوافق عليها المشرف. وأخيرًا، ستعمل على تمرير التعليق ومعرف المنشور post ID ومعرف المستخدم user ID إلى <code>CommentSectionComponent</code>.
</p>

<p>
	الملف <code>CommentSectionComponent.vue:</code>
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_536_19" style=""><span class="pun">&lt;</span><span class="pln">template</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">"home"</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><span class="pln">
    </span><span class="pun">&lt;!--</span><span class="pln"> </span><span class="typ">Comment</span><span class="pln"> </span><span class="typ">Section</span><span class="pln"> </span><span class="pun">--&gt;</span><span class="pln">
    </span><span class="pun">&lt;!--</span><span class="pln"> </span><span class="typ">Pass</span><span class="pln"> the approved comments</span><span class="pun">,</span><span class="pln"> the user id and the post id to the comment section component </span><span class="pun">--&gt;</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">comment</span><span class="pun">-</span><span class="pln">section</span><span class="pun">-</span><span class="pln">component
      v</span><span class="pun">-</span><span class="kwd">if</span><span class="pun">=</span><span class="str">"this.approvedComments"</span><span class="pln">
      </span><span class="pun">:</span><span class="pln">comments</span><span class="pun">=</span><span class="str">"this.approvedComments"</span><span class="pln">
      </span><span class="pun">:</span><span class="pln">postID</span><span class="pun">=</span><span class="str">"this.postBySlug.id"</span><span class="pln">
      </span><span class="pun">:</span><span class="pln">userID</span><span class="pun">=</span><span class="str">"this.userID"</span><span class="pln">
    </span><span class="pun">&gt;&lt;/</span><span class="pln">comment</span><span class="pun">-</span><span class="pln">section</span><span class="pun">-</span><span class="pln">component</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">template</span><span class="pun">&gt;</span></pre>

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

<p>
	الملف <code>CommentSection.vue</code>
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_536_21" style=""><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> SUBMIT_COMMENT </span><span class="pun">}</span><span class="pln"> from </span><span class="str">"@/mutations"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="typ">CommentSingle</span><span class="pln"> from </span><span class="str">"@/components/CommentSingle.vue"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> useUserStore </span><span class="pun">}</span><span class="pln"> from </span><span class="str">"@/stores/user"</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">export</span><span class="pln"> </span><span class="kwd">default</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  components</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="typ">CommentSingle</span><span class="pln"> </span><span class="pun">},</span><span class="pln">
  name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"CommentSectionComponent"</span><span class="pun">,</span><span class="pln">

  setup</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> userStore </span><span class="pun">=</span><span class="pln"> useUserStore</span><span class="pun">();</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> userStore </span><span class="pun">};</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">

  data</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      commentContent</span><span class="pun">:</span><span class="pln"> </span><span class="str">""</span><span class="pun">,</span><span class="pln">
      commentSubmitSuccess</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">,</span><span class="pln">
      user</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        isAuthenticated</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">,</span><span class="pln">
        token</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">userStore</span><span class="pun">.</span><span class="pln">getToken </span><span class="pun">||</span><span class="pln"> </span><span class="str">""</span><span class="pun">,</span><span class="pln">
        info</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">userStore</span><span class="pun">.</span><span class="pln">getUser </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">
  </span><span class="pun">},</span><span class="pln">
  props</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    comments</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      type</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Array</span><span class="pun">,</span><span class="pln">
      required</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">},</span><span class="pln">
    postID</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      type</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">,</span><span class="pln">
      required</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">},</span><span class="pln">
    userID</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      type</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">,</span><span class="pln">
      required</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</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">async</span><span class="pln"> created</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="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">user</span><span class="pun">.</span><span class="pln">token</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">user</span><span class="pun">.</span><span class="pln">isAuthenticated </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  methods</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    submitComment</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="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">commentContent </span><span class="pun">!==</span><span class="pln"> </span><span class="str">""</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">$apollo
          </span><span class="pun">.</span><span class="pln">mutate</span><span class="pun">({</span><span class="pln">
            mutation</span><span class="pun">:</span><span class="pln"> SUBMIT_COMMENT</span><span class="pun">,</span><span class="pln">
            variables</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">this</span><span class="pun">.</span><span class="pln">commentContent</span><span class="pun">,</span><span class="pln">
              userID</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">userID</span><span class="pun">,</span><span class="pln">
              postID</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">postID</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">then</span><span class="pun">(()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">commentSubmitSuccess </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">true</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">
</span><span class="pun">};</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	من المُفترض أنك على معرفة جيدة بكيفية استخدام الوحدة Pinia التي ثبتناها في <a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AD%D8%AF%D9%8A%D8%AB-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-django-%D9%88-vue-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%B1%D8%A8%D8%B7-%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-%D9%88%D8%A7%D9%84%D8%A3%D9%85%D8%A7%D9%85%D9%8A%D8%A9-r2430/" rel="">المقال السابق</a> للتحقق مما إذا كان المستخدم قد سجل دخوله، وكيفية استخدام <code>props</code> لتمرير المعلومات بين المكونات المختلفة، لذلك سنتخطى هذه الأمور وسنركز على تابع <code>SubmitComment()‎</code>.
</p>

<p>
	عند استدعاء هذا التابع، فإنها ستختبر ما إذا كان التعليق فارغًا، وإذا لم يكن كذلك، فسوف تستخدم طفرة <code>SUBMIT_COMMENT</code> لإنشاء تعليق جديد. تُعرّف طفرة <code>SUBMIT_COMMENT</code> كالتالي:
</p>

<p>
	الملف <code>mutations.js:</code>
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_536_23" style=""><span class="kwd">export</span><span class="pln"> </span><span class="kwd">const</span><span class="pln"> SUBMIT_COMMENT </span><span class="pun">=</span><span class="pln"> gql</span><span class="pun">`</span><span class="pln">
  mutation </span><span class="pun">(</span><span class="pln">$content</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">!,</span><span class="pln"> $userID</span><span class="pun">:</span><span class="pln"> ID</span><span class="pun">!,</span><span class="pln"> $postID</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">
    createComment</span><span class="pun">(</span><span class="pln">content</span><span class="pun">:</span><span class="pln"> $content</span><span class="pun">,</span><span class="pln"> userId</span><span class="pun">:</span><span class="pln"> $userID</span><span class="pun">,</span><span class="pln"> postId</span><span class="pun">:</span><span class="pln"> $postID</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">
        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="pun">`;</span></pre>

<p>
	الشيفرة التالية هي كود HTML ضمن ملف <code>CommentSection.vue</code> وهي المسؤولة عن عرض قسم التعليقات في واجهة المستخدم. لاحظ أننا استخدمنا مكونًا آخر في نهاية الشيفرة وهو <code>CommentSingle.vue</code> وذلك لعرض تعليق واحد.
</p>

<p>
	قسم HTML من ملف <code>CommentSection.vue</code>
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_536_25" style=""><span class="tag">&lt;template&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">". . ."</span><span class="tag">&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">"font-bold text-2xl"</span><span class="tag">&gt;</span><span class="pln">Comments:</span><span class="tag">&lt;/p&gt;</span><span class="pln">

    </span><span class="com">&lt;!-- If the user is not authenticated --&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">v-if</span><span class="pun">=</span><span class="atv">"!this.user.isAuthenticated"</span><span class="tag">&gt;</span><span class="pln">
      You need to
      </span><span class="tag">&lt;router-link</span><span class="pln"> </span><span class="atn">to</span><span class="pun">=</span><span class="atv">"/account"</span><span class="tag">&gt;</span><span class="pln">sign in</span><span class="tag">&lt;/router-link&gt;</span><span class="pln">
      before you can leave your comment.
    </span><span class="tag">&lt;/div&gt;</span><span class="pln">

    </span><span class="com">&lt;!-- If the user is authenticated --&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">v-else</span><span class="tag">&gt;</span><span class="pln">
      </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">v-if</span><span class="pun">=</span><span class="atv">"this.commentSubmitSuccess"</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">""</span><span class="tag">&gt;</span><span class="pln">
        Your comment will show up here after is has been approved.
      </span><span class="tag">&lt;/div&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">"POST"</span><span class="pln"> @</span><span class="atn">submit</span><span class="pln">.</span><span class="atn">prevent</span><span class="pun">=</span><span class="atv">"submitComment"</span><span class="tag">&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">class</span><span class="pun">=</span><span class="atv">". . ."</span><span class="pln"> </span><span class="atn">rows</span><span class="pun">=</span><span class="atv">"5"</span><span class="pln"> </span><span class="atn">v-model</span><span class="pun">=</span><span class="atv">"commentContent"</span><span class="pln"> </span><span class="tag">/&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">". . ."</span><span class="tag">&gt;</span><span class="pln">Submit Comment</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="com">&lt;!-- List all comments --&gt;</span><span class="pln">
    </span><span class="tag">&lt;comment-single</span><span class="pln">
      </span><span class="atn">v-for</span><span class="pun">=</span><span class="atv">"comment in comments"</span><span class="pln">
      :</span><span class="atn">key</span><span class="pun">=</span><span class="atv">"comment.id"</span><span class="pln">
      :</span><span class="atn">comment</span><span class="pun">=</span><span class="atv">"comment"</span><span class="pln">
      :</span><span class="atn">userID</span><span class="pun">=</span><span class="atv">"this.userID"</span><span class="pln">
    </span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;/comment-single&gt;</span><span class="pln">
  </span><span class="tag">&lt;/div&gt;</span><span class="pln">
</span><span class="tag">&lt;/template&gt;</span></pre>

<p>
	والآن دعونا نلقي نظرة عميقة على الملف <code>CommentSingle.vue</code>
</p>

<p>
	قسم HTML من ملف <code>CommentSingle.vue</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_536_27" style=""><span class="pun">&lt;</span><span class="pln">template</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">"border-2 p-4"</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">"flex flex-row justify-start content-center items-center space-x-2 mb-2"</span><span class="pln">
    </span><span class="pun">&gt;</span><span class="pln">
      </span><span class="pun">&lt;</span><span class="pln">img
        </span><span class="pun">:</span><span class="pln">src</span><span class="pun">=</span><span class="str">"`http://127.0.0.1:8000/media/${this.comment.user.avatar}`"</span><span class="pln">
        alt</span><span class="pun">=</span><span class="str">""</span><span class="pln">
        </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"w-10"</span><span class="pln">
      </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">"text-lg font-sans font-bold"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">{{</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">comment</span><span class="pun">.</span><span class="pln">user</span><span class="pun">.</span><span class="pln">username </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">p</span><span class="pun">&gt;</span><span class="pln">
      </span><span class="pun">{{</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">comment</span><span class="pun">.</span><span class="pln">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">&lt;/</span><span class="pln">template</span><span class="pun">&gt;</span></pre>

<p>
	قسم جافا سكريبت من ملف <code>CommentSingle.vue</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_536_29" style=""><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
</span><span class="kwd">export</span><span class="pln"> </span><span class="kwd">default</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"CommentSingleComponent"</span><span class="pun">,</span><span class="pln">
  data</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</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"> </span><span class="pun">.</span><span class="pln">
    </span><span class="pun">};</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  props</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="pun">{</span><span class="pln">
      type</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Object</span><span class="pun">,</span><span class="pln">
      required</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">},</span><span class="pln">
    userID</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      type</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">,</span><span class="pln">
      required</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</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">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<h2 id="like">
	إنشاء نظام التفاعل بالإعجاب Like
</h2>

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

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

<h3 id="-2">
	إعداد الواجهة الخلفية
</h3>

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

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_536_31" style=""><span class="pun">#</span><span class="pln"> </span><span class="typ">Post</span><span class="pln"> model

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">(</span><span class="pln">models</span><span class="pun">.</span><span class="typ">Model</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">

    </span><span class="pun">#</span><span class="pln"> </span><span class="typ">Each</span><span class="pln"> post can receive likes from multiple users</span><span class="pun">,</span><span class="pln"> and each user can like multiple posts
    likes </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">ManyToManyField</span><span class="pun">(</span><span class="typ">User</span><span class="pun">,</span><span class="pln"> related_name</span><span class="pun">=</span><span class="str">'post_like'</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">

    def get_number_of_likes</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"> self</span><span class="pun">.</span><span class="pln">likes</span><span class="pun">.</span><span class="pln">count</span><span class="pun">()</span></pre>

<p>
	ومن ثم نضيف الأنواع types والطفرات mutations.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_536_33" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">PostType</span><span class="pun">(</span><span class="typ">DjangoObjectType</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Meta</span><span class="pun">:</span><span class="pln">
        model </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">Post</span><span class="pln">

    number_of_likes </span><span class="pun">=</span><span class="pln"> graphene</span><span class="pun">.</span><span class="typ">String</span><span class="pun">()</span><span class="pln">

    def resolve_number_of_likes</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> info</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">get_number_of_likes</span><span class="pun">()</span></pre>

<p>
	لاحظ التعليمة <code>self.get_number_of_likes()‎</code> في السطر 8 التي تستدعي الدالة <code>get_number_of_likes()‎</code> التي عرّفتها في النموذج. ولإضافة إعجاب إلى منشور، يجب عليك معرفة كل من معرّف <code>id</code> المقال، ومعرّف <code>id</code> المستخدم الذي يريد التفاعل بإعجاب مع هذا المقال.
</p>

<p>
	لاحظ الكود المكتوب من السطر 11 إلى السطر 14، إذا كان المنشور يحتوي على إعجاب من المستخدم الحالي، فسيؤدي الضغط على زر الإعجاب إلى إزالة الإعجاب السابق، وإذا لم يكن كذلك، سيُضاف إعجاب إلى المقال.
</p>

<h3 id="-3">
	إعداد الواجهة الأمامية
</h3>

<p>
	سنحتاج الآن إلى إضافة زر الإعجاب إلى صفحة النشر، لذا ارجع إلى <code>Post.vue</code>.
</p>

<p>
	قسم HTML من <code>Post.vue</code>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_536_35" style=""><span class="pun">&lt;</span><span class="pln">template</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">"home"</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><span class="pln">

    </span><span class="pun">&lt;!--</span><span class="pln"> </span><span class="typ">Like</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Comment</span><span class="pln"> and </span><span class="typ">Share</span><span class="pln"> </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">". . ."</span><span class="pun">&gt;</span><span class="pln">
      </span><span class="pun">&lt;</span><span class="pln">div v</span><span class="pun">-</span><span class="kwd">if</span><span class="pun">=</span><span class="str">"this.liked === true"</span><span class="pln"> </span><span class="lit">@click</span><span class="pun">=</span><span class="str">"this.updateLike()"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">i </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"fa-solid fa-thumbs-up"</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">"font-sans font-semibold ml-1"</span><span class="pun">&gt;{{</span><span class="pln">
            </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">numberOfLikes
          </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">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">&lt;</span><span class="pln">div v</span><span class="pun">-</span><span class="kwd">else</span><span class="pln"> </span><span class="lit">@click</span><span class="pun">=</span><span class="str">"this.updateLike()"</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="pln">i </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"fa-regular fa-thumbs-up"</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">"font-sans font-semibold ml-1"</span><span class="pun">&gt;{{</span><span class="pln">
            </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">numberOfLikes
          </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">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"> </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="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><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">template</span><span class="pun">&gt;</span></pre>

<p>
	قسم جافا سكريبت من <code>Post.vue</code>
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_536_37" style=""><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> POST_BY_SLUG </span><span class="pun">}</span><span class="pln"> from </span><span class="str">"@/queries"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> UPDATE_POST_LIKE </span><span class="pun">}</span><span class="pln"> from </span><span class="str">"@/mutations"</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">

</span><span class="kwd">export</span><span class="pln"> </span><span class="kwd">default</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"> </span><span class="pun">.</span><span class="pln">
  </span><span class="kwd">async</span><span class="pln"> created</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"> </span><span class="pun">.</span><span class="pln">
    </span><span class="com">// Find if the current user has liked the post</span><span class="pln">
    </span><span class="kwd">let</span><span class="pln"> likedUsers </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">postBySlug</span><span class="pun">.</span><span class="pln">likes</span><span class="pun">;</span><span class="pln">

    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">let</span><span class="pln"> likedUser in likedUsers</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="pun">(</span><span class="pln">likedUsers</span><span class="pun">[</span><span class="pln">likedUser</span><span class="pun">].</span><span class="pln">id </span><span class="pun">===</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">userID</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">liked </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">true</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="com">// Get the number of likes</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">numberOfLikes </span><span class="pun">=</span><span class="pln"> parseInt</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">postBySlug</span><span class="pun">.</span><span class="pln">numberOfLikes</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">

  methods</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    updateLike</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="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">liked </span><span class="pun">===</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">numberOfLikes </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">numberOfLikes </span><span class="pun">-</span><span class="pln"> </span><span class="lit">1</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="kwd">this</span><span class="pun">.</span><span class="pln">numberOfLikes </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">numberOfLikes </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
      </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">liked </span><span class="pun">=</span><span class="pln"> </span><span class="pun">!</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">liked</span><span class="pun">;</span><span class="pln">

      </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">$apollo</span><span class="pun">.</span><span class="pln">mutate</span><span class="pun">({</span><span class="pln">
        mutation</span><span class="pun">:</span><span class="pln"> UPDATE_POST_LIKE</span><span class="pun">,</span><span class="pln">
        variables</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
          postID</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">postBySlug</span><span class="pun">.</span><span class="pln">id</span><span class="pun">,</span><span class="pln">
          userID</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">userID</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">
  </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">script</span><span class="pun">&gt;</span></pre>

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

<p>
	أولاً، فيما يتعلق بالاستعلام <code>POST_BY_SLUG</code> الذي تستخدمه لاسترجاع المقال، يجب التأكد من أنه يجلب عدد الإعجابات والمستخدمين الذين تفاعلوا مع المقال.
</p>

<p>
	الملف <code>query.js:</code>
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_536_39" style=""><span class="kwd">export</span><span class="pln"> </span><span class="kwd">const</span><span class="pln"> POST_BY_SLUG </span><span class="pun">=</span><span class="pln"> gql</span><span class="pun">`</span><span class="pln">
  query </span><span class="pun">(</span><span class="pln">$slug</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">!)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    postBySlug</span><span class="pun">(</span><span class="pln">slug</span><span class="pun">:</span><span class="pln"> $slug</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"> </span><span class="pun">.</span><span class="pln">
      numberOfLikes
      likes </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"> </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">
</span><span class="pun">`;</span></pre>

<p>
	الخطوة التالية، في الخطاف <code>created()‎</code>، وبعد أن تسترجع المنشور، يجب عليك تحديد ما إذا كان المستخدم الحالي موجودًا في قائمة المستخدمين الذين تفاعلوا مع المنشور.
</p>

<p>
	فيما بعد، عند استدعاء الدالة <code>updateLike()</code>‎، ستعمل هذه الدالة على تغيير عدد الإعجابات وذلك في حال تفاعل المستخدم مع بالمنشور أو لم يتفاعل. وأخيرًا، ستعمل الدالة <code>updateLike()</code>‎،على تحديث عدد إعجابات المنشور في الواجهة الخلفية باستخدام طفرة <code>UPDATE_POST_LIKE</code>.
</p>

<p>
	الملف <code>mutations.js:</code>
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_536_41" style=""><span class="kwd">export</span><span class="pln"> </span><span class="kwd">const</span><span class="pln"> UPDATE_POST_LIKE </span><span class="pun">=</span><span class="pln"> gql</span><span class="pun">`</span><span class="pln">
  mutation </span><span class="pun">(</span><span class="pln">$postID</span><span class="pun">:</span><span class="pln"> ID</span><span class="pun">!,</span><span class="pln"> $userID</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">
    updatePostLike</span><span class="pun">(</span><span class="pln">postId</span><span class="pun">:</span><span class="pln"> $postID</span><span class="pun">,</span><span class="pln"> userId</span><span class="pun">:</span><span class="pln"> $userID</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">
        id
        title
        likes </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">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">`;</span></pre>

<h2 id="-4">
	تحدي برمجي
</h2>

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

<p>
	يمكنك الاطلاع على كامل <a href="https://github.com/ericsdevblog/django-vue-starter-blog" rel="external nofollow">الكود المصدري</a> الذي يحتوي على التطبيق العملي للوظائف التي مرت معنا في سلسلة المقالات هذه.
</p>

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

<p>
	بهذا نكون قد تعرفنا على طريقة إضافة التعليقات والإعجابات على المقالات ووصلنا لنهاية هذه السلسلة من المقالات التي شرحنا من خلالها كيفية إنشاء تطبيق ويب حديث من صفحة واحدة باستخدام إطارالعمل جانغو Django كواجهة خلفية، وإطار العمل فيو Vue كواجهة أمامية، مع استخدام GraphQL كلغة للتفاعل مع واجهة برمجة التطبيقات <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> التي تربط بينهما.
</p>

<p>
	ترجمة -وبتصرّف- للمقال <a href="https://www.ericsdevblog.com/posts/create-a-modern-application-with-django-and-vue-3/" rel="external nofollow">Create a Modern Application with Django and Vue #3</a>.
</p>

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

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AD%D8%AF%D9%8A%D8%AB-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-django-%D9%88-vue-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%B1%D8%A8%D8%B7-%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-%D9%88%D8%A7%D9%84%D8%A3%D9%85%D8%A7%D9%85%D9%8A%D8%A9-r2430/" rel="">إنشاء تطبيق حديث باستخدام Django و Vue | الجزء الثاني: ربط الواجهة الخلفية والأمامية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-graphql-%D9%88%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84%D8%A7%D8%AA%D9%87%D8%A7-%D9%81%D9%8A-%D8%A8%D9%86%D8%A7%D8%A1-%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%A7%D9%84%D8%AD%D8%AF%D9%8A%D8%AB%D8%A9-r1208/" rel="">مدخل إلى المكتبة GraphQL واستعمالاتها في بناء تطبيقات الويب الحديثة</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%88%D9%8A%D8%A8-%D9%84%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D9%85%D8%B9%D9%84%D9%88%D9%85%D8%A7%D8%AA-%D8%A7%D9%84%D8%B9%D9%85%D9%84%D8%A7%D8%A1-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-%D9%88%D8%B1%D9%8A%D8%A2%D9%83%D8%AA-react-r1776/" rel="">بناء تطبيق ويب لإدارة معلومات العملاء باستخدام جانغو Django وريآكت React</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%86%D9%85%D8%A7%D8%B0%D8%AC-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-models-%D9%88%D8%B1%D8%A8%D8%B7%D9%87%D8%A7-%D8%A8%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-r1627/" rel="">إنشاء نماذج جانغو Django Models وربطها بقاعدة البيانات</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A3%D9%85%D8%A7%D9%86-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2123/" rel="">تعرف على أمان تطبيقات جانغو</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2436</guid><pubDate>Sat, 26 Oct 2024 15:07:00 +0000</pubDate></item><item><title>&#x625;&#x646;&#x634;&#x627;&#x621; &#x62A;&#x637;&#x628;&#x64A;&#x642; &#x62D;&#x62F;&#x64A;&#x62B; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; Django &#x648; Vue | &#x627;&#x644;&#x62C;&#x632;&#x621; &#x627;&#x644;&#x62B;&#x627;&#x646;&#x64A;: &#x631;&#x628;&#x637; &#x627;&#x644;&#x648;&#x627;&#x62C;&#x647;&#x629; &#x627;&#x644;&#x62E;&#x644;&#x641;&#x64A;&#x629; &#x648;&#x627;&#x644;&#x623;&#x645;&#x627;&#x645;&#x64A;&#x629;</title><link>https://academy.hsoub.com/programming/python/django/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AD%D8%AF%D9%8A%D8%AB-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-django-%D9%88-vue-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%B1%D8%A8%D8%B7-%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-%D9%88%D8%A7%D9%84%D8%A3%D9%85%D8%A7%D9%85%D9%8A%D8%A9-r2430/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_10/-----Django--Vue----.png.77ed2f117c7082e8a40f3059966df476.png" /></p>
<p>
	سنوضح في مقال اليوم الخطوات اللازمة لربط الواجهة الخلفية backend مع الواجهة الأمامية frontend من سلسلة مقالات تطوير تطبيق مدونة حديث باستخدام جانغو وفيو.
</p>

<p>
	هذا المقال هو جزء من سلسلة مقالات تتحدث حول كيفية إنشاء تطبيق حديث باستخدام جانغو <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/javascript/vuejs/" rel="">Vue</a>:
</p>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AD%D8%AF%D9%8A%D8%AB-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-django-%D9%88-vue-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A7%D9%84%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-r2421/" rel="">إنشاء تطبيق حديث باستخدام جانغو Django وفيو Vue - الجزء الأول.</a>
	</li>
	<li>
		<strong>إنشاء تطبيق حديث باستخدام جانغو Django وفيو Vue - الجزء الثاني.</strong>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AD%D8%AF%D9%8A%D8%AB-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-django-%D9%88-vue-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-%D8%A7%D9%84%D8%AA%D8%B9%D9%84%D9%8A%D9%82%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D8%A5%D8%B9%D8%AC%D8%A7%D8%A8%D8%A7%D8%AA-r2436/" rel="">إنشاء تطبيق حديث باستخدام جانغو Django وفيو Vue - الجزء الثالث.</a>
	</li>
</ul>

<p>
	إن الشائع حاليًا في ربط الواجهتين الأمامية والخلفية لتطبيق ما هو استخدام تقنية تسمى <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="">REST <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr></a>. ومصطلح <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-api-r1314/" rel=""><abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr></a> هو اختصار للواجهة البرمجية للتطبيقات Application Programming Interface، وهي تشير إلى الاتصال بين تطبيقين برمجيين، أما REST فهو اختصار لنقل الحالة التمثيلية Representational State Transfer، وهو يشير إلى بنية محددة يتبعها هذا النوع من الاتصال.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="159913" href="https://academy.hsoub.com/uploads/monthly_2024_10/01---.jpg.786547d88ad253a29e3f7af88cd59e2d.jpg" rel=""><img alt="01 الواجهة البرمجية للتطبيقات" class="ipsImage ipsImage_thumbnailed" data-fileid="159913" data-unique="otlahufzj" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_10/01---.thumb.jpg.19872e1fef8cd5bb0da5aeb1fff970c0.jpg"> </a>
</p>

<p>
	يتكون طلب REST <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> عادةً من نقطة وصول endpoint تتصل مع الخادم، إضافةً إلى طريقة HTTP وترويسة Head وجسم Body. تحتوي الترويسة على عناصر التعريف meta، مثل التخبئة caching واستيثاق المستخدم user authentication واختبار AB، بينما يحتوي الجسم على البيانات التي يريد العميل إرسالها إلى الخادم.
</p>

<p>
	ومع ذلك، فإنه ثمة خلل بسيط في REST <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>، وهو أنه من المستحيل تصميم واجهات برمجية تجلب البيانات التي يحتاجها العميل بالضبط، إذ من الشائع أن تجلب REST <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> بيانات زائدة أو ناقصة. ولذلك، طُوّرت لغة الاستعلام غراف كيو إل <a href="https://academy.hsoub.com/programming/javascript/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-graphql-%D9%88%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84%D8%A7%D8%AA%D9%87%D8%A7-%D9%81%D9%8A-%D8%A8%D9%86%D8%A7%D8%A1-%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%A7%D9%84%D8%AD%D8%AF%D9%8A%D8%AB%D8%A9-r1208/" rel="">GraphQL</a> بهدف حل هذه المشكلة، إذ تستخدم المخططات schemas للتأكد من جلب البيانات المحددة فقط لكل طلب، وسنرى كيف يحدث ذلك لاحقًا.
</p>

<h2 id="graphqldjango">
	إعداد غراف كيو إل GraphQL باستخدام جانغو Django
</h2>

<p>
	لنبدأ بإعداد غراف كيو إل <a href="https://academy.hsoub.com/programming/general/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-graphql-r2208/" rel="">GraphQL</a> في الواجهة الخلفية، ستحتاج إلى تثبيت حزمة جديدة تسمى <code>graphene-django</code> وهي حزمة توفر مجموعة من الأدوات والميزات التي تسهل عملية تطوير واجهات GraphQL داخل تطبيقات Django عبر تفعيل الأمر التالي:
</p>

<pre class="ipsCode">pip install graphene-django
</pre>

<p>
	انتقل بعد ذلك إلى مجلد <code>settings.py</code> وابحث عن المتغير <code>INSTALLED_APPS</code>. يجب عليك إضافة <code>graphene-django</code> داخل المجلد لكي يتمكن جانغو من العثور على هذه الحزمة.
</p>

<pre class="ipsCode">INSTALLED_APPS = [
  . . .
  "blog",
  "graphene_django",
]
</pre>

<h3 id="graphenedjango">
	إعداد graphene-django
</h3>

<p>
	ما زال هناك بعض المهام التي يجب عليك إنجازها قبل أن تتمكن من استخدام GraphQL. أولاً، ستحتاج إلى إعداد نمط الرابط URL pattern لخدمة الواجهات البرمجية للتطبيقات APIs في GraphQL، والذي يحدد كيفية تطابق عناوين URL مع عرض معين في تطبيق Django ويستخدم لتحويل طلبات المستخدم إلى العرض المناسب لمعالجتها. للقيام بذلك انتقل إلى الملف urls.py وأضف الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_61_8" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">views</span><span class="pun">.</span><span class="pln">decorators</span><span class="pun">.</span><span class="pln">csrf </span><span class="kwd">import</span><span class="pln"> csrf_exempt
</span><span class="kwd">from</span><span class="pln"> graphene_django</span><span class="pun">.</span><span class="pln">views </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">GraphQLView</span><span class="pln">

urlpatterns </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"> </span><span class="pun">.</span><span class="pln">
    path</span><span class="pun">(</span><span class="str">"graphql"</span><span class="pun">,</span><span class="pln"> csrf_exempt</span><span class="pun">(</span><span class="typ">GraphQLView</span><span class="pun">.</span><span class="pln">as_view</span><span class="pun">(</span><span class="pln">graphiql</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">))),</span><span class="pln">
</span><span class="pun">]</span></pre>

<p>
	وبعد ذلك، أنشئ المخططات schemas وحدد لجانغو مكان العثور عليها في مجلد <code>settings.py</code>. تتبع مخططات GraphQL نمطًا يسمح لجانغو بترجمة نماذج قاعدة البيانات database إلى GraphQL وبالعكس، ولنأخذ فيما يلي نموذج الموقع Site model كمثال:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_61_10" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Site</span><span class="pun">(</span><span class="pln">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
    name </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">200</span><span class="pun">)</span><span class="pln">
    description </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">TextField</span><span class="pun">()</span><span class="pln">
    logo </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">ImageField</span><span class="pun">(</span><span class="pln">upload_to</span><span class="pun">=</span><span class="str">'site/logo/'</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Meta</span><span class="pun">:</span><span class="pln">
        verbose_name </span><span class="pun">=</span><span class="pln"> </span><span class="str">'site'</span><span class="pln">
        verbose_name_plural </span><span class="pun">=</span><span class="pln"> </span><span class="str">'1. Site'</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> __str__</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"> self</span><span class="pun">.</span><span class="pln">name</span></pre>

<p>
	أنشئ ملف <code>schema.py</code> داخل مجلد المدونة blog.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_61_12" style=""><span class="kwd">import</span><span class="pln"> graphene
</span><span class="kwd">from</span><span class="pln"> graphene_django </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">DjangoObjectType</span><span class="pln">
</span><span class="kwd">from</span><span class="pln"> blog </span><span class="kwd">import</span><span class="pln"> models

</span><span class="com"># Define type</span><span class="pln">
</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">SiteType</span><span class="pun">(</span><span class="typ">DjangoObjectType</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Meta</span><span class="pun">:</span><span class="pln">
        model </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">Site</span><span class="pln">

</span><span class="com"># The Query class</span><span class="pln">
</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Query</span><span class="pun">(</span><span class="pln">graphene</span><span class="pun">.</span><span class="typ">ObjectType</span><span class="pun">):</span><span class="pln">
    site </span><span class="pun">=</span><span class="pln"> graphene</span><span class="pun">.</span><span class="typ">Field</span><span class="pun">(</span><span class="pln">types</span><span class="pun">.</span><span class="typ">SiteType</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> resolve_site</span><span class="pun">(</span><span class="pln">root</span><span class="pun">,</span><span class="pln"> info</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
            models</span><span class="pun">.</span><span class="typ">Site</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">first</span><span class="pun">()</span><span class="pln">
        </span><span class="pun">)</span></pre>

<p>
	كما هو واضح، يُقسم هذا الملف إلى ثلاثة أجزاء. أولًا، يجب عليك استيراد الحزم والنماذج اللازمة. ثم يُصرّح عن صنف <code>SiteType</code> ويُربط مع نموذج الموقع. وأخيرًا، يستخدم الصنف <code>Query</code> الذي يسمح لك باسترداد المعلومات عبر GraphQL <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>. أما لإنشاء معلومات أو تحديثها، فستحتاج إلى استخدام صنف آخر يُدعى <code>Mutation</code> والذي سنتحدث عنه بالتفصيل في المقال الثالث من السلسلة.
</p>

<p>
	توجد الدالة <code>Resolve_site</code> داخل الصنف <code>Query</code> وهي تعمل على استرجاع السجل الأول من النموذج Site model (أي تعيد الكائن الأول الموجود في قاعدة البيانات المرتبطة بهذا النموذج). ترتبط هذه الدالة تلقائيًا مع متغير يحمل نفس الاسم <code>site</code>، هذا يعني أنه عند استدعاء الدالة، ستكون النتيجة متاحة مباشرةً من خلال المتغير site دون الحاجة إلى تحديد اسم جديد له، إذ يعمل هذا الجزء تمامًا مثل Django QuerySet لاسترجاع البيانات من قاعدة البيانات. إذَا عندما تستدعي الدالة <code>Resolve_site</code> فإنها سترجع لك السجل الأول من نموذج Site ووتخزنها في المتغير site تلقائيًا، مما يسهل الوصول إلى هذه البيانات لاحقًا.
</p>

<h3 id="schemas">
	إنشاء المخططات Schemas
</h3>

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

<ol>
	<li>
		<code>schema.py</code>
	</li>
	<li>
		<code>types.py</code>
	</li>
	<li>
		<code>queries.py</code>
	</li>
</ol>

<p>
	ملف <code>schema.py</code>
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_61_14" style=""><span class="kwd">import</span><span class="pln"> graphene
</span><span class="kwd">from</span><span class="pln"> blog </span><span class="kwd">import</span><span class="pln"> queries


schema </span><span class="pun">=</span><span class="pln"> graphene</span><span class="pun">.</span><span class="typ">Schema</span><span class="pun">(</span><span class="pln">query</span><span class="pun">=</span><span class="pln">queries</span><span class="pun">.</span><span class="typ">Query</span><span class="pun">)</span></pre>

<p>
	ملف <code>types.py</code>
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_61_16" style=""><span class="kwd">import</span><span class="pln"> graphene
</span><span class="kwd">from</span><span class="pln"> graphene_django </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">DjangoObjectType</span><span class="pln">
</span><span class="kwd">from</span><span class="pln"> blog </span><span class="kwd">import</span><span class="pln"> models


</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">SiteType</span><span class="pun">(</span><span class="typ">DjangoObjectType</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Meta</span><span class="pun">:</span><span class="pln">
        model </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">Site</span><span class="pln">


</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">UserType</span><span class="pun">(</span><span class="typ">DjangoObjectType</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Meta</span><span class="pun">:</span><span class="pln">
        model </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">User</span><span class="pln">


</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">CategoryType</span><span class="pun">(</span><span class="typ">DjangoObjectType</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Meta</span><span class="pun">:</span><span class="pln">
        model </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">Category</span><span class="pln">


</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">TagType</span><span class="pun">(</span><span class="typ">DjangoObjectType</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Meta</span><span class="pun">:</span><span class="pln">
        model </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">Tag</span><span class="pln">


</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">PostType</span><span class="pun">(</span><span class="typ">DjangoObjectType</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Meta</span><span class="pun">:</span><span class="pln">
        model </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">Post</span></pre>

<p>
	<code>queries.py</code>
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_61_18" style=""><span class="kwd">import</span><span class="pln"> graphene
</span><span class="kwd">from</span><span class="pln"> blog </span><span class="kwd">import</span><span class="pln"> models
</span><span class="kwd">from</span><span class="pln"> blog </span><span class="kwd">import</span><span class="pln"> types


</span><span class="com"># The Query class</span><span class="pln">
</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Query</span><span class="pun">(</span><span class="pln">graphene</span><span class="pun">.</span><span class="typ">ObjectType</span><span class="pun">):</span><span class="pln">
    site </span><span class="pun">=</span><span class="pln"> graphene</span><span class="pun">.</span><span class="typ">Field</span><span class="pun">(</span><span class="pln">types</span><span class="pun">.</span><span class="typ">SiteType</span><span class="pun">)</span><span class="pln">
    all_posts </span><span class="pun">=</span><span class="pln"> graphene</span><span class="pun">.</span><span class="typ">List</span><span class="pun">(</span><span class="pln">types</span><span class="pun">.</span><span class="typ">PostType</span><span class="pun">)</span><span class="pln">
    all_categories </span><span class="pun">=</span><span class="pln"> graphene</span><span class="pun">.</span><span class="typ">List</span><span class="pun">(</span><span class="pln">types</span><span class="pun">.</span><span class="typ">CategoryType</span><span class="pun">)</span><span class="pln">
    all_tags </span><span class="pun">=</span><span class="pln"> graphene</span><span class="pun">.</span><span class="typ">List</span><span class="pun">(</span><span class="pln">types</span><span class="pun">.</span><span class="typ">TagType</span><span class="pun">)</span><span class="pln">
    posts_by_category </span><span class="pun">=</span><span class="pln"> graphene</span><span class="pun">.</span><span class="typ">List</span><span class="pun">(</span><span class="pln">types</span><span class="pun">.</span><span class="typ">PostType</span><span class="pun">,</span><span class="pln"> category</span><span class="pun">=</span><span class="pln">graphene</span><span class="pun">.</span><span class="typ">String</span><span class="pun">())</span><span class="pln">
    posts_by_tag </span><span class="pun">=</span><span class="pln"> graphene</span><span class="pun">.</span><span class="typ">List</span><span class="pun">(</span><span class="pln">types</span><span class="pun">.</span><span class="typ">PostType</span><span class="pun">,</span><span class="pln"> tag</span><span class="pun">=</span><span class="pln">graphene</span><span class="pun">.</span><span class="typ">String</span><span class="pun">())</span><span class="pln">
    post_by_slug </span><span class="pun">=</span><span class="pln"> graphene</span><span class="pun">.</span><span class="typ">Field</span><span class="pun">(</span><span class="pln">types</span><span class="pun">.</span><span class="typ">PostType</span><span class="pun">,</span><span class="pln"> slug</span><span class="pun">=</span><span class="pln">graphene</span><span class="pun">.</span><span class="typ">String</span><span class="pun">())</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> resolve_site</span><span class="pun">(</span><span class="pln">root</span><span class="pun">,</span><span class="pln"> info</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
            models</span><span class="pun">.</span><span class="typ">Site</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">first</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"> resolve_all_posts</span><span class="pun">(</span><span class="pln">root</span><span class="pun">,</span><span class="pln"> info</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
            models</span><span class="pun">.</span><span class="typ">Post</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">all</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"> resolve_all_categories</span><span class="pun">(</span><span class="pln">root</span><span class="pun">,</span><span class="pln"> info</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
            models</span><span class="pun">.</span><span class="typ">Category</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">all</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"> resolve_all_tags</span><span class="pun">(</span><span class="pln">root</span><span class="pun">,</span><span class="pln"> info</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
            models</span><span class="pun">.</span><span class="typ">Tag</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">all</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"> resolve_posts_by_category</span><span class="pun">(</span><span class="pln">root</span><span class="pun">,</span><span class="pln"> info</span><span class="pun">,</span><span class="pln"> category</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
            models</span><span class="pun">.</span><span class="typ">Post</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">category__slug__iexact</span><span class="pun">=</span><span class="pln">category</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"> resolve_posts_by_tag</span><span class="pun">(</span><span class="pln">root</span><span class="pun">,</span><span class="pln"> info</span><span class="pun">,</span><span class="pln"> tag</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
            models</span><span class="pun">.</span><span class="typ">Post</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">tag__slug__iexact</span><span class="pun">=</span><span class="pln">tag</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"> resolve_post_by_slug</span><span class="pun">(</span><span class="pln">root</span><span class="pun">,</span><span class="pln"> info</span><span class="pun">,</span><span class="pln"> slug</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
            models</span><span class="pun">.</span><span class="typ">Post</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">slug__iexact</span><span class="pun">=</span><span class="pln">slug</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">)</span></pre>

<p>
	والآن، يجب عليك إخبار جانغو بمكان العثور على ملف المخطط، لذا انتقل إلى <code>settings.py</code> وأضف الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_61_20" style=""><span class="com"># Configure GraphQL</span><span class="pln">
GRAPHENE </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">"SCHEMA"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"blog.schema.schema"</span><span class="pun">,</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	ولكي تتأكد من عمل المخططات على النحو الصحيح، افتح المتصفح وانتقل إلى <a href="http://127.0.0.1:8000/graphql" rel="external nofollow">http://127.0.0.1:8000/graphql</a>، يجب أن تشاهد واجهة GraphiQL على النحو التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="159907" href="https://academy.hsoub.com/uploads/monthly_2024_10/02--GraphiQL.jpg.6a6a90a76a3b68b9f326781c20e3cc3b.jpg" rel=""><img alt="02 واجهة graphiql" class="ipsImage ipsImage_thumbnailed" data-fileid="159907" data-unique="txadccutp" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_10/02--GraphiQL.thumb.jpg.bdfd28005cf03aa1cc7b43c2a0ead84f.jpg"> </a>
</p>

<p>
	لاحظ كيف نسترجع المعلومات في هذا المثال، هذه لغة GraphQL، وهذه هي الطريقة التي سنسترجع بها البيانات في الواجهة الأمامية، والتي سنشرحها لاحقًا.
</p>

<h2 id="cors">
	إعداد CORS
</h2>

<p>
	قبل أن ننتقل إلى الواجهة الأمامية، ثمة شيء يجب الاهتمام به. افتراضيًا، لا يمكن نقل البيانات إلا داخل التطبيق نفسه لأسباب الحماية، ولكننا نحتاج إلى تدفق البيانات بين تطبيقين مختلفين، ولمعالجة هذه المشكلة، يجب تمكين وظيفة CORS، وهي اختصار لـ Cross Origin Resource Sharing والتي تعني مشاركة الموارد ذات الأصول المختلفة.
</p>

<p>
	أولاً، ثبِّت حزمة <code>Django-cors-headers</code>. ومن داخل تطبيق الواجهة الخلفية، فعِّل الأمر التالي:
</p>

<pre class="ipsCode">pip install django-cors-headers
</pre>

<p>
	أضف <code>"corsheaders"</code> إلى المتغير <code>INSTALLED_APPS</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_61_23" style=""><span class="pln">INSTALLED_APPS </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"> </span><span class="pun">.</span><span class="pln">
  </span><span class="str">"corsheaders"</span><span class="pun">,</span><span class="pln">
</span><span class="pun">]</span></pre>

<p>
	ثم أضف <code>"corsheaders.middleware.CorsMiddleware"</code> إلى المتغير <code>MIDDLEWARE</code> لتمكين دعم CORS في تطبيق جانغو:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_61_25" style=""><span class="pln">MIDDLEWARE </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
  </span><span class="str">"corsheaders.middleware.CorsMiddleware"</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">
</span><span class="pun">]</span></pre>

<p>
	وأخيرًا، أضف الشيفرة التالية إلى <code>settings.py</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_61_27" style=""><span class="pln">CORS_ORIGIN_ALLOW_ALL </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">False</span><span class="pln">
CORS_ORIGIN_WHITELIST </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="str">"http://localhost:8080"</span><span class="pun">,)</span><span class="pln"> </span><span class="com"># Matches the port that Vue.js is using</span></pre>

<h2 id="apollovuejs">
	إعداد Apollo باستخدام Vue.js
</h2>

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

<pre class="ipsCode">npm install --save graphql graphql-tag @apollo/client
</pre>

<p>
	ضمن مجلد src، أنشئ ملف جديد باسم <code>apollo-config.js</code> وأضف له الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_61_29" style=""><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="typ">ApolloClient</span><span class="pun">,</span><span class="pln">
  createHttpLink</span><span class="pun">,</span><span class="pln">
  </span><span class="typ">InMemoryCache</span><span class="pun">,</span><span class="pln">
</span><span class="pun">}</span><span class="pln"> from </span><span class="str">"@apollo/client/core"</span><span class="pun">;</span><span class="pln">

</span><span class="com">// HTTP connection to the API</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> httpLink </span><span class="pun">=</span><span class="pln"> createHttpLink</span><span class="pun">({</span><span class="pln">
  uri</span><span class="pun">:</span><span class="pln"> </span><span class="str">"http://127.0.0.1:8000/graphql"</span><span class="pun">,</span><span class="pln"> </span><span class="com">// Matches the url and port that Django is using</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="com">// Cache implementation</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> cache </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">InMemoryCache</span><span class="pun">();</span><span class="pln">

</span><span class="com">// Create the apollo client</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> apolloClient </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">ApolloClient</span><span class="pun">({</span><span class="pln">
  link</span><span class="pun">:</span><span class="pln"> httpLink</span><span class="pun">,</span><span class="pln">
  cache</span><span class="pun">,</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	ثم انتقل إلى <code>main.js</code> واستورد <code>apolloClient</code> كي تتيح لتطبيقك القدرة على التفاعل مع واجهات GraphQL واسترجاع البيانات بشكل ديناميكي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_61_31" style=""><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> apolloClient </span><span class="pun">}</span><span class="pln"> from </span><span class="str">"@/apollo-config"</span><span class="pun">;</span><span class="pln">
createApp</span><span class="pun">(</span><span class="typ">App</span><span class="pun">).</span><span class="pln">use</span><span class="pun">(</span><span class="pln">router</span><span class="pun">).</span><span class="pln">use</span><span class="pun">(</span><span class="pln">apolloClient</span><span class="pun">).</span><span class="pln">mount</span><span class="pun">(</span><span class="str">"#app"</span><span class="pun">);</span></pre>

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

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_61_33" style=""><span class="pun">&lt;</span><span class="pln">template</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 mx-auto max-w-3xl px-4 sm:px-6 xl:max-w-5xl xl:px-0"</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">"flex flex-col justify-between h-screen"</span><span class="pun">&gt;</span><span class="pln">
      </span><span class="pun">&lt;</span><span class="pln">header </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"flex flex-row items-center justify-between py-10"</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">"nav-logo text-2xl font-bold"</span><span class="pun">&gt;</span><span class="pln">
          </span><span class="pun">&lt;</span><span class="pln">router</span><span class="pun">-</span><span class="pln">link to</span><span class="pun">=</span><span class="str">"/"</span><span class="pln"> v</span><span class="pun">-</span><span class="kwd">if</span><span class="pun">=</span><span class="str">"mySite"</span><span class="pun">&gt;{{</span><span class="pln"> mySite</span><span class="pun">.</span><span class="pln">name </span><span class="pun">}}&lt;/</span><span class="pln">router</span><span class="pun">-</span><span class="pln">link</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="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
      </span><span class="pun">&lt;/</span><span class="pln">header</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><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">template</span><span class="pun">&gt;</span><span class="pln">

</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> gql from </span><span class="str">"graphql-tag"</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">export</span><span class="pln"> </span><span class="kwd">default</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  data</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      mySite</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">null</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">async</span><span class="pln"> created</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> siteInfo </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">$apollo</span><span class="pun">.</span><span class="pln">query</span><span class="pun">({</span><span class="pln">
      query</span><span class="pun">:</span><span class="pln"> gql</span><span class="pun">`</span><span class="pln">
        query </span><span class="pun">{</span><span class="pln">
          site </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="pun">});</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">mySite </span><span class="pun">=</span><span class="pln"> siteInfo</span><span class="pun">.</span><span class="pln">data</span><span class="pun">.</span><span class="pln">site</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">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	يُفضِّل البعض إنشاء ملف منفصل لجميع الاستعلامات queries، ومن ثم استيراده إلى ملف فيو كما في الكود التالي:
</p>

<p>
	ملف <code>src/queries.js</code>
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_61_37" style=""><span class="kwd">import</span><span class="pln"> gql from </span><span class="str">"graphql-tag"</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">export</span><span class="pln"> </span><span class="kwd">const</span><span class="pln"> SITE_INFO </span><span class="pun">=</span><span class="pln"> gql</span><span class="pun">`</span><span class="pln">
  query </span><span class="pun">{</span><span class="pln">
    site </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></pre>

<p>
	ملف <code>App.vue</code>
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_61_39" 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">script</span><span class="pun">&gt;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> SITE_INFO </span><span class="pun">}</span><span class="pln"> from </span><span class="str">"@/queries"</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">export</span><span class="pln"> </span><span class="kwd">default</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  data</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      mySite</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">null</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">async</span><span class="pln"> created</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> siteInfo </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">$apollo</span><span class="pun">.</span><span class="pln">query</span><span class="pun">({</span><span class="pln">
      query</span><span class="pun">:</span><span class="pln"> SITE_INFO</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">mySite </span><span class="pun">=</span><span class="pln"> siteInfo</span><span class="pun">.</span><span class="pln">data</span><span class="pun">.</span><span class="pln">site</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">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<h2 id="category">
	صفحة الصنف Category
</h2>

<p>
	تبقى لدينا الآن مشكلة من <a href="https://academy.hsoub.com/programming/general/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-graphql-r2208/" rel="">المقال السابق</a>، عندما نستدعي موجه router، فكيف لهذا الموجه أن يعرف الصفحة التي يجب إعادتها؟ فعلى سبيل المثال، عندما نضغط على رابط <code>Category One</code> يجب إرجاع قائمة المنشورات التي تنتمي إلى الصنف الأول Category One، ولكن كيف يمكن للموجه القيام بذلك؟ دعونا نرى مثالًا.
</p>

<p>
	أولًا، في ملف <code>router/index.js</code> الذي عرفنا فيه جميع الوجهات لدينا، يجب علينا تعيين جزء من نمط الرابط URL pattern كمتغير. ففي المثال التالي، سيتم تعيين الكلمة بعد <code>/category/</code> كمتغير <code>Category</code>. وسيكون بالإمكان الوصول إلى هذا المتغير في مكون <code>CategoryView</code>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_61_41" style=""><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> createRouter</span><span class="pun">,</span><span class="pln"> createWebHistory </span><span class="pun">}</span><span class="pln"> from </span><span class="str">"vue-router"</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">

</span><span class="kwd">const</span><span class="pln"> routes </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
  </span><span class="pun">{</span><span class="pln">
    path</span><span class="pun">:</span><span class="pln"> </span><span class="str">"/"</span><span class="pun">,</span><span class="pln">
    name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Home"</span><span class="pun">,</span><span class="pln">
    component</span><span class="pun">:</span><span class="pln"> </span><span class="typ">HomeView</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="pun">{</span><span class="pln">
    path</span><span class="pun">:</span><span class="pln"> </span><span class="str">"/category/:category"</span><span class="pun">,</span><span class="pln">
    name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Category"</span><span class="pun">,</span><span class="pln">
    component</span><span class="pun">:</span><span class="pln"> </span><span class="typ">CategoryView</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"> </span><span class="pun">.</span><span class="pln">
</span><span class="pun">];</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> router </span><span class="pun">=</span><span class="pln"> createRouter</span><span class="pun">({</span><span class="pln">
  history</span><span class="pun">:</span><span class="pln"> createWebHistory</span><span class="pun">(</span><span class="pln">process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">BASE_URL</span><span class="pun">),</span><span class="pln">
  routes</span><span class="pun">,</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="kwd">export</span><span class="pln"> </span><span class="kwd">default</span><span class="pln"> router</span><span class="pun">;</span></pre>

<p>
	وبعد ذلك، في عرض AllCategories (الذي سيُظهر قائمة بجميع التصنيفات)، سنعمل على تمرير بعض المعلومات إلى المتغير <code>Category</code>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_61_43" style=""><span class="pun">&lt;</span><span class="pln">template</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">"flex flex-col place-content-center place-items-center"</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">"py-8 border-b-2"</span><span class="pun">&gt;</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">"text-5xl font-extrabold"</span><span class="pun">&gt;</span><span class="typ">All</span><span class="pln"> </span><span class="typ">Categories</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="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">"flex flex-wrap py-8"</span><span class="pun">&gt;</span><span class="pln">
      </span><span class="pun">&lt;</span><span class="pln">router</span><span class="pun">-</span><span class="pln">link
        v</span><span class="pun">-</span><span class="kwd">for</span><span class="pun">=</span><span class="str">"category in this.allCategories"</span><span class="pln">
        </span><span class="pun">:</span><span class="pln">key</span><span class="pun">=</span><span class="str">"category.name"</span><span class="pln">
        </span><span class="kwd">class</span><span class="pun">=</span><span class="str">". . ."</span><span class="pln">
        </span><span class="pun">:</span><span class="pln">to</span><span class="pun">=</span><span class="str">"`/category/${category.slug}`"</span><span class="pln">
        </span><span class="pun">&gt;{{</span><span class="pln"> category</span><span class="pun">.</span><span class="pln">name </span><span class="pun">}}&lt;/</span><span class="pln">router</span><span class="pun">-</span><span class="pln">link
      </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">template</span><span class="pun">&gt;</span></pre>

<p>
	وفي عرض <code>Category</code> يمكننا الوصول إلى المتغير <code>Category</code> باستخدام الخاصية <code>this.$route</code>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_61_45" style=""><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
</span><span class="com">// @ is an alias to /src</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="typ">PostList</span><span class="pln"> from </span><span class="str">"@/components/PostList.vue"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> POSTS_BY_CATEGORY </span><span class="pun">}</span><span class="pln"> from </span><span class="str">"@/queries"</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">export</span><span class="pln"> </span><span class="kwd">default</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  components</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="typ">PostList</span><span class="pln"> </span><span class="pun">},</span><span class="pln">
  name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"CategoryView"</span><span class="pun">,</span><span class="pln">

  data</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      postsByCategory</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">null</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">async</span><span class="pln"> created</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> posts </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">$apollo</span><span class="pun">.</span><span class="pln">query</span><span class="pun">({</span><span class="pln">
      query</span><span class="pun">:</span><span class="pln"> POSTS_BY_CATEGORY</span><span class="pun">,</span><span class="pln">
      variables</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        category</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">$route</span><span class="pun">.</span><span class="pln">params</span><span class="pun">.</span><span class="pln">category</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">this</span><span class="pun">.</span><span class="pln">postsByCategory </span><span class="pun">=</span><span class="pln"> posts</span><span class="pun">.</span><span class="pln">data</span><span class="pun">.</span><span class="pln">postsByCategory</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">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	وأخيرًا، يمكن استرجاع المنشورات باستخدام الاستعلام <code>POSTS_BY_CATEGORY</code>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_61_47" style=""><span class="kwd">export</span><span class="pln"> </span><span class="kwd">const</span><span class="pln"> POSTS_BY_CATEGORY </span><span class="pun">=</span><span class="pln"> gql</span><span class="pun">`</span><span class="pln">
  query </span><span class="pun">(</span><span class="pln">$category</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">!)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    postsByCategory</span><span class="pun">(</span><span class="pln">category</span><span class="pun">:</span><span class="pln"> $category</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      title
      slug
      content
      isPublished
      isFeatured
      createdAt
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">`;</span></pre>

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

<h2 id="mutations">
	إنشاء وتحديث المعلومات باستخدام الطفرات Mutations
</h2>

<p>
	ذكرنا في <a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AD%D8%AF%D9%8A%D8%AB-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-django-%D9%88-vue-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A7%D9%84%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-r2421/" rel="">المقال السابق</a> من هذه السلسلة أنه يمكننا استخدام الاستعلامات queries لاسترجاع المعلومات من الواجهة الخلفية وإرسالها إلى الواجهة الأمامية. ومع ذلك، من الشائع في تطبيقات الويب الحديثة أن ترسل المعلومات من الواجهة الأمامية إلى الواجهة الخلفية. وللقيام بذلك، لا بُدّ من الحديث عن مفهوم جديد في <a href="https://academy.hsoub.com/programming/javascript/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-graphql-%D9%88%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84%D8%A7%D8%AA%D9%87%D8%A7-%D9%81%D9%8A-%D8%A8%D9%86%D8%A7%D8%A1-%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%A7%D9%84%D8%AD%D8%AF%D9%8A%D8%AB%D8%A9-r1208/" rel="">GraphQL</a> يُسمى الطفرة mutation.
</p>

<p>
	<strong>ملاحظة</strong>: تُستخدم الطفرات mutations لتغيير البيانات في الخادم. فإذا أراد المستخدم مثلًا إضافة أو تحديث أو حذف بيانات عليه أن يستخدم mutation لإرسال طلب بالتعديل المطلوب والخادم يقوم بتحديث البيانات وإرجاع النتيجة.
</p>

<p>
	نعود إلى الواجهة الخلفية وننتقل إلى ملف <code>cd</code> في مجلد <code>blog</code> وننشئ ملف باسم <code>Mutations.py</code>. سنكتشف في المثال التالي كيفية نقل البيانات إلى الواجهة الخلفية لإنشاء مستخدم جديد.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_61_49" style=""><span class="kwd">import</span><span class="pln"> graphene
</span><span class="kwd">from</span><span class="pln"> blog </span><span class="kwd">import</span><span class="pln"> models</span><span class="pun">,</span><span class="pln"> types


</span><span class="com"># Mutation sends data to the database</span><span class="pln">
</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">CreateUser</span><span class="pun">(</span><span class="pln">graphene</span><span class="pun">.</span><span class="typ">Mutation</span><span class="pun">):</span><span class="pln">
    user </span><span class="pun">=</span><span class="pln"> graphene</span><span class="pun">.</span><span class="typ">Field</span><span class="pun">(</span><span class="pln">types</span><span class="pun">.</span><span class="typ">UserType</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Arguments</span><span class="pun">:</span><span class="pln">
        username </span><span class="pun">=</span><span class="pln"> graphene</span><span class="pun">.</span><span class="typ">String</span><span class="pun">(</span><span class="pln">required</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
        password </span><span class="pun">=</span><span class="pln"> graphene</span><span class="pun">.</span><span class="typ">String</span><span class="pun">(</span><span class="pln">required</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
        email </span><span class="pun">=</span><span class="pln"> graphene</span><span class="pun">.</span><span class="typ">String</span><span class="pun">(</span><span class="pln">required</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> mutate</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> info</span><span class="pun">,</span><span class="pln"> username</span><span class="pun">,</span><span class="pln"> password</span><span class="pun">,</span><span class="pln"> email</span><span class="pun">):</span><span class="pln">
        user </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">User</span><span class="pun">(</span><span class="pln">
            username</span><span class="pun">=</span><span class="pln">username</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">
        </span><span class="pun">)</span><span class="pln">
        user</span><span class="pun">.</span><span class="pln">set_password</span><span class="pun">(</span><span class="pln">password</span><span class="pun">)</span><span class="pln">
        user</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">

        </span><span class="kwd">return</span><span class="pln"> </span><span class="typ">CreateUser</span><span class="pun">(</span><span class="pln">user</span><span class="pun">=</span><span class="pln">user</span><span class="pun">)</span></pre>

<p>
	لاحظ في السطر 7 من الكود أعلاه أن <code>UserType</code> مرتبط بنموذج المستخدم User model.
</p>

<p>
	أما في الأسطر من 9 إلى 12، فلاحظ أنه لإنشاء مستخدم جديد، يجب إضافة ثلاث عناصر هي اسم المستخدم وكلمة المرور والبريد الإلكتروني.
</p>

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

<p>
	السطر 19 مسؤول عن تعيين كلمة المرور، ولأسباب تتعلق الحماية لا يمكنك حفظ كلمة المرور الأصلية للمستخدم في قاعدة البيانات لديك، ويمكن تشفيرها من خلال التابع <code>set_password()‎‎</code>.
</p>

<p>
	والآن، يجب عليك تضمين ملف <code>Mutation.py</code> في مخطط GraphQL، لذا انتقل إلى <code>schema.py</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_61_78" style=""><span class="kwd">import</span><span class="pln"> graphene
</span><span class="kwd">from</span><span class="pln"> blog </span><span class="kwd">import</span><span class="pln"> queries</span><span class="pun">,</span><span class="pln"> mutations


schema </span><span class="pun">=</span><span class="pln"> graphene</span><span class="pun">.</span><span class="typ">Schema</span><span class="pun">(</span><span class="pln">query</span><span class="pun">=</span><span class="pln">queries</span><span class="pun">.</span><span class="typ">Query</span><span class="pun">,</span><span class="pln"> mutation</span><span class="pun">=</span><span class="pln">mutations</span><span class="pun">.</span><span class="typ">Mutation</span><span class="pun">)</span></pre>

<p>
	ولتتأكد من أنه يعمل على النحو الصحيح، افتح المتصفح وانتقل إلى <a href="http://127.0.0.1:8000/graphql" rel="external nofollow">http://127.0.0.1:8000/graphql</a> للوصول إلى واجهة GraphiQL.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="159908" href="https://academy.hsoub.com/uploads/monthly_2024_10/03--GraphiQL.jpg.ba811bd7a93933a2c5b3cd4309493eaf.jpg" rel=""><img alt="03 واجهة graphiql" class="ipsImage ipsImage_thumbnailed" data-fileid="159908" data-unique="mmf87vima" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_10/03--GraphiQL.thumb.jpg.6dfbb6e98dfc0f95da797dcf78d5590c.jpg"> </a>
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_61_76" style=""><span class="pln">mutation </span><span class="pun">{</span><span class="pln">
  createUser</span><span class="pun">(</span><span class="pln">
    username</span><span class="pun">:</span><span class="pln"> </span><span class="str">"testuser2022"</span><span class="pln">
    email</span><span class="pun">:</span><span class="pln"> </span><span class="str">"testuser2022@test.com"</span><span class="pln">
    password</span><span class="pun">:</span><span class="pln"> </span><span class="str">"testuser2022"</span><span class="pln">
  </span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    user </span><span class="pun">{</span><span class="pln">
      id
      username
    </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-javascript prettyprinted" id="ips_uid_61_74" style=""><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> USER_SIGNUP </span><span class="pun">}</span><span class="pln"> from </span><span class="str">"@/mutations"</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">export</span><span class="pln"> </span><span class="kwd">default</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"SignUpView"</span><span class="pun">,</span><span class="pln">

  data</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">

  methods</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">async</span><span class="pln"> userSignUp</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="com">// Register user</span><span class="pln">
      </span><span class="kwd">const</span><span class="pln"> user </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">$apollo</span><span class="pun">.</span><span class="pln">mutate</span><span class="pun">({</span><span class="pln">
        mutation</span><span class="pun">:</span><span class="pln"> USER_SIGNUP</span><span class="pun">,</span><span class="pln">
        variables</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
          username</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">signUpDetails</span><span class="pun">.</span><span class="pln">username</span><span class="pun">,</span><span class="pln">
          email</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">signUpDetails</span><span class="pun">.</span><span class="pln">email</span><span class="pun">,</span><span class="pln">
          password</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">signUpDetails</span><span class="pun">.</span><span class="pln">password</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="com">// Do something with the variable user</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">
    </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">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	<code>src/mutations.js</code>
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_61_72" style=""><span class="kwd">import</span><span class="pln"> gql from </span><span class="str">"graphql-tag"</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">export</span><span class="pln"> </span><span class="kwd">const</span><span class="pln"> USER_SIGNUP </span><span class="pun">=</span><span class="pln"> gql</span><span class="pun">`</span><span class="pln">
  mutation </span><span class="pun">(</span><span class="pln">$username</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">!,</span><span class="pln"> $email</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">!,</span><span class="pln"> $password</span><span class="pun">:</span><span class="pln"> </span><span class="typ">String</span><span class="pun">!)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    createUser</span><span class="pun">(</span><span class="pln">username</span><span class="pun">:</span><span class="pln"> $username</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"> password</span><span class="pun">:</span><span class="pln"> $password</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      user </span><span class="pun">{</span><span class="pln">
        id
        username
      </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>

<h2 id="userauthentication">
	استيثاق المستخدم User authentication
</h2>

<p>
	والآن بعد أن تعرفت على كيفية إرسال البيانات إلى الواجهة الخلفية، لن تواجه صعوبةً كبيرةً في <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B3%D8%A7%D8%A8%D8%B9-%D8%A7%D8%B3%D8%AA%D9%8A%D8%AB%D8%A7%D9%82-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D9%8A%D9%86-%D9%88%D8%A3%D8%B0%D9%88%D9%86%D8%A7%D8%AA%D9%87%D9%85-r2119/" rel="">استيثاق المستخدم</a> User authentication. في البداية، ستطلب من المستخدم إدخال اسم المستخدم وكلمة المرور الخاصين به وإرسال تلك المعلومات إلى الواجهة الخلفية، ثم في الواجهة الخلفية، سيعثر جانغو على المستخدم بناءً على الاسم، وسيحاول مقارنة كلمة المرور المُدخلة مع كلمة المرور المخزنة في قاعدة البيانات، وإذا تطابقت الكلمتان، سيكون بإمكان المستخدم تسجيل الدخول.
</p>

<p>
	ومع ذلك، قد تواجه هذه العملية بعض العقبات في الممارسة العملية. فبدايةً، تُعد عملية إرسال كلمة مرور المستخدم ذهابًا وإيابًا عملية غير آمنة تمامًا. لذا ستحتاج إلى طريقة ما <a href="https://academy.hsoub.com/apps/general/%D8%A7%D9%84%D8%AA%D8%B4%D9%81%D9%8A%D8%B1-%D9%88%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84%D8%A7%D8%AA%D9%87-%D9%81%D9%8A-%D8%A7%D9%84%D8%B9%D8%A7%D9%84%D9%85-%D8%A7%D9%84%D8%B1%D9%82%D9%85%D9%8A-r380/" rel="">لتشفير البيانات</a>. الطريقة الأكثر استخدامًا هي تقنية JWT وهي اختصار لـ JSON Web Token. إذ تعمل هذه الطريقة على تشفير بيانات بصيغة JSON إلى ترميز بصيغة token. يمكنك رؤية مثال عليها من <a href="https://jwt.io/" rel="external nofollow">هنا</a>. سيُحتفظ بهذا الترميز ضمن <a href="https://academy.hsoub.com/programming/html/%D8%A7%D9%84%D8%AA%D8%AE%D8%B2%D9%8A%D9%86-%D8%A7%D9%84%D9%85%D8%AD%D9%84%D9%8A-local-storage-%D9%81%D9%8A-html5-r362/" rel="">التخزين المحلي للمتصفح</a>، وطالما بقي هذا الترميز ضمن ذاكرة المتصفح، سيبقى تسجيل الدخول قائمًا.
</p>

<p>
	المشكلة الثانية سببها نظام مكونات فيو Vue. فكما تعلم كل مكون مستقل بذاته فإذا تغير أحد المكونات، فإنه لا يؤثر على المكونات الأخرى. لكننا في هذه الحالة نريد أن تشترك جميع المكونات في نفس الوضعية، فإذا سجل المستخدم دخوله، نريد أن تتعرف جميع المكونات الأخرى على حالة المستخدم أثناء تسجيل الدخول. لذلك ستحتاج إلى مكان مركزي لتخزين هذه المعلومات حين يقوم المستخدم بتسجيل الدخول، ويجب أن تكون جميع المكونات قادرة على قراءة البيانات من ذلك المكان. ولتحقيق بذلك، ستحتاج إلى استخدام <a href="https://pinia.vuejs.org/introduction.html" rel="external nofollow">Pinia</a>، وهي مكتبة Vue جديدة أُنشئت من خلال Vuex.
</p>

<h3 id="jwt">
	دمج JWT في الواجهة الخلفية
</h3>

<p>
	في البداية، سندمج JWT مع الواجهة الخلفية لجانغو، لذلك ستحتاج إلى تثبيت حزمة أخرى باسم <code>django-graphql-jwt</code>:
</p>

<pre class="ipsCode">pip install django-graphql-jwt
</pre>

<p>
	انتقل الآن إلى ملف <code>settings.py</code> وأضف برنامجًا وسيطًا، وكذلك الاستيثاق في الواجهة الخلفية. ستحل هذه الإعدادات مكان الإعدادات الافتراضية لجانغو، وهو ما يتيح استخدام JWT.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_61_67" style=""><span class="pln">MIDDLEWARE </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    </span><span class="str">"django.contrib.auth.middleware.AuthenticationMiddleware"</span><span class="pun">,</span><span class="pln">
</span><span class="pun">]</span><span class="pln">

</span><span class="com"># Configure GraphQL</span><span class="pln">

GRAPHENE </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">"SCHEMA"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"blog.schema.schema"</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'MIDDLEWARE'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
        </span><span class="str">'graphql_jwt.middleware.JSONWebTokenMiddleware'</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="com"># Auth Backends</span><span class="pln">

AUTHENTICATION_BACKENDS </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    </span><span class="str">'graphql_jwt.backends.JSONWebTokenBackend'</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'django.contrib.auth.backends.ModelBackend'</span><span class="pun">,</span><span class="pln">
</span><span class="pun">]</span></pre>

<p>
	لاستخدام هذه الحزمة، انتقل إلى موقع <code>muts.py</code> وأضف الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_61_69" style=""><span class="kwd">import</span><span class="pln"> graphql_jwt


</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Mutation</span><span class="pun">(</span><span class="pln">graphene</span><span class="pun">.</span><span class="typ">ObjectType</span><span class="pun">):</span><span class="pln">
    token_auth </span><span class="pun">=</span><span class="pln"> graphql_jwt</span><span class="pun">.</span><span class="typ">ObtainJSONWebToken</span><span class="pun">.</span><span class="typ">Field</span><span class="pun">()</span><span class="pln">
    verify_token </span><span class="pun">=</span><span class="pln"> graphql_jwt</span><span class="pun">.</span><span class="typ">Verify</span><span class="pun">.</span><span class="typ">Field</span><span class="pun">()</span><span class="pln">
    refresh_token </span><span class="pun">=</span><span class="pln"> graphql_jwt</span><span class="pun">.</span><span class="typ">Refresh</span><span class="pun">.</span><span class="typ">Field</span><span class="pun">()</span></pre>

<p>
	يمكننا الآن اختباره في واجهة GraphiQL:
</p>

<p>
	في حال كلمة مرور خاطئة
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="159909" href="https://academy.hsoub.com/uploads/monthly_2024_10/04---.jpg.eb9feb884765b19117bad910b9dc42e7.jpg" rel=""><img alt="04 كلمة مرور خاطئة" class="ipsImage ipsImage_thumbnailed" data-fileid="159909" data-unique="ltot6zuvb" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_10/04---.thumb.jpg.5304ae23e1a04996266e34285dd5a29c.jpg"> </a>
</p>

<p>
	في حال مستخدم موثوق
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="159910" href="https://academy.hsoub.com/uploads/monthly_2024_10/05--.jpg.ee10fb69377503ede6f463d1a4a027bd.jpg" rel=""><img alt="05 استيثاق المستخدم" class="ipsImage ipsImage_thumbnailed" data-fileid="159910" data-unique="9vxizh67f" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_10/05--.thumb.jpg.abca3390e3d83b0fdfe076ef33f7e035.jpg"> </a>
</p>

<p>
	كما هو واضح، فإن متطلبات تسجيل الدخول هي اسم المستخدم وكلمة المرور، وإذا تم التحقق من وثوقية هذا المستخدم بنجاح (أي أدخل اسم مستخدم وكلمة مرور صحيحة) سيُرسل من الواجهة الخلفية ترميز مشفر token. ويمكن حفظ هذا الترميز في ذاكرة المتصفح.
</p>

<p>
	يمكنك أيضًا تخصيص سلوك <code>ObtainJSONWebToken</code> من خلال العودة إلى <code>Mutions.py</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_61_65" style=""><span class="com"># Customize the ObtainJSONWebToken behavior to include the user info</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">ObtainJSONWebToken</span><span class="pun">(</span><span class="pln">graphql_jwt</span><span class="pun">.</span><span class="typ">JSONWebTokenMutation</span><span class="pun">):</span><span class="pln">
    user </span><span class="pun">=</span><span class="pln"> graphene</span><span class="pun">.</span><span class="typ">Field</span><span class="pun">(</span><span class="pln">types</span><span class="pun">.</span><span class="typ">UserType</span><span class="pun">)</span><span class="pln">

    </span><span class="lit">@classmethod</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> resolve</span><span class="pun">(</span><span class="pln">cls</span><span class="pun">,</span><span class="pln"> root</span><span class="pun">,</span><span class="pln"> info</span><span class="pun">,</span><span class="pln"> </span><span class="pun">**</span><span class="pln">kwargs</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> cls</span><span class="pun">(</span><span class="pln">user</span><span class="pun">=</span><span class="pln">info</span><span class="pun">.</span><span class="pln">context</span><span class="pun">.</span><span class="pln">user</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Mutation</span><span class="pun">(</span><span class="pln">graphene</span><span class="pun">.</span><span class="typ">ObjectType</span><span class="pun">):</span><span class="pln">
    token_auth </span><span class="pun">=</span><span class="pln"> </span><span class="typ">ObtainJSONWebToken</span><span class="pun">.</span><span class="typ">Field</span><span class="pun">()</span></pre>

<p>
	لاحظ أن <code>ObtainJSONWebToken</code> يتوسع إلى <code>JSONWebTokenMutation</code> الافتراضي، ومن ثم في صنف <code>Mutation</code>، ويمكنك استخدام <code>ObtainJSONWebToken</code> عوضًا عن ذلك.
</p>

<p>
	يمكن الآن الحصول على مزيد من المعلومات حول المستخدم عبر GraphQL.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="159911" href="https://academy.hsoub.com/uploads/monthly_2024_10/06---.jpg.3462195321ce538d9e9b5e71c2a15807.jpg" rel=""><img alt="06 تخصيص استيثاق المستخدم" class="ipsImage ipsImage_thumbnailed" data-fileid="159911" data-unique="i4hacuxp0" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2024_10/06---.thumb.jpg.d25ddb14e092206e73890808545ada7c.jpg"> </a>
</p>

<h3 id="pinia">
	استخدام حزمة بينيا Pinia في الواجهة الأمامية
</h3>

<p>
	حان الوقت الآن لحل المشكلة الثانية في الواجهة الأمامية. والخطوة الأولى هي تثبيت بينيا Pinia.
</p>

<pre class="ipsCode">npm install pinia
</pre>

<p>
	اذهب الآن إلى <code>main.js</code> وتأكد من أن تطبيقك يستخدم بينيا Pinia.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_61_63" style=""><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> createPinia </span><span class="pun">}</span><span class="pln"> from </span><span class="str">"pinia"</span><span class="pun">;</span><span class="pln">

createApp</span><span class="pun">(</span><span class="typ">App</span><span class="pun">).</span><span class="pln">use</span><span class="pun">(</span><span class="pln">createPinia</span><span class="pun">()).</span><span class="pln">use</span><span class="pun">(</span><span class="pln">router</span><span class="pun">).</span><span class="pln">use</span><span class="pun">(</span><span class="pln">apolloProvider</span><span class="pun">).</span><span class="pln">mount</span><span class="pun">(</span><span class="str">"#app"</span><span class="pun">);</span></pre>

<p>
	ارجع الآن إلى مجلد <code>src</code> وأنشئ مجلد باسم <code>stores</code>، وهو المجلد الذي سنخزن فيه جميع بيانات التطبيق. في الوقت الحالي، كل ما تحتاج إليه هو تخزين القيم الخاصة بالمستخدم (بيانات تسجيل الدخول) وسنحافظ على المعلومات فيه حتى بعد تحديث الصفحة، لذا أنشئ ملف باسم user.js من أجل التعامل مع التخزين كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_61_61" style=""><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> defineStore </span><span class="pun">}</span><span class="pln"> from </span><span class="str">"pinia"</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">export</span><span class="pln"> </span><span class="kwd">const</span><span class="pln"> useUserStore </span><span class="pun">=</span><span class="pln"> defineStore</span><span class="pun">({</span><span class="pln">
  id</span><span class="pun">:</span><span class="pln"> </span><span class="str">"user"</span><span class="pun">,</span><span class="pln">
  state</span><span class="pun">:</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">({</span><span class="pln">
    token</span><span class="pun">:</span><span class="pln"> localStorage</span><span class="pun">.</span><span class="pln">getItem</span><span class="pun">(</span><span class="str">"token"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">||</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">,</span><span class="pln">
    user</span><span class="pun">:</span><span class="pln"> localStorage</span><span class="pun">.</span><span class="pln">getItem</span><span class="pun">(</span><span class="str">"user"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">||</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">}),</span><span class="pln">
  getters</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    getToken</span><span class="pun">:</span><span class="pln"> </span><span class="pun">(</span><span class="pln">state</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">token</span><span class="pun">,</span><span class="pln">
    getUser</span><span class="pun">:</span><span class="pln"> </span><span class="pun">(</span><span class="pln">state</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> JSON</span><span class="pun">.</span><span class="pln">parse</span><span class="pun">(</span><span class="pln">state</span><span class="pun">.</span><span class="pln">user</span><span class="pun">),</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  actions</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    setToken</span><span class="pun">(</span><span class="pln">token</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">this</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">// Save token to local storage</span><span class="pln">
      localStorage</span><span class="pun">.</span><span class="pln">setItem</span><span class="pun">(</span><span class="str">"token"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">token</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">},</span><span class="pln">
    removeToken</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">token </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">;</span><span class="pln">

      </span><span class="com">// Delete token from local storage</span><span class="pln">
      localStorage</span><span class="pun">.</span><span class="pln">removeItem</span><span class="pun">(</span><span class="str">"token"</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">},</span><span class="pln">
    setUser</span><span class="pun">(</span><span class="pln">user</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">user </span><span class="pun">=</span><span class="pln"> JSON</span><span class="pun">.</span><span class="pln">stringify</span><span class="pun">(</span><span class="pln">user</span><span class="pun">);</span><span class="pln">

      </span><span class="com">// Save user to local storage</span><span class="pln">
      localStorage</span><span class="pun">.</span><span class="pln">setItem</span><span class="pun">(</span><span class="str">"user"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">user</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">},</span><span class="pln">
    removeUser</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">user </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">;</span><span class="pln">

      </span><span class="com">// Delete user from local storage</span><span class="pln">
      localStorage</span><span class="pun">.</span><span class="pln">removeItem</span><span class="pun">(</span><span class="str">"user"</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>
	لاحظ أن هذا مخزن البيانات يتكون من ثلاثة أقسام، وهم <code>state</code> و <code>getters</code> و <code>actions</code>. تساعد هذه الأقسام الثلاثة في إدارة حالة التطبيق بطريقة منظمة وسهلة وإذا كنت على معرفة جيدة بكيفية <a href="https://academy.hsoub.com/programming/javascript/vuejs/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%A8%D8%B3%D9%8A%D8%B7-%D9%85%D9%86-%D8%AE%D9%84%D8%A7%D9%84-vuejs-r2064/" rel="">إنشاء تطبيق فيو Vue</a> فيُفترض أن يكون ذلك مفهومًا بالنسبة لك.
</p>

<p>
	تشبه <code>state</code> في Pinia الدالة <span ipsnoautolink="true"><a href="https://vuejs.org/api/options-state.html#data" rel="external nofollow">data()</a></span> ‎  في مكون فيو Vue، إذ تعمل على تحديد المتغيرات، باستثناء أن يمكن لجميع المكونات الوصول إلى هذه المتغيرات. في مثالنا، سيحاول فيو Vue الحصول على الترميز المشفر في ذاكرة المتصفح، فإذا لم يكن موجودًا، سيأخذ المتغير قيمة <code>null</code>.
</p>

<p>
	أما <code>getters</code> فهي توازي المتغيرات <code>computed</code>، إذ تنفذ إجراءات بسيطة، وعادةً ما يقتصر عملها على إرجاع قيمة <code>state</code> فحسب. وهنا أيضًا يمكن لجميع المكونات والصفحات الوصول إلى المتغيرات.
</p>

<p>
	وأخيرًا، يمكن تشبيه <code>actions</code> بـ <code>methods</code> الموجودة في مكون فيو Vue. كما أنها تؤدي بعض الإجراءات باستخدام الحالات states. وفي هذه الحالة، أنت تعمل على حفظ أو إزالة الترميز المشفر للمستخدم ومعلوماته.
</p>

<p>
	ثمة شيء آخر عليك ملاحظته، وهو أنه لا يمكنك حفظ الكائنات objects في الذاكرة المحلية، وإنما يمكنك حفظ السلاسل النصية strings فقط. لذا يجب عليك استخدام stringify()<code>‎</code> و <code>parse()‎</code> لتحويل البيانات إلى سلسلة، ومن ثم إعادتها إلى كائن.
</p>

<p>
	ستحتاج بعد ذلك إلى استخدام هذا المخزن عند تسجيل دخول المستخدم، وقد أنشأنا ملف SignIn.vue مثل هذا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_61_59" style=""><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> useUserStore </span><span class="pun">}</span><span class="pln"> from </span><span class="str">"@/stores/user"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> USER_SIGNIN </span><span class="pun">}</span><span class="pln"> from </span><span class="str">"@/mutations"</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">export</span><span class="pln"> </span><span class="kwd">default</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"SignInView"</span><span class="pun">,</span><span class="pln">

  setup</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> userStore </span><span class="pun">=</span><span class="pln"> useUserStore</span><span class="pun">();</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> userStore </span><span class="pun">};</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">

  data</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      signInDetails</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        username</span><span class="pun">:</span><span class="pln"> </span><span class="str">""</span><span class="pun">,</span><span class="pln">
        password</span><span class="pun">:</span><span class="pln"> </span><span class="str">""</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">

  methods</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">async</span><span class="pln"> userSignIn</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">const</span><span class="pln"> user </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">$apollo</span><span class="pun">.</span><span class="pln">mutate</span><span class="pun">({</span><span class="pln">
        mutation</span><span class="pun">:</span><span class="pln"> USER_SIGNIN</span><span class="pun">,</span><span class="pln">
        variables</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
          username</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">signInDetails</span><span class="pun">.</span><span class="pln">username</span><span class="pun">,</span><span class="pln">
          password</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">signInDetails</span><span class="pun">.</span><span class="pln">password</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">this</span><span class="pun">.</span><span class="pln">userStore</span><span class="pun">.</span><span class="pln">setToken</span><span class="pun">(</span><span class="pln">user</span><span class="pun">.</span><span class="pln">data</span><span class="pun">.</span><span class="pln">tokenAuth</span><span class="pun">.</span><span class="pln">token</span><span class="pun">);</span><span class="pln">
      </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">userStore</span><span class="pun">.</span><span class="pln">setUser</span><span class="pun">(</span><span class="pln">user</span><span class="pun">.</span><span class="pln">data</span><span class="pun">.</span><span class="pln">tokenAuth</span><span class="pun">.</span><span class="pln">user</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">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	نلاحظ في السطر 2 استيراد مخزن المستخدم User store الذي أنشأته.
</p>

<p>
	في السطور 9-12، استدعاء مخزن المستخدم User store في الخطاف <code>setup</code>، وهو ما يجعل التعامل مع بينيا Pinia أسهل دون أي وظائف خريطة إضافية.
</p>

<p>
	في السطرين 32-33، استدعاء الإجراءين <code>setToken()‎</code> و <code>setUser()‎</code> اللذيْن أنشأناهما منذ قليل، وهو ما سيحفظ المعلومات داخل الذاكرة المحلية.
</p>

<p>
	هذه هي الطريقة التي يمكنك من خلالها تسجيل دخول المستخدم، ولكن ماذا لو كان المستخدم قد سجل دخوله أساسًا، لنلقي نظرة على هذا المثال:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_61_57" style=""><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> SITE_INFO </span><span class="pun">}</span><span class="pln"> from </span><span class="str">"@/queries"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> useUserStore </span><span class="pun">}</span><span class="pln"> from </span><span class="str">"@/stores/user"</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">export</span><span class="pln"> </span><span class="kwd">default</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  setup</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> userStore </span><span class="pun">=</span><span class="pln"> useUserStore</span><span class="pun">();</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> userStore </span><span class="pun">};</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">

  data</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      menuOpen</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">,</span><span class="pln">
      mySite</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">,</span><span class="pln">
      user</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        isAuthenticated</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">,</span><span class="pln">
        token</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">userStore</span><span class="pun">.</span><span class="pln">getToken </span><span class="pun">||</span><span class="pln"> </span><span class="str">""</span><span class="pun">,</span><span class="pln">
        info</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">userStore</span><span class="pun">.</span><span class="pln">getUser </span><span class="pun">||</span><span class="pln"> </span><span class="pun">{},</span><span class="pln">
      </span><span class="pun">},</span><span class="pln">
      dataLoaded</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">false</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">async</span><span class="pln"> created</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">const</span><span class="pln"> siteInfo </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">await</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">$apollo</span><span class="pun">.</span><span class="pln">query</span><span class="pun">({</span><span class="pln">
      query</span><span class="pun">:</span><span class="pln"> SITE_INFO</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">mySite </span><span class="pun">=</span><span class="pln"> siteInfo</span><span class="pun">.</span><span class="pln">data</span><span class="pun">.</span><span class="pln">site</span><span class="pun">;</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">user</span><span class="pun">.</span><span class="pln">token</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">user</span><span class="pun">.</span><span class="pln">isAuthenticated </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">

  methods</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    userSignOut</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">userStore</span><span class="pun">.</span><span class="pln">removeToken</span><span class="pun">();</span><span class="pln">
      </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">userStore</span><span class="pun">.</span><span class="pln">removeUser</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">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	نحاول في السطرين 18- 19 الحصول على الترميز المشفر token ومعلومات المستخدم user info من المخزن Store.
</p>

<p>
	في السطور 31-33، إذا كان الترميز المشفر token موجودًا، فسيُعد المستخدم مستوثقًا authentication .
</p>

<p>
	في السطور 38-41، ستعمل الطريقة <code>userSignOut()‎</code> على تسجيل خروج المستخدم عند استدعائها.
</p>

<p>
	بعد أن تعرفنا على كيفية <a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D9%84%D9%86%D9%85%D8%A7%D8%B0%D8%AC-models-%D9%88%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D8%B9%D9%84%D8%A7%D9%85-%D8%B9%D9%86-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%81%D9%8A-django-r405/" rel="">استرجاع البيانات باستخدام الاستعلامات queries</a> ووضحنا كيفية إرسال البيانات باستخدام الطفرات mutations، سنشرح في المقال التالي طريقة إنشاء نظام تعليقات وتفاعلات إعجاب لتطبيق المدونة الخاص بنا.
</p>

<p>
	ترجمة -وبتصرّف- للمقال <a href="https://www.ericsdevblog.com/posts/create-a-modern-application-with-django-and-vue-2/" rel="external nofollow">Create a Modern Application with Django and Vue #2</a>.
</p>

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

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AD%D8%AF%D9%8A%D8%AB-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-django-%D9%88-vue-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A7%D9%84%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-r2421/" rel="">إنشاء تطبيق حديث باستخدام Django و Vue | الجزء الأول: الأساسيات</a>
	</li>
	<li>
		<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%A3%D9%85%D8%A7%D9%85%D9%8A%D8%A9-frontend-web-development/" rel="">تطوير الواجهة الأمامية لمواقع الويب Frontend Web Development</a>
	</li>
	<li>
		<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>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D9%85%D9%82%D8%A7%D8%B1%D9%86%D8%A9-%D8%A8%D9%8A%D9%86-%D8%A3%D8%B7%D8%B1-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D8%A3%D9%85%D8%A7%D9%85%D9%8A%D8%A9-angular-%D9%88-react-%D9%88-vue-r1596/" rel="">مقارنة بين أطر الواجهات الأمامية: Angular و React و Vue</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2430</guid><pubDate>Tue, 15 Oct 2024 15:08:00 +0000</pubDate></item><item><title>&#x625;&#x646;&#x634;&#x627;&#x621; &#x62A;&#x637;&#x628;&#x64A;&#x642; &#x62D;&#x62F;&#x64A;&#x62B; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; Django &#x648; Vue | &#x627;&#x644;&#x62C;&#x632;&#x621; &#x627;&#x644;&#x623;&#x648;&#x644;: &#x627;&#x644;&#x623;&#x633;&#x627;&#x633;&#x64A;&#x627;&#x62A;</title><link>https://academy.hsoub.com/programming/python/django/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AD%D8%AF%D9%8A%D8%AB-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-django-%D9%88-vue-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A7%D9%84%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-r2421/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_10/-----Django--Vue----.png.59143f9b37d097b12b881e6352fb7fe3.png" /></p>
<p>
	يعد <span ipsnoautolink="true">إطار العمل جانغو Django</span><span style="display: none;">    </span> إطار ويب كامل full-stack يمكننا من خلاله إنشاء كل من الواجهة الأمامية frontend والواجهة الخلفية backend معًا وهو قائم على لغة بايثون Python، ويتبع نمط تصميم MTV وهو اختصار للنمط البنائي للبرمجيات نموذج Model قالب Template عرض View. لكن في جانغو، عندما يطلب المستخدم النهائي صفحة ويب، يجب تصيير  rendering الصفحة أولًا في الواجهة الخلفية، ومن ثم تُرسل صفحة HTML الناتجة إلى المستخدم. وفي هذه الحالة، عندما يكون لديك عدد كبير من المستخدمين، سيضع ذلك ضغطًا كبيرًا على خادمك.
</p>

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

<p>
	سنتحدث في سلسلة المقالات هذه حول كيفية إنشاء تطبيق حديث من صفحة واحدة باستخدام جانغو Django كواجهة خلفية، وفيو Vue كواجهة أمامية، وغراف كيو إل <a href="https://academy.hsoub.com/programming/general/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-graphql-r2208/" rel="">GraphQL</a> كلغة لمعالجة <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-api-r1314/" rel=""><abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr></a> تربطهما معًا.
</p>

<p>
	هذا المقال هو جزء من سلسلة مقالات تتحدث حول كيفية إنشاء تطبيق حديث باستخدام جانغو Django وفيو Vue:
</p>

<ul>
	<li>
		<a href="http://%20%D8%A5%D9%86%D8%B4%D8%A7%D8%A1%20%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%20%D8%AD%D8%AF%D9%8A%D8%AB%20%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85%20%D8%AC%D8%A7%D9%86%D8%BA%D9%88%20Django%20%D9%88%D9%81%D9%8A%D9%88%20Vue%20-%20%D8%A7%D9%84%D8%AC%D8%B2%D8%A1%20%D8%A7%D9%84%D8%A3%D9%88%D9%84.%20" rel="external nofollow">إنشاء تطبيق حديث باستخدام جانغو Django وفيو Vue - الجزء الأول.</a>
	</li>
	<li>
		<strong><span ipsnoautolink="true">إنشاء تطبيق حديث باستخدام جانغو Django وفيو Vue - الجزء الثاني.</span></strong>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AD%D8%AF%D9%8A%D8%AB-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-django-%D9%88-vue-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-%D8%A7%D9%84%D8%AA%D8%B9%D9%84%D9%8A%D9%82%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D8%A5%D8%B9%D8%AC%D8%A7%D8%A8%D8%A7%D8%AA-r2436/" rel="">إنشاء تطبيق حديث باستخدام جانغو Django وفيو Vue - الجزء الثالث.</a>
	</li>
</ul>

<h2 id="">
	مراجعة سريعة عن جانغو
</h2>

<p>
	لنبدأ بمراجعة سريعة حول <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>، جانغو هو إطار عمل ويب يعتمد على لغة بايثون ويتبع النمط البنائي MTV، الذي هو اختصار لثلاث كلمات:
</p>

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

<p>
	في سلسلة المقالات الحالية؛ سنستخدم جانغو للواجهة الخلفية فقط، أي لن نستخدم <a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D9%84%D8%B9%D8%B1%D9%88%D8%B6-%D9%88%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-%D9%81%D9%8A-django-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-r436/" rel="">القوالب والعروض في جانغو</a>، ونستستخدم عوضًا عن ذلك <a href="https://academy.hsoub.com/programming/javascript/vuejs/" rel="">Vue.js</a> لبناء الواجهة الأمامية و <a href="https://academy.hsoub.com/programming/javascript/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-graphql-%D9%88%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84%D8%A7%D8%AA%D9%87%D8%A7-%D9%81%D9%8A-%D8%A8%D9%86%D8%A7%D8%A1-%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%A7%D9%84%D8%AD%D8%AF%D9%8A%D8%AB%D8%A9-r1208/" rel="">GraphQL</a> كطريقة للتواصل بين الواجهة الأمامية والخلفية.
</p>

<h2 id="-1">
	إنشاء مشروع جانغو جديد
</h2>

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

<pre class="ipsCode">blog
├── backend
└── frontend
</pre>

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

<pre class="ipsCode">cd backend
</pre>

<pre class="ipsCode">python3 -m venv env
</pre>

<p>
	سيؤدي الأمر السابق إلى إنشاء مجلد جديد يُدعى <code>env</code> مع إنشاء البيئة الافتراضية بداخله. ولتنشيط هذه البيئة الافتراضية، استخدم الأمر التالي:
</p>

<pre class="ipsCode">source env/bin/activate
</pre>

<p>
	إذا كنت تستخدم نظام تشغيل ويندوز Windows، فاستخدم الأمر التالي عوضًا عن السابق، كما يُنصح <a href="https://learn.microsoft.com/en-us/windows/wsl/install" rel="external nofollow">بتثبيت WSL</a>:
</p>

<pre class="ipsCode">env/Scripts/activate
</pre>

<p>
	بعد تنشيط البيئة الافتراضية، ستبدو <a href="https://academy.hsoub.com/programming/workflow/%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B3%D8%B7%D8%B1-%D8%A7%D9%84%D8%A3%D9%88%D8%A7%D9%85%D8%B1-%D9%81%D9%8A-%D8%B9%D9%85%D9%84%D9%8A%D8%A9-%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%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%B9%D9%85%D9%8A%D9%84-r1471/" rel="">الطرفية Terminal</a> كما في الشكل أدناه. لاحظ وجود <code>(env)</code> أمام اسم المستخدم، منا يدل على أنك تعمل حاليًا في البيئة الافتراضية.
</p>

<p style="text-align: center;">
	<img alt="virtual-env.png" class="ipsImage ipsImage_thumbnailed" data-fileid="159150" data-ratio="9.48" data-unique="va31hcua5" style="width: 500px; height: auto;" width="844" src="https://academy.hsoub.com/uploads/monthly_2024_10/virtual-env.png.b8648302e62d7741090bedf7be5a727e.png">
</p>

<p>
	يمكنك الآن إنشاء مشروع جانغو جديد.
</p>

<pre class="ipsCode">python -m pip install Django
</pre>

<pre class="ipsCode">django-admin startproject backend
</pre>

<p>
	بعدها يمكنك إنشاء تطبيق جديد باسم blog:
</p>

<pre class="ipsCode">python manage.py startapp blog
</pre>

<p>
	في النهاية، يجب أن يكون هيكل المشروع كما يلي:
</p>

<pre class="ipsCode">.
├── backend
│   ├── backend
│   ├── blog
│   ├── manage.py
│   └── requirements.txt
└── frontend
</pre>

<h2 id="models">
	إنشاء النماذج Models
</h2>

<p>
	تذكر أن النماذج هي عبارة عن واجهة يمكننا استخدامها للتفاعل مع <a href="https://academy.hsoub.com/devops/servers/databases/%D8%A3%D9%86%D9%88%D8%A7%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/" rel="">قاعدة البيانات</a>، وواحدة من المزايا الرائعة في <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="">جانغو</a> هي أنه يمكنك تلقائيًا معرفة التغييرات التي أجريتها على النماذج، وتوليد ملفات التهجير migration files المقابلة، والتي يمكن استخدامها لإجراء تغييرات على قاعدة البيانات.
</p>

<h3 id="thesitemodel">
	نموذج الموقع Site Model
</h3>

<p>
	لنبدأ بالنموذج Site الذي يخزن المعلومات الأساسية لموقعك على الويب.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9406_12" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Site</span><span class="pun">(</span><span class="pln">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
    name </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">200</span><span class="pun">)</span><span class="pln">
    description </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">TextField</span><span class="pun">()</span><span class="pln">
    logo </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">ImageField</span><span class="pun">(</span><span class="pln">upload_to</span><span class="pun">=</span><span class="str">'site/logo/'</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Meta</span><span class="pun">:</span><span class="pln">
        verbose_name </span><span class="pun">=</span><span class="pln"> </span><span class="str">'site'</span><span class="pln">
        verbose_name_plural </span><span class="pun">=</span><span class="pln"> </span><span class="str">'1. Site'</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> __str__</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"> self</span><span class="pun">.</span><span class="pln">name</span></pre>

<p>
	نلاحظ في السطر الرابع الأمر <code>ImageField</code> الذي يخبر جانغو بتحميل الصورة إلى مجلد <code>'site/logo/'</code> ولتحقيق ذلك، عليك القيام بشيئين اثنين.
</p>

<p>
	أولاً، يجب عليك <a href="https://pillow.readthedocs.io/en/stable/" rel="external nofollow">تثبيت حزمة Pillow</a>، إذ يحتاجها جانغو لمعالجة الصور.
</p>

<pre class="ipsCode">python -m pip install Pillow
</pre>

<p>
	ثانيًا، ستحتاج إلى إعداد توجيه جديد لمكان التخزين في <code>settings.py</code> إذ يجب أن تخبر جانغو بالمكان الذي ستخزن فيه هذه الوسائط، وما هي عناوين URL التي ستستخدمها للوصول إلى هذه الملفات.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9406_14" style=""><span class="kwd">import</span><span class="pln"> os


</span><span class="com"># Media Files</span><span class="pln">
MEDIA_ROOT </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">BASE_DIR</span><span class="pun">,</span><span class="pln"> </span><span class="str">'mediafiles'</span><span class="pun">)</span><span class="pln">
MEDIA_URL </span><span class="pun">=</span><span class="pln"> </span><span class="str">'/media/'</span></pre>

<p>
	تشير الإعدادات السابقة إلى أن ملفات الوسائط ستُخزن داخل ملف <code>‎‎/‎‎‎‍‎‎‎‎‎‎‎‎‎‎‎‎‎mediafiles</code> وسنحتاج إلى استخدام البادئة <code>/media/</code> قبل عنوان URL للوصول إلى الملفات. فعلى سبيل المثال، سيؤدي عنوان URL التالي <code><a href="http://localhost:3000/media/example.png" ipsnoembed="true" rel="external nofollow">http://localhost:3000/media/example.png</a></code> إلى استرجاع الصورة <code>/mediafiles/example.png</code>.
</p>

<h3 id="theusermodel">
	نموذج المستخدم  User Model
</h3>

<p>
	النموذج التالي هو نموذج المستخدم User model، ولحسن الحظ، فإن جانغو يأتي مزودًا بنموذج مستخدم مدمج، والذي يوفر الوظائف الأساسية المتعلقة بالأذونات permission والتراخيص authorization. ومع ذلك، سنجرب في مشروعنا هذا شيئًا أكثر تعقيدًا، مثل إضافة صور أفاتار avatar للملف الشخصي وإضافة سيرة ذاتية وبعض المعلومات الأخرى، ولتحقيق ذلك، ستحتاج إلى إنشاء نماذج مستخدم جديدة، والتي نوسع فيها الصنف AbstractUser، ومعنى نوسعه extends أي نرث منه صنف جديد لنخصصه وفق حاجتنا أو نضيف له المزيد من الميزات.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9406_16" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">contrib</span><span class="pun">.</span><span class="pln">auth</span><span class="pun">.</span><span class="pln">models </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">AbstractUser</span><span class="pln">


</span><span class="com"># New user model</span><span class="pln">
</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">User</span><span class="pun">(</span><span class="typ">AbstractUser</span><span class="pun">):</span><span class="pln">
    avatar </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">ImageField</span><span class="pun">(</span><span class="pln">
        upload_to</span><span class="pun">=</span><span class="str">'users/avatars/%Y/%m/%d/'</span><span class="pun">,</span><span class="pln">
        default</span><span class="pun">=</span><span class="str">'users/avatars/default.jpg'</span><span class="pln">
    </span><span class="pun">)</span><span class="pln">
    bio </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">TextField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">500</span><span class="pun">,</span><span class="pln"> null</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
    location </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">30</span><span class="pun">,</span><span class="pln"> null</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
    website </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">100</span><span class="pun">,</span><span class="pln"> null</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
    joined_date </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">DateField</span><span class="pun">(</span><span class="pln">auto_now_add</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Meta</span><span class="pun">:</span><span class="pln">
        verbose_name </span><span class="pun">=</span><span class="pln"> </span><span class="str">'user'</span><span class="pln">
        verbose_name_plural </span><span class="pun">=</span><span class="pln"> </span><span class="str">'2. Users'</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> __str__</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"> self</span><span class="pun">.</span><span class="pln">username</span></pre>

<p>
	هكذا يبدو صنف AbstractUser في جانغو:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9406_18" style=""><span class="pun">‎</span><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">contrib</span><span class="pun">.</span><span class="pln">auth</span><span class="pun">.</span><span class="pln">models </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">AbstractUser</span><span class="pln">


</span><span class="com"># New user model</span><span class="pln">
</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">User</span><span class="pun">(</span><span class="typ">AbstractUser</span><span class="pun">):</span><span class="pln">
    avatar </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">ImageField</span><span class="pun">(</span><span class="pln">
        upload_to</span><span class="pun">=</span><span class="str">'users/avatars/%Y/%m/%d/'</span><span class="pun">,</span><span class="pln">
        default</span><span class="pun">=</span><span class="str">'users/avatars/default.jpg'</span><span class="pln">
    </span><span class="pun">)</span><span class="pln">
    bio </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">TextField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">500</span><span class="pun">,</span><span class="pln"> null</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
    location </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">30</span><span class="pun">,</span><span class="pln"> null</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
    website </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">100</span><span class="pun">,</span><span class="pln"> null</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
    joined_date </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">DateField</span><span class="pun">(</span><span class="pln">auto_now_add</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Meta</span><span class="pun">:</span><span class="pln">
        verbose_name </span><span class="pun">=</span><span class="pln"> </span><span class="str">'user'</span><span class="pln">
        verbose_name_plural </span><span class="pun">=</span><span class="pln"> </span><span class="str">'2. Users'</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> __str__</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"> self</span><span class="pun">.</span><span class="pln">username</span></pre>

<p>
	كما هو واضح، يوفر هذا النموذج بعض الحقول الأساسية، مثل الاسم الأول واسم العائلة ونحو ذلك.
</p>

<p>
	ستحتاج بعد ذلك إلى التأكد من أن جانغو يستخدم نموذج المستخدم الجديد كنموذج مستخدم افتراضي، لذلك انتقل إلى ملف إعدادات التطبيق <code>settings.py</code> وأضف له التوجيه directive التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9406_20" style=""><span class="com"># Change Default User Model</span><span class="pln">
AUTH_USER_MODEL </span><span class="pun">=</span><span class="pln"> </span><span class="str">'blog.User'</span></pre>

<h3 id="categorytagpost">
	نماذج الفئة Category والوسم Tag والمنشور Post
</h3>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9406_22" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Category</span><span class="pun">(</span><span class="pln">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
    name </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">200</span><span class="pun">)</span><span class="pln">
    slug </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">SlugField</span><span class="pun">()</span><span class="pln">
    description </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">TextField</span><span class="pun">()</span><span class="pln">

    </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Meta</span><span class="pun">:</span><span class="pln">
        verbose_name </span><span class="pun">=</span><span class="pln"> </span><span class="str">'category'</span><span class="pln">
        verbose_name_plural </span><span class="pun">=</span><span class="pln"> </span><span class="str">'3. Categories'</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> __str__</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"> self</span><span class="pun">.</span><span class="pln">name</span></pre>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9406_24" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Tag</span><span class="pun">(</span><span class="pln">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
    name </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">200</span><span class="pun">)</span><span class="pln">
    slug </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">SlugField</span><span class="pun">()</span><span class="pln">
    description </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">TextField</span><span class="pun">()</span><span class="pln">

    </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Meta</span><span class="pun">:</span><span class="pln">
        verbose_name </span><span class="pun">=</span><span class="pln"> </span><span class="str">'tag'</span><span class="pln">
        verbose_name_plural </span><span class="pun">=</span><span class="pln"> </span><span class="str">'4. Tags'</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> __str__</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"> self</span><span class="pun">.</span><span class="pln">name</span></pre>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9406_26" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Post</span><span class="pun">(</span><span class="pln">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
    title </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">200</span><span class="pun">)</span><span class="pln">
    slug </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">SlugField</span><span class="pun">()</span><span class="pln">
    content </span><span class="pun">=</span><span class="pln"> </span><span class="typ">RichTextField</span><span class="pun">()</span><span class="pln">
    featured_image </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">ImageField</span><span class="pun">(</span><span class="pln">
        upload_to</span><span class="pun">=</span><span class="str">'posts/featured_images/%Y/%m/%d/'</span><span class="pun">)</span><span class="pln">
    is_published </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">BooleanField</span><span class="pun">(</span><span class="pln">default</span><span class="pun">=</span><span class="kwd">False</span><span class="pun">)</span><span class="pln">
    is_featured </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">BooleanField</span><span class="pun">(</span><span class="pln">default</span><span class="pun">=</span><span class="kwd">False</span><span class="pun">)</span><span class="pln">
    created_at </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">DateField</span><span class="pun">(</span><span class="pln">auto_now_add</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
    modified_at </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">DateField</span><span class="pun">(</span><span class="pln">auto_now</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># Each post can receive likes from multiple users, and each user can like multiple posts</span><span class="pln">
    likes </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">ManyToManyField</span><span class="pun">(</span><span class="typ">User</span><span class="pun">,</span><span class="pln"> related_name</span><span class="pun">=</span><span class="str">'post_like'</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># Each post belong to one user and one category.</span><span class="pln">
    </span><span class="com"># Each post has many tags, and each tag has many posts.</span><span class="pln">
    category </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">ForeignKey</span><span class="pun">(</span><span class="pln">
        </span><span class="typ">Category</span><span class="pun">,</span><span class="pln"> on_delete</span><span class="pun">=</span><span class="pln">models</span><span class="pun">.</span><span class="pln">SET_NULL</span><span class="pun">,</span><span class="pln"> null</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
    tag </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">ManyToManyField</span><span class="pun">(</span><span class="typ">Tag</span><span class="pun">)</span><span class="pln">
    user </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">ForeignKey</span><span class="pun">(</span><span class="typ">User</span><span class="pun">,</span><span class="pln"> on_delete</span><span class="pun">=</span><span class="pln">models</span><span class="pun">.</span><span class="pln">SET_NULL</span><span class="pun">,</span><span class="pln"> null</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Meta</span><span class="pun">:</span><span class="pln">
        verbose_name </span><span class="pun">=</span><span class="pln"> </span><span class="str">'post'</span><span class="pln">
        verbose_name_plural </span><span class="pun">=</span><span class="pln"> </span><span class="str">'5. Posts'</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> __str__</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"> self</span><span class="pun">.</span><span class="pln">title

    </span><span class="kwd">def</span><span class="pln"> get_number_of_likes</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"> self</span><span class="pun">.</span><span class="pln">likes</span><span class="pun">.</span><span class="pln">count</span><span class="pun">()</span></pre>

<p>
	لاحظ كيف تنفذ ميزة الإعجاب بالمنشورات (Like system) في السطر 13، وهو لا يعتمد على النوع البسيط IntegerField (وهو حقل لتخزين القيم الصحيحة) ولكنه يعمل تمامًا مثل الوسوم Tags. ويمكنك استخدام الدالة <code>get_number_of_likes()‎‎‎</code> لتحصل على عدد الإعجابات لكل منشور.
</p>

<h3 id="thecommentmodel">
	نموذج التعليقات Comment model
</h3>

<p>
	والآن دعونا نخطو خطوةً نحو الأمام، وننشئ قسمًا مخصصًا للتعليقات لهذا التطبيق:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9406_28" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Comment</span><span class="pun">(</span><span class="pln">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
    content </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">TextField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">1000</span><span class="pun">)</span><span class="pln">
    created_at </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">DateField</span><span class="pun">(</span><span class="pln">auto_now_add</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
    is_approved </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">BooleanField</span><span class="pun">(</span><span class="pln">default</span><span class="pun">=</span><span class="kwd">False</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># Each comment can receive likes from multiple users, and each user can like multiple comments</span><span class="pln">
    likes </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">ManyToManyField</span><span class="pun">(</span><span class="typ">User</span><span class="pun">,</span><span class="pln"> related_name</span><span class="pun">=</span><span class="str">'comment_like'</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># Each comment belongs to one user and one post</span><span class="pln">
    user </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">ForeignKey</span><span class="pun">(</span><span class="typ">User</span><span class="pun">,</span><span class="pln"> on_delete</span><span class="pun">=</span><span class="pln">models</span><span class="pun">.</span><span class="pln">SET_NULL</span><span class="pun">,</span><span class="pln"> null</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
    post </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">ForeignKey</span><span class="pun">(</span><span class="typ">Post</span><span class="pun">,</span><span class="pln"> on_delete</span><span class="pun">=</span><span class="pln">models</span><span class="pun">.</span><span class="pln">SET_NULL</span><span class="pun">,</span><span class="pln"> null</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Meta</span><span class="pun">:</span><span class="pln">
        verbose_name </span><span class="pun">=</span><span class="pln"> </span><span class="str">'comment'</span><span class="pln">
        verbose_name_plural </span><span class="pun">=</span><span class="pln"> </span><span class="str">'6. Comments'</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> __str__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">content</span><span class="pun">)</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">50</span><span class="pun">:</span><span class="pln">
            comment </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">content</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="str">'...'</span><span class="pln">
        </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
            comment </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">content
        </span><span class="kwd">return</span><span class="pln"> comment

    </span><span class="kwd">def</span><span class="pln"> get_number_of_likes</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"> self</span><span class="pun">.</span><span class="pln">likes</span><span class="pun">.</span><span class="pln">count</span><span class="pun">()</span></pre>

<h2 id="-2">
	إعداد لوحة التحكم في جانغو
</h2>

<p>
	والآن حان الوقت لإعداد لوحة التحكم أو <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D9%86%D8%B4%D9%8A%D8%B7-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D9%85%D8%AF%D9%8A%D8%B1-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%88%D8%A7%D9%84%D8%A7%D8%AA%D8%B5%D8%A7%D9%84-%D8%A8%D9%87%D8%A7-r1628/" rel="">لوحة الإدارة</a> في جانغو، لذا افتح ملف <code>admin.py</code> واكتب فيه التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9406_30" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">contrib </span><span class="kwd">import</span><span class="pln"> admin
</span><span class="kwd">from</span><span class="pln"> </span><span class="pun">.</span><span class="pln">models </span><span class="kwd">import</span><span class="pln"> </span><span class="pun">*</span><span class="pln">

</span><span class="com"># Register your models here.</span><span class="pln">
</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">UserAdmin</span><span class="pun">(</span><span class="pln">admin</span><span class="pun">.</span><span class="typ">ModelAdmin</span><span class="pun">):</span><span class="pln">
    list_display </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="str">'username'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'first_name'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'last_name'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'email'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'date_joined'</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">CategoryAdmin</span><span class="pun">(</span><span class="pln">admin</span><span class="pun">.</span><span class="typ">ModelAdmin</span><span class="pun">):</span><span class="pln">
    prepopulated_fields </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="str">'slug'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">(</span><span class="str">'name'</span><span class="pun">,)}</span><span class="pln">


</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">TagAdmin</span><span class="pun">(</span><span class="pln">admin</span><span class="pun">.</span><span class="typ">ModelAdmin</span><span class="pun">):</span><span class="pln">
    prepopulated_fields </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="str">'slug'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">(</span><span class="str">'name'</span><span class="pun">,)}</span><span class="pln">


</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">PostAdmin</span><span class="pun">(</span><span class="pln">admin</span><span class="pun">.</span><span class="typ">ModelAdmin</span><span class="pun">):</span><span class="pln">
    prepopulated_fields </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="str">'slug'</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">
    list_display </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">'is_published'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'is_featured'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'created_at'</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">CommentAdmin</span><span class="pun">(</span><span class="pln">admin</span><span class="pun">.</span><span class="typ">ModelAdmin</span><span class="pun">):</span><span class="pln">
    list_display </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="str">'__str__'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'is_approved'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'created_at'</span><span class="pun">)</span><span class="pln">


admin</span><span class="pun">.</span><span class="pln">site</span><span class="pun">.</span><span class="pln">register</span><span class="pun">(</span><span class="typ">Site</span><span class="pun">)</span><span class="pln">
admin</span><span class="pun">.</span><span class="pln">site</span><span class="pun">.</span><span class="pln">register</span><span class="pun">(</span><span class="typ">User</span><span class="pun">,</span><span class="pln"> </span><span class="typ">UserAdmin</span><span class="pun">)</span><span class="pln">
admin</span><span class="pun">.</span><span class="pln">site</span><span class="pun">.</span><span class="pln">register</span><span class="pun">(</span><span class="typ">Category</span><span class="pun">,</span><span class="pln"> </span><span class="typ">CategoryAdmin</span><span class="pun">)</span><span class="pln">
admin</span><span class="pun">.</span><span class="pln">site</span><span class="pun">.</span><span class="pln">register</span><span class="pun">(</span><span class="typ">Tag</span><span class="pun">,</span><span class="pln"> </span><span class="typ">TagAdmin</span><span class="pun">)</span><span class="pln">
admin</span><span class="pun">.</span><span class="pln">site</span><span class="pun">.</span><span class="pln">register</span><span class="pun">(</span><span class="typ">Post</span><span class="pun">,</span><span class="pln"> </span><span class="typ">PostAdmin</span><span class="pun">)</span><span class="pln">
admin</span><span class="pun">.</span><span class="pln">site</span><span class="pun">.</span><span class="pln">register</span><span class="pun">(</span><span class="typ">Comment</span><span class="pun">,</span><span class="pln"> </span><span class="typ">CommentAdmin</span><span class="pun">)</span></pre>

<p>
	فيما يتعلق بـ CommentAdmin، فإن <code>__str__</code> تشير إلى الدالة <code>__str__()</code> في نموذج التعليق. والتي ستعيد أول 50 حرفًا في السلسلة وتدمجها مع السلسلة <code>...</code>.
</p>

<p>
	والآن، فعِّل خادم التطوير وتأكد ما إذا كانت كل وظيفة تعمل على النحو الصحيح:
</p>

<pre class="ipsCode">python manage.py runserver
</pre>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="159151" href="https://academy.hsoub.com/uploads/monthly_2024_10/admin.png.0bb0ecad04023d5dd556bbdb5fa7476f.png" rel=""><img alt="admin.png" class="ipsImage ipsImage_thumbnailed" data-fileid="159151" data-ratio="47.67" data-unique="aj4u8j2v6" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2024_10/admin.thumb.png.cc3d9eea2903b5ae348a3842549f9e24.png"></a>
</p>

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

<h2 id="vuejs">
	مراجعة سريعة حول فيو جي إس Vue.js
</h2>

<p>
	والآن بعد أن انتهينا من إعداد الواجهة الخلفية، حان الوقت للانتقال إلى الواجهة الأمامية، وبذلك الانتقال سنبدأ باستخدام فيو جي إس Vue.js في الجزء الثاني من مقالنا هذا لإعداد الواجهة الأمامية في تطبيقنا.
</p>

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

<p>
	المقصود بـ "معتمدًا على <a href="https://academy.hsoub.com/programming/javascript/vuejs/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D9%85%D9%83%D9%88%D9%86%D8%A7%D8%AA-%D9%81%D9%8A-vuejs-r1031/" rel="">المكونات</a>" يعني أن المكون الجذري App.vue بإمكانه استيراد مكونات أخرى، وهي الملفات ذات الامتداد .vue، ويمكن لهذه المكونات استيراد المزيد من المكونات الأخرى، ما يسمح لك بإنشاء أنظمة معقدة ومتطورة للغاية.
</p>

<p>
	يحتوي الملف النموذجي ذو الامتداد .vue على ثلاثة أقسام، القسم الأول هو <code>&lt;template&gt;</code> يتضمن شيفرات بلغة HTML أما القسم الثاني فهو <code>&lt;script&gt;</code> ويتضمن شيفرات بلغة JavaScript، والقسم الأخير <code>&lt;style&gt;</code> يتضمن شيفرات بلغة CSS.
</p>

<p>
	يمكنك في قسم <code>&lt;script&gt;</code> التصريح عن ارتباطات bindings جديدة داخل التابع <code>data()‎‎</code> ، ويمكنك بعد ذلك عرض هذه الارتباطات داخل قسم <code>&lt;template&gt;</code> باستخدام الأقواس المزدوجة المتعرجة <code>{{Binding}}</code>. ستُغطى هذه الارتباطات المُصرَّح عنها في التابع <code>data()‎‎</code> تلقائيًا داخل نظام تفاعل فيو Vue، وهذا يعني أنه عندما تتغير قيمة الارتباط Binding، سيتغير المكون المقابل تلقائيًا، دون الحاجة إلى تحديث الصفحة.
</p>

<p>
	يمكن أن يحتوي القسم <code>&lt;script&gt;</code> أيضًا على توابع أخرى غير <code>data()‎‎</code> ، مثل <code>computed</code> و<code>props</code>. كما يتيح لنا <code>&lt;template&gt;</code> ربط البيانات باستخدام توجيهات مثل <code>v-bind</code> و <code>v-on</code> و <code>v-model</code>.
</p>

<h2 id="vuejs-1">
	إنشاء مشروع Vue.js جديد
</h2>

<p>
	يمكن تثبيت وإنشاء تطبيق Vue باستخدام أداة سطر الأوامر Vue، لكننا في هذه المرة سنتبع طريقة مختلفة إذ سنستخدم أداة إنشاء واجهات أمامية تسمى Vite، مع ملاحظة أن الكلمة تُنطق veet وتعني سريع باللغة الفرنسية، والتي أنشأها ذات الشخص الذي أنشأ Vue.js.
</p>

<p>
	انتقل الآن إلى مجلد <code>frontend</code> ونفذ الأمر التالي:
</p>

<pre class="ipsCode">npm init vue@latest
</pre>

<p>
	سيُطلب منك العديد من الخيارات، ولهذا المشروع، كل ما عليك فعله إضافة Vue Router:
</p>

<pre class="ipsCode">✔ Project name: … &lt;your_project_name&gt;
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit testing? … No / Yes
✔ Add Cypress for both Unit and End-to-End testing? … No / Yes
✔ Add ESLint for code quality? … No / Yes
✔ Add Prettier for code formating? … No / Yes

Scaffolding project in ./&lt;your_project_name&gt;. . .
Done.
</pre>

<p>
	إذا كنت ترتاح أكثر مع لغة برمجة جيدة، فيمكنك اختيار <a href="https://www.typescriptlang.org/" rel="external nofollow">تثبيت TypeScript</a> وإن كنت تحتاج إلى التصحيح التلقائي والتنسيق التلقائي للتعليمات البرمجية التي تكتبها، فيمكنك تثبيت ESlint وPrettier، ستؤدي عملية التثبيت هذه إلى إنشاء ملف package.json في دليل مشروعك، والذي يقوم بتخزين الحزم المطلوبة وإصداراتها. ستحتاج بعد ذلك إلى تثبيت هذه الحزم داخل مشروعك.
</p>

<pre class="ipsCode">cd &lt;your_project_name&gt;
</pre>

<pre class="ipsCode">npm install
</pre>

<pre class="ipsCode">npm run dev
</pre>

<p>
	ثمة شيء أخير قبل البدء في إنشاء الواجهة الأمامية للتطبيق، نحن نستخدم في مشروعنا هذا إطار عمل بلغة CSS يُدعى <a href="https://tailwindcss.com/" rel="external nofollow">TailwindCSS</a>، لذا لتثبيته، نفذ الأمر التالي:
</p>

<pre class="ipsCode">npm install -D tailwindcss postcss autoprefixer
</pre>

<pre class="ipsCode">npx tailwindcss init -p
</pre>

<p>
	سيؤدي ذلك إلى إنشاء ملفين، الأول هو <code>tailwind.config.js</code> والثاني <code>postcss.config.js</code>، وإذا كنت تريد معرفة المزيد عن Tailwind فيمكنك الاطلاع على مقال <a href="https://academy.hsoub.com/programming/css/bootstrap/%D9%85%D9%82%D8%A7%D8%B1%D9%86%D8%A9-%D8%A8%D9%8A%D9%86-bootstrap-%D9%88-tailwind-css-r1595/" rel="">مقارنة بين Bootstrap و Tailwind CSS</a>، كما يمكنك الاطلاع على <a href="https://tailwindcss.com/docs/editor-setup" rel="external nofollow">الدليل الرسمي لـ Tailwind</a>.
</p>

<p>
	انتقل إلى <code>tailwind.config.js</code> وأضف المسار إلى جميع ملفات قالبك:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9406_34" style=""><span class="pln">module</span><span class="pun">.</span><span class="pln">exports </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="pun">[</span><span class="str">"./index.html"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"./src/**/*.{vue,js,ts,jsx,tsx}"</span><span class="pun">],</span><span class="pln">
  theme</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    extend</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{},</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  plugins</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[],</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	أنشئ ملف <code>‎./src/index.css</code> وأضف توجيهات <code>‎@tailwind</code> لكل طبقة من طبقات Tailwind’s layers.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9406_36" style=""><span class="lit">@tailwind</span><span class="pln"> base</span><span class="pun">;</span><span class="pln">
</span><span class="lit">@tailwind</span><span class="pln"> components</span><span class="pun">;</span><span class="pln">
</span><span class="lit">@tailwind</span><span class="pln"> utilities</span><span class="pun">;</span></pre>

<p>
	والآن، استورد الملف الذي أنشأته حديثًا <code>‎‎‎‎‎./src/index.css</code> إلى ملف <code>‎‎./src/main.js‎‎‎‎‎‎‎‎‎‎‎‎‎</code>
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9406_38" style=""><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> createApp </span><span class="pun">}</span><span class="pln"> from </span><span class="str">"vue"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="typ">App</span><span class="pln"> from </span><span class="str">"./App.vue"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> router from </span><span class="str">"./router"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="str">"./index.css"</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> app </span><span class="pun">=</span><span class="pln"> createApp</span><span class="pun">(</span><span class="typ">App</span><span class="pun">);</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">router</span><span class="pun">);</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">mount</span><span class="pun">(</span><span class="str">"#app"</span><span class="pun">);</span></pre>

<p>
	والآن، من المفترض أن تكون قادرًا على استخدام Tailwind داخل ملفات <code>‎.vue</code>، وللتأكد، دعنا نختبر ذلك.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9406_40" style=""><span class="pun">&lt;</span><span class="pln">template</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="pun">&lt;</span><span class="pln">header</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><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">"wrapper"</span><span class="pun">&gt;</span><span class="pln">
      </span><span class="pun">&lt;</span><span class="typ">HelloWorld</span><span class="pln"> msg</span><span class="pun">=</span><span class="str">"You did it!"</span><span class="pln"> </span><span class="pun">/&gt;</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">"text-3xl font-bold underline"</span><span class="pun">&gt;</span><span class="typ">Hello</span><span class="pln"> world</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="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="pun">&gt;</span><span class="pln">
  </span><span class="pun">&lt;/</span><span class="pln">header</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><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">template</span><span class="pun">&gt;</span></pre>

<p>
	لاحظ أننا أضفنا عنوان <code>&lt;h1&gt;</code> بعد <code>&lt;HelloWorld&gt;</code>، إذ يستخدم العنوان أصناف Tailwind.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="159152" href="https://academy.hsoub.com/uploads/monthly_2024_10/vue-welcome.png.900b2414c28cb8d018508ede54283d4b.png" rel=""><img alt="vue-welcome.png" class="ipsImage ipsImage_thumbnailed" data-fileid="159152" data-ratio="52.89" data-unique="wr5g4lv7i" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2024_10/vue-welcome.thumb.png.8456828472ce98aa6bd9ce03af1e2b56.png"></a>
</p>

<h3 id="vuerouter">
	مكتبة Vue Router
</h3>

<p>
	لاحظ أن مجلد مشروعك مختلف قليلًا في هذه المرة:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="159153" href="https://academy.hsoub.com/uploads/monthly_2024_10/vue-router.png.07dc5211823c30b031561a7a8cbc7d90.png" rel=""><img alt="vue-router.png" class="ipsImage ipsImage_thumbnailed" data-fileid="159153" data-ratio="163.00" data-unique="adyefq35h" style="width: 200px; height: auto;" width="368" src="https://academy.hsoub.com/uploads/monthly_2024_10/vue-router.thumb.png.24f7b6d4de7ef48bc5809b6965e22b4c.png"></a>
</p>

<p>
	يوجد داخل المجلد الرئيسي src مجلد router ومجلد views، إذ يحتوي مجلد router على ملف index.js، والذي يمكنك من خلاله تحديد وجهات مختلفة، بحيث تشير كل وجهة route إلى مكون view موجود داخل مجلد views، ويمكن أن يوسع كل view بعد ذلك إلى مكونات أخرى داخل مجلد المكونات Components. تزودنا Vue بمثال على ذلك في ملف index.js:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9406_43" style=""><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> createRouter</span><span class="pun">,</span><span class="pln"> createWebHistory </span><span class="pun">}</span><span class="pln"> from </span><span class="str">"vue-router"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="typ">HomeView</span><span class="pln"> from </span><span class="str">"../views/HomeView.vue"</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> router </span><span class="pun">=</span><span class="pln"> createRouter</span><span class="pun">({</span><span class="pln">
  history</span><span class="pun">:</span><span class="pln"> createWebHistory</span><span class="pun">(</span><span class="kwd">import</span><span class="pun">.</span><span class="pln">meta</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">BASE_URL</span><span class="pun">),</span><span class="pln">
  routes</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
      path</span><span class="pun">:</span><span class="pln"> </span><span class="str">"/"</span><span class="pun">,</span><span class="pln">
      name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"home"</span><span class="pun">,</span><span class="pln">
      component</span><span class="pun">:</span><span class="pln"> </span><span class="typ">HomeView</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">},</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">
      path</span><span class="pun">:</span><span class="pln"> </span><span class="str">"/about"</span><span class="pun">,</span><span class="pln">
      name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"about"</span><span class="pun">,</span><span class="pln">
      </span><span class="com">// route level code-splitting</span><span class="pln">
      </span><span class="com">// this generates a separate chunk (About.[hash].js) for this route</span><span class="pln">
      </span><span class="com">// which is lazy-loaded when the route is visited.</span><span class="pln">
      component</span><span class="pun">:</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pun">(</span><span class="str">"../views/AboutView.vue"</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">

</span><span class="kwd">export</span><span class="pln"> </span><span class="kwd">default</span><span class="pln"> router</span><span class="pun">;</span></pre>

<p>
	لاستدعاء موجه router ما، انظر في داخل ملف App.vue، وعوضًا عن الوسم <code>&lt;a&gt;</code>، يُستخدم الوسم <code>&lt;RouterLink&gt;</code> المستورد من<br>
	حزمة vue-router لإدارة التنقل بين الصفحات في تطبيق Vue.js.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8226_16" style=""><span class="pun">&lt;</span><span class="pln">script setup</span><span class="pun">&gt;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="typ">RouterLink</span><span class="pun">,</span><span class="pln"> </span><span class="typ">RouterView</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> from </span><span class="str">"vue-router"</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">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">template</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="pun">&lt;</span><span class="pln">header</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><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">"wrapper"</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><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="typ">RouterLink</span><span class="pln"> to</span><span class="pun">=</span><span class="str">"/"</span><span class="pun">&gt;</span><span class="typ">Home</span><span class="pun">&lt;/</span><span class="typ">RouterLink</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="pun">&lt;</span><span class="typ">RouterLink</span><span class="pln"> to</span><span class="pun">=</span><span class="str">"/about"</span><span class="pun">&gt;</span><span class="typ">About</span><span class="pun">&lt;/</span><span class="typ">RouterLink</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="pun">&gt;</span><span class="pln">
  </span><span class="pun">&lt;/</span><span class="pln">header</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="pun">&lt;</span><span class="typ">RouterView</span><span class="pln"> </span><span class="pun">/&gt;</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">template</span><span class="pun">&gt;</span></pre>

<p>
	عند إخراج الصفحة، سيُستبدل الوسم <code> ‎&lt;RouterView/&gt;‎‎‎‎‎ </code> بالعرض المطابق، وإذا لم تكن ترغب في استيراد هذه المكونات، فكل ما عليك هو استخدام الوسم <code>router-link to=""&gt;‎&gt;</code> و <code>&lt;router-view&gt;</code> عوضًا عن ذلك.
</p>

<h2 id="routesvuerouter">
	إنشاء وجهات routes باستخدام Vue router
</h2>

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

<p>
	فيما يلي الموجهات التي أنشأناها، إذ يوجه الوسم @ نحو مجلد src.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4413_12" style=""><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> createRouter</span><span class="pun">,</span><span class="pln"> createWebHistory </span><span class="pun">}</span><span class="pln"> from </span><span class="str">"vue-router"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="typ">HomeView</span><span class="pln"> from </span><span class="str">"@/views/main/Home.vue"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="typ">PostView</span><span class="pln"> from </span><span class="str">"@/views/main/Post.vue"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="typ">CategoryView</span><span class="pln"> from </span><span class="str">"@/views/main/Category.vue"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="typ">TagView</span><span class="pln"> from </span><span class="str">"@/views/main/Tag.vue"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="typ">AllCategoriesView</span><span class="pln"> from </span><span class="str">"@/views/main/AllCategories.vue"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="typ">AllTagsView</span><span class="pln"> from </span><span class="str">"@/views/main/AllTags.vue"</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> routes </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
  </span><span class="pun">{</span><span class="pln">
    path</span><span class="pun">:</span><span class="pln"> </span><span class="str">"/"</span><span class="pun">,</span><span class="pln">
    name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Home"</span><span class="pun">,</span><span class="pln">
    component</span><span class="pun">:</span><span class="pln"> </span><span class="typ">HomeView</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="pun">{</span><span class="pln">
    path</span><span class="pun">:</span><span class="pln"> </span><span class="str">"/category"</span><span class="pun">,</span><span class="pln">
    name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Category"</span><span class="pun">,</span><span class="pln">
    component</span><span class="pun">:</span><span class="pln"> </span><span class="typ">CategoryView</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="pun">{</span><span class="pln">
    path</span><span class="pun">:</span><span class="pln"> </span><span class="str">"/tag"</span><span class="pun">,</span><span class="pln">
    name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Tag"</span><span class="pun">,</span><span class="pln">
    component</span><span class="pun">:</span><span class="pln"> </span><span class="typ">TagView</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="pun">{</span><span class="pln">
    path</span><span class="pun">:</span><span class="pln"> </span><span class="str">"/post"</span><span class="pun">,</span><span class="pln">
    name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Post"</span><span class="pun">,</span><span class="pln">
    component</span><span class="pun">:</span><span class="pln"> </span><span class="typ">PostView</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="pun">{</span><span class="pln">
    path</span><span class="pun">:</span><span class="pln"> </span><span class="str">"/categories"</span><span class="pun">,</span><span class="pln">
    name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Categories"</span><span class="pun">,</span><span class="pln">
    component</span><span class="pun">:</span><span class="pln"> </span><span class="typ">AllCategoriesView</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  </span><span class="pun">{</span><span class="pln">
    path</span><span class="pun">:</span><span class="pln"> </span><span class="str">"/tags"</span><span class="pun">,</span><span class="pln">
    name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Tags"</span><span class="pun">,</span><span class="pln">
    component</span><span class="pun">:</span><span class="pln"> </span><span class="typ">AllTagsView</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">const</span><span class="pln"> router </span><span class="pun">=</span><span class="pln"> createRouter</span><span class="pun">({</span><span class="pln">
  history</span><span class="pun">:</span><span class="pln"> createWebHistory</span><span class="pun">(</span><span class="pln">process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">BASE_URL</span><span class="pun">),</span><span class="pln">
  routes</span><span class="pun">,</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="kwd">export</span><span class="pln"> </span><span class="kwd">default</span><span class="pln"> router</span><span class="pun">;</span></pre>

<p>
	لاحظ أننا نعمل في هذا المقال على إنشاء الواجهة الأمامية فقط، ولم نصل إلى مرحلة نقل البيانات حتى الآن، لذا لا تقلق حول كيفية العثور على المنشورات أو الأقسام أو الوسوم في الوقت الحالي.
</p>

<h2 id="-3">
	إنشاء العروض والصفحات والمكونات
</h2>

<p>
	<span ipsnoautolink="true">توضح الصور التالية </span>واجهة المستخدم الأمامية التي أنشأناها لهذا المشروع، ويمكنك إنشاؤها لديك إما باعتماد الشيفرات البرمجية في هذا المقال، أو كتابة الشيفرات البرمجية الخاصة الخاص بك كما يمكنك العثور على الكود البرمجي الكامل من <a href="https://github.com/ericsdevblog/django-vue-starter-blog/tree/main/frontend/src/views" rel="external nofollow">هنا</a>.
</p>

<ul>
	<li>
		<strong>الصفحة الرئيسية Home Page</strong>
	</li>
</ul>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="159155" href="https://academy.hsoub.com/uploads/monthly_2024_10/homepage.png.b9e252cb52d4e8ca8da3f6a05695ed04.png" rel=""><img alt="homepage.png" class="ipsImage ipsImage_thumbnailed" data-fileid="159155" data-ratio="55.17" data-unique="a5srzgp29" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2024_10/homepage.thumb.png.f5666802edde52ce3deffd28a4a1770d.png"></a>
</p>

<ul>
	<li>
		<strong>جميع التصنيفات All Categories</strong>
	</li>
</ul>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="159156" href="https://academy.hsoub.com/uploads/monthly_2024_10/all-categories.png.8b346fb3f8ddbc4a9d50089e60dce3f2.png" rel=""><img alt="all-categories.png" class="ipsImage ipsImage_thumbnailed" data-fileid="159156" data-ratio="55.17" data-unique="46cfg7ynp" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2024_10/all-categories.thumb.png.24d4d1525ca67e50405155b5bbc4cda9.png"></a>
</p>

<ul>
	<li>
		<strong>جميع الوسوم All Tags</strong>
	</li>
</ul>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="159157" href="https://academy.hsoub.com/uploads/monthly_2024_10/all-tags.png.611fc938dbf0683e53fb16d8353268d4.png" rel=""><img alt="all-tags.png" class="ipsImage ipsImage_thumbnailed" data-fileid="159157" data-ratio="55.17" data-unique="6h7w860g5" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2024_10/all-tags.thumb.png.15b14eb98bc7f4105109caee9724e4e8.png"></a>
</p>

<ul>
	<li>
		<strong>صفحة تسجيل الدخول Sign In</strong>
	</li>
</ul>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="159160" href="https://academy.hsoub.com/uploads/monthly_2024_10/signin.png.eb944538e9944369f59a2ecfc6e8b161.png" rel=""><img alt="signin.png" class="ipsImage ipsImage_thumbnailed" data-fileid="159160" data-ratio="55.17" data-unique="5oar1wkpk" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2024_10/signin.thumb.png.236eb1bc151786e447d718696d6f8059.png"></a>
</p>

<ul>
	<li>
		<strong>صفحة إنشاء حساب Sign Up</strong>
	</li>
</ul>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="159161" href="https://academy.hsoub.com/uploads/monthly_2024_10/signup.png.b8282e79f7f6da59599b770a2b2753ab.png" rel=""><img alt="signup.png" class="ipsImage ipsImage_thumbnailed" data-fileid="159161" data-ratio="55.17" data-unique="ybgz0jh51" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2024_10/signup.thumb.png.548f068120639d0a098dc05530aef14f.png"></a>
</p>

<ul>
	<li>
		<strong>صفحة المنشور المفرد Post</strong>
	</li>
</ul>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="159162" href="https://academy.hsoub.com/uploads/monthly_2024_10/post.png.62a9fa0b1244ee747c7287f4ba0e3f46.png" rel=""><img alt="post.png" class="ipsImage ipsImage_thumbnailed" data-fileid="159162" data-ratio="55.17" data-unique="8wdt8w1ms" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2024_10/post.thumb.png.0fe888a7297f1ef3e4fbd11ba26f81c8.png"></a>
</p>

<ul>
	<li>
		<strong>قسم التعليقات على المنشور Comments</strong>
	</li>
</ul>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="159163" href="https://academy.hsoub.com/uploads/monthly_2024_10/comments.png.8398d8ba959f8a9292390fa88507040c.png" rel=""><img alt="comments.png" class="ipsImage ipsImage_thumbnailed" data-fileid="159163" data-ratio="54.08" data-unique="g46vbzbv4" style="width: 699px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2024_10/comments.thumb.png.ae2f993a2a60f355fdb3c0cec23e0324.png"></a>
</p>

<ul>
	<li>
		<strong>صفحة الملف الشخصي للمستخدم Profile </strong>
	</li>
</ul>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="159164" href="https://academy.hsoub.com/uploads/monthly_2024_10/profile.png.2ef82e58116bf1ef6cc55afbbb3aef42.png" rel=""><img alt="profile.png" class="ipsImage ipsImage_thumbnailed" data-fileid="159164" data-ratio="55.17" data-unique="ftenacgwx" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2024_10/profile.thumb.png.aac3e3aad6bb566d2d5f3d425c948ebf.png"></a>
</p>

<ul>
	<li>
		<strong>قسم تعليقات المستخدمين User Comments</strong>
	</li>
</ul>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="159165" href="https://academy.hsoub.com/uploads/monthly_2024_10/user-comments.png.1918c1005422e87190ab5588113332e3.png" rel=""><img alt="user-comments.png" class="ipsImage ipsImage_thumbnailed" data-fileid="159165" data-ratio="55.17" data-unique="rlmh0cfzt" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2024_10/user-comments.thumb.png.e89277e9417500301603d6ff098ce549.png"></a>
</p>

<p>
	ترجمة -وبتصرّف- للمقال <a href="https://www.ericsdevblog.com/posts/create-a-modern-application-with-django-and-vue-1/" rel="external nofollow">Create a Modern Application with Django and Vue #1</a><span style="display: none;"> </span>
</p>

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

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2090/" rel="">تطبيق عملي لتعلم إطار عمل جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%A9-%D8%B7%D9%84%D8%A8%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D8%B9%D8%A8%D8%B1-%D8%A7%D9%84%D8%B9%D8%B1%D9%88%D8%B6-views-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r1629/" rel="">معالجة طلبات الويب عبر العروض views في تطبيق جانغو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%88%D9%8A%D8%A8-%D9%84%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D9%85%D8%B9%D9%84%D9%88%D9%85%D8%A7%D8%AA-%D8%A7%D9%84%D8%B9%D9%85%D9%84%D8%A7%D8%A1-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-%D9%88%D8%B1%D9%8A%D8%A2%D9%83%D8%AA-react-r1776/" rel="">بناء تطبيق ويب لإدارة معلومات العملاء باستخدام جانغو Django وريآكت React</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/vuejs/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%A8%D8%B3%D9%8A%D8%B7-%D9%85%D9%86-%D8%AE%D9%84%D8%A7%D9%84-vuejs-r2064/" rel="">إنشاء تطبيق بسيط من خلال Vue.js</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%88%D8%AA%D9%88%D8%B5%D9%8A%D9%84%D9%87-%D8%A8%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-r1626/" rel="">إنشاء تطبيق جانغو وتوصيله بقاعدة بيانات</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2421</guid><pubDate>Sat, 05 Oct 2024 15:00:00 +0000</pubDate></item><item><title>&#x62A;&#x639;&#x631;&#x641; &#x639;&#x644;&#x649; &#x623;&#x645;&#x627;&#x646; &#x62A;&#x637;&#x628;&#x64A;&#x642;&#x627;&#x62A; &#x62C;&#x627;&#x646;&#x63A;&#x648;</title><link>https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A3%D9%85%D8%A7%D9%86-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2123/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_09/----.png.7955afca899f034f816a02a2155cc7b5.png" /></p>
<p>
	تُعَد حماية بيانات المستخدم جزءًا أساسيًا من تصميم أيّ موقع ويب، لذا يقدّم هذا المقال شرحًا عمليًا لكيفية تعامل أدوات الحماية المبنية مسبقًا في جانغو <a href="https://academy.hsoub.com/programming/python/django/" rel="">Django</a> مع الهجمات التي تعرّفنا على بعضٍ منها سابقًا في مقال <a href="https://academy.hsoub.com/programming/advanced/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A3%D9%85%D8%A7%D9%86-%D9%85%D9%88%D8%A7%D9%82%D8%B9-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r2020/" rel="">تعرف على أمان مواقع الويب</a>.
</p>

<ul>
	<li>
		<strong>المتطلبات الأساسية</strong>: قراءة مقال <a href="https://academy.hsoub.com/programming/advanced/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A3%D9%85%D8%A7%D9%86-%D9%85%D9%88%D8%A7%D9%82%D8%B9-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r2020/" rel="">تعرف على أمان مواقع الويب</a> الخاص بالبرمجة من طرف الخادم، والاطلاع على سلسلة مقالات تعلم جانغو حتى مقال <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%85%D9%86-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-forms-r2120/" rel="">العمل مع الاستمارات Forms</a> على الأقل.
	</li>
	<li>
		<strong>الهدف</strong>: فهم الأشياء الرئيسية التي يجب تطبيقها، أو عدم تطبيقها لتأمين تطبيق ويب جانغو.
	</li>
</ul>

<p>
	يوفر <a href="https://academy.hsoub.com/programming/advanced/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A3%D9%85%D8%A7%D9%86-%D9%85%D9%88%D8%A7%D9%82%D8%B9-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r2020/" rel="">أمان موقع الويب</a> نظرةً عامة حول ما يعنيه أمان موقع الويب للتصميم من طرف الخادم وبعض الهجمات الأكثر شيوعًا التي يجب عليك حماية موقعك منها، فإحدى الدروس الرئيسية التي نتعلمها من ذلك المقال أن جميع الهجمات تنجح تقريبًا عندما يثق تطبيق الويب بالبيانات الواردة من المتصفح.
</p>

<p>
	<strong>ملاحظة</strong>: الدرس الوحيد الأكثر أهمية الذي يمكنك تعلّمه حول أمان مواقع الويب هو عدم الوثوق أبدًا في البيانات الواردة من المتصفح، ويتضمن ذلك بيانات طلب <code>GET</code> في معاملات عنوان URL وبيانات <code>POST</code> وترويسات HTTP وملفات تعريف الارتباط والملفات التي يرفعها المستخدم وغير ذلك، لذا تحقق دائمًا من جميع البيانات الواردة وطهّرها، وافترض الأسوأ دائمًا.
</p>

<p>
	يتعامل جانغو مع العديد من الهجمات الأكثر شيوعًا، ويمكنك الاطلاع على <a href="https://docs.djangoproject.com/en/4.0/topics/security/" rel="external nofollow">الأمان في توثيق جانغو</a> لمعرفة ميزات أمان جانغو وكيفية تأمين موقع ويب يدعمه.
</p>

<p style="text-align: center;">
	<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="" frameborder="0" height="315" id="ips_uid_6584_6" src="https://academy.hsoub.com/applications/core/interface/index.html" title="YouTube video player" width="560" data-embed-src="https://www.youtube.com/embed/5jAgs6BMmYg?si=yMeqJMn0i93sUswi"></iframe>
</p>

<p>
	تتألف هذه السلسلة الفرعية من السلسلة الأشمل <a href="https://academy.hsoub.com/tags/%D8%AA%D8%B9%D9%84%D9%85%20%D8%AA%D8%B7%D9%88%D9%8A%D8%B1%20%D8%A7%D9%84%D9%88%D9%8A%D8%A8/" rel="">تعلم تطوير الويب</a> من المقالات التالية:
</p>

<ul>
	<li>
		<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>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%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%AC%D8%A7%D9%86%D8%BA%D9%88-django-r2049/" rel="">إعداد بيئة تطوير تطبيقات جانغو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2090/" rel="">تطبيق عملي لتعلم جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%86%D9%85%D8%A7%D8%B0%D8%AC-models-r2092/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D9%85%D9%88%D9%82%D8%B9-%D9%85%D8%AF%D9%8A%D8%B1-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-admin-r2105/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B1%D8%A7%D8%A8%D8%B9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B5%D9%81%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%A7%D9%84%D8%B1%D8%A6%D9%8A%D8%B3%D9%8A%D8%A9-r2106/" rel="">تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AE%D8%A7%D9%85%D8%B3-%D8%A7%D9%84%D8%B9%D8%B1%D9%88%D8%B6-views-%D8%A7%D9%84%D8%B9%D8%A7%D9%85%D8%A9-%D9%88%D8%A7%D9%84%D8%AA%D9%81%D8%B5%D9%8A%D9%84%D9%8A%D8%A9-r2107/" rel="">تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B3%D8%A7%D8%AF%D8%B3-%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D8%AC%D9%84%D8%B3%D8%A7%D8%AA-sessions-r2118/" rel="">تطبيق عملي لتعلم جانغو - الجزء السادس: إدارة الجلسات Sessions</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B3%D8%A7%D8%A8%D8%B9-%D8%A7%D8%B3%D8%AA%D9%8A%D8%AB%D8%A7%D9%82-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D9%8A%D9%86-%D9%88%D8%A3%D8%B0%D9%88%D9%86%D8%A7%D8%AA%D9%87%D9%85-r2119/" rel="">تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%85%D9%86-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-forms-r2120/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AA%D8%A7%D8%B3%D8%B9-%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2121/" rel="">تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-10-%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%81%D9%8A-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%A7%D8%AC-r2122/" rel="">تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج</a>
	</li>
	<li>
		<span ipsnoautolink="true">تعرف على أمان تطبيقات جانغو</span>
	</li>
</ul>

<h2>
	الهجمات الشائعة وحماية موقعك منها
</h2>

<p>
	سنشرح في هذا المقال عددًا من ميزات الأمان في سياق تطبيقنا العملي لموقع <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2090/" rel="">المكتبة المحلية LocalLibrary</a> لتعلم جانغو.
</p>

<h3>
	هجمات السكربتات العابرة للمواقع XSS
</h3>

<p>
	يُستخدَم مصطلح هجمات السكربتات العابرة للمواقع Cross Site Scripting -أو اختصارًا XSS- لوصف فئة من الهجمات التي تسمح للمهاجمين بحقن سكربتات من طرف العميل عبر موقع الويب في متصفحات المستخدمين الآخرين، ويمكن تحقيقه من خلال تخزين سكربتات ضارة في قاعدة البيانات حيث يمكن استرجاعها وإظهارها للمستخدمين الآخرين، أو من خلال جعل المستخدمين ينقرون على رابط يؤدي إلى أن ينفِّذ متصفحُ المستخدم شيفرة جافا سكريبت الخاصة بالمهاجم.
</p>

<p>
	يحميك نظام قوالب جانغو من أغلب هجمات XSS من خلال محارف هروب معينة Escaping specific characters، والتي تُعَد خطيرة في لغة HTML. يمكننا إثبات ذلك من خلال محاولة إدخال شيفرة جافا سكريبت في موقع المكتبة المحلية LocalLibrary باستخدام استمارة إنشاء مؤلف Create-author التي أنشأناها في مقال <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%85%D9%86-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-forms-r2120/" rel="">العمل مع الاستمارات Forms</a> باتباع الخطوات التالية:
</p>

<ol>
	<li>
		شغّل موقع الويب باستخدام خادم التطوير <code>python3 manage.py runserver</code>.
	</li>
	<li>
		افتح الموقع في متصفحك المحلي وسجّل الدخول إلى حساب مستخدمك المميز.
	</li>
	<li>
		انتقل إلى صفحة إنشاء المؤلف، والتي يجب أن تكون على عنوان URL الذي هو: <code>http://127.0.0.1:8000/catalog/author/create/‎</code>.
	</li>
	<li>
		أدخِل الأسماء وتفاصيل التاريخ لمستخدم جديد، ثم ألحِق نصًا بحقل اسم العائلة Last Name هو: <code>‎&lt;script&gt;alert('Test alert');&lt;/script&gt;‎</code>.
	</li>
</ol>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135506" href="https://academy.hsoub.com/uploads/monthly_2023_09/01_author_create_form_alert_xss.png.f51726f26900a2afbd1b163cb096bd1f.png" rel=""><img alt="01_author_create_form_alert_xss.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135506" data-unique="lsdoavwx3" src="https://academy.hsoub.com/uploads/monthly_2023_09/01_author_create_form_alert_xss.png.f51726f26900a2afbd1b163cb096bd1f.png"> </a>
</p>

<p>
	<strong>ملاحظة</strong>: يُعَد النص الذي أدخلناه سكربتًا غير ضار، إذ سيعرض مربع تنبيه في متصفحك إذا جرى تنفيذه. إذا عُرِض التنبيه عند إرسال السجل، فسيكون الموقع عُرضة لهجمات XSS.
</p>

<ol start="5">
	<li>
		اضغط على زر إرسال Submit لحفظ السجل.
	</li>
	<li>
		سيظهر ما يلي عند حفظ المؤلف، إذ يجب عدم تنفيذ التابع <code>alert()‎</code> بسبب الحماية من هجمات XSS، لذا يُعرَض السكربت بوصفه نصًا عاديًا.
	</li>
</ol>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135505" href="https://academy.hsoub.com/uploads/monthly_2023_09/02_author_detail_alert_xss.png.41fa6aac9b3ef7625efe70dee7ea78c7.png" rel=""><img alt="02_author_detail_alert_xss.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135505" data-unique="bnog6lso3" src="https://academy.hsoub.com/uploads/monthly_2023_09/02_author_detail_alert_xss.png.41fa6aac9b3ef7625efe70dee7ea78c7.png"> </a>
</p>

<p>
	إذا عرضتَ الشيفرة المصدرية لصفحة HTML، فيمكنك أن ترى أن المحارف الخطيرة لوسوم السكربت مُحوَّلة إلى ما يكافؤها من رموز الهروب غير الضارة، إذ تحوّل <code>&lt;</code> إلى <code>‎&amp;gt;‎</code> مثلًا.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_6514_8" style=""><span class="tag">&lt;h1&gt;</span><span class="pln">
  Author: Boon&amp;lt;script&amp;gt;alert(&amp;#39;Test alert&amp;#39;);&amp;lt;/script&amp;gt;, David
  (Boonie)
</span><span class="tag">&lt;/h1&gt;</span></pre>

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

<p>
	يمكن أيضًا أن تنشأ هجمات XSS من مصدر بيانات آخر غير موثوق به مثل <a href="https://academy.hsoub.com/programming/javascript/%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D8%AA%D8%B9%D8%B1%D9%8A%D9%81-%D8%A7%D9%84%D8%A7%D8%B1%D8%AA%D8%A8%D8%A7%D8%B7-%D9%88%D8%B6%D8%A8%D8%B7%D9%87%D8%A7-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1330/" rel="">ملفات تعريف الارتباط Cookies</a> أو خدمات الويب أو الملفات المرفوعة عند عدم تعقيم sanitize البيانات تعقيمًا كافيًا قبل تضمينها في الصفحة، لذا يمكن أن تحتاج إلى إضافة رمز تعقيمك الخاص إذا أردتَ عرض بيانات من هذه المصادر.
</p>

<h3>
	الحماية من هجمات تزوير الطلبات عبر المواقع CSRF
</h3>

<p>
	تسمح هجمات Cross-Site Request Forgery -أو اختصارًا CSRF- للمستخدم الضار بتنفيذ الإجراءات باستخدام اعتماديات مستخدم آخر دون معرفة هذا المستخدم أو موافقته. لنأخذ مثلًا الحالة التي يكون لدينا فيها مخترق يريد إنشاء مؤلفين إضافيين لمكتبتنا المحلية.
</p>

<p>
	<strong>ملاحظة</strong>: من الواضح أن المخترق لن يعمل ذلك من أجل المال، ولكن يمكن للمخترق الأكثر طموحًا استخدام الأسلوب نفسه على المواقع الأخرى لإجراء مهام أكثر ضررًا مثل تحويل الأموال إلى حسابه الخاص وغير ذلك.
</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> مثل الملف الآتي الذي يحتوي على استمارة إنشاء المؤلف، مثل الاستمارة التي استخدمناها في القسم السابق، والتي تُرسَل بمجرد تحميل الملف، ثم يرسل المخترق الملف إلى جميع أمناء المكتبات ويقترح عليهم فتح الملف الذي سيحتوي على بعض المعلومات غير الضارة في مثالنا. إذا فتح أمينُ المكتبة الذي سجّل دخوله الملفَ، فستُرسَل الاستمارة مع اعتمادياتها وسيُنشأ مؤلف جديد.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_6514_10" style=""><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;body</span><span class="pln"> </span><span class="atn">onload</span><span class="pun">=</span><span class="atv">"</span><span class="pln">document</span><span class="pun">.</span><span class="typ">EvilForm</span><span class="pun">.</span><span class="pln">submit</span><span class="pun">()</span><span class="atv">"</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">"http://127.0.0.1:8000/catalog/author/create/"</span><span class="pln">
      </span><span class="atn">method</span><span class="pun">=</span><span class="atv">"post"</span><span class="pln">
      </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"EvilForm"</span><span class="tag">&gt;</span><span class="pln">
      </span><span class="tag">&lt;table&gt;</span><span class="pln">
        </span><span class="tag">&lt;tr&gt;</span><span class="pln">
          </span><span class="tag">&lt;th&gt;&lt;label</span><span class="pln"> </span><span class="atn">for</span><span class="pun">=</span><span class="atv">"id_first_name"</span><span class="tag">&gt;</span><span class="pln">First name:</span><span class="tag">&lt;/label&gt;&lt;/th&gt;</span><span class="pln">
          </span><span class="tag">&lt;td&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">"id_first_name"</span><span class="pln">
              </span><span class="atn">maxlength</span><span class="pun">=</span><span class="atv">"100"</span><span class="pln">
              </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"first_name"</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">value</span><span class="pun">=</span><span class="atv">"Mad"</span><span class="pln">
              </span><span class="atn">required</span><span class="pln"> </span><span class="tag">/&gt;</span><span class="pln">
          </span><span class="tag">&lt;/td&gt;</span><span class="pln">
        </span><span class="tag">&lt;/tr&gt;</span><span class="pln">
        </span><span class="tag">&lt;tr&gt;</span><span class="pln">
          </span><span class="tag">&lt;th&gt;&lt;label</span><span class="pln"> </span><span class="atn">for</span><span class="pun">=</span><span class="atv">"id_last_name"</span><span class="tag">&gt;</span><span class="pln">Last name:</span><span class="tag">&lt;/label&gt;&lt;/th&gt;</span><span class="pln">
          </span><span class="tag">&lt;td&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">"id_last_name"</span><span class="pln">
              </span><span class="atn">maxlength</span><span class="pun">=</span><span class="atv">"100"</span><span class="pln">
              </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"last_name"</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">value</span><span class="pun">=</span><span class="atv">"Man"</span><span class="pln">
              </span><span class="atn">required</span><span class="pln"> </span><span class="tag">/&gt;</span><span class="pln">
          </span><span class="tag">&lt;/td&gt;</span><span class="pln">
        </span><span class="tag">&lt;/tr&gt;</span><span class="pln">
        </span><span class="tag">&lt;tr&gt;</span><span class="pln">
          </span><span class="tag">&lt;th&gt;&lt;label</span><span class="pln"> </span><span class="atn">for</span><span class="pun">=</span><span class="atv">"id_date_of_birth"</span><span class="tag">&gt;</span><span class="pln">Date of birth:</span><span class="tag">&lt;/label&gt;&lt;/th&gt;</span><span class="pln">
          </span><span class="tag">&lt;td&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">"id_date_of_birth"</span><span class="pln"> </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"date_of_birth"</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="tag">/&gt;</span><span class="pln">
          </span><span class="tag">&lt;/td&gt;</span><span class="pln">
        </span><span class="tag">&lt;/tr&gt;</span><span class="pln">
        </span><span class="tag">&lt;tr&gt;</span><span class="pln">
          </span><span class="tag">&lt;th&gt;&lt;label</span><span class="pln"> </span><span class="atn">for</span><span class="pun">=</span><span class="atv">"id_date_of_death"</span><span class="tag">&gt;</span><span class="pln">Died:</span><span class="tag">&lt;/label&gt;&lt;/th&gt;</span><span class="pln">
          </span><span class="tag">&lt;td&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">"id_date_of_death"</span><span class="pln">
              </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"date_of_death"</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">value</span><span class="pun">=</span><span class="atv">"12/10/2016"</span><span class="pln"> </span><span class="tag">/&gt;</span><span class="pln">
          </span><span class="tag">&lt;/td&gt;</span><span class="pln">
        </span><span class="tag">&lt;/tr&gt;</span><span class="pln">
      </span><span class="tag">&lt;/table&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">"Submit"</span><span class="pln"> </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;/body&gt;</span><span class="pln">
</span><span class="tag">&lt;/html&gt;</span></pre>

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

<p>
	تُفعَّل الحماية من خلال تضمين وسم القالب <code>{% csrf_token %}</code> في تعريف استمارتك، ثم يُعرَض هذا المفتاح في شيفرة HTML كما يلي مع قيمة خاصة بالمستخدم في المتصفح الحالي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_6514_12" style=""><span class="tag">&lt;input</span><span class="pln">
  </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"hidden"</span><span class="pln">
  </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"csrfmiddlewaretoken"</span><span class="pln">
  </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"0QRWHnYVg776y2l66mcvZqp8alrv4lb8S8lZ4ZJUWGZFA5VHrVfL2mpH29YZ39PW"</span><span class="pln"> </span><span class="tag">/&gt;</span></pre>

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

<p>
	تُشغَّل حماية جانغو من هجمات CSRF افتراضيًا، ويجب عليك دائمًا استخدام وسم القالب <code>{% csrf_token %}</code> في استماراتك واستخدام طلبات <code>POST</code> التي يمكن أن تغير أو تضيف بيانات إلى قاعدة البيانات.
</p>

<h3>
	الحماية من الهجمات الأخرى
</h3>

<p>
	يوفّر جانغو أشكالًا أخرى من الحماية، إليك بعضًا منها.
</p>

<h4>
	الحماية من هجمات حقن استعلامات SQL
</h4>

<p>
	تمكّن الثغرات الأمنية الخاصة بحقن <a href="https://academy.hsoub.com/programming/sql/%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D8%B9%D9%84%D8%A7%D9%85-%D8%B9%D9%86-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%81%D9%8A-sql-r588/" rel="">استعلامات SQL</a> المستخدمين الضارين من تنفيذ شيفرة SQL عشوائية على قاعدة بيانات، مما يسمح بالوصول إلى البيانات أو تعديلها أو حذفها بغض النظر عن أذونات المستخدم. ستصل في كل حالة تقريبًا إلى قاعدة البيانات باستخدام <a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D9%84%D9%86%D9%85%D8%A7%D8%B0%D8%AC-models-%D9%88%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D8%B9%D9%84%D8%A7%D9%85-%D8%B9%D9%86-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%81%D9%8A-django-r405/" rel="">مجموعات استعلام أو نماذج جانغو</a>، لذلك سيهرِّب مشغّل قاعدة البيانات الأساسية شيفرة SQL الناتجة بصورة صحيحة. إذا كنت بحاجة إلى كتابة استعلامات بسيطة أو استعلامات SQL مُخصَّصة، فيجب عليك التفكير في منع حقن استعلامات SQL.
</p>
<iframe allowfullscreen="" data-controller="core.front.core.autosizeiframe" data-embedauthorid="3889" data-embedcontent="" src="https://academy.hsoub.com/files/16-%D9%85%D9%84%D8%A7%D8%AD%D8%B8%D8%A7%D8%AA-%D9%84%D9%84%D8%B9%D8%A7%D9%85%D9%84%D9%8A%D9%86-%D8%A8%D9%84%D8%BA%D8%A9-sql/?do=embed" style="margin: auto;"></iframe>

<h4>
	الحماية من هجمات الاختطاف بالنقر Clickjacking
</h4>

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

<h4>
	فرض بروتوكول <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة"><abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr></abbr>/HTTPS
</h4>

<p>
	يمكن تفعيل <a href="https://academy.hsoub.com/devops/servers/web/apache/%D9%83%D9%8A%D9%81-%D8%AA%D9%86%D8%B4%D8%A6-%D8%B4%D9%87%D8%A7%D8%AF%D8%A9-ssl-%D9%85%D9%88%D9%91%D9%8E%D9%82%D8%B9%D8%A9-%D8%B0%D8%A7%D8%AA%D9%8A%D9%91%D9%8B%D8%A7-%D9%84%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85%D9%87%D8%A7-%D9%85%D8%B9-%D8%AE%D8%A7%D8%AF%D9%88%D9%85-%D8%A7%D9%84%D9%88%D9%90%D8%A8-apache-%D8%B9%D9%84%D9%89-%D8%A3%D9%88%D8%A8%D9%88%D9%86%D8%AA%D9%88-1604-r325/" rel="">بروتوكول <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة"><abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr></abbr>/HTTPS</a> على خادم الويب لتشفير كل حركة المرور بين الموقع والمتصفح، بما في ذلك اعتماديات الاستيثاق التي يمكن إرسالها ضمن نص عادي. يوصَى جدًا بتفعيل HTTPS، إذ سيوفّر جانغو بتفعيله عددًا من وسائل الحماية الأخرى التي يمكنك استخدامها وهي:
</p>

<ul>
	<li>
		يمكن استخدام <code>SECURE_PROXY_SSL_HEADER</code> للتحقق مما إذا كان المحتوى آمنًا، حتى وإن كان قادمًا من وكيل غير HTTP.
	</li>
	<li>
		يُستخدَم <a href="https://docs.djangoproject.com/en/4.0/ref/settings/#std:setting-SECURE_SSL_REDIRECT" rel="external nofollow"><code>SECURE_SSL_REDIRECT</code></a> لإعادة توجيه جميع طلبات HTTP إلى HTTPS.
	</li>
	<li>
		استخدم أمن نقل HTTP الصارم HTTP Strict Transport Security -أو اختصارًا HSTS، وهو ترويسة HTTP التي تخبر المتصفح بأن جميع الاتصالات المستقبلية إلى موقع معين يجب أن تستخدم HTTPS دائمًا. يضمن هذا الإعداد -إلى جانب إعادة توجيه طلبات HTTP إلى HTTPS- استخدام بروتوكول HTTPS دائمًا بعد نجاح الاتصال. يمكن ضبط HSTS مع <a href="https://docs.djangoproject.com/en/4.0/ref/settings/#std:setting-SECURE_HSTS_SECONDS" rel="external nofollow"><code>SECURE_HSTS_SECONDS</code></a> و <a href="https://docs.djangoproject.com/en/4.0/ref/settings/#std:setting-SECURE_HSTS_INCLUDE_SUBDOMAINS" rel="external nofollow"><code>SECURE_HSTS_INCLUDE_SUBDOMAINS</code></a> أو على خادم الويب.
	</li>
	<li>
		استخدم ملفات تعريف الارتباط "الآمنة" من خلال ضبط <a href="https://docs.djangoproject.com/en/4.0/ref/settings/#std:setting-SESSION_COOKIE_SECURE" rel="external nofollow"><code>SESSION_COOKIE_SECURE</code></a> و <a href="https://docs.djangoproject.com/en/4.0/ref/settings/#std:setting-CSRF_COOKIE_SECURE" rel="external nofollow">CSRF_COOKIE_SECURE</a> على القيمة <code>True</code>، مما يضمن إرسال ملفات تعريف الارتباط عبر بروتوكول HTTPS فقط.
	</li>
</ul>

<h4>
	التحقق من صحة ترويسة المضيف
</h4>

<p>
	استخدم <a href="https://docs.djangoproject.com/en/4.0/ref/settings/#std:setting-ALLOWED_HOSTS" rel="external nofollow"><code>ALLOWED_HOSTS</code></a> لقبول الطلبات من المضيفين الموثوق بهم فقط.
</p>

<p>
	هناك العديد من وسائل الحماية والتحذيرات لاستخدام الآليات السابقة، ولكننا نأمل أن نكون قد أعطيناك نظرةً عامةً على ما يقدمه جانغو، إذ يجب عليك أيضًا الاطلاع على <a href="https://docs.djangoproject.com/en/4.0/topics/security/" rel="external nofollow">توثيق أمان جانغو</a>.
</p>

<h2>
	الخلاصة
</h2>

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

<p>
	ترجمة -وبتصرُّف- للمقال <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/web_application_security" rel="external nofollow">Django web application security</a>.
</p>

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

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-10-%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%81%D9%8A-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%A7%D8%AC-r2122/" rel="">تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج</a>
	</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>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/advanced/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A3%D9%85%D8%A7%D9%86-%D9%85%D9%88%D8%A7%D9%82%D8%B9-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r2020/" rel="">تعرف على أمان مواقع الويب</a>.
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/topics/security/" rel="external nofollow">الأمان في جانغو</a> (في توثيق جانغو)
	</li>
</ul>
]]></description><guid isPermaLink="false">2123</guid><pubDate>Thu, 19 Oct 2023 13:07:06 +0000</pubDate></item><item><title>&#x62A;&#x637;&#x628;&#x64A;&#x642; &#x639;&#x645;&#x644;&#x64A; &#x644;&#x62A;&#x639;&#x644;&#x645; &#x62C;&#x627;&#x646;&#x63A;&#x648; - &#x627;&#x644;&#x62C;&#x632;&#x621; 10: &#x646;&#x634;&#x631; &#x62A;&#x637;&#x628;&#x64A;&#x642; &#x62C;&#x627;&#x646;&#x63A;&#x648; &#x641;&#x64A; &#x628;&#x64A;&#x626;&#x629; &#x627;&#x644;&#x625;&#x646;&#x62A;&#x627;&#x62C;</title><link>https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-10-%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%81%D9%8A-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%A7%D8%AC-r2122/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_09/-----.png.be85f2023dad72966e1c0aaee35f8b1e.png" /></p>
<p>
	أنشأنا واختبرنا موقع <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2090/" rel="">المكتبة المحلية LocalLibrary</a>، ويجب الآن تثبيته على خادم ويب عام ليصل إليه موظفو المكتبة وأعضاؤها عبر الإنترنت، لذا يقدّم هذا المقال نظرةً عامةً حول كيفية البحث عن مضيف لنشر موقعك، وما عليك تطبيقه لتجهيز موقعك لمرحلة الإنتاج.
</p>

<ul>
	<li>
		<strong>المتطلبات الأساسية</strong>: اطّلع على جميع المقالات السابقة من هذه السلسلة، بما في ذلك المقال الخاص <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AA%D8%A7%D8%B3%D8%B9-%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2121/" rel="">باختبار تطبيق جانغو</a>.
	</li>
	<li>
		<strong>الهدف</strong>: معرفة مكان وكيفية نشر تطبيق جانغو في بيئة الإنتاج.
	</li>
</ul>

<p>
	يجب استضافة موقعك بعد الانتهاء منه (أو الانتهاء منه "بما يكفي" لبدء الاختبار العام) في مكان أعم ويمكن الوصول إليه من أجهزة أخرى غير حاسوبك الشخصي الخاص بالتطوير.
</p>

<p>
	عملتَ حتى الآن في بيئة تطوير باستخدام خادم ويب تطوير جانغو <a href="https://academy.hsoub.com/programming/python/django/" rel="">Django</a> لمشاركة موقعك على المتصفح أو الشبكة المحلية، وشغّلتَ موقعك باستخدام إعدادات تطوير غير آمنة، بحيث يمكن الوصول إلى معلومات تنقيح الأخطاء والمعلومات الخاصة الأخرى. ينبغي عليك أولًا تنفيذ الخطوات التالية قبل أن تتمكّن من استضافة موقع ويب خارجيًا:
</p>

<ul>
	<li>
		إجراء بعض التغييرات على إعدادات مشروعك.
	</li>
	<li>
		اختيار بيئة لاستضافة تطبيق جانغو.
	</li>
	<li>
		اختيار بيئة لاستضافة أيّ ملفات ثابتة.
	</li>
	<li>
		إعداد بنية تحتية على مستوى بيئة الإنتاج لتخديم موقع الويب.
	</li>
</ul>

<p>
	يقدّم هذا المقال بعض الإرشادات حول الخيارات المتاحة لاختيار موقع استضافة، ونظرة عامة مختصرة على ما يجب تطبيقه لتجهيز تطبيق جانغو لبيئة الإنتاج، ومثالًا عمليًا لكيفية تثبيت موقع المكتبة المحلية LocalLibrary على خدمة الاستضافة السحابية <a href="https://railway.app/" rel="external nofollow">Railway</a>.
</p>

<p>
	تتألف هذه السلسلة الفرعية من السلسلة الأشمل <a href="https://academy.hsoub.com/tags/%D8%AA%D8%B9%D9%84%D9%85%20%D8%AA%D8%B7%D9%88%D9%8A%D8%B1%20%D8%A7%D9%84%D9%88%D9%8A%D8%A8/" rel="">تعلم تطوير الويب</a> من المقالات التالية:
</p>

<ul>
	<li>
		<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>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%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%AC%D8%A7%D9%86%D8%BA%D9%88-django-r2049/" rel="">إعداد بيئة تطوير تطبيقات جانغو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2090/" rel="">تطبيق عملي لتعلم جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%86%D9%85%D8%A7%D8%B0%D8%AC-models-r2092/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D9%85%D9%88%D9%82%D8%B9-%D9%85%D8%AF%D9%8A%D8%B1-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-admin-r2105/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B1%D8%A7%D8%A8%D8%B9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B5%D9%81%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%A7%D9%84%D8%B1%D8%A6%D9%8A%D8%B3%D9%8A%D8%A9-r2106/" rel="">تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AE%D8%A7%D9%85%D8%B3-%D8%A7%D9%84%D8%B9%D8%B1%D9%88%D8%B6-views-%D8%A7%D9%84%D8%B9%D8%A7%D9%85%D8%A9-%D9%88%D8%A7%D9%84%D8%AA%D9%81%D8%B5%D9%8A%D9%84%D9%8A%D8%A9-r2107/" rel="">تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B3%D8%A7%D8%AF%D8%B3-%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D8%AC%D9%84%D8%B3%D8%A7%D8%AA-sessions-r2118/" rel="">تطبيق عملي لتعلم جانغو - الجزء السادس: إدارة الجلسات Sessions</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B3%D8%A7%D8%A8%D8%B9-%D8%A7%D8%B3%D8%AA%D9%8A%D8%AB%D8%A7%D9%82-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D9%8A%D9%86-%D9%88%D8%A3%D8%B0%D9%88%D9%86%D8%A7%D8%AA%D9%87%D9%85-r2119/" rel="">تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%85%D9%86-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-forms-r2120/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AA%D8%A7%D8%B3%D8%B9-%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2121/" rel="">تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو</a>
	</li>
	<li>
		<span ipsnoautolink="true">تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج</span>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A3%D9%85%D8%A7%D9%86-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2123/" rel="">تعرف على أمان تطبيقات جانغو</a>
	</li>
</ul>

<h2>
	ما هي بيئة الإنتاج؟
</h2>

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

<ul>
	<li>
		عتاد الحاسوب الذي يعمل عليه الموقع.
	</li>
	<li>
		نظام التشغيل، مثل لينكس وويندوز.
	</li>
	<li>
		وقت التشغيل الخاص بلغة البرمجة ومكتبات أطر العمل التي كُتِب عليها موقعك.
	</li>
	<li>
		خادم الويب المُستخدَم لتخديم الصفحات والمحتويات الأخرى، مثل <a href="https://academy.hsoub.com/devops/servers/web/nginx/%D9%81%D9%8A%D8%AF%D9%8A%D9%88-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%88%D8%B6%D8%A8%D8%B7-%D8%AE%D8%A7%D8%AF%D9%85-nginx-r401/" rel="">خادم Nginx</a> و<a href="https://academy.hsoub.com/devops/servers/web/apache/%D9%81%D9%8A%D8%AF%D9%8A%D9%88-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%88%D8%B6%D8%A8%D8%B7-%D8%AE%D8%A7%D8%AF%D9%85-apache-r407/" rel="">أباتشي Apache</a>.
	</li>
	<li>
		خادم التطبيق الذي يمرّر الطلبات "الديناميكية" بين موقع جانغو وخادم الويب.
	</li>
	<li>
		قواعد البيانات التي يعتمد عليها موقعك.
	</li>
</ul>

<p>
	<strong>ملاحظة</strong>: يمكن أن يكون لديك أيضًا وكيل عكسي Reverse Proxy و<a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%A7-%D8%A7%D9%84%D9%85%D9%82%D8%B5%D9%88%D8%AF-%D8%A8%D8%AA%D9%88%D8%B2%D9%8A%D8%B9-%D8%A7%D9%84%D8%AD%D9%90%D9%85%D9%84-load-balancing-r319/" rel="">موازن حِمل Load Balancer</a> وغير ذلك بناءً على كيفية ضبط بيئتك الإنتاجية. يمكنك الاطلاع على المقالين التاليين الموجودين على موقع أكاديمية حسوب لمزيد من المعلومات حول الوكيل العكسي، وهي: <a href="https://academy.hsoub.com/devops/servers/web/nginx/%D9%83%D9%8A%D9%81%D9%8A%D9%91%D8%A9-%D8%B6%D8%A8%D8%B7-nginx-%D9%84%D9%84%D8%B9%D9%85%D9%84-%D9%83%D8%AE%D8%A7%D8%AF%D9%85-%D9%88%D9%8A%D8%A8-%D9%88%D9%83%D9%88%D8%B3%D9%8A%D8%B7-%D8%B9%D9%83%D8%B3%D9%8A-%D9%84%D9%80-apache-%D8%B9%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-ubuntu-1804-r396/" rel="">كيفيّة ضبط Nginx للعمل كخادم ويب وكوسيط عكسي لـ Apache على خادم Ubuntu 18.04</a> و<a href="https://academy.hsoub.com/devops/servers/web/apache/%D9%83%D9%8A%D9%81-%D8%AA%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-apache-%D9%84%D9%8A%D8%B9%D9%85%D9%84-%D9%88%D8%B3%D9%8A%D8%B7%D9%8B%D8%A7-%D8%B9%D9%83%D8%B3%D9%8A%D9%91%D9%8B%D8%A7-reverse-proxy-%D8%B9%D9%84%D9%89-ubuntu-1604-r339/" rel="">كيف تستخدم Apache ليعمل وسيطًا عكسيًّا Reverse Proxy على Ubuntu 16.04</a>.
</p>

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

<p>
	يُشار إلى هذا النوع من عتاد المعالجة والتشبيك الذي يمكن الوصول إليه عن بُعد باسم البنية التحتية مثل خدمة Infrastructure as a Service -أو IaaS اختصارًا. يوفّر العديد من بائعي خدمة IaaS خيارات التثبيت المُسبَق لنظام تشغيل معين، والذي يجب تثبيت مكونات بيئتك الإنتاجية الأخرى عليه، ويسمح لك البائعون الآخرون باختيار بيئات كاملة الميزات، ويمكن أن يتضمن ذلك الإعداد الكامل لجانغو وخادم الويب.
</p>

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

<p>
	يدعم مزوّدو الاستضافة الآخرون إطار عمل جانغو بوصفه جزءًا من استضافة المنصة مثل خدمة Platform as a Service -أو PaaS اختصارًا، فلا داعٍ للقلق في هذا النوع من الاستضافة بشأن معظم أجزاء بيئتك الإنتاجية، أي خادم الويب وخادم التطبيق وموازن الحِمل لأن المنصة المضيفة تهتم بهذه الأشياء نيابةً عنك، إضافةً إلى أنها تهتم بمعظم ما يجب تطبيقه لتوسيع نطاق تطبيقك، مما يجعل النشر سهلًا جدًا، لأنك تحتاج فقط إلى التركيز على تطبيق الويب وليس على كامل بنية الخادم التحتية الأخرى.
</p>

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

<p>
	<strong>ملاحظة</strong>: إذا اخترت مزوّد استضافة متوافق مع بايثون/جانغو، فيجب أن يقدّم إرشادات حول كيفية إعداد موقع جانغو باستخدام عمليات ضبط مختلفة لخادم الويب وخادم التطبيق والوكيل العكسي وغير ذلك، ولكن لا يتطابق ذلك مع اختيارك لاستضافة PaaS.
</p>

<h2>
	اختيار مزود الاستضافة
</h2>

<p>
	يوجد أكثر من 100 مزوّد استضافة معروفين يدعمون جانغو بنشاط أو يعملون بصورة جيدة معه، ويمكنك العثور على قائمة كبيرة إلى حدٍ ما في <a href="https://djangofriendly.com/index.html" rel="external nofollow">مضيفي DjangoFriendly</a>، ويوفّر هؤلاء البائعون أنواعًا مختلفة من البيئات IaaS و PaaS، ومستويات مختلفة من موارد المعالجة والشبكات بأسعار مختلفة.
</p>

<p>
	إليك بعض الأشياء التي يجب مراعاتها عند اختيار مضيف:
</p>

<ul>
	<li>
		مدى انشغال موقعك وتكلفة البيانات وموارد المعالجة المطلوبة لتلبية هذا المطلب.
	</li>
	<li>
		مستوى الدعم للتوسّع أفقيًا (إضافة المزيد من الأجهزة) وعموديًا (الترقية إلى أجهزة أقوى) وتكاليف ذلك.
	</li>
	<li>
		مكان مراكز بيانات المزوّد، أي المكان الذي يكون الوصول إليه أسرع.
	</li>
	<li>
		أداء وقت التشغيل ووقت التعطل السابقين للمضيف.
	</li>
	<li>
		الأدوات المتوفرة لإدارة الموقع، لمعرفة إذا كانت سهلة الاستخدام وآمنة، مثل استخدام <a href="https://academy.hsoub.com/devops/linux/%D9%83%D9%8A%D9%81-%D8%AA%D8%B3%D8%AA%D8%AE%D8%AF%D9%90%D9%85-sftp-%D9%84%D9%86%D9%82%D9%84-%D8%A7%D9%84%D9%85%D9%84%D9%81%D9%91%D8%A7%D8%AA-%D8%A8%D8%A3%D9%85%D8%A7%D9%86-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%88%D9%85-%D8%A8%D8%B9%D9%8A%D8%AF-r30/" rel="">بروتوكول SFTP</a> أو استخدام <a href="https://academy.hsoub.com/questions/17807-%D9%83%D9%8A%D9%81-%D9%86%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-%D8%A8%D8%B1%D9%88%D8%AA%D9%88%D9%83%D9%88%D9%84-sftp-%D9%88%D9%85%D8%A7-%D9%81%D8%A7%D8%A6%D8%AF%D8%AA%D9%87%D8%9F/" rel="">بروتوكول FTP</a>.
	</li>
	<li>
		أطر العمل المبنية مسبقًا لمراقبة خادمك.
	</li>
	<li>
		القيود المعروفة، إذ سيوقِف بعض المضيفين عمدًا خدمات معينة مثل البريد الإلكتروني، ويقدّم البعض الآخر فقط عددًا معينًا من ساعات "النشاط" في بعض مستويات الأسعار، أو يقدّم فقط قدرًا صغيرًا من التخزين.
	</li>
	<li>
		الفوائد الإضافية، إذ ستقدّم بعض المزوّدات أسماء نطاقات مجانية ودعمًا <a href="https://academy.hsoub.com/devops/servers/web/apache/%D9%83%D9%8A%D9%81-%D8%AA%D9%86%D8%B4%D8%A6-%D8%B4%D9%87%D8%A7%D8%AF%D8%A9-ssl-%D9%85%D9%88%D9%91%D9%8E%D9%82%D8%B9%D8%A9-%D8%B0%D8%A7%D8%AA%D9%8A%D9%91%D9%8B%D8%A7-%D9%84%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85%D9%87%D8%A7-%D9%85%D8%B9-%D8%AE%D8%A7%D8%AF%D9%88%D9%85-%D8%A7%D9%84%D9%88%D9%90%D8%A8-apache-%D8%B9%D9%84%D9%89-%D8%A3%D9%88%D8%A8%D9%88%D9%86%D8%AA%D9%88-1604-r325/" rel="">لشهادات <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة"><abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr></abbr></a> التي يمكن أن يتعيّن عليك دفع ثمنها.
	</li>
	<li>
		معرفة ما إذا كان المستوى "المجاني" الذي تعتمد عليه تنتهي صلاحيته بمرور الوقت، وما إذا كانت تكلفة التهجير Migrating إلى مستوًى أغلى تعني أنه كان من الأفضل استخدام بعض الخدمات الأخرى من البداية.
	</li>
</ul>

<p>
	هناك عددٌ قليل جدًا من المواقع التي توفر بيئات معالجة "مجانية" مخصصة للتقييم والاختبار في البداية، وتكون عادةً بيئات مُقيَّدة أو محدودة الموارد إلى حدٍ ما، ويجب أن تدرك أنه يمكن أن تنتهي صلاحيتها بعد فترة أولية أو يكون لديها قيود أخرى، ولكنها رائعة لاختبار المواقع ذات حركة المرور المنخفضة في بيئة مُستضافة، ويمكن أن توفر تهجيرًا سهل الدفع مقابل المزيد من الموارد عندما يصبح موقعك أكثر انشغالًا. تشمل الخيارات الشائعة في هذه الفئة <a href="https://railway.app/" rel="external nofollow">Railway</a> و <a href="https://academy.hsoub.com/programming/python/flask/%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-flask-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-pythonanywhere-r423/" rel="">Python Anywhere</a> و <a href="https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/billing-free-tier.html" rel="external nofollow">Amazon Web Services</a> و <a href="https://azure.microsoft.com/en-us/pricing/details/app-service/windows/" rel="external nofollow">Microsoft Azure</a> وغير ذلك.
</p>

<p>
	تقدّم معظم المزوّدات مستوًى أساسيًا مخصصًا لمواقع الإنتاج الصغيرة، والذي يوفّر مستويات أكثر فائدة من قدرة المعالجة وقيودًا أقل. يُعَد <a href="https://www.heroku.com/" rel="external nofollow">Heroku</a> و <a href="https://www.digitalocean.com/" rel="external nofollow">Digital Ocean</a> و <a href="https://www.pythonanywhere.com/" rel="external nofollow">Python Anywhere</a> أمثلةً على مزودات الاستضافة المشهورة التي لديها مستوى معالجة أساسي غير مكلف نسبيًا (في نطاق يتراوح بين 5 و 10 دولارات أمريكية شهريًا).
</p>

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

<h2>
	تجهيز موقعك للنشر
</h2>

<p>
	ضُبِط <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2090/" rel="">موقع جانغو الهيكلي</a> الذي أنشأناه باستخدام أدوات "django-admin" و "manage.py" لتسهيل عملية التطوير، ويجب أن تكون العديد من إعدادات مشروع جانغو المُحدَّدة في الملف "settings.py" مختلفةً لعملية الإنتاج لأسباب تتعلق بالأمان أو الأداء.
</p>

<p>
	<strong>ملاحظة</strong>: من الشائع أن يكون لديك ملف "settings.py" منفصل لعملية الإنتاج و/أو أن تستورد إعدادات حساسة استيرادًا مشروطًا من ملف منفصل أو متغير بيئة، ويجب بعد ذلك حماية هذا الملف، حتى ولو كانت بقية الشيفرة المصدرية متاحةً في مستودع عام.
</p>

<p>
	الإعدادات الأساسية التي يجب التحقق منها هي:
</p>

<ul>
	<li>
		<code>DEBUG</code>: ينبغي ضبطه على القيمة <code>False</code> في مرحلة الإنتاج، أي <code>DEBUG = False</code>، مما يؤدي إلى إيقاف عرض تعقّب تنقيح الأخطاء ومعلومات المتغيرات الحساسة أو السرية.
	</li>
	<li>
		<code>SECRET_KEY</code>: هو قيمة عشوائية كبيرة تُستخدَم للحماية من <a href="https://academy.hsoub.com/programming/advanced/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A3%D9%85%D8%A7%D9%86-%D9%85%D9%88%D8%A7%D9%82%D8%B9-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r2020/" rel="">هجمات CSRF</a> وغير ذلك. من المهم ألّا يكون المفتاح المستخدم في عملية الإنتاج تحت سيطرة المصدر أو يمكن الوصول إليه خارج خادم الإنتاج، إذ يشير توثيق جانغو إلى أنه من الأفضل تحميله من متغير بيئة أو قراءته من ملف خادم فقط.
	</li>
</ul>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7053_8" style=""><span class="com"># اقرأ‫ المفتاح السري SECRET_KEY من متغير البيئة</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> os
SECRET_KEY </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">'SECRET_KEY'</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="kwd">with</span><span class="pln"> open</span><span class="pun">(</span><span class="str">'/etc/secret_key.txt'</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">
    SECRET_KEY </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">strip</span><span class="pun">()</span></pre>

<p>
	لنعدّل تطبيق المكتبة المحلية LocalLibrary حتى نقرأ متغيرات <code>SECRET_KEY</code> و <code>DEBUG</code> من متغيرات البيئة إذا كانت مُعرَّفة، وإلّا فاستخدم القيم الافتراضية في ملف الضبط.
</p>

<p>
	افتح الملف "‎/locallibrary/settings.py" وعطّل ضبط <code>SECRET_KEY</code> الأصلي وضِف الأسطر الجديدة الموضحة فيما يلي. لن تُحدَّد متغيرات بيئة للمفتاح أثناء عملية التطوير، لذلك ستُستخدَم القيمة الافتراضية. لا يهم ما هو المفتاح الذي تستخدمه هنا أو إذا تسرّب المفتاح، لأنك لن تستخدمه في عملية الإنتاج.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7053_10" style=""><span class="com"># تحذير أمني: حافظ على سرية المفتاح السري المُستخدَم في عملية الإنتاج</span><span class="pln">
</span><span class="com"># SECRET_KEY = "cg#p$g+j9tax!#a3cup@1$8obt2_+&amp;k3q+pmu)5%asj6yjpkag"</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> os
SECRET_KEY </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="pln">get</span><span class="pun">(</span><span class="str">'DJANGO_SECRET_KEY'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'cg#p$g+j9tax!#a3cup@1$8obt2_+&amp;k3q+pmu)5%asj6yjpkag'</span><span class="pun">)</span></pre>

<p>
	علّق بعد ذلك إعداد <code>DEBUG</code> الحالي وضِف السطر الجديد التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7053_12" style=""><span class="com"># تحذير أمني: لا تنفّذ أثناء تشغيل تنقيح الأخطاء في عملية الإنتاج</span><span class="pln">
</span><span class="com"># DEBUG = True</span><span class="pln">
DEBUG </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="pln">get</span><span class="pun">(</span><span class="str">'DJANGO_DEBUG'</span><span class="pun">,</span><span class="pln"> </span><span class="str">''</span><span class="pun">)</span><span class="pln"> </span><span class="pun">!=</span><span class="pln"> </span><span class="str">'False'</span></pre>

<p>
	سيكون <code>DEBUG</code> له القيمة <code>True</code> افتراضيًا، ولكنه سيكون <code>False</code> فقط عند ضبط قيمة متغير البيئة <code>DJANGO_DEBUG</code> على القيمة <code>False</code>.
</p>

<p>
	لاحظ أن متغيرات البيئة هي سلاسل نصية وليست من <a href="https://academy.hsoub.com/programming/python/%D9%81%D9%87%D9%85-%D8%A3%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-r720/" rel="">أنواع بايثون</a>، لذلك يجب موازنة السلاسل النصية، والطريقة الوحيدة لضبط المتغير <code>DEBUG</code> على القيمة <code>False</code> هي ضبطه على السلسلة النصية <code>False</code>.
</p>

<p>
	يمكنك ضبط متغير البيئة على القيمة "False" على نظام لينكس باستخدام الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7053_14" style=""><span class="pln">export DJANGO_DEBUG</span><span class="pun">=</span><span class="kwd">False</span></pre>

<p>
	توفّر <a href="https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/" rel="external nofollow">قائمة النشر</a> في توثيق جانغو قائمة كاملة من الإعدادات التي يمكن أن ترغب في تغييرها، ويمكنك سرد عددٍ منها باستخدام أمر الطرفية التالي:
</p>

<pre class="ipsCode">python3 manage.py check --deploy
</pre>

<h2>
	تطبيق عملي: تثبيت موقع المكتبة المحلية LocalLibrary على منصة Railway
</h2>

<p>
	يقدّم هذا القسم شرحًا عمليًا لكيفية تثبيت موقع المكتبة المحلية LocalLibrary على منصة Railway.
</p>

<h3>
	سبب استخدام Railway
</h3>

<p>
	اخترنا استخدام منصة Railway لعدة أسباب هي:
</p>

<ul>
	<li>
		<p>
			تمتلك Railway مستوًى مجانيًا <a href="https://docs.railway.app/reference/plans#starter-plan" rel="external nofollow">لخطة بداية</a> مع بعض القيود، إذ من المهم أن تكون بأسعار مقبولة لجميع المطورين.
		</p>
	</li>
	<li>
		<p>
			تهتم Railway بمعظم البنية التحتية، فلا حاجة للقلق بشأن الخوادم وموازني الحِمل والوكلاء العكسيين وغير ذلك، مما يجعل البدء أسهل بكثير.
		</p>
	</li>
	<li>
		<p>
			تركّز Railway على تجربة المطور للتطوير والنشر، مما يؤدي إلى وجود منحنى تعليمي أسرع وأسلس من العديد من البدائل الأخرى.
		</p>
	</li>
	<li>
		<p>
			تُعَد المهارات والمفاهيم التي ستتعلمها عند استخدام Railway قابلة للتحويل، إذ تمتلك Railway بعض الميزات الجديدة الممتازة، ولكن تستخدم خدماتُ الاستضافة الشائعة الأخرى العديد من الأفكار والأساليب نفسها.
		</p>
	</li>
	<li>
		<p>
			لا تؤثر قيود الخدمات والخطط على استخدامنا لمنصة Railway في مثالنا، فمثلًا:
		</p>

		<ul>
			<li>
				تقدّم خطة البداية 500 ساعة فقط من وقت النشر المستمر كل شهر و5 دولارات من الرصيد الذي يُستهلَك بناءً على الاستخدام، ويُعاد ضبط الساعات والرصيد ويجب إعادة نشر المشاريع في نهاية كل شهر. تعني هذه القيود أنه يمكنك تشغيل مثالنا بصورة مستمرة لمدة 21 يومًا تقريبًا، ويُعَد ذلك أكثر من كافٍ للتطوير والاختبار، ولكن لن تتمكّن من استخدام هذه الخطة لموقع حقيقي للإنتاج.
			</li>
			<li>
				تحتوي بيئة خطة البداية على 512 ميجابايت فقط من الذاكرة RAM و1 جيجابايت من ذاكرة التخزين، وهذا أكثر من كافٍ لمثالنا.
			</li>
			<li>
				لا توجد سوى منطقة واحدة مدعومة وهي الولايات المتحدة الأمريكية حاليًا، إذ يمكن أن تكون الخدمة خارج هذه المنطقة أبطأ أو تحظرها القوانين المحلية.
			</li>
			<li>
				يمكن العثور على قيود أخرى في <a href="https://docs.railway.app/reference/plans#starter-plan" rel="external nofollow">توثيق خطط Railway للدفع</a>.
			</li>
		</ul>
	</li>
	<li>
		<p>
			تبدو الخدمة موثوقة جدًا، وإذا كانت مناسبة لك، فإن الأسعار يمكن التنبؤ بها، ويكون توسيع تطبيقك سهلًا جدًا.
		</p>

		<p>
			تعَد Railway مناسبةً لاستضافة مثالنا، ولكن يجب أن تأخذ الوقت الكافي لتحديد ما إذا كانت مناسبة لموقعك.
		</p>
	</li>
</ul>

<h3>
	كيفية عمل Railway
</h3>

<p>
	يُشغَّل كل تطبيق ويب في حاوية افتراضية virtualized container معزولة ومستقلة خاصة به، لذا يجب أن تكون Railway قادرة على إعداد البيئة والاعتماديات المناسبة، وفهم كيفية إطلاقها من أجل تنفيذ تطبيقك. نقدّم هذه المعلومات في عددٍ من الملفات النصية بالنسبة لتطبيقات جانغو، وهي:
</p>

<ul>
	<li>
		runtime.txt: يوضح لغة البرمجة والنسخة المُراد استخدامها.
	</li>
	<li>
		requirements.txt: يسرد اعتماديات بايثون اللازمة لموقعك بما في ذلك جانغو.
	</li>
	<li>
		Procfile: قائمة العمليات التي ستُنفَّذ لبدء تطبيق الويب. سيكون هذا الملف عادةً في تطبيقات جانغو خادم تطبيق ويب Gunicorn مع سكربت <code>‎.wsgi</code>.
	</li>
	<li>
		wsgi.py: ضبط واجهة WSGI لاستدعاء تطبيق جانغو في بيئة Railway.
	</li>
</ul>

<p>
	يمكن للتطبيق بعد تشغيله ضبط نفسه باستخدام المعلومات المقدمة في <a href="https://docs.railway.app/develop/variables" rel="external nofollow">متغيرات البيئة</a>، فمثلًا يمكن للتطبيق الذي يستخدم قاعدة بيانات الحصول على العنوان باستخدام المتغير <code>DATABASE_URL</code>، ويمكن أن تستضيف Railway خدمة قاعدة البيانات نفسها أو على أي مزوّد آخر.
</p>

<p>
	يتفاعل المطورون مع Railway من خلال موقع Railway وباستخدام أداة <a href="https://docs.railway.app/develop/cli" rel="external nofollow">واجهة سطر أوامر CLI</a> خاصة، إذ تسمح لك واجهة CLI بربط مستودع غيت هب GitHub محلي بمشروع Railway، ورفع المستودع من الفرع المحلي إلى الموقع المباشر، وفحص سجلات العملية الجارية، وإعداد متغيرات الضبط والحصول عليها وغير ذلك. من أهم الميزات أنه يمكنك استخدام واجهة CLI لتشغيل مشروعك المحلي مع متغيرات البيئة نفسها للمشروع المباشر.
</p>

<p>
	يجب وضع تطبيق جانغو الخاص بنا في مستودع غيت git وإضافة الملفات السابقة والتكامل مع إضافة قاعدة البيانات وإجراء تغييرات للتعامل بصورة صحيحة مع الملفات الثابتة ليعمل تطبيقنا على Railway، ثم يمكننا إعداد حساب على Railway والحصول على عميل Railway وتثبيت موقعنا.
</p>

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

<h3>
	إنشاء مستودع للتطبيق على غيت هب GitHub
</h3>

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

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

<p>
	هناك العديد من الطرق للعمل مع غيت، ولكن من أسهل هذه الطرق إعداد حساب على غيت هب GitHub أولًا وإنشاء المستودع عليه، ثم المزامنة معه محليًا كما يلي:
</p>

<ol>
	<li>
		انتقل إلى موقع <a href="https://github.com/" rel="external nofollow">GitHub</a> الرسمي وأنشئ حسابًا عليه.
	</li>
	<li>
		انقر على رابط "+" في شريط الأدوات العلوي وحدّد خيار "مستودع جديد New repository" بعد تسجيل الدخول.
	</li>
	<li>
		املأ جميع الحقول في هذه الاستمارة، إذ يمكن أن تكون هذه الحقول غير إلزامية، ولكن يُوصَى بها بشدة.
		<ul>
			<li>
				أدخِل اسم المستودع الجديد والوصف، فمثلًا يمكنك استخدام الاسم "django<em>local</em>library" والوصف "Local Library website written in Django" -أي موقع المكتبة المحلية المكتوب باستخدام جانغو.
			</li>
			<li>
				اختر الخيار Python في قائمة الاختيار "Add .gitignore".
			</li>
			<li>
				اختر الترخيص المفضل لديك في قائمة الاختيار "Add license".
			</li>
			<li>
				تحقّق من تهيئة المستودع باستخدام README.
			</li>
		</ul>
	</li>
	<li>
		اضغط على إنشاء مستودع Create repository.
	</li>
	<li>
		انقر فوق الزر الأخضر نسخ Clone أو تنزيل Download في صفحة مستودعك الجديد.
	</li>
	<li>
		انسخ قيمة URL من حقل النص الموجود في مربع الحوار الذي يظهر. إذا استخدمت اسم المستودع "djangolocallibrary"، فيجب أن يكون عنوان URL مثل العنوان: "https://github.com/<your_git_user_id>/django_local_library.git".</your_git_user_id>
	</li>
</ol>

<p>
	انتهينا من إنشاء المستودع، ويجب الآن نسخه على الحاسوب المحلي باتباع الخطوات التالية:
</p>

<ol>
	<li>
		ثبّت غيت git على حاسوبك المحلي، إذ يمكنك العثور على <a href="https://git-scm.com/downloads" rel="external nofollow">نسخ لأنظمة مختلفة</a>.
	</li>
	<li>
		افتح موجه الأوامر أو الطرفية وانسخ مستودعك باستخدام عنوان URL الذي نسخته سابقًا كما في الأمر التالي:
	</li>
</ol>

<pre class="ipsCode">git clone https://github.com/&lt;your_git_user_id&gt;/django_local_library.git
</pre>

<p>
	سيؤدي هذا الأمر إلى إنشاء المستودع في مجلد جديد في مجلد العمل الحالي.
</p>

<ol start="3">
	<li>
		انتقل إلى المستودع الجديد باستخدام الأمر التالي:
	</li>
</ol>

<pre class="ipsCode">cd django_local_library
</pre>

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

<p>
	أولًا، انسخ تطبيق جانغو في هذا المجلد. جميع الملفات موجودة في مستوى الملف manager.py نفسه وأدنى منه، وليست بمستوى المجلد locallibrary الذي يحتوي عليها.
</p>

<p>
	ثانيًا، افتح ملف "‎.gitignore" وانسخ الأسطر التالية في نهايته، ثم احفظه، إذ يُستخدم هذا الملف لتحديد الملفات التي لا يجب تحميلها إلى غيت افتراضيًا.
</p>

<pre class="ipsCode"># الملفات النصية الاحتياطية
*.bak

# قاعدة البيانات
*.sqlite3
</pre>

<p>
	ثالثًا، افتح موجه الأوامر أو الطرفية واستخدم الأمر <code>add</code> لإضافة جميع الملفات إلى غيت، مما يؤدي إلى إضافة الملفات التي لا يتجاهلها ملف "‎.gitignore" إلى "منطقة مرحلة التحضير Staging Area".
</p>

<pre class="ipsCode">git add -A
</pre>

<p>
	رابعًا، استخدم الأمر <code>status</code> للتحقق من صحة جميع الملفات التي تريد تثبيتها <code>commit</code>، إذ نريد تضمين الملفات المصدرية، وليس الملفات الثنائية والمؤقتة وما إلى ذلك، إذ يجب أن تبدو كما يلي:
</p>

<pre class="ipsCode">&gt; git status
On branch main
Your branch is up-to-date with 'origin/main'.
Changes to be committed:
  (use "git reset HEAD &lt;file&gt;..." to unstage)

        modified:   .gitignore
        new file:   catalog/__init__.py
        ...
        new file:   catalog/migrations/0001_initial.py
        ...
        new file:   templates/registration/password_reset_form.html
</pre>

<p>
	خامسًا، ثبّت باستخدام <code>commit</code> الملفات في مستودعك المحلي عندما تكون راضيًا عن النتيجة، إذ يكافئ ذلك الإقرار بالتغييرات وجعلها جزءًا رسميًا من المستودع المحلي.
</p>

<pre class="ipsCode">git commit -m "First version of application moved into GitHub"
</pre>

<p>
	سادسًا، لم يتغيّر المستودع البعيد حتى الآن، لذا يجب مزامنة مستودعك المحلي مع مستودع غيت هب البعيد باستخدام الأمر <code>push</code>:
</p>

<pre class="ipsCode">git push origin main
</pre>

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

<p>
	<strong>ملاحظة</strong>: يمكنك الآن إنشاء نسخة احتياطية من شيفرة مشروعك البرمجية، إذ يمكن أن تكون بعض التغييرات التي سنجريها في الأقسام التالية مفيدة للنشر أو التطوير على أيّ نظام، ويمكن أن تكون بعض التغييرات الأخرى غير مفيدة. أفضل طريقة لذلك هي استخدام غيت لإدارة الإصدارات، إذ يمكنك باستخدامه الرجوع إلى إصدار قديم معين، ويمكنك الاحتفاظ به في فرع منفصل عن التغييرات الإنتاجية واختيار أيّ تغييرات للتنقل بين فروع الإنتاج والتطوير. يستحق تعلم غيت الجهد المبذول، لذا اطلع على مجموعة مقالات <a href="https://academy.hsoub.com/programming/workflow/git/" rel="">Git</a> في أكاديمية حسوب لمزيدٍ من المعلومات حول ذلك. يُعَد نسخ ملفاتك في موقع آخر الطريقة الأسهل، ولكن يمكنك استخدام أيّ طريقة تتناسب مع معرفتك لنظام غيت.
</p>

<h3>
	تحديث التطبيق ليعمل على Railway
</h3>

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

<h4>
	الملف Procfile
</h4>

<p>
	يُعَد الملف Procfile "نقطة الدخول" إلى تطبيق الويب، فهو يسرد الأوامر التي ستنفّذها Railway لبدء موقعك. لذا أنشئ ملف <code>Procfile</code> دون امتداد في جذر مستودع غيت هب وضع فيه النص التالي:
</p>

<pre class="ipsCode">web: python manage.py migrate &amp;&amp; python manage.py collectstatic --no-input &amp;&amp; gunicorn locallibrary.wsgi
</pre>

<p>
	تخبر البادئة <code>web:‎</code> منصة Railway أن هذه عملية ويب ويمكن إرسالها عبر حمولة HTTP، ثم نستدعي أمر تهجير جانغو <code>python manage.py migrate</code> لإعداد جداول قاعدة البيانات، ثم نستدعي أمر جانغو <code>python manage.py collectstatic</code> لتجميع الملفات الثابتة في المجلد الذي حدّده إعداد مشروع <code>STATIC_ROOT</code> الذي سنتعرّف عليه لاحقًا. أخيرًا، نبدأ عملية gunicorn الذي هو خادم تطبيقات ويب شائع، ونمرّر له معلومات الضبط في الوحدة <code>locallibrary.wsgi</code> التي أُنشئت باستخدام تطبيقنا الهيكلي: "‎/locallibrary/wsgi.py".
</p>

<p>
	لاحظ أنه يمكنك أيضًا استخدام الملف Procfile لبدء العمليات العاملة أو لتشغيل مهام أخرى غير تفاعلية قبل نشر الإصدار.
</p>

<h4>
	خادم Gunicorn
</h4>

<p>
	<a href="https://gunicorn.org/" rel="external nofollow">Gunicorn</a> هو خادم HTTP من بايثون يُستخدَم بصورة شائعة لتخديم تطبيقات جانغو WSGI على Railway كما أشرنا في فقرة ملف Procfile السابقة.
</p>

<p>
	لسنا بحاجة خادم Gunicorn لتخديم تطبيق المكتبة المحلية LocalLibrary أثناء عملية التطوير، ولكننا سنثبّته محليًا، بحيث يصبح جزءًا من متطلباتنا التي تعِدّها Railway على الخادم البعيد.
</p>

<p>
	تأكّد أولًا من أنك في بيئة بايثون الافتراضية التي أُنشِئت عند <a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%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%AC%D8%A7%D9%86%D8%BA%D9%88-django-r2049/" rel="">إعداد بيئة التطوير</a>. استخدم الأمر <code>workon [name-of-virtual-environment]‎</code>، ثم ثبّت الخادم Gunicorn محليًا في سطر الأوامر باستخدام الأداة <code>pip</code> كما يلي:
</p>

<pre class="ipsCode">pip3 install gunicorn
</pre>

<h4>
	ضبط قاعدة البيانات
</h4>

<p>
	تُعَد <a href="https://academy.hsoub.com/devops/servers/databases/%D9%85%D9%82%D8%A7%D8%B1%D9%86%D8%A9-%D8%A8%D9%8A%D9%86-%D8%A3%D9%86%D8%B8%D9%85%D8%A9-%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-%D8%A7%D9%84%D8%B9%D9%84%D8%A7%D9%82%D9%8A%D8%A9-sqlite-%D9%85%D8%B9-mysql-%D9%85%D8%B9-postgresql-r72/" rel="">SQLite</a> قاعدة بيانات جانغو الافتراضية التي استخدمناها لعملية التطوير، وهي خيار مقبول للمواقع الصغيرة إلى المتوسطة، ولكن لا يمكن استخدامها في بعض خدمات الاستضافة الشائعة مثل Heroku، لأنها لا توفر تخزينًا دائمًا للبيانات في بيئة التطبيق (أحد متطلبات SQLite). يمكن ألّا يؤثّر ذلك علينا في Railway، ولكننا سنعرض لك طريقةً أخرى تعمل على Railway و Heroku وبعض الخدمات الأخرى.
</p>

<p>
	تتمثل هذه الطريقة في استخدام قاعدة بيانات تُشغَّل ضمن عمليتها الخاصة في مكانٍ ما على الإنترنت ويصل إليها تطبيق مكتبة جانغو باستخدام عنوان مُمرَّر بوصفه متغير بيئة، إذ سنستخدم في هذه الحالة قاعدة بيانات <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="">Postgres</a> التي نستضيفها أيضًا Railway، ولكن يمكنك استخدام خدمة استضافة قاعدة البيانات التي تريدها.
</p>

<p>
	سنوفّر معلومات اتصال قاعدة البيانات لجانغو باستخدام متغير البيئة <code>DATABASE_URL</code>، إذ سنستخدم حزمة <a href="https://pypi.org/project/dj-database-url/" rel="external nofollow">dj-database-url</a> بدلًا من تثبيت هذه المعلومات في جانغو لتحليل متغير البيئة <code>DATABASE_URL</code> وتحويله تلقائيًا إلى تنسيق ضبط جانغو المطلوب، ويجب أيضًا تثبيت <a href="https://www.psycopg.org/" rel="external nofollow">psycopg2</a> الذي يحتاجه جانغو للتفاعل مع قواعد بيانات Postgres.
</p>

<h5>
	حزمة dj-database-url
</h5>

<p>
	تُستخدَم حزمة dj-database-url لاستخراج ضبط قاعدة بيانات جانغو من متغير البيئة. ثبّت هذه الحزمة محليًا بحيث تصبح جزءًا من متطلباتنا التي تُعِّدها Railway على الخادم البعيد كما يلي:
</p>

<pre class="ipsCode">pip3 install dj-database-url
</pre>

<h5>
	الملف settings.py
</h5>

<p>
	افتح الملف ‎/locallibrary/settings.py وانسخ الضبط التالي وضعه في نهاية الملف:
</p>

<pre class="ipsCode"># ‫تحديث ضبط قاعدة البيانات من ‎$DATABASE_URL
import dj_database_url
db_from_env = dj_database_url.config(conn_max_age=500)
DATABASES['default'].update(db_from_env)
</pre>

<p>
	سيستخدم جانغو الآن ضبط قاعدة البيانات الموجود في <code>DATABASE_URL</code> إذا كان متغير البيئة مضبوطًا، وإلّا فإنه يستخدم قاعدة بيانات SQLite الافتراضية. تجعل القيمة <code>conn_max_age=500</code> الاتصال مستمرًا، وهو أكثر كفاءة بكثير من إعادة إنشاء الاتصال في كل دورة طلب، ويُعَد ذلك اختياريًا ويمكن إزالته عند الحاجة.
</p>

<h5>
	psycopg2
</h5>

<p>
	يحتاج جانغو إلى psycopg2 للعمل مع قواعد بيانات Postgres، لذا ثبّته محليًا بحيث يصبح جزءًا من متطلباتنا التي تُعِّدها Railway على الخادم البعيد كما يلي:
</p>

<pre class="ipsCode">pip3 install psycopg2-binary
</pre>

<p>
	لاحظ أن جانغو سيستخدم قاعدة بيانات SQLite أثناء عملية التطوير افتراضيًا إذا لم يُضبَط متغير البيئة <code>DATABASE_URL</code>، ولكن يمكنك التبديل إلى قاعدة بيانات Postgres واستخدام قاعدة البيانات المُستضافة نفسها لعمليتي التطوير والإنتاج من خلال ضبط متغير البيئة نفسه في بيئة التطوير، إذ تسهّل Railway استخدام البيئة نفسها للإنتاج والتطوير. يمكنك بدلًا من ذلك تثبيت واستخدام <a href="https://www.psycopg.org/docs/install.html" rel="external nofollow">قاعدة بيانات Postgres ذاتية الاستضافة</a> على حاسوبك المحلي.
</p>

<h5>
	تخديم الملفات الثابتة في عملية الإنتاج
</h5>

<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> والملفات الثابتة، مثل ملفات <a href="https://wiki.hsoub.com/CSS" rel="external">CSS</a> و<a href="https://wiki.hsoub.com/JavaScript" rel="external">جافا سكريبت JavaScript</a> وغيرها، ولكن يُعَد ذلك غير فعال للملفات الثابتة، لأن الطلبات يجب أن تمر عبر جانغو بالرغم من أن جانغو لا يفعل أيّ شيء بها. لا يُعَد ذلك مهمًا أثناء عملية التطوير، إلّا أنه سيكون له تأثير كبير على الأداء إذا استخدمنا الطريقة نفسها في عملية الإنتاج.
</p>

<p>
	نفصل عادةً الملفات الثابتة عن تطبيق جانغو في بيئة الإنتاج، مما يسهل تخديمها مباشرةً من خادم الويب أو من <a href="https://academy.hsoub.com/devops/networking/%D8%B4%D8%A8%D9%83%D8%A7%D8%AA-%D8%AA%D9%88%D8%B2%D9%8A%D8%B9-%D8%A7%D9%84%D9%85%D8%AD%D8%AA%D9%88%D9%89-content-distribution-networks-r569/" rel="">شبكة توصيل المحتوى Content Delivery Network</a> -أو CDN اختصارًا.
</p>

<p>
	إليك متغيرات الإعداد المهمة التي يجب تضعها في حساباتك:
</p>

<ul>
	<li>
		<code>STATIC_URL</code>: موقع عنوان URL الأساسي الذي ستُخدَّم منه الملفات الثابتة مثل شبكة توصيل المحتوى CDN.
	</li>
	<li>
		<code>STATIC_ROOT</code>: المسار المطلق للمجلد الذي ستجمِّع فيه أداة جانغو <code>collectstatic</code> الملفات الثابتة المشار إليها في قوالبنا، ثم يمكن رفعها على أنها مجموعة إلى مكان استضافة الملفات.
	</li>
	<li>
		<code>STATICFILES_DIRS</code>: يسرد المجلدات الإضافية التي يجب أن تبحث فيها أداة جانغو <code>collectstatic</code> عن الملفات الثابتة.
	</li>
</ul>

<p>
	تشير قوالب جانغو إلى مواقع الملفات الثابتة المتعلقة بالوسم <code>static</code> (يمكنك رؤيتها في القالب الأساسي المُعَّرف في <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B1%D8%A7%D8%A8%D8%B9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B5%D9%81%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%A7%D9%84%D8%B1%D8%A6%D9%8A%D8%B3%D9%8A%D8%A9-r2106/" rel="">مقال إنشاء صفحتنا الرئيسية</a>)، والتي بدورها تربطها بإعداد <code>STATIC_URL</code>، وبالتالي يمكن رفع الملفات الثابتة إلى أيّ مضيف ويمكنك تحديث تطبيقك للعثور عليها باستخدام هذا الإعداد.
</p>

<p>
	تُستخدَم الأداة <code>collectstatic</code> لتجميع الملفات الثابتة في المجلد الذي يحدّده إعداد المشروع <code>STATIC_ROOT</code>، إذ تُستدعَى باستخدام الأمر التالي:
</p>

<pre class="ipsCode">python3 manage.py collectstatic
</pre>

<p>
	تشغِّل Railway أداة <code>collectstatic</code> في هذا المقال قبل رفع التطبيق، مما يؤدي إلى نسخ جميع الملفات الثابتة في التطبيق إلى الموقع المُحدَّد في <code>STATIC_ROOT</code>، ثم تجد مكتبة <code>Whitenoise</code> الملفات في الموقع الذي يحدّده <code>STATIC_ROOT</code> افتراضيًا وتخدّمها في عنوان URL الأساسي الذي يحدّده <code>STATIC_URL</code>.
</p>

<h5>
	الملف settings.py
</h5>

<p>
	افتح الملف "‎/locallibrary/settings.py" وانسخ الضبط الآتي في نهاية الملف، إذ يجب أن يكون <code>BASE_DIR</code> مُعرَّفًا مسبقًا في ملفك، ويمكن أن يكون <code>STATIC_URL</code> معرَّفًا ضمن الملف عند إنشائه، ويمكنك أيضًا حذف الإشارة إليه السابقة المكررة بالرغم من أنها لن تسبب أيّ ضرر.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7053_17" style=""><span class="com"># ‫الملفات الثابتة (ملفات CSS و JavaScript والصور)</span><span class="pln">
</span><span class="com"># https://docs.djangoproject.com/en/4.0/howto/static-files/</span><span class="pln">

</span><span class="com"># ‫مسار المجلد المطلق حيث ستجمع أداة collectstatic الملفات الثابتة للنشر.</span><span class="pln">
STATIC_ROOT </span><span class="pun">=</span><span class="pln"> BASE_DIR </span><span class="pun">/</span><span class="pln"> </span><span class="str">'staticfiles'</span><span class="pln">

</span><span class="com"># ‫عنوان URL المراد استخدامه عند الإشارة إلى الملفات الثابتة (أي من حيث ستُخدَّم)</span><span class="pln">
STATIC_URL </span><span class="pun">=</span><span class="pln"> </span><span class="str">'/static/'</span></pre>

<p>
	سنخدّم الملف باستخدام المكتبة <a href="https://pypi.org/project/whitenoise/" rel="external nofollow">WhiteNoise</a>، والتي سنثبّتها ونضبطها في القسم التالي.
</p>

<h4>
	مكتبة Whitenoise
</h4>

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

<p>
	تستدعي Railway أداة <code>collectstatic</code> لتحضير ملفاتك الثابتة لتستخدمها المكتبة WhiteNoise بعد رفع تطبيقك. اطّلع على <a href="https://pypi.org/project/whitenoise/" rel="external nofollow">توثيق المكتبة WhiteNoise</a> للحصول على شرح لكيفية عملها وسبب عَدّ عملية التقديم Implementation هذه طريقةً فعالة نسبيًا لتخديم هذه الملفات.
</p>

<p>
	سنوضح فيما يلي خطوات إعداد المكتبة WhiteNoise لاستخدامها مع المشروع.
</p>

<h5>
	تثبيت المكتبة whitenoise
</h5>

<p>
	ثبّت المكتبة whitenoise محليًا باستخدام الأمر التالي:
</p>

<pre class="ipsCode">pip3 install whitenoise
</pre>

<h5>
	الملف settings.py
</h5>

<p>
	افتح الملف "‎/locallibrary/settings.py"، وابحث عن الإعداد <code>MIDDLEWARE</code> وضف <code>WhiteNoiseMiddleware</code> بالقرب من أعلى القائمة وبعد <code>SecurityMiddleware</code> مباشرةً لتثبيت مكتبة WhiteNoise في تطبيق جانغو كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7053_19" style=""><span class="pln">MIDDLEWARE </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    </span><span class="str">'django.middleware.security.SecurityMiddleware'</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'whitenoise.middleware.WhiteNoiseMiddleware'</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'django.contrib.sessions.middleware.SessionMiddleware'</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'django.middleware.common.CommonMiddleware'</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'django.middleware.csrf.CsrfViewMiddleware'</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'django.contrib.auth.middleware.AuthenticationMiddleware'</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'django.contrib.messages.middleware.MessageMiddleware'</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'django.middleware.clickjacking.XFrameOptionsMiddleware'</span><span class="pun">,</span><span class="pln">
</span><span class="pun">]</span></pre>

<p>
	يمكنك اختياريًا تقليل حجم الملفات الثابتة عند تخديمها، إذ يُعَد ذلك فعّالًا أكثر، فما عليك سوى إضافة ما يلي في نهاية الملف "‎/locallibrary/settings.py":
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7053_21" style=""><span class="com"># تخديم ملف ثابت مبسط</span><span class="pln">
</span><span class="com"># https://pypi.org/project/whitenoise/</span><span class="pln">
STATICFILES_STORAGE </span><span class="pun">=</span><span class="pln"> </span><span class="str">'whitenoise.storage.CompressedManifestStaticFilesStorage'</span></pre>

<p>
	لست بحاجة إلى إجراء أيّ شيء آخر لضبط مكتبة WhiteNoise لأنها تستخدم إعدادات مشروعك لكل من <code>STATIC_ROOT</code> و <code>STATIC_URL</code> افتراضيًا.
</p>

<h4>
	ملف المتطلبات Requirements
</h4>

<p>
	يجب تخزين متطلبات بايثون لتطبيقك في الملف "requirements.txt" ضمن جذر مستودعك، ثم ستثبّت Railway بعد ذلك هذه المتطلبات تلقائيًا عندما تعيد بناء بيئتك، إذ يمكنك إنشاء هذا الملف باستخدام الأداة <code>pip</code> في سطر الأوامر.
</p>

<p>
	شغّل الأمر التالي في جذر المستودع:
</p>

<pre class="ipsCode">pip3 freeze &gt; requirements.txt
</pre>

<p>
	يجب أن يحتوي الملف "requirements.txt" على العناصر الآتية على الأقل بعد تثبيت جميع الاعتماديات السابقة (يمكن أن تكون أرقام النسخ مختلفة)، لذا احذف أيّ اعتماديات أخرى ليست موجودة فيما يلي، إن لم تضِفها صراحةً لتطبيقك:
</p>

<pre class="ipsCode">dj-database-url==0.5.0
Django==4.0.2
gunicorn==20.1.0
psycopg2-binary==2.9.3
wheel==0.37.1
whitenoise==6.0.0
</pre>

<h4>
	ملف وقت التشغيل Runtime
</h4>

<p>
	يخبر الملف runtime.txt -إذا كان مُعرَّفًا مسبقًا- منصةَ Railway بنسخة بايثون التي يجب استخدامها. أنشئ هذا الملف في جذر المستودع وضِف إليه النص التالي:
</p>

<pre class="ipsCode">python-3.10.2
</pre>

<p>
	<strong>ملاحظة</strong>: لا يدعم مزوّدو الاستضافة بالضرورة جميع النسخ الثانوية لوقت تشغيل بايثون، إذ سيستخدمون أقرب نسخة مدعومة للقيمة التي تحددها.
</p>

<h4>
	إعادة الاختبار وحفظ التغييرات في غيت هب
</h4>

<p>
	اختبر الموقع أولًا محليًا مرةً أخرى قبل المتابعة وتأكّد من عدم تعطله بسبب أيِّ من التغييرات التي ذكرناها سابقًا، لذا شغّل خادم الويب الخاص بالتطوير كما هو معتاد، ثم تحقق من أن الموقع لا يزال يعمل كما هو متوقع على متصفحك كما يلي:
</p>

<pre class="ipsCode">python3 manage.py runserver
</pre>

<p>
	لندفع باستخدام الأمر <code>push</code> بعد ذلك التغييرات إلى غيت هب، لذا أدخِل الأوامر التالية في الطرفية بعد الانتقال إلى مستودعنا المحلي:
</p>

<pre class="ipsCode">git add -A
git commit -m "Added files and changes required for deployment"
git push origin main
</pre>

<p>
	يجب أن نكون جاهزين الآن لبدء نشر موقع المكتبة المحلية LocalLibrary على Railway.
</p>

<h3>
	الحصول على حساب على Railway
</h3>

<p>
	يجب أولًا إنشاء حساب لبدء استخدام Railway باتباع الخطوات التالية:
</p>

<ul>
	<li>
		اذهب إلى <a href="https://railway.app/" rel="external nofollow">موقع Railway الرسمي</a> وانقر على رابط تسجيل الدخول Login في شريط الأدوات العلوي.
	</li>
	<li>
		اختر "GitHub" في النافذة المنبثقة لتسجيل الدخول باستخدام اعتماديات غيت هب الخاصة بك.
	</li>
	<li>
		يمكن أن تحتاج بعد ذلك إلى الانتقال إلى بريدك الإلكتروني والتحقق من حسابك.
	</li>
	<li>
		ستسجل بعد ذلك الدخول إلى <a href="https://railway.app/dashboard" rel="external nofollow">لوحة تحكم Railway</a>.
	</li>
</ul>

<h3>
	النشر على Railway من غيت هب
</h3>

<p>
	يجب الآن إعداد Railway لنشر موقع مكتبتنا من غيت هب، لذا اختر أولًا خيار لوحة التحكم Dashboard من القائمة العلوية للموقع، ثم حدّد زر مشروع جديد New Project:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135494" href="https://academy.hsoub.com/uploads/monthly_2023_09/01_railway_new_project_button.png.98e334c8714557e3511efcb6f4691c87.png" rel=""><img alt="01_railway_new_project_button.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135494" data-unique="thjezqmgd" src="https://academy.hsoub.com/uploads/monthly_2023_09/01_railway_new_project_button.png.98e334c8714557e3511efcb6f4691c87.png"> </a>
</p>

<p>
	ستعرض Railway قائمةً بالخيارات الخاصة بالمشروع الجديد، بما في ذلك خيار نشر مشروع من قالب أنشأته لأول مرة في حسابك على غيت هب، وعددًا من <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>، لذا حدّد خيار النشر "Deploy from GitHub repo".
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135493" href="https://academy.hsoub.com/uploads/monthly_2023_09/02_railway_new_project_button_deploy_github_repo.png.fbb4244ce70321c2ac0a56caeb8d9e31.png" rel=""><img alt="02_railway_new_project_button_deploy_github_repo.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135493" data-unique="ux073qy5g" src="https://academy.hsoub.com/uploads/monthly_2023_09/02_railway_new_project_button_deploy_github_repo.png.fbb4244ce70321c2ac0a56caeb8d9e31.png"> </a>
</p>

<p>
	ستُعرَض جميع المشاريع في مستودعات غيت هب التي شاركتها مع Railway أثناء عملية الإعداد، ولكنك ستختار مستودع غيت هب الخاص بموقع المكتبة المحلية: <code>‎&lt;user-name&gt;/django-locallibrary-tutorial</code>.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135492" href="https://academy.hsoub.com/uploads/monthly_2023_09/03_railway_new_project_button_deploy_github_selectrepo.png.4ce1bfe6c77898f7d45d40eacd70d66a.png" rel=""><img alt="03_railway_new_project_button_deploy_github_selectrepo.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135492" data-unique="7c1u3saq3" src="https://academy.hsoub.com/uploads/monthly_2023_09/03_railway_new_project_button_deploy_github_selectrepo.png.4ce1bfe6c77898f7d45d40eacd70d66a.png"> </a>
</p>

<p>
	أكّد النشر من خلال تحديد خيار النشر حالًا "Deploy Now".
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135491" href="https://academy.hsoub.com/uploads/monthly_2023_09/04_railway_new_project_deploy_confirm.png.0c6b8e13519dfb1939952d9eb2a75235.png" rel=""><img alt="04_railway_new_project_deploy_confirm.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135491" data-unique="wke3mrjoq" src="https://academy.hsoub.com/uploads/monthly_2023_09/04_railway_new_project_deploy_confirm.png.0c6b8e13519dfb1939952d9eb2a75235.png"> </a>
</p>

<p>
	ستحمّل Railway بعد ذلك مشروعك وتنشره، مع عرض التقدم في تبويب عمليات النشر Deployments، ثم سترى شيئًا يشبه ما يلي عند اكتمال النشر بنجاح:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135490" href="https://academy.hsoub.com/uploads/monthly_2023_09/05_railway_project_deploy.png.066179eaefc88f664eea8351c1c5d487.png" rel=""><img alt="05_railway_project_deploy.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135490" data-unique="mt2n5b839" src="https://academy.hsoub.com/uploads/monthly_2023_09/05_railway_project_deploy.png.066179eaefc88f664eea8351c1c5d487.png"> </a>
</p>

<p>
	يمكنك النقر على عنوان URL الخاص بالموقع (المُحدَّد في الشكل السابق) لفتح الموقع في المتصفح. لن يعمل الموقع حاليًا، لأن الإعداد لم يكتمل بعد.
</p>

<h3>
	ضبط ALLOWED_HOSTS و CSRF_TRUSTED_ORIGINS
</h3>

<p>
	سترى الآن شاشة تنقيح أخطاء كما يلي عند فتح الموقع، وهذا خطأ أمان جانغو الذي ظهر بسبب أن شيفرتك المصدرية لا تعمل على مضيف مسموح به "allowed host":
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135489" href="https://academy.hsoub.com/uploads/monthly_2023_09/06_site_error_dissallowed_host.png.fcdb2a9dc777fc7448f3d7f31060c9b0.png" rel=""><img alt="06_site_error_dissallowed_host.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135489" data-unique="pekpmpyqk" src="https://academy.hsoub.com/uploads/monthly_2023_09/06_site_error_dissallowed_host.png.fcdb2a9dc777fc7448f3d7f31060c9b0.png"> </a>
</p>

<p>
	<strong>ملاحظة</strong>: يُعَد هذا النوع من معلومات تنقيح الأخطاء مفيدًا جدًا في عملية الإعداد، ولكنه يمثل خطرًا أمنيًا عند نشر موقع، لذا سنوضح كيفية تعطيل تنقيح الأخطاء عند رفع وتشغيل الموقع.
</p>

<p>
	افتح الملف "‎/locallibrary/settings.py" في مشروع غيت هب وعدّل الإعداد <code>ALLOWED_HOSTS</code> لتضمين عنوان URL لموقعك على Railway كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7053_23" style=""><span class="com">## مثلًا ‫لعنوان URL لموقع على 'web-production-3640.up.railway.app'</span><span class="pln">
</span><span class="com">## ‏(ض‫ع عنوان URL الخاص بموقعك بدلًا من السلسلة النصية التالية):</span><span class="pln">
ALLOWED_HOSTS </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">'web-production-3640.up.railway.app'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'127.0.0.1'</span><span class="pun">]</span><span class="pln">

</span><span class="com"># يمكنك بدلًا من ذلك ضبط‫ عنوان URL الأساسي أثناء التطوير</span><span class="pln">
</span><span class="com"># (يمكن أن تقرر تغيير الموقع عدة مرات)</span><span class="pln">
</span><span class="com"># ALLOWED_HOSTS = ['.railway.com','127.0.0.1']</span></pre>

<p>
	تستخدم التطبيقات الحماية ضد هجمات CSRF، لذا يجب أيضًا ضبط مفتاح <a href="https://docs.djangoproject.com/en/4.0/ref/settings/#csrf-trusted-origins" rel="external nofollow"><code>CSRF_TRUSTED_ORIGINS</code></a>.
</p>

<p>
	افتح الملف "/locallibrary/settings.py" وضِف سطرًا مثل السطر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7053_25" style=""><span class="com">## مثلًا ‫لعنوان URL لموقع على 'web-production-3640.up.railway.app'</span><span class="pln">
</span><span class="com">## ‏(ض‫ع عنوان URL الخاص بموقعك بدلًا من السلسلة النصية التالية):</span><span class="pln">
CSRF_TRUSTED_ORIGINS </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">'https://web-production-3640.up.railway.app'</span><span class="pun">]</span><span class="pln">

</span><span class="com"># يمكنك بدلًا من ذلك ضبط‫ عنوان URL الأساسي أثناء التطوير في هذا المقال</span><span class="pln">
</span><span class="com"># CSRF_TRUSTED_ORIGINS = ['https://*.railway.app']</span></pre>

<p>
	احفظ بعد ذلك إعداداتك وثبّتها في مستودع غيت هب. ستحدّث Railway تطبيقَك وتعيد نشره تلقائيًا.
</p>

<pre class="ipsCode">git add -A
git commit -m 'Update ALLOWED_HOSTS and CSRF_TRUSTED_ORIGINS with site URL'
git push origin main
</pre>

<h3>
	تجهيز وتوصيل قاعدة بيانات Postgres SQL
</h3>

<p>
	يجب الآن إنشاء قاعدة بيانات Postgres وتوصيلها بتطبيق جانغو الذي نشرناه، فإذا فتحتَ الموقع الآن، فستحصل على خطأ جديد لأنه لا يمكن الوصول إلى قاعدة البيانات. سننشئ قاعدة البيانات بوصفها جزءًا من مشروع التطبيق، ولكن يمكنك إنشاء قاعدة البيانات في مشروع منفصل خاص بها.
</p>
<iframe allowfullscreen="" data-controller="core.front.core.autosizeiframe" data-embedauthorid="3889" data-embedcontent="" src="https://academy.hsoub.com/files/18-%D8%A7%D9%84%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%B9%D9%85%D9%84%D9%8A-%D8%A5%D9%84%D9%89-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-postgresql/?do=embed" style="margin: auto;"></iframe>

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

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135488" href="https://academy.hsoub.com/uploads/monthly_2023_09/07_railway_project_open_no_database.png.59038520da13608317524eee19c85f1a.png" rel=""><img alt="07_railway_project_open_no_database.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135488" data-unique="yl6wimy19" src="https://academy.hsoub.com/uploads/monthly_2023_09/07_railway_project_open_no_database.png.59038520da13608317524eee19c85f1a.png"> </a>
</p>

<p>
	اختر قاعدة البيانات Database عندما يُطلَب منك تحديد نوع الخدمة المراد إضافتها كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135487" href="https://academy.hsoub.com/uploads/monthly_2023_09/08_railway_project_add_database.png.1506a26f4e53446f0d0b445abb962e6b.png" rel=""><img alt="08_railway_project_add_database.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135487" data-unique="cq7pwwdby" src="https://academy.hsoub.com/uploads/monthly_2023_09/08_railway_project_add_database.png.1506a26f4e53446f0d0b445abb962e6b.png"> </a>
</p>

<p>
	اختر بعد ذلك خيار الإضافة Add PostgreSQL لبدء إضافة قاعدة البيانات:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135486" href="https://academy.hsoub.com/uploads/monthly_2023_09/09_railway_project_add_database_select_type.png.bab5632c33c5b2fb01d1b9363bed11f5.png" rel=""><img alt="09_railway_project_add_database_select_type.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135486" data-unique="rzj7m8gsm" src="https://academy.hsoub.com/uploads/monthly_2023_09/09_railway_project_add_database_select_type.png.bab5632c33c5b2fb01d1b9363bed11f5.png"> </a>
</p>

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

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135485" href="https://academy.hsoub.com/uploads/monthly_2023_09/10_railway_project_two_services.png.e0fd348684d1173f82cdfc2de2065a1a.png" rel=""><img alt="10_railway_project_two_services.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135485" data-unique="me5cwyzq2" src="https://academy.hsoub.com/uploads/monthly_2023_09/10_railway_project_two_services.png.e0fd348684d1173f82cdfc2de2065a1a.png"> </a>
</p>

<p>
	اختر خدمة PostgreSQL لعرض معلومات حول قاعدة البيانات، ثم افتح نافذة "الاتصال Connect" وانسخ "عنوان URL لاتصال Postgres"، وهو العنوان الذي ضبطنا موقع المكتبة المحلية locallibrary لقراءته بوصفه متغير بيئة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135484" href="https://academy.hsoub.com/uploads/monthly_2023_09/11_railway_postgresql_connect.png.99e99e3f0b71acc041797834a2f5f0b2.png" rel=""><img alt="11_railway_postgresql_connect.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135484" data-unique="aluk2fq8t" src="https://academy.hsoub.com/uploads/monthly_2023_09/11_railway_postgresql_connect.png.99e99e3f0b71acc041797834a2f5f0b2.png"> </a>
</p>

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

<p>
	أدخِل اسم المتغير <code>DATABASE_URL</code> وعنوان URL للاتصال الذي نسخته لقاعدة البيانات، وسيظهر لديك ما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135483" href="https://academy.hsoub.com/uploads/monthly_2023_09/12_railway_variables_database_url.png.8f0eb08bff3042d5e92b2ae8c1db33fc.png" rel=""><img alt="12_railway_variables_database_url.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135483" data-unique="ueb1fbzu3" src="https://academy.hsoub.com/uploads/monthly_2023_09/12_railway_variables_database_url.png.8f0eb08bff3042d5e92b2ae8c1db33fc.png"> </a>
</p>

<p>
	حدّد زر الإضافة Add لإضافة المتغير، ثم سيُعاد نشر المشروع.
</p>

<p>
	إذا فتحت المشروع الآن، فسيُعرَض تمامًا كما عُرِض محليًا، ولكن لاحظ أنه لا توجد طريقة لملء المكتبة بالبيانات حتى الآن، لأننا لم ننشئ حساب مستخدم مميز Superuser بعد، إذ سنفعّل ذلك باستخدام أداة CLI على حاسوبنا المحلي.
</p>

<h3>
	تثبيت العميل
</h3>

<p>
	نزّل وثبّت عميل Railway لنظام تشغيلك المحلي باتباع <a href="https://docs.railway.app/develop/cli" rel="external nofollow">التعليمات</a> الواردة في توثيق Railway.
</p>

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

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

<pre class="ipsCode">railway help
</pre>

<p>
	<strong>ملاحظة</strong>: سنستخدم في القسم التالي الأمرين <code>railway login</code> و <code>railway link</code> لربط المشروع الحالي بمجلدٍ ما، وإذا سجّل النظام خروجك، فيجب استدعاء كلا الأمرين مرةً أخرى لإعادة ربط المشروع.
</p>

<h3>
	ضبط مستخدم مميز
</h3>

<p>
	يمكن إنشاء مستخدم مميز من خلال استدعاء أمر جانغو <code>createsuperuser</code> لقاعدة بيانات الإنتاج، وهي العملية نفسها التي أجريناها محليًا في <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D9%85%D9%88%D9%82%D8%B9-%D9%85%D8%AF%D9%8A%D8%B1-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-admin-r2105/" rel="">مقال مدير موقع جانغو</a>. لا توفّر Railway وصولًا مباشرًا من الطرفية إلى الخادم، ولا يمكننا إضافة هذا الأمر إلى الملف Procfile لأنه تفاعلي، لذا نستدعي هذا الأمر محليًا في مشروع جانغو عندما يكون متصلًا بقاعدة بيانات الإنتاج، إذ يسهّل عميل Railway ذلك من خلال توفير آلية لتشغيل الأوامر محليًا باستخدام متغيرات بيئة خادم الإنتاج نفسها، بما في ذلك سلسلة اتصال قاعدة البيانات.
</p>

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

<pre class="ipsCode">railway login
</pre>

<p>
	اربط مجلد locallibrary الحالي بمشروع Railway المرتبط به باستخدام الأمر التالي بعد تسجيل الدخول. لاحظ أنه يجب تحديد أو إدخال مشروع معين عندما يُطلَب منك ذلك:
</p>

<pre class="ipsCode">railway link
</pre>

<p>
	ربطنا المجلد المحلي والمشروع، ويمكنك الآن تشغيل مشروع جانغو المحلي مع إعدادات من بيئة الإنتاج، ولكن تأكّد أولًا من أن بيئة تطوير جانغو العادية جاهزة، ثم استدعِ الأمر التالي مع إدخال الاسم والبريد الإلكتروني وكلمة المرور المطلوبة:
</p>

<pre class="ipsCode">railway run python manage.py createsuperuser
</pre>

<p>
	ينبغي أن تكون الآن قادرًا على فتح منطقة مدير موقعك <code>https://[your-url].railway.app/admin/‎</code> وملء قاعدة البيانات كما تعلمنا في مقال <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D9%85%D9%88%D9%82%D8%B9-%D9%85%D8%AF%D9%8A%D8%B1-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-admin-r2105/" rel="">موقع مدير جانغو</a>.
</p>

<h3>
	إعداد متغيرات الضبط
</h3>

<p>
	يجب الآن أن نجعل الموقع آمنًا، إذ يجب تعطيل تسجيل تنقيح الأخطاء وضبط مفتاح CSRF السري، إذ عملنا مسبقًا في هذا المقال على قراءة القيم المطلوبة من متغيرات البيئة <code>DJANGO_DEBUG</code> و <code>DJANGO_SECRET_KEY</code>.
</p>

<p>
	افتح شاشة المعلومات الخاصة بالمشروع، وحدّد نافذة المتغيرات Variables التي يجب أن تحتوي على <code>DATABASE_URL</code> كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135482" href="https://academy.hsoub.com/uploads/monthly_2023_09/13_railway_variable_new.png.34a87ac131892166c959b529e9199c0c.png" rel=""><img alt="13_railway_variable_new.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135482" data-unique="k11idkin7" src="https://academy.hsoub.com/uploads/monthly_2023_09/13_railway_variable_new.png.34a87ac131892166c959b529e9199c0c.png"> </a>
</p>

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

<pre class="ipsCode">python -c "import secrets; print(secrets.token_urlsafe())"
</pre>

<p>
	حدّد زر متغير جديد New Variable وأدخِل المفتاح <code>DJANGO_SECRET_KEY</code> مع قيمتك السرية، ثم اضغط إضافة Add. أدخِل بعد ذلك المفتاح <code>DJANGO_DEBUG</code> مع القيمة <code>False</code>، إذ يجب أن يبدو الضبط النهائي للمتغيرات كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135481" href="https://academy.hsoub.com/uploads/monthly_2023_09/14_railway_variables_all.png.4a0636e38b54c63d9b4826ff6c39daf4.png" rel=""><img alt="14_railway_variables_all.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135481" data-unique="pnfdlji8l" src="https://academy.hsoub.com/uploads/monthly_2023_09/14_railway_variables_all.png.4a0636e38b54c63d9b4826ff6c39daf4.png"> </a>
</p>

<h3>
	تنقيح الأخطاء Debugging
</h3>

<p>
	يوفّر عميل Railway الأمر <code>logs</code> لإظهار السجلات الأخيرة (يتوفر سجل كامل على الموقع لكل مشروع):
</p>

<pre class="ipsCode">railway logs
</pre>

<p>
	اطلع على <a href="https://docs.djangoproject.com/en/4.0/topics/logging/" rel="external nofollow">توثيق جانغو</a> لمعلومات أكثر.
</p>

<h2>
	الخلاصة
</h2>

<p>
	وصلنا إلى نهاية مقالنا حول إعداد تطبيقات جانغو في بيئة الإنتاج، وكذلك إلى نهاية سلسلة مقالات إطار عمل جانغو، لذا نأمل أنها كانت مفيدة لك، ولا تنسَ أنه يمكنك التحقق من النسخة الكاملة من <a href="https://github.com/mdn/django-locallibrary-tutorial" rel="external nofollow">الشيفرة المصدرية على غيت هب GitHub</a>.
</p>

<p>
	ترجمة -وبتصرُّف- للمقال <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Deployment" rel="external nofollow">Django Tutorial Part 11: Deploying Django to production</a>.
</p>

<h2>
	اقرأ المزيد
</h2>

<ul>
	<li>
		المقال التالي: <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A3%D9%85%D8%A7%D9%86-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2123/" rel="">تعرف على أمان تطبيقات جانغو</a>
	</li>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AA%D8%A7%D8%B3%D8%B9-%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2121/" rel="">تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/deployment/%D9%85%D8%B1%D8%AD%D9%84%D8%A9-%D9%86%D8%B4%D8%B1-%D8%A7%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%81%D9%8A-%D8%B9%D9%85%D9%84%D9%8A%D8%A9-%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r589/" rel="">مرحلة نشر التطبيق في عملية تطوير الويب</a>.
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/howto/static-files/deployment/" rel="external nofollow">كيفية نشر الملفات الثابتة</a> (توثيق جانغو)
	</li>
	<li>
		<a href="https://devcenter.heroku.com/categories/working-with-django" rel="external nofollow">نشر تطبيق Django على Heroku</a> (توثيق Heroku)
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/deployment/%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A2%D9%85%D9%86-%D9%88%D9%82%D8%A7%D8%A8%D9%84-%D9%84%D9%84%D8%AA%D9%88%D8%B3%D9%8A%D8%B9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%83%D9%88%D8%A8%D9%8A%D8%B1%D9%86%D8%AA%D8%B3-kubernetes-r662/" rel="">نشر تطبيق جانغو آمن وقابل للتوسيع باستخدام كوبيرنتس Kubernetes</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/deployment/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%AC%D8%A7%D9%87%D8%B2-%D9%84%D9%84%D9%86%D8%B4%D8%B1-%D9%85%D8%B9-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-postgres-%D9%88%D8%AE%D8%A7%D8%AF%D9%85-nginx-%D9%88-gunicorn-r663/" rel="">إعداد تطبيق جانغو جاهز للنشر مع قاعدة بيانات Postgres وخادم Nginx و Gunicorn</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2122</guid><pubDate>Sat, 14 Oct 2023 13:05:05 +0000</pubDate></item><item><title>&#x62A;&#x637;&#x628;&#x64A;&#x642; &#x639;&#x645;&#x644;&#x64A; &#x644;&#x62A;&#x639;&#x644;&#x645; &#x62C;&#x627;&#x646;&#x63A;&#x648; - &#x627;&#x644;&#x62C;&#x632;&#x621; &#x627;&#x644;&#x62A;&#x627;&#x633;&#x639;: &#x627;&#x62E;&#x62A;&#x628;&#x627;&#x631; &#x62A;&#x637;&#x628;&#x64A;&#x642; &#x62C;&#x627;&#x646;&#x63A;&#x648;</title><link>https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AA%D8%A7%D8%B3%D8%B9-%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2121/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_09/--.png.56ab571e1c834906f60505730c6db59b.png" /></p>
<p>
	يصبح اختبار مواقع الويب أصعب يدويًا عند نموها، إذ يصبح هناك مزيدٌ من الأمور لاختبارها، وتصبح التفاعلات بين المكونات أكثر تعقيدًا، إذ يمكن أن يؤثر تغيير بسيط في منطقةٍ ما على مناطق أخرى، لذلك ستكون هناك حاجة إلى مزيد من التغييرات لضمان استمرار عمل كل شيء بنجاح وعدم ظهور أخطاء عند إجراء مزيدٍ من التغييرات. تتمثل إحدى طرق التخفيف من هذه المشاكل في كتابة اختبارات آلية، والتي يمكن تشغيلها بسهولة وموثوقية في كل مرة تجري فيها تغييرًا. يوضح هذا المقال كيفية أتمتة اختبار الوحدة Unit Testing لموقعك باستخدام إطار اختبار جانغو <a href="https://academy.hsoub.com/programming/python/django/" rel="">Django</a>.
</p>

<ul>
	<li>
		<strong>المتطلبات الأساسية</strong>: اطّلع على جميع المقالات السابقة من هذه السلسلة، بما في ذلك المقال الخاص <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%85%D9%86-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-forms-r2120/" rel="">بالعمل مع الاستمارات</a>.
	</li>
	<li>
		<strong>الهدف</strong>: فهم كيفية كتابة اختبارات الوحدة لمواقع الويب المستندة إلى جانغو.
	</li>
</ul>

<p>
	يحتوي موقع <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2090/" rel="">المكتبة المحلية Local Library</a> حاليًا على صفحات لعرض قوائم بجميع الكتب والمؤلفين، والعروض التفصيلية لعناصر <code>Book</code> و <code>Author</code>، وصفحة لتجديد عناصر <code>BookInstance</code>، وصفحات لإنشاء عناصر <code>Author</code> وتحديثها وحذفها، إضافةً إلى سجلات <code>Book</code> أيضًا إذا أكملت قسم التحدي في <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%85%D9%86-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-forms-r2120/" rel="">المقال السابق</a>.
</p>

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

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

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

<p>
	يوضح هذا المقال كيفية كتابة الاختبارات الآلية لإطار عمل جانغو من خلال إضافة عدد من الاختبارات إلى موقع المكتبة المحلية LocalLibrary.
</p>

<p>
	تتألف هذه السلسلة الفرعية من السلسلة الأشمل <a href="https://academy.hsoub.com/tags/%D8%AA%D8%B9%D9%84%D9%85%20%D8%AA%D8%B7%D9%88%D9%8A%D8%B1%20%D8%A7%D9%84%D9%88%D9%8A%D8%A8/" rel="">تعلم تطوير الويب</a> من المقالات التالية:
</p>

<ul>
	<li>
		<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>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%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%AC%D8%A7%D9%86%D8%BA%D9%88-django-r2049/" rel="">إعداد بيئة تطوير تطبيقات جانغو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2090/" rel="">تطبيق عملي لتعلم جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%86%D9%85%D8%A7%D8%B0%D8%AC-models-r2092/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D9%85%D9%88%D9%82%D8%B9-%D9%85%D8%AF%D9%8A%D8%B1-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-admin-r2105/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B1%D8%A7%D8%A8%D8%B9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B5%D9%81%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%A7%D9%84%D8%B1%D8%A6%D9%8A%D8%B3%D9%8A%D8%A9-r2106/" rel="">تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AE%D8%A7%D9%85%D8%B3-%D8%A7%D9%84%D8%B9%D8%B1%D9%88%D8%B6-views-%D8%A7%D9%84%D8%B9%D8%A7%D9%85%D8%A9-%D9%88%D8%A7%D9%84%D8%AA%D9%81%D8%B5%D9%8A%D9%84%D9%8A%D8%A9-r2107/" rel="">تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B3%D8%A7%D8%AF%D8%B3-%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D8%AC%D9%84%D8%B3%D8%A7%D8%AA-sessions-r2118/" rel="">تطبيق عملي لتعلم جانغو - الجزء السادس: إدارة الجلسات Sessions</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B3%D8%A7%D8%A8%D8%B9-%D8%A7%D8%B3%D8%AA%D9%8A%D8%AB%D8%A7%D9%82-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D9%8A%D9%86-%D9%88%D8%A3%D8%B0%D9%88%D9%86%D8%A7%D8%AA%D9%87%D9%85-r2119/" rel="">تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%85%D9%86-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-forms-r2120/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms</a>
	</li>
	<li>
		<span ipsnoautolink="true">تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو</span>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-10-%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%81%D9%8A-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%A7%D8%AC-r2122/" rel="">تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A3%D9%85%D8%A7%D9%86-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2123/" rel="">تعرف على أمان تطبيقات جانغو</a>
	</li>
</ul>

<h2>
	أنواع الاختبارات
</h2>

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

<ul>
	<li>
		اختبارات الوحدة Unit Tests: التي تتحقق من السلوك الوظيفي للمكونات الفردية على مستوى الأصناف والدوال غالبًا.
	</li>
	<li>
		الاختبارات التراجعية Regression Tests: هي الاختبارات التي تعيد إنتاج أخطاء قديمة، إذ يُشغَّل كل اختبار للتحقق من إصلاح الخطأ في البداية، ثم يُعَاد تشغيله للتأكد من عدم ظهوره مرةً أخرى بعد إجراء تغييرات لاحقة على الشيفرة البرمجية.
	</li>
	<li>
		اختبارات التكامل Integration Tests: تتحقق من كيفية عمل مجموعات المكونات عند استخدامها مع بعضها بعضًا، إذ تكون اختبارات التكامل على دراية بالتفاعلات المطلوبة بين المكونات، ولكنها ليست بالضرورة على دراية بالعمليات الداخلية لكل مكون. يمكن أن تغطي اختبارات التكامل مجموعات بسيطة من المكونات في موقع الويب الكامل.
	</li>
</ul>

<p>
	<strong>ملاحظة</strong>: تشمل أنواع الاختبارات الشائعة الأخرى اختبارات الصندوق الأسود والصندوق الأبيض والاختبارات اليدوية والآلية واختبارات الكناري Canary والدخان Smoke والمطابقة والقبول والوظيفية والنظام والأداء والحِمل واختبارات الإجهاد.
</p>

<h3>
	ماذا يقدم جانغو لعملية الاختبار؟
</h3>

<p>
	يُعَد اختبار موقع الويب مهمةً معقدة، لأنه يتكون من عدة طبقات من الشيفرة البرمجية، بدءًا من معالجة الطلبات على مستوى <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-http-%D8%B4%D8%B1%D8%AD-%D8%A7%D9%84%D8%AA%D8%AE%D8%A7%D8%B7%D8%A8-%D8%A8%D9%8A%D9%86-%D8%A7%D9%84%D8%B9%D9%85%D9%8A%D9%84-%D9%88%D8%A7%D9%84%D8%AE%D8%A7%D8%AF%D9%85-r74/" rel="">طلبات HTTP</a>، إلى استعلامات النموذج، إلى التحقق من صحة الاستمارة ومعالجتها، وإظهار القالب.
</p>

<p>
	يوفّر جانغو إطار عمل للاختبار مع تسلسل هرمي صغير من الأصناف التي تعتمد على مكتبة بايثون المعيارية <code>unittest</code>، إذ يُعَد إطار عمل الاختبار هذا مناسبًا لكل من اختبارات الوحدة والتكامل، فهو يضيف توابع وأدوات <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%A7-%D9%87%D9%8A-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-%D9%84%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-api%D8%9F-r1512/" rel="">واجهة برمجة التطبيقات <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr></a> للمساعدة في اختبار الويب وسلوك جانغو المُحدَّد، مما يسمح لك بمحاكاة الطلبات وإدخال بيانات الاختبار وفحص خرج تطبيقك. يوفر جانغو أيضًا واجهة برمجة تطبيقات (<a href="https://docs.djangoproject.com/en/4.0/topics/testing/tools/#liveservertestcase" rel="external nofollow">LiveServerTestCase</a>) وأدوات <a href="https://docs.djangoproject.com/en/4.0/topics/testing/advanced/#other-testing-frameworks" rel="external nofollow">لاستخدام أطر عمل اختبار مختلفة</a>، فمثلًا يمكنك التكامل مع <a href="https://academy.hsoub.com/programming/general/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A6%D8%A9-%D9%84%D9%84%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1%D8%A7%D8%AA-%D8%A7%D9%84%D8%A2%D9%84%D9%8A%D8%A9-%D9%81%D9%8A-%D9%85%D8%B4%D8%A7%D8%B1%D9%8A%D8%B9-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D9%84%D9%84%D8%AA%D9%88%D8%A7%D9%81%D9%82-%D9%85%D8%B9-%D8%A7%D9%84%D9%85%D8%AA%D8%B5%D9%81%D8%AD%D8%A7%D8%AA-r1990/" rel="">إطار عمل سيلينيوم Selenium</a> الشهير لمحاكاة مستخدم يتفاعل مع متصفح مباشرةً.
</p>

<p>
	يمكنك كتابة اختبار من خلال اشتقاق أيٍّ من أصناف اختبار جانغو الأساسية unittest، مثل <code>SimpleTestCase</code> و <code>TransactionTestCase</code> و <code>TestCase</code> و <code>LiveServerTestCase</code>، ثم كتابة توابع منفصلة للتحقق من أن وظيفة معينة تعمل كما هو متوقع، إذ تستخدم الاختبارات توابع "assert" لاختبار أن ناتج التعابير هو القيمة <code>True</code> أو القيمة <code>False</code> أو تساوي قيمتين أو غير ذلك.
</p>

<p>
	ينفّذ إطار العمل توابع الاختبار المختارة في الأصناف المشتقة عند بدء تشغيل الاختبار، إذ تُشغَّل توابع الاختبار بطريقة مستقلة مع سلوك الإعداد و/أو الهدم tear-down الشائع المُعرَّف في الصنف كما هو موضح فيما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5599_8" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">YourTestClass</span><span class="pun">(</span><span class="typ">TestCase</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> setUp</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># تشغيل الإعداد قبل كل تابع اختبار</span><span class="pln">
        </span><span class="kwd">pass</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> tearDown</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># تنظيف التشغيل بعد كل تابع اختبار</span><span class="pln">
        </span><span class="kwd">pass</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> test_something_that_will_pass</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertFalse</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"> test_something_that_will_fail</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertTrue</span><span class="pun">(</span><span class="kwd">False</span><span class="pun">)</span></pre>

<p>
	أفضل صنف أساسي لمعظم الاختبارات هو الصنف <a href="https://docs.djangoproject.com/en/4.0/topics/testing/tools/#testcase" rel="external nofollow">django.test.TestCase</a>، الذي ينشئ قاعدة بيانات نظيفة قبل تشغيل اختباراته، ويشغِّل كل دالة اختبار في تعاملاته الخاصة. يمتلك هذا الصنف أيضًا صنف الاختبار <a href="https://docs.djangoproject.com/en/4.0/topics/testing/tools/#django.test.Client" rel="external nofollow">Client</a> الذي يمكنك استخدامه لمحاكاة مستخدم يتفاعل مع الشيفرة البرمجية على مستوى العرض View. سنركز في الأقسام التالية على اختبارات الوحدة المُنشَأة باستخدام صنف TestCase الأساسي.
</p>

<p>
	<strong>ملاحظة</strong>: يُعَد الصنف django.test.TestCase سهل الاستخدام، ولكنه يمكن أن يؤدي إلى أن تكون بعض الاختبارات أبطأ مما يجب، إذ لن يحتاج كل اختبار إلى إعداد قاعدة بياناته أو محاكاة تفاعل العرض. قد ترغب في استبدال بعض اختباراتك بأصناف الاختبار الأبسط المتاحة بعد أن تتعرف على ما يمكنك تطبيقه باستخدام هذا الصنف.
</p>

<h3>
	ماذا يجب أن تختبر؟
</h3>

<p>
	يجب أن تختبر جميع جوانب شيفرتك البرمجية، ولكن لا حاجة إلى اختبار أيّ مكتبات أو وظائف متوفرة مثل جزء من بايثون أو جانغو.
</p>

<p>
	ليكن لدينا مثلًا نموذج المؤلف <code>Author</code> الآتي، فلا حاجة لاختبار التخزين الصحيح للحقلين <code>first_name</code> و <code>last_name</code> بوصفهما حقلين من النوع <code>CharField</code> في قاعدة البيانات صراحةً لأن ذلك شيء حدّده جانغو، بالرغم من أنك حتمًا ستختبر هذه الوظيفة أثناء التطوير، ولا حاجة أيضًا لاختبار التحقق من أن الحقل <code>date_of_birth</code> حقل تاريخ، لأنه شيء طبّقه جانغو، لكن ينبغي التحقق من النص المستخدم مع التسميات "First name" و "Last name" و "Date of birth" و "Died" وحجم الحقل المخصص للنص (100 محرف)، لأنها جزء من تصميمك وشيء يمكن أن يُكسَر أو يتغيّر في المستقبل.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5599_10" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Author</span><span class="pun">(</span><span class="pln">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
    first_name </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">100</span><span class="pun">)</span><span class="pln">
    last_name </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">100</span><span class="pun">)</span><span class="pln">
    date_of_birth </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">DateField</span><span class="pun">(</span><span class="pln">null</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">,</span><span class="pln"> blank</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
    date_of_death </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">DateField</span><span class="pun">(</span><span class="str">'Died'</span><span class="pun">,</span><span class="pln"> null</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">,</span><span class="pln"> blank</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> get_absolute_url</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"> reverse</span><span class="pun">(</span><span class="str">'author-detail'</span><span class="pun">,</span><span class="pln"> args</span><span class="pun">=[</span><span class="pln">str</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">id</span><span class="pun">)])</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> __str__</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"> </span><span class="str">'%s, %s'</span><span class="pln"> </span><span class="pun">%</span><span class="pln"> </span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">last_name</span><span class="pun">,</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">first_name</span><span class="pun">)</span></pre>

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

<p>
	<strong>ملاحظة</strong>: يمكن أن يلاحظ القراء المتمرسون أننا نرغب في تقييد تاريخ الميلاد والوفاة بقيم مناسبة، والتحقق من أن الوفاة تأتي بعد الميلاد، ويمكن تحقيق هذا التقييد في جانغو من خلال إضافته إلى أصناف استمارتك. يمكنك تعريف أدوات تحقق لحقول النموذج وأدوات تحقق من النماذج نفسها، ولكنها تُستخدَم فقط على مستوى الاستمارة إذا استدعاها التابع <code>clean()‎</code> الخاص بالنموذج، وهذا يتطلب الصنف <code>ModelForm</code>، أو استدعاء التابع <code>clean()‎</code> الخاص بالنموذج على وجه التحديد.
</p>

<p>
	لنبدأ الآن في التعرّف على كيفية تعريف الاختبارات وتشغيلها.
</p>

<h2>
	نظرة عامة على بنية الاختبار
</h2>

<p>
	لنلقِ أولًا نظرة سريعة على مكان وكيفية تعريف الاختبارات قبل أن ندخل في تفاصيل ما الذي يجب اختباره.
</p>

<p>
	يستخدم جانغو اكتشاف الاختبار المبني مسبقًا في الوحدة unittest، والذي سيكتشف الاختبارات ضمن مجلد العمل الحالي في أي ملف يسمى بالنمط test*.py، إذ يمكنك استخدام أي بنية تريدها شريطة أن تسمي الملفات بصورة مناسبة. نوصي بإنشاء وحدة لشيفرة اختبارك، وأن يكون لديك ملفات منفصلة <a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D9%84%D9%86%D9%85%D8%A7%D8%B0%D8%AC-models-%D9%88%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D8%B9%D9%84%D8%A7%D9%85-%D8%B9%D9%86-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%81%D9%8A-django-r405/" rel="">للنماذج Models</a>و<a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D9%84%D8%B9%D8%B1%D9%88%D8%B6-%D9%88%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-%D9%81%D9%8A-django-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-r436/" rel="">العروض Views</a> و<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%85%D9%86-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-forms-r2120/" rel="">الاستمارات Forms</a> وأيّ أنواع أخرى من الشيفرة البرمجية التي يجب اختبارها كما يلي:
</p>

<pre class="ipsCode">catalog/
  /tests/
    __init__.py
    test_models.py
    test_forms.py
    test_views.py
</pre>

<p>
	أنشئ بنية الملفات السابقة في مشروع المكتبة المحلية LocalLibrary، وينبغي أن يكون الملف "‎<strong>init</strong>.py" فارغًا، مما يخبر بايثون أن المجلد هو حزمة package. يمكنك إنشاء ملفات الاختبار الثلاثة من خلال نسخ وإعادة تسمية ملف الاختبار الهيكلي "‎/catalog/tests.py".
</p>

<p>
	<strong>ملاحظة</strong>: أُنشِئ ملف اختبار الهيكلي ‎/catalog/tests.py تلقائيًا عند بناء موقع ويب جانغو الهيكلي، إذ يُعَد وضع جميع اختباراتك ضمنه أمرًا "مسموحًا به"، ولكن إذا أجريت الاختبار بصورة صحيحة، فسينتهي بك الأمر سريعًا مع ملف اختبار كبير جدًا ولا يمكن إدارته، لذا احذف الملف الهيكلي لأننا لن نحتاج إليه.
</p>

<p>
	افتح الملف "‎/catalog/tests/test_models.py"، الذي ينبغي أن يستورد الصنف <code>django.test.TestCase</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5599_13" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">test </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">TestCase</span><span class="pln">

</span><span class="com"># أنشئ اختباراتك هنا</span></pre>

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

<p>
	أضِف صنف الاختبار التالي إلى نهاية الملف، إذ يوضح هذا الصنف كيفية بناء صنف حالة اختبار من خلال اشتقاق الصنف <code>TestCase</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5599_15" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">YourTestClass</span><span class="pun">(</span><span class="typ">TestCase</span><span class="pun">):</span><span class="pln">
    </span><span class="lit">@classmethod</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> setUpTestData</span><span class="pun">(</span><span class="pln">cls</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="str">"setUpTestData: Run once to set up non-modified data for all class methods."</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">pass</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> setUp</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="str">"setUp: Run once for every test method to setup clean data."</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">pass</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> test_false_is_false</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="str">"Method: test_false_is_false."</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertFalse</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"> test_false_is_true</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="str">"Method: test_false_is_true."</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertTrue</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"> test_one_plus_one_equals_two</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="str">"Method: test_one_plus_one_equals_two."</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="lit">1</span><span class="pln"> </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="pun">)</span></pre>

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

<ul>
	<li>
		<code>setUpTestData()‎</code>: يُستدعَى مرةً واحدة في بداية تشغيل الاختبار للإعداد على مستوى الصنف، ويمكنك استخدامه لإنشاء كائنات لن تُعدَّل أو تُغيَّر في أيٍّ من توابع الاختبار.
	</li>
	<li>
		<code>setUp()‎</code>: يُستدعَى قبل دوال الاختبار لإعداد الكائنات التي يمكن أن يعدّلها الاختبار، وستحصل كل دالة اختبار على نسخة "جديدة" من هذه الكائنات.
	</li>
</ul>

<p>
	<strong>ملاحظة</strong>: تحتوي أصناف الاختبار على تابع <code>tearDown()‎</code> التي لم نستخدمه، ولا يُعَد هذا التابع مفيدًا بخاصة لاختبارات <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>، لأن الصنف الأساسي <code>TestCase</code> يهتم بهدم قاعدة البيانات نيابةً عنك.
</p>

<p>
	لدينا فيما يلي عددٌ من توابع الاختبار التي تستخدم دوال <code>Assert</code> لاختبار ما إذا كانت الشروط صحيحة أو خاطئة أو متساوية، وهي: <code>AssertTrue</code> و <code>AssertFalse</code> و <code>AssertEqual</code>. إذا لم يُقيَّم الشرط كما هو متوقع، فسيفشل الاختبار وسيرسَل الخطأ إلى طرفيتك.
</p>

<p>
	تُعَد <code>AssertTrue</code> و <code>AssertFalse</code> و <code>AssertEqual</code> تأكيدات معيارية توفّرها المكتبة unittest، وهناك تأكيدات معيارية أخرى في إطار العمل، و<a href="https://docs.djangoproject.com/en/4.0/topics/testing/tools/#assertions" rel="external nofollow">تأكيدات خاصة بجانغو</a> لاختبار ما إذا كان العرض يعيد التوجيه <code>assertRedirects</code>، أو يستخدم قالب معين <code>assertTemplateUsed</code> وغير ذلك.
</p>

<p>
	<strong>ملاحظة</strong>: <strong>لا ينبغي</strong> عادةً تضمين دوال <code>print()‎</code> في اختباراتك كما هو موضح سابقًا، لكننا نطبّق ذلك في القسم التالي فقط لتتمكّن من رؤية ترتيب استدعاء دوال الإعداد في الطرفية.
</p>

<h2>
	كيفية تشغيل الاختبارات
</h2>

<p>
	أسهل طريقة لتشغيل جميع الاختبارات هي استخدام الأمر التالي:
</p>

<pre class="ipsCode">python3 manage.py test
</pre>

<p>
	سيؤدي هذا الأمر إلى اكتشاف جميع الملفات المُسمَّاة باستخدام النمط test*.py في المجلد الحالي وتشغيل جميع الاختبارات التي تعرّفها أصناف أساسية مناسبة، فلدينا هنا عدد من ملفات الاختبار، ولكن يحتوي الملف ‎/catalog/tests/test_models.py فقط على الاختبارات حاليًا. ستقدم الاختبارات افتراضيًا تقريرًا فرديًا فقط عن حالات فشل الاختبار ويتبعه ملخص الاختبار.
</p>

<p>
	<strong>ملاحظة</strong>: إذا حصلتَ على أخطاء مشابهة للخطأ التالي:
</p>

<pre class="ipsCode">ValueError: Missing staticfiles manifest entry...‎
</pre>

<p>
	يمكن أن يكون هذا الخطأ بسبب عدم تشغيل الاختبار للأمر <code>collectstatic</code> افتراضيًا، وبسبب أن تطبيقك يستخدم صنف تخزين يتطلب ذلك. اطلع على <a href="https://docs.djangoproject.com/en/4.0/ref/contrib/staticfiles/#django.contrib.staticfiles.storage.ManifestStaticFilesStorage.manifest_strict" rel="external nofollow">manifest_strict</a> لمزيد من المعلومات. هناك عدد من الطرق التي يمكنك من خلالها التغلب على هذه المشكلة، وأسهلها هو تشغيل الأمر <code>collectstatic</code> قبل تشغيل الاختبارات كما يلي:
</p>

<pre class="ipsCode">python3 manage.py collectstatic
</pre>

<p>
	شغّل الاختبارات في المجلد الجذر لمشروع المكتبة المحلية LocalLibrary، ويجب أن ترى خرجًا مثل الخرج التالي:
</p>

<pre class="ipsCode">&gt; python3 manage.py test

Creating test database for alias 'default'...
setUpTestData: Run once to set up non-modified data for all class methods.
setUp: Run once for every test method to setup clean data.
Method: test_false_is_false.
setUp: Run once for every test method to setup clean data.
Method: test_false_is_true.
setUp: Run once for every test method to setup clean data.
Method: test_one_plus_one_equals_two.
.
======================================================================
FAIL: test_false_is_true (catalog.tests.tests_models.YourTestClass)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "D:\GitHub\django_tmp\library_w_t_2\locallibrary\catalog\tests\tests_models.py", line 22, in test_false_is_true
    self.assertTrue(False)
AssertionError: False is not true

----------------------------------------------------------------------
Ran 3 tests in 0.075s

FAILED (failures=1)
Destroying test database for alias 'default'...
</pre>

<p>
	نرى في الخرج السابق أن لدينا فشل اختبار واحد، ويمكننا أن نرى فعلًا الدالة التي فشلت وسبب فشلها، إذ يُعَد هذا الفشل متوقعًا، لأن القيمة <code>False</code> ليست القيمة <code>True</code>.
</p>

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

<p>
	يُظهِر خرج دوال <code>print()‎</code> كيفية استدعاء تابع <code>setUpTestData()‎</code> مرةً واحدةً للصنف واستدعاء التابع <code>setUp()‎</code> قبل كل تابع. تذكّر أنك لن تضيف عادةً هذا النوع من دوال <code>print()‎</code> إلى اختباراتك.
</p>

<p>
	توضّح الأقسام التالية كيفية تشغيل اختبارات محددة، وكيفية التحكم في مقدار المعلومات التي تعرضها الاختبارات.
</p>

<h3>
	عرض مزيد من معلومات الاختبار
</h3>

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

<pre class="ipsCode">python3 manage.py test --verbosity 2
</pre>

<p>
	مستويات <code>verbosity</code> المسموح بها هي: 0 و 1 و 2 و 3 والقيمة الافتراضية هي "1".
</p>

<h3>
	تسريع الاختبارات
</h3>

<p>
	إذا كانت اختباراتك مستقلة، فيمكنك تسريعها على جهاز متعدد المعالجات من خلال تشغيلها على التوازي، إذ يؤدي استخدام الأمر <code>test ‎--parallel auto</code> الآتي إلى تشغيل عملية اختبار واحدة لكل نواة متاحة، ويُعَد استخدام <code>auto</code> اختياريًا، ويمكنك تحديد عدد معين من الأنوية لاستخدامها.
</p>

<pre class="ipsCode">python3 manage.py test --parallel auto
</pre>

<p>
	اطّلع على <a href="https://docs.djangoproject.com/en/4.0/ref/django-admin/#envvar-DJANGO_TEST_PROCESSES" rel="external nofollow">DJANGO_TEST_PROCESSES</a> لمزيدٍ من المعلومات، بما في ذلك ما يجب عليك فعله إن لم تكن اختباراتك مستقلة.
</p>

<h3>
	تشغيل اختبارات محددة
</h3>

<p>
	إذا أردتَ تشغيل مجموعة فرعية من اختباراتك، فيمكنك ذلك من خلال تحديد المسار النقطي الكامل للحزمة (أو لمجموعة الحزم) أو الوحدة أو الصنف <code>TestCase</code> الفرعي أو التابع كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5599_17" style=""><span class="com"># تشغيل الوحدة المُحدَّدة</span><span class="pln">
python3 manage</span><span class="pun">.</span><span class="pln">py test catalog</span><span class="pun">.</span><span class="pln">tests

</span><span class="com"># تشغيل الوحدة المُحدَّدة</span><span class="pln">
python3 manage</span><span class="pun">.</span><span class="pln">py test catalog</span><span class="pun">.</span><span class="pln">tests</span><span class="pun">.</span><span class="pln">test_models

</span><span class="com"># تشغيل الصنف المُحدَّد</span><span class="pln">
python3 manage</span><span class="pun">.</span><span class="pln">py test catalog</span><span class="pun">.</span><span class="pln">tests</span><span class="pun">.</span><span class="pln">test_models</span><span class="pun">.</span><span class="typ">YourTestClass</span><span class="pln">

</span><span class="com"># تشغيل التابع المُحدَّد</span><span class="pln">
python3 manage</span><span class="pun">.</span><span class="pln">py test catalog</span><span class="pun">.</span><span class="pln">tests</span><span class="pun">.</span><span class="pln">test_models</span><span class="pun">.</span><span class="typ">YourTestClass</span><span class="pun">.</span><span class="pln">test_one_plus_one_equals_two</span></pre>

<h3>
	خيارات مشغل الاختبارات الأخرى
</h3>

<p>
	يوفر مشغِّل الاختبارات العديد من الخيارات الأخرى، بما في ذلك القدرة على خلط الاختبارات <code>‎--shuffle</code>، وتشغيلها في وضع تنقيح الأخطاء <code>‎--debug-mode</code>، واستخدام مسجِّل بايثون Python logger لالتقاط النتائج. اطلع على توثيق <a href="https://docs.djangoproject.com/en/4.0/ref/django-admin/#test" rel="external nofollow">مشغّل اختبار</a> جانغو لمزيد من المعلومات، كما يمكنك الاطلاع على المقال التالي <a href="https://academy.hsoub.com/programming/python/%D9%83%D9%8A%D9%81-%D8%AA%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-%D8%A7%D9%84%D8%AA%D8%B3%D8%AC%D9%8A%D9%84-logging-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-r546/" rel="">كيف تستخدم التسجيل Logging في بايثون 3</a> على أكاديمية حسوب لمزيدٍ من المعلومات حوال التسجيل.
</p>

<h2>
	اختبارات موقع المكتبة المحلية LocalLibrary
</h2>

<p>
	نعرف الآن كيفية تشغيل اختباراتنا ونوع الأمور التي يجب اختبارها، ولنلقِ الآن نظرةً على بعض الأمثلة العملية.
</p>

<p>
	<strong>ملاحظة</strong>: لن نكتب جميع الاختبارات الممكنة، ولكن سيعطيك هذا القسم فكرةً عن كيفية عمل الاختبارات، وما الأمور الأخرى التي يمكنك تطبيقها.
</p>

<h3>
	النماذج Models
</h3>

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

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5599_19" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Author</span><span class="pun">(</span><span class="pln">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
    first_name </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">100</span><span class="pun">)</span><span class="pln">
    last_name </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">100</span><span class="pun">)</span><span class="pln">
    date_of_birth </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">DateField</span><span class="pun">(</span><span class="pln">null</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">,</span><span class="pln"> blank</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
    date_of_death </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">DateField</span><span class="pun">(</span><span class="str">'Died'</span><span class="pun">,</span><span class="pln"> null</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">,</span><span class="pln"> blank</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> get_absolute_url</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"> reverse</span><span class="pun">(</span><span class="str">'author-detail'</span><span class="pun">,</span><span class="pln"> args</span><span class="pun">=[</span><span class="pln">str</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">id</span><span class="pun">)])</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> __str__</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">'{self.last_name}, {self.first_name}'</span></pre>

<p>
	افتح الملف "‎/catalog/tests/test_models.py"، وضع شيفرة الاختبار الآتية لنموذج المؤلف <code>Author</code> مكان أي شيفرة برمجية موجودة مسبقًا، إذ سترى أننا نستورد الصنف <code>TestCase</code> أولًا ونشتق منه صنف الاختبار <code>AuthorModelTest</code> باستخدام اسم وصفي لنتمكّن بسهولة من تحديد أيّ اختبارات فاشلة في خرج الاختبار، ثم نستدعي التابع <code>setUpTestData()‎</code> لإنشاء كائن المؤلف الذي سنستخدمه دون تعديل في أيٍّ من الاختبارات.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5599_21" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">test </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">TestCase</span><span class="pln">

</span><span class="kwd">from</span><span class="pln"> catalog</span><span class="pun">.</span><span class="pln">models </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Author</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">AuthorModelTest</span><span class="pun">(</span><span class="typ">TestCase</span><span class="pun">):</span><span class="pln">
    </span><span class="lit">@classmethod</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> setUpTestData</span><span class="pun">(</span><span class="pln">cls</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># إعداد الكائنات غير المُعدَّلة التي تستخدمها جميع توابع الاختبار</span><span class="pln">
        </span><span class="typ">Author</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">create</span><span class="pun">(</span><span class="pln">first_name</span><span class="pun">=</span><span class="str">'Big'</span><span class="pun">,</span><span class="pln"> last_name</span><span class="pun">=</span><span class="str">'Bob'</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> test_first_name_label</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        author </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Author</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">get</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">
        field_label </span><span class="pun">=</span><span class="pln"> author</span><span class="pun">.</span><span class="pln">_meta</span><span class="pun">.</span><span class="pln">get_field</span><span class="pun">(</span><span class="str">'first_name'</span><span class="pun">).</span><span class="pln">verbose_name
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">field_label</span><span class="pun">,</span><span class="pln"> </span><span class="str">'first name'</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> test_date_of_death_label</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        author </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Author</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">get</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">
        field_label </span><span class="pun">=</span><span class="pln"> author</span><span class="pun">.</span><span class="pln">_meta</span><span class="pun">.</span><span class="pln">get_field</span><span class="pun">(</span><span class="str">'date_of_death'</span><span class="pun">).</span><span class="pln">verbose_name
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">field_label</span><span class="pun">,</span><span class="pln"> </span><span class="str">'died'</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> test_first_name_max_length</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        author </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Author</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">get</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">
        max_length </span><span class="pun">=</span><span class="pln"> author</span><span class="pun">.</span><span class="pln">_meta</span><span class="pun">.</span><span class="pln">get_field</span><span class="pun">(</span><span class="str">'first_name'</span><span class="pun">).</span><span class="pln">max_length
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">max_length</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">def</span><span class="pln"> test_object_name_is_last_name_comma_first_name</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        author </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Author</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">get</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">
        expected_object_name </span><span class="pun">=</span><span class="pln"> f</span><span class="str">'{author.last_name}, {author.first_name}'</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">str</span><span class="pun">(</span><span class="pln">author</span><span class="pun">),</span><span class="pln"> expected_object_name</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> test_get_absolute_url</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        author </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Author</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">get</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">
        </span><span class="com"># سيفشل هذا الاختبار أيضًا إن لم يكن‫ urlconf مُعرَّفًا</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">author</span><span class="pun">.</span><span class="pln">get_absolute_url</span><span class="pun">(),</span><span class="pln"> </span><span class="str">'/catalog/author/1'</span><span class="pun">)</span></pre>

<p>
	تتحقق اختبارات الحقول من أن قيم تسميات الحقول <code>verbose_name</code> وأن حجم الحقول المحرفية كما هو متوقع. تمتلك جميع هذه التوابع أسماءً وصفية وتتبع النمط نفسه:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5599_23" style=""><span class="com"># الحصول على كائن مؤلف لاختباره</span><span class="pln">
author </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Author</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">get</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">

</span><span class="com"># الحصول على البيانات الوصفية للحقل المطلوب واستخدامها للاستعلام عن بيانات الحقل المطلوبة</span><span class="pln">
field_label </span><span class="pun">=</span><span class="pln"> author</span><span class="pun">.</span><span class="pln">_meta</span><span class="pun">.</span><span class="pln">get_field</span><span class="pun">(</span><span class="str">'first_name'</span><span class="pun">).</span><span class="pln">verbose_name

</span><span class="com"># موازنة القيمة بالنتيجة المتوقعة</span><span class="pln">
self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">field_label</span><span class="pun">,</span><span class="pln"> </span><span class="str">'first name'</span><span class="pun">)</span></pre>

<p>
	الأمور المهمة التي يجب ملاحظتها هي:
</p>

<ul>
	<li>
		لا يمكننا الحصول على <code>verbose_name</code> مباشرةً باستخدام <code>author.first_name.verbose_name</code>، لأن <code>author.first_name</code> هو سلسلة نصية أي ليس مؤشرًا للكائن <code>first_name</code> الذي يمكننا استخدامه للوصول إلى خاصياته، لذا ينبغي بدلًا من ذلك استخدام السمة <code>‎_meta</code> الخاصة بالمؤلف للحصول على نسخة من الحقل واستخدامها للاستعلام عن المعلومات الإضافية.
	</li>
	<li>
		اخترنا استخدام <code>assertEqual(field_label,'first name')‎</code> عوضًا عن <code>assertTrue(field_label == 'first name')‎</code>، والسبب في ذلك هو أن خرج <code>assertEqual</code> يخبرك عن التسمية الفعلية في حالة فشل الاختبار، مما يجعل تنقيح أخطاء المشكلة أسهل قليلًا.
	</li>
</ul>

<p>
	<strong>ملاحظة</strong>: أُهمِلت اختبارات تسميات <code>last_name</code> و <code>date_of_birth</code> واختبار طول الحقل <code>last_name</code>، لذا ضِف نسخك الخاصة الآن باتباع اصطلاحات التسمية والأساليب الموضَّحة سابقًا.
</p>

<p>
	يجب أيضًا اختبار توابعنا الخاصة، فمثلًا تتحقق الاختبارات التالية من بناء اسم الكائن كما توقعنا باستخدام تنسيق "Last Name", "First Name"، وأن عنوان URL الذي نحصل عليه لعنصر المؤلف <code>Author</code> كما هو متوقع:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5599_25" style=""><span class="kwd">def</span><span class="pln"> test_object_name_is_last_name_comma_first_name</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
    author </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Author</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">get</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">
    expected_object_name </span><span class="pun">=</span><span class="pln"> f</span><span class="str">'{author.last_name}, {author.first_name}'</span><span class="pln">
    self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">str</span><span class="pun">(</span><span class="pln">author</span><span class="pun">),</span><span class="pln"> expected_object_name</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> test_get_absolute_url</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
    author </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Author</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">get</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">
    </span><span class="com"># سيفشل هذا الاختبار أيضًا إن لم يكن‫ urlconf مُعرَّفًا</span><span class="pln">
    self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">author</span><span class="pun">.</span><span class="pln">get_absolute_url</span><span class="pun">(),</span><span class="pln"> </span><span class="str">'/catalog/author/1'</span><span class="pun">)</span></pre>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5599_27" style=""><span class="pun">======================================================================</span><span class="pln">
FAIL</span><span class="pun">:</span><span class="pln"> test_date_of_death_label </span><span class="pun">(</span><span class="pln">catalog</span><span class="pun">.</span><span class="pln">tests</span><span class="pun">.</span><span class="pln">test_models</span><span class="pun">.</span><span class="typ">AuthorModelTest</span><span class="pun">)</span><span class="pln">
</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">"D:\...\locallibrary\catalog\tests\test_models.py"</span><span class="pun">,</span><span class="pln"> line </span><span class="lit">32</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> test_date_of_death_label
    self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">field_label</span><span class="pun">,</span><span class="str">'died'</span><span class="pun">)</span><span class="pln">
</span><span class="typ">AssertionError</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Died'</span><span class="pln"> </span><span class="pun">!=</span><span class="pln"> </span><span class="str">'died'</span><span class="pln">
</span><span class="pun">-</span><span class="pln"> </span><span class="typ">Died</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"> died
</span><span class="pun">?</span><span class="pln"> </span><span class="pun">^</span></pre>

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

<p>
	<strong>ملاحظة</strong>: عدّل تسمية الحقل <code>date_of_death</code> في الملف "‎/catalog/models.py" إلى "died" وأعِد تشغيل الاختبارات.
</p>

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

<h3>
	الاستمارات Forms
</h3>

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

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5599_29" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">RenewBookForm</span><span class="pun">(</span><span class="pln">forms</span><span class="pun">.</span><span class="typ">Form</span><span class="pun">):</span><span class="pln">
    </span><span class="str">"""Form for a librarian to renew books."""</span><span class="pln">
    renewal_date </span><span class="pun">=</span><span class="pln"> forms</span><span class="pun">.</span><span class="typ">DateField</span><span class="pun">(</span><span class="pln">help_text</span><span class="pun">=</span><span class="str">"Enter a date between now and 4 weeks (default 3)."</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> clean_renewal_date</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        data </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">cleaned_data</span><span class="pun">[</span><span class="str">'renewal_date'</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"> data </span><span class="pun">&lt;</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">date</span><span class="pun">.</span><span class="pln">today</span><span class="pun">():</span><span class="pln">
            </span><span class="kwd">raise</span><span class="pln"> </span><span class="typ">ValidationError</span><span class="pun">(</span><span class="pln">_</span><span class="pun">(</span><span class="str">'Invalid date - renewal in past'</span><span class="pun">))</span><span class="pln">

        </span><span class="com"># تحقق من أن التاريخ موجود ضمن المجال المسموح به (+4 أسابيع من اليوم‫)</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> data </span><span class="pun">&gt;</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">date</span><span class="pun">.</span><span class="pln">today</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">timedelta</span><span class="pun">(</span><span class="pln">weeks</span><span class="pun">=</span><span class="lit">4</span><span class="pun">):</span><span class="pln">
            </span><span class="kwd">raise</span><span class="pln"> </span><span class="typ">ValidationError</span><span class="pun">(</span><span class="pln">_</span><span class="pun">(</span><span class="str">'Invalid date - renewal more than 4 weeks ahead'</span><span class="pun">))</span><span class="pln">

        </span><span class="com"># تذكّر دائمًا أن تعيد البيانات النظيفة</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> data</span></pre>

<p>
	افتح الملف "‎/catalog/tests/test_forms.py" وضع الاختبار التالي لاستمارة <code>RenewBookForm</code> مكان أيّ شيفرة برمجية موجودة مسبقًا. نبدأ باستيراد استمارتنا وبعض مكتبات بايثون وجانغو للمساعدة في اختبار الوظائف المتعلقة بالوقت، ثم نصرّح عن صنف اختبار الاستمارة بالطريقة نفسها التي طبّقناها على النماذج باستخدام اسم وصفي لصنف الاختبار المشتق من الصنف <code>TestCase</code>.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5599_31" style=""><span class="kwd">import</span><span class="pln"> datetime

</span><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">test </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">TestCase</span><span class="pln">
</span><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">utils </span><span class="kwd">import</span><span class="pln"> timezone

</span><span class="kwd">from</span><span class="pln"> catalog</span><span class="pun">.</span><span class="pln">forms </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">RenewBookForm</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">RenewBookFormTest</span><span class="pun">(</span><span class="typ">TestCase</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> test_renew_form_date_field_label</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        form </span><span class="pun">=</span><span class="pln"> </span><span class="typ">RenewBookForm</span><span class="pun">()</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertTrue</span><span class="pun">(</span><span class="pln">form</span><span class="pun">.</span><span class="pln">fields</span><span class="pun">[</span><span class="str">'renewal_date'</span><span class="pun">].</span><span class="pln">label </span><span class="kwd">is</span><span class="pln"> </span><span class="kwd">None</span><span class="pln"> </span><span class="kwd">or</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">fields</span><span class="pun">[</span><span class="str">'renewal_date'</span><span class="pun">].</span><span class="pln">label </span><span class="pun">==</span><span class="pln"> </span><span class="str">'renewal date'</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> test_renew_form_date_field_help_text</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        form </span><span class="pun">=</span><span class="pln"> </span><span class="typ">RenewBookForm</span><span class="pun">()</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">form</span><span class="pun">.</span><span class="pln">fields</span><span class="pun">[</span><span class="str">'renewal_date'</span><span class="pun">].</span><span class="pln">help_text</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Enter a date between now and 4 weeks (default 3).'</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> test_renew_form_date_in_past</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        date </span><span class="pun">=</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">date</span><span class="pun">.</span><span class="pln">today</span><span class="pun">()</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">timedelta</span><span class="pun">(</span><span class="pln">days</span><span class="pun">=</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
        form </span><span class="pun">=</span><span class="pln"> </span><span class="typ">RenewBookForm</span><span class="pun">(</span><span class="pln">data</span><span class="pun">={</span><span class="str">'renewal_date'</span><span class="pun">:</span><span class="pln"> date</span><span class="pun">})</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertFalse</span><span class="pun">(</span><span class="pln">form</span><span class="pun">.</span><span class="pln">is_valid</span><span class="pun">())</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> test_renew_form_date_too_far_in_future</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        date </span><span class="pun">=</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">date</span><span class="pun">.</span><span class="pln">today</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">timedelta</span><span class="pun">(</span><span class="pln">weeks</span><span class="pun">=</span><span class="lit">4</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">timedelta</span><span class="pun">(</span><span class="pln">days</span><span class="pun">=</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
        form </span><span class="pun">=</span><span class="pln"> </span><span class="typ">RenewBookForm</span><span class="pun">(</span><span class="pln">data</span><span class="pun">={</span><span class="str">'renewal_date'</span><span class="pun">:</span><span class="pln"> date</span><span class="pun">})</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertFalse</span><span class="pun">(</span><span class="pln">form</span><span class="pun">.</span><span class="pln">is_valid</span><span class="pun">())</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> test_renew_form_date_today</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        date </span><span class="pun">=</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">date</span><span class="pun">.</span><span class="pln">today</span><span class="pun">()</span><span class="pln">
        form </span><span class="pun">=</span><span class="pln"> </span><span class="typ">RenewBookForm</span><span class="pun">(</span><span class="pln">data</span><span class="pun">={</span><span class="str">'renewal_date'</span><span class="pun">:</span><span class="pln"> date</span><span class="pun">})</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertTrue</span><span class="pun">(</span><span class="pln">form</span><span class="pun">.</span><span class="pln">is_valid</span><span class="pun">())</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> test_renew_form_date_max</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        date </span><span class="pun">=</span><span class="pln"> timezone</span><span class="pun">.</span><span class="pln">localtime</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">timedelta</span><span class="pun">(</span><span class="pln">weeks</span><span class="pun">=</span><span class="lit">4</span><span class="pun">)</span><span class="pln">
        form </span><span class="pun">=</span><span class="pln"> </span><span class="typ">RenewBookForm</span><span class="pun">(</span><span class="pln">data</span><span class="pun">={</span><span class="str">'renewal_date'</span><span class="pun">:</span><span class="pln"> date</span><span class="pun">})</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertTrue</span><span class="pun">(</span><span class="pln">form</span><span class="pun">.</span><span class="pln">is_valid</span><span class="pun">())</span></pre>

<p>
	تختبر الدالتان الأوليتان أن تسمية <code>label</code> ونص التعليمات <code>help_text</code> الخاصين بالحقل كما هو متوقع. ينبغي الوصول إلى الحقل باستخدام قاموس الحقول، مثل <code>form.fields['renewal_date']‎</code>. لاحظ أنه ينبغي اختبار ما إذا كانت قيمة التسمية هي <code>None</code>، لأن جانغو يعيد القيمة <code>None</code> إن لم تُضبَط قيمة التسمية صراحةً بالرغم من أنه سيعرض التسمية الصحيحة.
</p>

<p>
	تختبر بقية الدوال أن الاستمارة صالحة لتواريخ التجديد ضمن المجال المقبول وغير صالحة للقيم الموجودة خارج المجال. لاحظ كيفية بناء قيم تاريخ الاختبار حول تاريخنا الحالي <code>datetime.date.today()‎</code> باستخدام <code>datetime.timedelta()‎</code> (تحديد عدد الأيام أو الأسابيع في هذه الحالة)، ثم ننشئ الاستمارة فقط، ونمرر بياناتنا، ونختبر فيما إذا كانت صالحة أم لا.
</p>

<p>
	<strong>ملاحظة</strong>: لم نستخدم هنا قاعدة البيانات أو عميل الاختبار فعليًا، لذا ضع في بالك تعديل هذه الاختبارات لاستخدام الصنف <a href="https://docs.djangoproject.com/en/4.0/topics/testing/tools/#django.test.SimpleTestCase" rel="external nofollow">SimpleTestCase</a>. يجب أيضًا التحقق من ظهور الأخطاء الصحيحة إذا كانت الاستمارة غير صالحة، ولكن ذلك يحدث عادةً بوصفه جزءًا من معالجة العرض، لذا سنعالج ذلك في القسم التالي.
</p>

<p>
	<strong>تحذير</strong>: إذا استخدمتَ صنف ModelForm الذي هو <code>RenewBookModelForm(forms.ModelForm)‎</code> بدلًا من الصنف <code>RenewBookForm(forms.Form)‎</code>، فسيكون اسم حقل الاستمارة "due_back" بدلًا من "renewal_date".
</p>

<p>
	هذا كل ما لدينا للتعامل مع الاستمارات وهناك بعض الأمور الأخرى، ولكن تنشِئها عروض التعديل المُعمَّمة المستندة إلى الأصناف Generic Class-based Editing Views تلقائيًا، ويجب اختبارها هناك، لذا يمكنك إجراء الاختبارات والتأكد من أن شيفرتك البرمجية لا تزال تعمل بنجاح.
</p>

<h3>
	العروض Views
</h3>

<p>
	يمكن التحقق من صحة سلوك العرض من خلال استخدام الصنف <code>Client</code> لاختبار جانغو، إذ يعمل هذا الصنف بوصفه متصفح ويب وهمي dummy web browser يمكننا استخدامه لمحاكاة طلبات <code>GET</code> و <code>POST</code> على عنوان URL ومراقبة الاستجابة. يمكننا أن نرى كل شيء متعلق بالاستجابة تقريبًا، بدءًا من HTTP منخفض المستوى، أي ترويسات النتائج ورموز الحالة، إلى القالب الذي نستخدمه لعرض <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="">صفحة HTML</a> وبيانات السياق التي نمررها إليه، ويمكننا رؤية سلسلة عمليات إعادة التوجيه (إن وجدت) والتحقق من عنوان URL ورمز الحالة في كل خطوة، مما يسمح لنا بالتحقق من أن كل عرض يطبّق المتوقع منه.
</p>

<p>
	لنبدأ بواحدٍ من أبسط العروض، والذي يوفّر قائمة بجميع المؤلفين، ويُعرَض على عنوان URL، الذي هو "/catalog/authors/" (عنوان URL الذي اسمه "authors" في ضبط عناوين URL):
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5599_33" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">AuthorListView</span><span class="pun">(</span><span class="pln">generic</span><span class="pun">.</span><span class="typ">ListView</span><span class="pun">):</span><span class="pln">
    model </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Author</span><span class="pln">
    paginate_by </span><span class="pun">=</span><span class="pln"> </span><span class="lit">10</span></pre>

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

<p>
	افتح الملف "‎"/catalog/tests/test_views.py وضع فيه شيفرة الاختبار التالية للصنف <code>AuthorListView</code> بدلًا من أيّ نص آخر موجود فيه، إذ سنستورد نموذجنا وبعض الأصناف المفيدة، ونضبط في التابع <code>setUpTestData()‎</code> عددًا من كائنات <code>Author</code> لنتمكّن من اختبار ترقيم الصفحات:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5599_35" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">test </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">TestCase</span><span class="pln">
</span><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">urls </span><span class="kwd">import</span><span class="pln"> reverse

</span><span class="kwd">from</span><span class="pln"> catalog</span><span class="pun">.</span><span class="pln">models </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Author</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">AuthorListViewTest</span><span class="pun">(</span><span class="typ">TestCase</span><span class="pun">):</span><span class="pln">
    </span><span class="lit">@classmethod</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> setUpTestData</span><span class="pun">(</span><span class="pln">cls</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># إنشاء 13 مؤلفًا لاختبارات ترقيم الصفحات</span><span class="pln">
        number_of_authors </span><span class="pun">=</span><span class="pln"> </span><span class="lit">13</span><span class="pln">

        </span><span class="kwd">for</span><span class="pln"> author_id </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="pln">number_of_authors</span><span class="pun">):</span><span class="pln">
            </span><span class="typ">Author</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">create</span><span class="pun">(</span><span class="pln">
                first_name</span><span class="pun">=</span><span class="pln">f</span><span class="str">'Dominique {author_id}'</span><span class="pun">,</span><span class="pln">
                last_name</span><span class="pun">=</span><span class="pln">f</span><span class="str">'Surname {author_id}'</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"> test_view_url_exists_at_desired_location</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        response </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="str">'/catalog/authors/'</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">status_code</span><span class="pun">,</span><span class="pln"> </span><span class="lit">200</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> test_view_url_accessible_by_name</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        response </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">reverse</span><span class="pun">(</span><span class="str">'authors'</span><span class="pun">))</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">status_code</span><span class="pun">,</span><span class="pln"> </span><span class="lit">200</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> test_view_uses_correct_template</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        response </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">reverse</span><span class="pun">(</span><span class="str">'authors'</span><span class="pun">))</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">status_code</span><span class="pun">,</span><span class="pln"> </span><span class="lit">200</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertTemplateUsed</span><span class="pun">(</span><span class="pln">response</span><span class="pun">,</span><span class="pln"> </span><span class="str">'catalog/author_list.html'</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> test_pagination_is_ten</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        response </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">reverse</span><span class="pun">(</span><span class="str">'authors'</span><span class="pun">))</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">status_code</span><span class="pun">,</span><span class="pln"> </span><span class="lit">200</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertTrue</span><span class="pun">(</span><span class="str">'is_paginated'</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">context</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertTrue</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">context</span><span class="pun">[</span><span class="str">'is_paginated'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">len</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">context</span><span class="pun">[</span><span class="str">'author_list'</span><span class="pun">]),</span><span class="pln"> </span><span class="lit">10</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> test_lists_all_authors</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># الحصول على الصفحة الثانية والتأكد من أنها تحتوي (بالضبط) على 3 عناصر متبقية</span><span class="pln">
        response </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">reverse</span><span class="pun">(</span><span class="str">'authors'</span><span class="pun">)+</span><span class="str">'?page=2'</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">status_code</span><span class="pun">,</span><span class="pln"> </span><span class="lit">200</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertTrue</span><span class="pun">(</span><span class="str">'is_paginated'</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">context</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertTrue</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">context</span><span class="pun">[</span><span class="str">'is_paginated'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">len</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">context</span><span class="pun">[</span><span class="str">'author_list'</span><span class="pun">]),</span><span class="pln"> </span><span class="lit">3</span><span class="pun">)</span></pre>

<p>
	تستخدم جميع الاختبارات العميل، الذي ينتمي إلى الصنف المشتق من <code>TestCase</code> لمحاكاة طلب <code>GET</code> والحصول على استجابة. تتحقق النسخة الأولى من عنوان URL المحدد (لاحظ وجود المسار المحدد فقط بدون النطاق)، بينما تولّد النسخة الثانية <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 كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5599_37" style=""><span class="pln">response </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="str">'/catalog/authors/'</span><span class="pun">)</span><span class="pln">
response </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">reverse</span><span class="pun">(</span><span class="str">'authors'</span><span class="pun">))</span></pre>

<p>
	نستعلم عن الاستجابة بعد الحصول عليها لنحصل على رمز حالتها والقالب المُستخدَم وما إذا كانت الاستجابة مرقمة paginated أم لا وعدد العناصر المُعادة وعدد العناصر الإجمالي.
</p>

<p>
	<strong>ملاحظة</strong>: إذا ضبطتَ المتغير <code>paginate_by</code> في الملف ‎/catalog/views.py إلى عدد آخر غير العدد 10، فتأكد من تحديث الأسطر التي تختبر عرضَ عدد العناصر الصحيح في القوالب ذات الصفحات المرقمة السابقة وفي الأقسام التالية، فمثلًا إذا ضبطتَ متغيرًا لصفحة قائمة المؤلفين على القيمة 5، فعدّل السطر السابق إلى ما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5599_39" style=""><span class="pln">self</span><span class="pun">.</span><span class="pln">assertTrue</span><span class="pun">(</span><span class="pln">len</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">context</span><span class="pun">[</span><span class="str">'author_list'</span><span class="pun">])</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="lit">5</span><span class="pun">)</span></pre>

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

<h4>
	العروض المقيدة على المستخدمين الذين سجلوا الدخول
</h4>

<p>
	سترغب في بعض الحالات في اختبار العرض المقتصر على المستخدمين الذين سجّلوا الدخول فقط، فالعرض <code>LoanedBooksByUserListView</code> مثلًا مشابه جدًا لعرضنا السابق، ولكنه متاحٌ فقط للمستخدمين الذين سجّلوا الدخول، ويعرض فقط سجلات نسخ الكتاب <code>BookInstance</code> التي استعارها المستخدم الحالي والتي لها الحالة 'on loan' ومرتبة بحسب الأقدم أولًا "oldest first".
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5599_41" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">contrib</span><span class="pun">.</span><span class="pln">auth</span><span class="pun">.</span><span class="pln">mixins </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">LoginRequiredMixin</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">LoanedBooksByUserListView</span><span class="pun">(</span><span class="typ">LoginRequiredMixin</span><span class="pun">,</span><span class="pln"> generic</span><span class="pun">.</span><span class="typ">ListView</span><span class="pun">):</span><span class="pln">
    </span><span class="str">"""Generic class-based view listing books on loan to current user."""</span><span class="pln">
    model </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BookInstance</span><span class="pln">
    template_name </span><span class="pun">=</span><span class="str">'catalog/bookinstance_list_borrowed_user.html'</span><span class="pln">
    paginate_by </span><span class="pun">=</span><span class="pln"> </span><span class="lit">10</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> get_queryset</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"> </span><span class="typ">BookInstance</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">borrower</span><span class="pun">=</span><span class="pln">self</span><span class="pun">.</span><span class="pln">request</span><span class="pun">.</span><span class="pln">user</span><span class="pun">).</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">status__exact</span><span class="pun">=</span><span class="str">'o'</span><span class="pun">).</span><span class="pln">order_by</span><span class="pun">(</span><span class="str">'due_back'</span><span class="pun">)</span></pre>

<p>
	ضِف شيفرة الاختبار الآتية إلى الملف "‎/catalog/tests/test_views.py"، إذ نستخدم هنا أولًا التابع <code>SetUp()‎</code> لإنشاء بعض حسابات تسجيل دخول المستخدم وكائنات <code>BookInstance</code> -مع الكتب المرتبطة بها والسجلات الأخرى- التي سنستخدمها لاحقًا في الاختبارات. يستعير كل مستخدم تجريبي نصف الكتب، ولكننا ضبطنا في البداية حالة جميع الكتب على أنها قيد الصيانة "maintenance"، واستخدمنا التابع <code>SetUp()‎</code> بدلًا من <code>setUpTestData()‎</code> لأننا سنعدّل بعض هذه الكائنات لاحقًا.
</p>

<p>
	<strong>ملاحظة</strong>: تنشئ شيفرة <code>setUp()‎</code> التالية كتابًا بلغة <code>Language</code> محددة، ولكن يمكن ألّا تحتوي شيفرتك البرمجية على النموذج <code>Language</code> الذي تركناه بمثابة تحدٍ لك، لذا يمكنك تعليق أجزاء الشيفرة البرمجية التي تنشئ أو تستورد كائنات <code>Language</code>، ويجب أيضًا تطبيق ذلك في قسم <code>RenewBookInstancesViewTest</code>.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5599_43" style=""><span class="kwd">import</span><span class="pln"> datetime

</span><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">utils </span><span class="kwd">import</span><span class="pln"> timezone
</span><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">contrib</span><span class="pun">.</span><span class="pln">auth</span><span class="pun">.</span><span class="pln">models </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">User</span><span class="pln"> </span><span class="com"># مطلوب لضبط المستخدم بوصفه مستعيرًا</span><span class="pln">

</span><span class="kwd">from</span><span class="pln"> catalog</span><span class="pun">.</span><span class="pln">models </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">BookInstance</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Book</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Genre</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Language</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">LoanedBookInstancesByUserListViewTest</span><span class="pun">(</span><span class="typ">TestCase</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> setUp</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># أنشئ مستخدمَين</span><span class="pln">
        test_user1 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">User</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">create_user</span><span class="pun">(</span><span class="pln">username</span><span class="pun">=</span><span class="str">'testuser1'</span><span class="pun">,</span><span class="pln"> password</span><span class="pun">=</span><span class="str">'1X&lt;ISRUkw+tuK'</span><span class="pun">)</span><span class="pln">
        test_user2 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">User</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">create_user</span><span class="pun">(</span><span class="pln">username</span><span class="pun">=</span><span class="str">'testuser2'</span><span class="pun">,</span><span class="pln"> password</span><span class="pun">=</span><span class="str">'2HJ1vRV0Z&amp;3iD'</span><span class="pun">)</span><span class="pln">

        test_user1</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">
        test_user2</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">

        </span><span class="com"># أنشئ كتابًا</span><span class="pln">
        test_author </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Author</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">create</span><span class="pun">(</span><span class="pln">first_name</span><span class="pun">=</span><span class="str">'John'</span><span class="pun">,</span><span class="pln"> last_name</span><span class="pun">=</span><span class="str">'Smith'</span><span class="pun">)</span><span class="pln">
        test_genre </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Genre</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">create</span><span class="pun">(</span><span class="pln">name</span><span class="pun">=</span><span class="str">'Fantasy'</span><span class="pun">)</span><span class="pln">
        test_language </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Language</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">create</span><span class="pun">(</span><span class="pln">name</span><span class="pun">=</span><span class="str">'English'</span><span class="pun">)</span><span class="pln">
        test_book </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Book</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">create</span><span class="pun">(</span><span class="pln">
            title</span><span class="pun">=</span><span class="str">'Book Title'</span><span class="pun">,</span><span class="pln">
            summary</span><span class="pun">=</span><span class="str">'My book summary'</span><span class="pun">,</span><span class="pln">
            isbn</span><span class="pun">=</span><span class="str">'ABCDEFG'</span><span class="pun">,</span><span class="pln">
            author</span><span class="pun">=</span><span class="pln">test_author</span><span class="pun">,</span><span class="pln">
            language</span><span class="pun">=</span><span class="pln">test_language</span><span class="pun">,</span><span class="pln">
        </span><span class="pun">)</span><span class="pln">

        </span><span class="com"># ‫أنشئ نوع الكتاب genre لخطوة لاحقة</span><span class="pln">
        genre_objects_for_book </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Genre</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
        </span><span class="com"># الإسناد المباشر لأنواع متعدد إلى متعدد غير مسموح به</span><span class="pln">
        test_book</span><span class="pun">.</span><span class="pln">genre</span><span class="pun">.</span><span class="pln">set</span><span class="pun">(</span><span class="pln">genre_objects_for_book</span><span class="pun">)</span><span class="pln"> 
        test_book</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">

        </span><span class="com"># ‫أنشئ 30 كائن BookInstance</span><span class="pln">
        number_of_book_copies </span><span class="pun">=</span><span class="pln"> </span><span class="lit">30</span><span class="pln">
        </span><span class="kwd">for</span><span class="pln"> book_copy </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="pln">number_of_book_copies</span><span class="pun">):</span><span class="pln">
            return_date </span><span class="pun">=</span><span class="pln"> timezone</span><span class="pun">.</span><span class="pln">localtime</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">timedelta</span><span class="pun">(</span><span class="pln">days</span><span class="pun">=</span><span class="pln">book_copy</span><span class="pun">%</span><span class="lit">5</span><span class="pun">)</span><span class="pln">
            the_borrower </span><span class="pun">=</span><span class="pln"> test_user1 </span><span class="kwd">if</span><span class="pln"> book_copy </span><span class="pun">%</span><span class="pln"> </span><span class="lit">2</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> test_user2
            status </span><span class="pun">=</span><span class="pln"> </span><span class="str">'m'</span><span class="pln">
            </span><span class="typ">BookInstance</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">create</span><span class="pun">(</span><span class="pln">
                book</span><span class="pun">=</span><span class="pln">test_book</span><span class="pun">,</span><span class="pln">
                imprint</span><span class="pun">=</span><span class="str">'Unlikely Imprint, 2016'</span><span class="pun">,</span><span class="pln">
                due_back</span><span class="pun">=</span><span class="pln">return_date</span><span class="pun">,</span><span class="pln">
                borrower</span><span class="pun">=</span><span class="pln">the_borrower</span><span class="pun">,</span><span class="pln">
                status</span><span class="pun">=</span><span class="pln">status</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"> test_redirect_if_not_logged_in</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        response </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">reverse</span><span class="pun">(</span><span class="str">'my-borrowed'</span><span class="pun">))</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertRedirects</span><span class="pun">(</span><span class="pln">response</span><span class="pun">,</span><span class="pln"> </span><span class="str">'/accounts/login/?next=/catalog/mybooks/'</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> test_logged_in_uses_correct_template</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        login </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">login</span><span class="pun">(</span><span class="pln">username</span><span class="pun">=</span><span class="str">'testuser1'</span><span class="pun">,</span><span class="pln"> password</span><span class="pun">=</span><span class="str">'1X&lt;ISRUkw+tuK'</span><span class="pun">)</span><span class="pln">
        response </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">reverse</span><span class="pun">(</span><span class="str">'my-borrowed'</span><span class="pun">))</span><span class="pln">

        </span><span class="com"># تحقق من أن المستخدم قد سجل الدخول</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">str</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">context</span><span class="pun">[</span><span class="str">'user'</span><span class="pun">]),</span><span class="pln"> </span><span class="str">'testuser1'</span><span class="pun">)</span><span class="pln">
        </span><span class="com"># تحقق من حصولنا على استجابة تمثل "النجاح‫"</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">status_code</span><span class="pun">,</span><span class="pln"> </span><span class="lit">200</span><span class="pun">)</span><span class="pln">

        </span><span class="com"># تحقق من استخدامنا للقالب الصحيح</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertTemplateUsed</span><span class="pun">(</span><span class="pln">response</span><span class="pun">,</span><span class="pln"> </span><span class="str">'catalog/bookinstance_list_borrowed_user.html'</span><span class="pun">)</span></pre>

<p>
	يمكن التحقق من أن العرض سيعيد توجيه المستخدم إلى صفحة تسجيل الدخول إن لم يسجّل دخول من خلال استخدام <code>assertRedirects</code> كما هو موضح في <code>test_redirect_if_not_logged_in()‎</code>. يمكن التحقق من إظهار الصفحة لمستخدم سجّل الدخول من خلال تسجيل الدخول للمستخدم التجريبي أولًا، ثم الوصول إلى الصفحة مرةً أخرى والتحقق من حصولنا على رمز الحالة <code>status_code</code> التي هي 200 وتمثل النجاح.
</p>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5599_45" style=""><span class="pln">   </span><span class="kwd">def</span><span class="pln"> test_only_borrowed_books_in_list</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        login </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">login</span><span class="pun">(</span><span class="pln">username</span><span class="pun">=</span><span class="str">'testuser1'</span><span class="pun">,</span><span class="pln"> password</span><span class="pun">=</span><span class="str">'1X&lt;ISRUkw+tuK'</span><span class="pun">)</span><span class="pln">
        response </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">reverse</span><span class="pun">(</span><span class="str">'my-borrowed'</span><span class="pun">))</span><span class="pln">

        </span><span class="com"># تحقق من أن المستخدم قد سجّل الدخول</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">str</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">context</span><span class="pun">[</span><span class="str">'user'</span><span class="pun">]),</span><span class="pln"> </span><span class="str">'testuser1'</span><span class="pun">)</span><span class="pln">
        </span><span class="com"># تحقق من حصولنا على استجابة تمثل "النجاح‫"</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">status_code</span><span class="pun">,</span><span class="pln"> </span><span class="lit">200</span><span class="pun">)</span><span class="pln">

        </span><span class="com"># تحقق من عدم وجود أيّ كتب في القائمة في البداية (‫لا توجد كتب مُعارة)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertTrue</span><span class="pun">(</span><span class="str">'bookinstance_list'</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">context</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">len</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">context</span><span class="pun">[</span><span class="str">'bookinstance_list'</span><span class="pun">]),</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln">

        </span><span class="com"># غيّر الآن جميع الكتب لتكون مُعارة</span><span class="pln">
        books </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BookInstance</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">all</span><span class="pun">()[:</span><span class="lit">10</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">
            book</span><span class="pun">.</span><span class="pln">status </span><span class="pun">=</span><span class="pln"> </span><span class="str">'o'</span><span class="pln">
            book</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">

        </span><span class="com"># تحقق من أننا قد استعرنا الكتب في القائمة</span><span class="pln">
        response </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">reverse</span><span class="pun">(</span><span class="str">'my-borrowed'</span><span class="pun">))</span><span class="pln">
        </span><span class="com"># تحقق من أن المستخدم قد سجّل الدخول</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">str</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">context</span><span class="pun">[</span><span class="str">'user'</span><span class="pun">]),</span><span class="pln"> </span><span class="str">'testuser1'</span><span class="pun">)</span><span class="pln">
        </span><span class="com"># تحقق من حصولنا على استجابة تمثل "النجاح‫"</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">status_code</span><span class="pun">,</span><span class="pln"> </span><span class="lit">200</span><span class="pun">)</span><span class="pln">

        self</span><span class="pun">.</span><span class="pln">assertTrue</span><span class="pun">(</span><span class="str">'bookinstance_list'</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">context</span><span class="pun">)</span><span class="pln">

        </span><span class="com"># ‫تأكد من أن جميع الكتب تعود إلى المستخدم testuser1 وأنها مُعارة</span><span class="pln">
        </span><span class="kwd">for</span><span class="pln"> bookitem </span><span class="kwd">in</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">context</span><span class="pun">[</span><span class="str">'bookinstance_list'</span><span class="pun">]:</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">context</span><span class="pun">[</span><span class="str">'user'</span><span class="pun">],</span><span class="pln"> bookitem</span><span class="pun">.</span><span class="pln">borrower</span><span class="pun">)</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">bookitem</span><span class="pun">.</span><span class="pln">status</span><span class="pun">,</span><span class="pln"> </span><span class="str">'o'</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> test_pages_ordered_by_due_date</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># غيّر جميع الكتب لتكون مُعارة</span><span class="pln">
        </span><span class="kwd">for</span><span class="pln"> book </span><span class="kwd">in</span><span class="pln"> </span><span class="typ">BookInstance</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">all</span><span class="pun">():</span><span class="pln">
            book</span><span class="pun">.</span><span class="pln">status</span><span class="pun">=</span><span class="str">'o'</span><span class="pln">
            book</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">

        login </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">login</span><span class="pun">(</span><span class="pln">username</span><span class="pun">=</span><span class="str">'testuser1'</span><span class="pun">,</span><span class="pln"> password</span><span class="pun">=</span><span class="str">'1X&lt;ISRUkw+tuK'</span><span class="pun">)</span><span class="pln">
        response </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">reverse</span><span class="pun">(</span><span class="str">'my-borrowed'</span><span class="pun">))</span><span class="pln">

        </span><span class="com"># تحقق من أن المستخدم قد سجّل الدخول</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">str</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">context</span><span class="pun">[</span><span class="str">'user'</span><span class="pun">]),</span><span class="pln"> </span><span class="str">'testuser1'</span><span class="pun">)</span><span class="pln">
        </span><span class="com"># تحقق من حصولنا على استجابة تمثل "النجاح‫"</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">status_code</span><span class="pun">,</span><span class="pln"> </span><span class="lit">200</span><span class="pun">)</span><span class="pln">

        </span><span class="com"># تأكد من عرض 10 عناصر فقط بسبب ترقيم الصفحات</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">len</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">context</span><span class="pun">[</span><span class="str">'bookinstance_list'</span><span class="pun">]),</span><span class="pln"> </span><span class="lit">10</span><span class="pun">)</span><span class="pln">

        last_date </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pln">
        </span><span class="kwd">for</span><span class="pln"> book </span><span class="kwd">in</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">context</span><span class="pun">[</span><span class="str">'bookinstance_list'</span><span class="pun">]:</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> last_date </span><span class="pun">==</span><span class="pln"> </span><span class="lit">0</span><span class="pun">:</span><span class="pln">
                last_date </span><span class="pun">=</span><span class="pln"> book</span><span class="pun">.</span><span class="pln">due_back
            </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
                self</span><span class="pun">.</span><span class="pln">assertTrue</span><span class="pun">(</span><span class="pln">last_date </span><span class="pun">&lt;=</span><span class="pln"> book</span><span class="pun">.</span><span class="pln">due_back</span><span class="pun">)</span><span class="pln">
                last_date </span><span class="pun">=</span><span class="pln"> book</span><span class="pun">.</span><span class="pln">due_back</span></pre>

<p>
	يمكنك أيضًا إضافة اختبارات لترقيم الصفحات Pagination إذا أردتَ ذلك.
</p>

<h4>
	اختبار العروض مع الاستمارات
</h4>

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

<p>
	لنكتب بعض اختبارات العرض المُستخدَم لتجديد الكتب <code>renew_book_librarian()‎</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5599_47" style=""><span class="kwd">from</span><span class="pln"> catalog</span><span class="pun">.</span><span class="pln">forms </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">RenewBookForm</span><span class="pln">

</span><span class="lit">@permission_required</span><span class="pun">(</span><span class="str">'catalog.can_mark_returned'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> renew_book_librarian</span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> pk</span><span class="pun">):</span><span class="pln">
    </span><span class="str">"""View function for renewing a specific BookInstance by librarian."""</span><span class="pln">
    book_instance </span><span class="pun">=</span><span class="pln"> get_object_or_404</span><span class="pun">(</span><span class="typ">BookInstance</span><span class="pun">,</span><span class="pln"> pk</span><span class="pun">=</span><span class="pln">pk</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># إذا كان هذا الطلب من النوع‫ POST، فعالج بيانات الاستمارة</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">

        </span><span class="com"># أنشئ نسخة من الاستمارة واملأها ببيانات من الطلب (الربط‫ Binding):</span><span class="pln">
        book_renewal_form </span><span class="pun">=</span><span class="pln"> </span><span class="typ">RenewBookForm</span><span class="pun">(</span><span class="pln">request</span><span class="pun">.</span><span class="pln">POST</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"> form</span><span class="pun">.</span><span class="pln">is_valid</span><span class="pun">():</span><span class="pln">
            </span><span class="com"># ‫معالجة البيانات في form.cleaned_data كما هو مطلوب (نكتبها هنا فقط في حقل النموذج due_back)</span><span class="pln">
            book_instance</span><span class="pun">.</span><span class="pln">due_back </span><span class="pun">=</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">cleaned_data</span><span class="pun">[</span><span class="str">'renewal_date'</span><span class="pun">]</span><span class="pln">
            book_instance</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">

            </span><span class="com"># ‫إعادة التوجيه إلى عنوان URL جديد</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> </span><span class="typ">HttpResponseRedirect</span><span class="pun">(</span><span class="pln">reverse</span><span class="pun">(</span><span class="str">'all-borrowed'</span><span class="pun">))</span><span class="pln">

    </span><span class="com"># إذا كان تابع‫ GET (أو أي تابع آخر)، فأنشئ الاستمارة الافتراضية</span><span class="pln">
    </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
        proposed_renewal_date </span><span class="pun">=</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">date</span><span class="pun">.</span><span class="pln">today</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">timedelta</span><span class="pun">(</span><span class="pln">weeks</span><span class="pun">=</span><span class="lit">3</span><span class="pun">)</span><span class="pln">
        book_renewal_form </span><span class="pun">=</span><span class="pln"> </span><span class="typ">RenewBookForm</span><span class="pun">(</span><span class="pln">initial</span><span class="pun">={</span><span class="str">'renewal_date'</span><span class="pun">:</span><span class="pln"> proposed_renewal_date</span><span class="pun">})</span><span class="pln">

    context </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="str">'book_renewal_form'</span><span class="pun">:</span><span class="pln"> book_renewal_form</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'book_instance'</span><span class="pun">:</span><span class="pln"> book_instance</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="kwd">return</span><span class="pln"> render</span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> </span><span class="str">'catalog/book_renew_librarian.html'</span><span class="pun">,</span><span class="pln"> context</span><span class="pun">)</span></pre>

<p>
	ينبغي اختبار أن العرض متاح فقط للمستخدمين الذين لديهم الإذن <code>can_mark_returned</code>، ويُعاد توجيه المستخدمين إلى صفحة خطأ <a href="https://academy.hsoub.com/questions/18358-%D9%85%D8%A7%D8%B0%D8%A7-%D8%AA%D8%B9%D9%86%D9%8A-%D8%AD%D8%A7%D9%84%D8%A9-http-status-codes-4xx/" rel="">HTTP 404</a> إذا حاولوا تجديد نسخة كتاب <code>BookInstance</code> ليست موجودة. ينبغي أن نتحقق من ضبط القيمة الأولية للاستمارة بتاريخ لثلاثة أسابيع في المستقبل، وأنه إذا نجحت عملية التحقق من صحة البيانات، فسيُعاد توجيهنا إلى عرض "جميع الكتب المستعارة"، وسنتحقق -كجزء من فحص اختبارات فشل التحقق من صحة البيانات- من أن استمارتنا ترسل رسائل الخطأ المناسبة.
</p>

<p>
	أضِف الجزء الأول من صنف الاختبار الموضح فيما يلي إلى نهاية الملف "‎/catalog/tests/test_views.py"، إذ يؤدي ذلك إلى إنشاء مستخدمَين ونسختين من الكتاب، ولكنه يمنح مستخدمًا واحدًا فقط الإذن المطلوب للوصول إلى العرض:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5599_49" style=""><span class="kwd">import</span><span class="pln"> uuid

</span><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">contrib</span><span class="pun">.</span><span class="pln">auth</span><span class="pun">.</span><span class="pln">models </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Permission</span><span class="pln"> </span><span class="com"># مطلوب لمنح الإذن اللازم لضبط كتاب على أنه مُعاد</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">RenewBookInstancesViewTest</span><span class="pun">(</span><span class="typ">TestCase</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> setUp</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># أنشئ مستخدمًا</span><span class="pln">
        test_user1 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">User</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">create_user</span><span class="pun">(</span><span class="pln">username</span><span class="pun">=</span><span class="str">'testuser1'</span><span class="pun">,</span><span class="pln"> password</span><span class="pun">=</span><span class="str">'1X&lt;ISRUkw+tuK'</span><span class="pun">)</span><span class="pln">
        test_user2 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">User</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">create_user</span><span class="pun">(</span><span class="pln">username</span><span class="pun">=</span><span class="str">'testuser2'</span><span class="pun">,</span><span class="pln"> password</span><span class="pun">=</span><span class="str">'2HJ1vRV0Z&amp;3iD'</span><span class="pun">)</span><span class="pln">

        test_user1</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">
        test_user2</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">

        </span><span class="com"># امنح المستخدم‫ test_user2 الإذن لتجديد الكتب</span><span class="pln">
        permission </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Permission</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">name</span><span class="pun">=</span><span class="str">'Set book as returned'</span><span class="pun">)</span><span class="pln">
        test_user2</span><span class="pun">.</span><span class="pln">user_permissions</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="pln">permission</span><span class="pun">)</span><span class="pln">
        test_user2</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">

        </span><span class="com"># أنشئ كتابًا</span><span class="pln">
        test_author </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Author</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">create</span><span class="pun">(</span><span class="pln">first_name</span><span class="pun">=</span><span class="str">'John'</span><span class="pun">,</span><span class="pln"> last_name</span><span class="pun">=</span><span class="str">'Smith'</span><span class="pun">)</span><span class="pln">
        test_genre </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Genre</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">create</span><span class="pun">(</span><span class="pln">name</span><span class="pun">=</span><span class="str">'Fantasy'</span><span class="pun">)</span><span class="pln">
        test_language </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Language</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">create</span><span class="pun">(</span><span class="pln">name</span><span class="pun">=</span><span class="str">'English'</span><span class="pun">)</span><span class="pln">
        test_book </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Book</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">create</span><span class="pun">(</span><span class="pln">
            title</span><span class="pun">=</span><span class="str">'Book Title'</span><span class="pun">,</span><span class="pln">
            summary</span><span class="pun">=</span><span class="str">'My book summary'</span><span class="pun">,</span><span class="pln">
            isbn</span><span class="pun">=</span><span class="str">'ABCDEFG'</span><span class="pun">,</span><span class="pln">
            author</span><span class="pun">=</span><span class="pln">test_author</span><span class="pun">,</span><span class="pln">
            language</span><span class="pun">=</span><span class="pln">test_language</span><span class="pun">,</span><span class="pln">
        </span><span class="pun">)</span><span class="pln">

        </span><span class="com"># أنشئ نوع الكتاب‫ genre لخطوة لاحقة</span><span class="pln">
        genre_objects_for_book </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Genre</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">all</span><span class="pun">()</span><span class="pln">
        </span><span class="com"># الإسناد المباشر لأنواع متعدد إلى متعدد غير مسموح به</span><span class="pln">
        test_book</span><span class="pun">.</span><span class="pln">genre</span><span class="pun">.</span><span class="pln">set</span><span class="pun">(</span><span class="pln">genre_objects_for_book</span><span class="pun">)</span><span class="pln"> 
        test_book</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">

        </span><span class="com"># أنشئ كائن‫ BookInstance للمستخدم test_user1</span><span class="pln">
        return_date </span><span class="pun">=</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">date</span><span class="pun">.</span><span class="pln">today</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">timedelta</span><span class="pun">(</span><span class="pln">days</span><span class="pun">=</span><span class="lit">5</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">test_bookinstance1 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BookInstance</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">create</span><span class="pun">(</span><span class="pln">
            book</span><span class="pun">=</span><span class="pln">test_book</span><span class="pun">,</span><span class="pln">
            imprint</span><span class="pun">=</span><span class="str">'Unlikely Imprint, 2016'</span><span class="pun">,</span><span class="pln">
            due_back</span><span class="pun">=</span><span class="pln">return_date</span><span class="pun">,</span><span class="pln">
            borrower</span><span class="pun">=</span><span class="pln">test_user1</span><span class="pun">,</span><span class="pln">
            status</span><span class="pun">=</span><span class="str">'o'</span><span class="pun">,</span><span class="pln">
        </span><span class="pun">)</span><span class="pln">

        </span><span class="com"># أنشئ كائن‫ BookInstance للمستخدم test_user2</span><span class="pln">
        return_date </span><span class="pun">=</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">date</span><span class="pun">.</span><span class="pln">today</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">timedelta</span><span class="pun">(</span><span class="pln">days</span><span class="pun">=</span><span class="lit">5</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">test_bookinstance2 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BookInstance</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">create</span><span class="pun">(</span><span class="pln">
            book</span><span class="pun">=</span><span class="pln">test_book</span><span class="pun">,</span><span class="pln">
            imprint</span><span class="pun">=</span><span class="str">'Unlikely Imprint, 2016'</span><span class="pun">,</span><span class="pln">
            due_back</span><span class="pun">=</span><span class="pln">return_date</span><span class="pun">,</span><span class="pln">
            borrower</span><span class="pun">=</span><span class="pln">test_user2</span><span class="pun">,</span><span class="pln">
            status</span><span class="pun">=</span><span class="str">'o'</span><span class="pun">,</span><span class="pln">
        </span><span class="pun">)</span></pre>

<p>
	أضِف الاختبارات التالية إلى نهاية صنف الاختبار، إذ تتحقق هذه الاختبارات من أن المستخدمين الذين لديهم الأذونات الصحيحة فقط (المستخدم testuser2) يمكنهم الوصول إلى العرض. سنتحقق من جميع الحالات وهي:
</p>

<ul>
	<li>
		عندما لا يسجّل المستخدم الدخول.
	</li>
	<li>
		عندما يسجّل المستخدم الدخول ولكن ليس لديه الأذونات الصحيحة.
	</li>
	<li>
		عندما يكون لدى المستخدم أذونات ولكنه ليس المستعير (يجب أن تنجح هذه الحالة).
	</li>
</ul>

<p>
	كما سنتحقق مما سيحدث عندما يحاولون الوصول إلى نسخة كتاب <code>BookInstance</code> غير موجودة، وكذلك من استخدام القالب الصحيح.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5599_51" style=""><span class="pln">  </span><span class="kwd">def</span><span class="pln"> test_redirect_if_not_logged_in</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        response </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">reverse</span><span class="pun">(</span><span class="str">'renew-book-librarian'</span><span class="pun">,</span><span class="pln"> kwargs</span><span class="pun">={</span><span class="str">'pk'</span><span class="pun">:</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">test_bookinstance1</span><span class="pun">.</span><span class="pln">pk</span><span class="pun">}))</span><span class="pln">
        </span><span class="com"># ‫تحقق يدويًا من إعادة التوجيه </span><span class="pln">
        </span><span class="com"># ‫لا يمكن استخدام assertRedirect، لأن عنوان URL لإعادة التوجيه لا يمكن التنبؤ به</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">status_code</span><span class="pun">,</span><span class="pln"> </span><span class="lit">302</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertTrue</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">url</span><span class="pun">.</span><span class="pln">startswith</span><span class="pun">(</span><span class="str">'/accounts/login/'</span><span class="pun">))</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> test_forbidden_if_logged_in_but_not_correct_permission</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        login </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">login</span><span class="pun">(</span><span class="pln">username</span><span class="pun">=</span><span class="str">'testuser1'</span><span class="pun">,</span><span class="pln"> password</span><span class="pun">=</span><span class="str">'1X&lt;ISRUkw+tuK'</span><span class="pun">)</span><span class="pln">
        response </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">reverse</span><span class="pun">(</span><span class="str">'renew-book-librarian'</span><span class="pun">,</span><span class="pln"> kwargs</span><span class="pun">={</span><span class="str">'pk'</span><span class="pun">:</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">test_bookinstance1</span><span class="pun">.</span><span class="pln">pk</span><span class="pun">}))</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">status_code</span><span class="pun">,</span><span class="pln"> </span><span class="lit">403</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> test_logged_in_with_permission_borrowed_book</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        login </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">login</span><span class="pun">(</span><span class="pln">username</span><span class="pun">=</span><span class="str">'testuser2'</span><span class="pun">,</span><span class="pln"> password</span><span class="pun">=</span><span class="str">'2HJ1vRV0Z&amp;3iD'</span><span class="pun">)</span><span class="pln">
        response </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">reverse</span><span class="pun">(</span><span class="str">'renew-book-librarian'</span><span class="pun">,</span><span class="pln"> kwargs</span><span class="pun">={</span><span class="str">'pk'</span><span class="pun">:</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">test_bookinstance2</span><span class="pun">.</span><span class="pln">pk</span><span class="pun">}))</span><span class="pln">

        </span><span class="com"># تأكد من أنه يتيح لنا تسجيل الدخول، فهذا كتابنا ولدينا الأذونات الصحيحة</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">status_code</span><span class="pun">,</span><span class="pln"> </span><span class="lit">200</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> test_logged_in_with_permission_another_users_borrowed_book</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        login </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">login</span><span class="pun">(</span><span class="pln">username</span><span class="pun">=</span><span class="str">'testuser2'</span><span class="pun">,</span><span class="pln"> password</span><span class="pun">=</span><span class="str">'2HJ1vRV0Z&amp;3iD'</span><span class="pun">)</span><span class="pln">
        response </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">reverse</span><span class="pun">(</span><span class="str">'renew-book-librarian'</span><span class="pun">,</span><span class="pln"> kwargs</span><span class="pun">={</span><span class="str">'pk'</span><span class="pun">:</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">test_bookinstance1</span><span class="pun">.</span><span class="pln">pk</span><span class="pun">}))</span><span class="pln">

        </span><span class="com"># تحقق من أنه يتيح لنا تسجيل الدخول، لأننا أمناء مكتبة، ويمكننا عرض جميع كتب المستخدمين</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">status_code</span><span class="pun">,</span><span class="pln"> </span><span class="lit">200</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> test_HTTP404_for_invalid_book_if_logged_in</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># ‫معرّف uid لمطابقة نسخة كتابنا- غير مرجح حدوثه</span><span class="pln">
        test_uid </span><span class="pun">=</span><span class="pln"> uuid</span><span class="pun">.</span><span class="pln">uuid4</span><span class="pun">()</span><span class="pln">
        login </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">login</span><span class="pun">(</span><span class="pln">username</span><span class="pun">=</span><span class="str">'testuser2'</span><span class="pun">,</span><span class="pln"> password</span><span class="pun">=</span><span class="str">'2HJ1vRV0Z&amp;3iD'</span><span class="pun">)</span><span class="pln">
        response </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">reverse</span><span class="pun">(</span><span class="str">'renew-book-librarian'</span><span class="pun">,</span><span class="pln"> kwargs</span><span class="pun">={</span><span class="str">'pk'</span><span class="pun">:</span><span class="pln">test_uid</span><span class="pun">}))</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">status_code</span><span class="pun">,</span><span class="pln"> </span><span class="lit">404</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> test_uses_correct_template</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        login </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">login</span><span class="pun">(</span><span class="pln">username</span><span class="pun">=</span><span class="str">'testuser2'</span><span class="pun">,</span><span class="pln"> password</span><span class="pun">=</span><span class="str">'2HJ1vRV0Z&amp;3iD'</span><span class="pun">)</span><span class="pln">
        response </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">reverse</span><span class="pun">(</span><span class="str">'renew-book-librarian'</span><span class="pun">,</span><span class="pln"> kwargs</span><span class="pun">={</span><span class="str">'pk'</span><span class="pun">:</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">test_bookinstance1</span><span class="pun">.</span><span class="pln">pk</span><span class="pun">}))</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">status_code</span><span class="pun">,</span><span class="pln"> </span><span class="lit">200</span><span class="pun">)</span><span class="pln">

        </span><span class="com"># تحقق من أننا نستخدم القالب الصحيح</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertTemplateUsed</span><span class="pun">(</span><span class="pln">response</span><span class="pun">,</span><span class="pln"> </span><span class="str">'catalog/book_renew_librarian.html'</span><span class="pun">)</span></pre>

<p>
	أضِف تابع الاختبار التالي الذي يتحقق من أن التاريخ الأولي للاستمارة هو ثلاثة أسابيع في المستقبل، ولاحظ كيف أننا قادرون على الوصول إلى القيمة الأولية لحقل الاستمارة <code>response.context['form'].initial['renewal_date']‎</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5599_53" style=""><span class="pln">   </span><span class="kwd">def</span><span class="pln"> test_form_renewal_date_initially_has_date_three_weeks_in_future</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        login </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">login</span><span class="pun">(</span><span class="pln">username</span><span class="pun">=</span><span class="str">'testuser2'</span><span class="pun">,</span><span class="pln"> password</span><span class="pun">=</span><span class="str">'2HJ1vRV0Z&amp;3iD'</span><span class="pun">)</span><span class="pln">
        response </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">reverse</span><span class="pun">(</span><span class="str">'renew-book-librarian'</span><span class="pun">,</span><span class="pln"> kwargs</span><span class="pun">={</span><span class="str">'pk'</span><span class="pun">:</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">test_bookinstance1</span><span class="pun">.</span><span class="pln">pk</span><span class="pun">}))</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">status_code</span><span class="pun">,</span><span class="pln"> </span><span class="lit">200</span><span class="pun">)</span><span class="pln">

        date_3_weeks_in_future </span><span class="pun">=</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">date</span><span class="pun">.</span><span class="pln">today</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">timedelta</span><span class="pun">(</span><span class="pln">weeks</span><span class="pun">=</span><span class="lit">3</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">context</span><span class="pun">[</span><span class="str">'form'</span><span class="pun">].</span><span class="pln">initial</span><span class="pun">[</span><span class="str">'renewal_date'</span><span class="pun">],</span><span class="pln"> date_3_weeks_in_future</span><span class="pun">)</span></pre>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5599_55" style=""><span class="pln">   </span><span class="kwd">def</span><span class="pln"> test_redirects_to_all_borrowed_book_list_on_success</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        login </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">login</span><span class="pun">(</span><span class="pln">username</span><span class="pun">=</span><span class="str">'testuser2'</span><span class="pun">,</span><span class="pln"> password</span><span class="pun">=</span><span class="str">'2HJ1vRV0Z&amp;3iD'</span><span class="pun">)</span><span class="pln">
        valid_date_in_future </span><span class="pun">=</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">date</span><span class="pun">.</span><span class="pln">today</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">timedelta</span><span class="pun">(</span><span class="pln">weeks</span><span class="pun">=</span><span class="lit">2</span><span class="pun">)</span><span class="pln">
        response </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="pln">reverse</span><span class="pun">(</span><span class="str">'renew-book-librarian'</span><span class="pun">,</span><span class="pln"> kwargs</span><span class="pun">={</span><span class="str">'pk'</span><span class="pun">:</span><span class="pln">self</span><span class="pun">.</span><span class="pln">test_bookinstance1</span><span class="pun">.</span><span class="pln">pk</span><span class="pun">,}),</span><span class="pln"> </span><span class="pun">{</span><span class="str">'renewal_date'</span><span class="pun">:</span><span class="pln">valid_date_in_future</span><span class="pun">})</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertRedirects</span><span class="pun">(</span><span class="pln">response</span><span class="pun">,</span><span class="pln"> reverse</span><span class="pun">(</span><span class="str">'all-borrowed'</span><span class="pun">))</span></pre>

<p>
	<strong>تحذير</strong>: أُضيف العرض all-borrowed كتحدٍ لك، ويمكن أن تعيد شيفرتك البرمجية التوجيه إلى الصفحة الرئيسية '/' بدلًا من ذلك، فإذا كان الأمر كذلك، فعدّل آخر سطرين من شيفرة الاختبار لتكون مماثلة للشيفرة البرمجية التالية، إذ يضمن الوسيط <code>follow=True</code> في الطلب أن يعيد الطلب عنوان URL للهدف النهائي وعندها يجب تحديد <code>/catalog/</code> بدلًا من <code>/</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5599_57" style=""><span class="pln"> response </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="pln">reverse</span><span class="pun">(</span><span class="str">'renew-book-librarian'</span><span class="pun">,</span><span class="pln"> kwargs</span><span class="pun">={</span><span class="str">'pk'</span><span class="pun">:</span><span class="pln">self</span><span class="pun">.</span><span class="pln">test_bookinstance1</span><span class="pun">.</span><span class="pln">pk</span><span class="pun">,}),</span><span class="pln"> </span><span class="pun">{</span><span class="str">'renewal_date'</span><span class="pun">:</span><span class="pln">valid_date_in_future</span><span class="pun">},</span><span class="pln"> follow</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
 self</span><span class="pun">.</span><span class="pln">assertRedirects</span><span class="pun">(</span><span class="pln">response</span><span class="pun">,</span><span class="pln"> </span><span class="str">'/catalog/'</span><span class="pun">)</span></pre>

<p>
	انسخ الدالتين التاليتين في الصنف، واللتين تختبران طلبات <code>POST</code> مرةً ثانية، ولكن مع تواريخ تجديد غير صالحة في هذه الحالة. سنستخدم الدالة <code>assertFormError()‎</code> للتحقق من أن رسائل الخطأ كما هو متوقع:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5599_59" style=""><span class="pln">   </span><span class="kwd">def</span><span class="pln"> test_form_invalid_renewal_date_past</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        login </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">login</span><span class="pun">(</span><span class="pln">username</span><span class="pun">=</span><span class="str">'testuser2'</span><span class="pun">,</span><span class="pln"> password</span><span class="pun">=</span><span class="str">'2HJ1vRV0Z&amp;3iD'</span><span class="pun">)</span><span class="pln">
        date_in_past </span><span class="pun">=</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">date</span><span class="pun">.</span><span class="pln">today</span><span class="pun">()</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">timedelta</span><span class="pun">(</span><span class="pln">weeks</span><span class="pun">=</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
        response </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="pln">reverse</span><span class="pun">(</span><span class="str">'renew-book-librarian'</span><span class="pun">,</span><span class="pln"> kwargs</span><span class="pun">={</span><span class="str">'pk'</span><span class="pun">:</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">test_bookinstance1</span><span class="pun">.</span><span class="pln">pk</span><span class="pun">}),</span><span class="pln"> </span><span class="pun">{</span><span class="str">'renewal_date'</span><span class="pun">:</span><span class="pln"> date_in_past</span><span class="pun">})</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">status_code</span><span class="pun">,</span><span class="pln"> </span><span class="lit">200</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertFormError</span><span class="pun">(</span><span class="pln">response</span><span class="pun">,</span><span class="pln"> </span><span class="str">'form'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'renewal_date'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Invalid date - renewal in past'</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> test_form_invalid_renewal_date_future</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        login </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">login</span><span class="pun">(</span><span class="pln">username</span><span class="pun">=</span><span class="str">'testuser2'</span><span class="pun">,</span><span class="pln"> password</span><span class="pun">=</span><span class="str">'2HJ1vRV0Z&amp;3iD'</span><span class="pun">)</span><span class="pln">
        invalid_date_in_future </span><span class="pun">=</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">date</span><span class="pun">.</span><span class="pln">today</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">timedelta</span><span class="pun">(</span><span class="pln">weeks</span><span class="pun">=</span><span class="lit">5</span><span class="pun">)</span><span class="pln">
        response </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="pln">reverse</span><span class="pun">(</span><span class="str">'renew-book-librarian'</span><span class="pun">,</span><span class="pln"> kwargs</span><span class="pun">={</span><span class="str">'pk'</span><span class="pun">:</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">test_bookinstance1</span><span class="pun">.</span><span class="pln">pk</span><span class="pun">}),</span><span class="pln"> </span><span class="pun">{</span><span class="str">'renewal_date'</span><span class="pun">:</span><span class="pln"> invalid_date_in_future</span><span class="pun">})</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertEqual</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">status_code</span><span class="pun">,</span><span class="pln"> </span><span class="lit">200</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">assertFormError</span><span class="pun">(</span><span class="pln">response</span><span class="pun">,</span><span class="pln"> </span><span class="str">'form'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'renewal_date'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Invalid date - renewal more than 4 weeks ahead'</span><span class="pun">)</span></pre>

<p>
	يمكن استخدام الأساليب نفسها لاختبار العرض الآخر.
</p>

<h3>
	القوالب
</h3>

<p>
	يوفر جانغو <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%A7-%D9%87%D9%8A-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-%D9%84%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-api%D8%9F-r1512/" rel="">واجهات برمجة التطبيقات <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr></a> خاصة بالاختبار للتحقق من أن عرضك يستدعي القالب الصحيح، وللسماح بالتحقق من إرسال المعلومات الصحيحة، ولكن لا يوجد دعم محدد لواجهة برمجة التطبيقات للاختبار في جانغو بحيث يُظهر خرج صفحة HTML كما هو متوقع.
</p>

<h2>
	أدوات الاختبار الأخرى الموصى بها
</h2>

<p>
	يمكن أن يساعدك إطار عمل اختبار جانغو في كتابة اختبارات الوحدة والتكامل الفعالة، إذ تعرّفنا فقط على جزء بسيط مما يمكن أن يفعله إطار عمل unittest الأساسي، بالإضافة إلى إضافات جانغو. اطلّع مثلًا على كيفية استخدام <a href="https://docs.python.org/3/library/unittest.mock-examples.html" rel="external nofollow">unittest.mock</a> لتعويض نقص المكتبات الخارجية لتتمكّن من اختبار شيفرتك البرمجية بدقة أكبر.
</p>

<p>
	هناك العديد من أدوات الاختبار الأخرى التي يمكنك استخدامها، ولكننا سنذكر أداتين هما:
</p>

<ul>
	<li>
		<a href="https://coverage.readthedocs.io/en/latest/" rel="external nofollow">Coverage</a>: تعطي هذه الأداة الخاصة ببايثون تقريرًا بمقدار شيفرتك البرمجية التي تنّفذها اختباراتك، وهي مفيدة خاصةً عندما تبدأ وتحاول تحديد ما يجب عليك اختباره بالضبط.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/general/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A6%D8%A9-%D9%84%D9%84%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1%D8%A7%D8%AA-%D8%A7%D9%84%D8%A2%D9%84%D9%8A%D8%A9-%D9%81%D9%8A-%D9%85%D8%B4%D8%A7%D8%B1%D9%8A%D8%B9-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D9%84%D9%84%D8%AA%D9%88%D8%A7%D9%81%D9%82-%D9%85%D8%B9-%D8%A7%D9%84%D9%85%D8%AA%D8%B5%D9%81%D8%AD%D8%A7%D8%AA-r1990/" rel="">إطار عمل سيلينيوم Selenium</a>: وهو إطار عمل لأتمتة الاختبار في متصفح حقيقي، ويسمح بمحاكاة تفاعل مستخدم حقيقي مع الموقع، ويوفر إطار عمل رائع لنظام اختبار موقعك (الخطوة التالية من اختبار التكامل).
	</li>
</ul>

<h2>
	تحدى نفسك
</h2>

<p>
	هناك الكثير من النماذج والعروض التي يمكننا اختبارها، لذا حاول إنشاء حالة اختبار للعرض <code>AuthorCreate</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5599_61" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">AuthorCreate</span><span class="pun">(</span><span class="typ">PermissionRequiredMixin</span><span class="pun">,</span><span class="pln"> </span><span class="typ">CreateView</span><span class="pun">):</span><span class="pln">
    model </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Author</span><span class="pln">
    fields </span><span class="pun">=</span><span class="pln"> </span><span class="str">'__all__'</span><span class="pln">
    initial </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="str">'date_of_death'</span><span class="pun">:</span><span class="str">'12/10/2016'</span><span class="pun">}</span><span class="pln">
    permission_required </span><span class="pun">=</span><span class="pln"> </span><span class="str">'catalog.can_mark_returned'</span></pre>

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

<h2>
	الخلاصة
</h2>

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

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

<p>
	سنوضح في المقال التالي كيفية نشر موقع جانغو الذي اختبَرناه بالكامل.
</p>

<p>
	ترجمة -وبتصرُّف- للمقال <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Testing" rel="external nofollow">Django Tutorial Part 10: Testing a Django web application</a>.
</p>

<h2>
	اقرأ المزيد
</h2>

<ul>
	<li>
		المقال التالي: <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-10-%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%81%D9%8A-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%A7%D8%AC-r2122/" rel="">تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج</a>
	</li>
	<li>
		المقال السابق <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%85%D9%86-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-forms-r2120/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms</a>
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/topics/testing/overview/" rel="external nofollow">كتابة وفحص الاختبارات</a> (توثيق جانغو)
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/intro/tutorial05/" rel="external nofollow">كتابة أول تطبيق جانغو - الجزء الخامس: كتابة اختبار آلي</a> (توثيق جانغو)
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/topics/testing/tools/" rel="external nofollow">أدوات الفحص</a> (توثيق جانغو)
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1%D8%A7%D8%AA-%D9%85%D8%B4%D8%A7%D8%B1%D9%8A%D8%B9-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D8%A7%D9%84%D8%A2%D9%84%D9%8A%D8%A9-%D9%84%D9%84%D8%AA%D9%88%D8%A7%D9%81%D9%82-%D9%85%D8%B9-%D8%A7%D9%84%D9%85%D8%AA%D8%B5%D9%81%D8%AD%D8%A7%D8%AA-r1989/" rel="">مدخل إلى اختبارات مشاريع الويب الآلية للتوافق مع المتصفحات</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%B3%D8%B1%D9%8A%D8%B9-%D8%A5%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-python-3-r535/" rel="">الدليل السريع إلى لغة البرمجة بايثون Python</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2121</guid><pubDate>Sat, 07 Oct 2023 13:05:31 +0000</pubDate></item><item><title>&#x62A;&#x637;&#x628;&#x64A;&#x642; &#x639;&#x645;&#x644;&#x64A; &#x644;&#x62A;&#x639;&#x644;&#x645; &#x62C;&#x627;&#x646;&#x63A;&#x648; - &#x627;&#x644;&#x62C;&#x632;&#x621; &#x627;&#x644;&#x62B;&#x627;&#x645;&#x646;: &#x627;&#x644;&#x639;&#x645;&#x644; &#x645;&#x639; &#x627;&#x644;&#x627;&#x633;&#x62A;&#x645;&#x627;&#x631;&#x627;&#x62A; Forms</title><link>https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%85%D9%86-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-forms-r2120/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_09/---Forms.png.07c3f719f4f2ac01bf504dc26e5a170f.png" /></p>
<p>
	سنوضح في هذا المقال كيفية العمل مع استمارات HTML في إطار العمل جانغو <a href="https://academy.hsoub.com/programming/python/django/" rel="">Django</a>، خاصةً الطريقة الأسهل لكتابة الاستمارات لإنشاء نسخ من النموذج وتحديثها وحذفها، إذ سنوسّع <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2090/" rel="">موقع المكتبة المحلية LocalLibrary</a> بحيث يمكن لأمناء المكتبة تجديد الكتب وإنشاء وتحديث وحذف المؤلفين باستخدام استماراتنا الخاصة عوضًا عن استخدام تطبيق المدير.
</p>

<ul>
	<li>
		<strong>المتطلبات الأساسية</strong>: اطّلع على جميع المقالات السابقة من هذه السلسلة، بما في ذلك المقال الخاص <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B3%D8%A7%D8%A8%D8%B9-%D8%A7%D8%B3%D8%AA%D9%8A%D8%AB%D8%A7%D9%82-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D9%8A%D9%86-%D9%88%D8%A3%D8%B0%D9%88%D9%86%D8%A7%D8%AA%D9%87%D9%85-r2119/" rel="">باستيثاق المستخدمين وأذوناتهم</a>.
	</li>
	<li>
		<strong>الهدف</strong>: فهم كيفية كتابة الاستمارات للحصول على معلومات من المستخدمين وتحديث قاعدة البيانات، وفهم كيف يمكن لعروض التعديل المُعمَّمة المستندة إلى الأصناف تبسيط إنشاء الاستمارات للعمل مع نموذج واحد فقط.
	</li>
</ul>

<p>
	تُعَد <a href="https://academy.hsoub.com/programming/html/%D8%A7%D9%84%D9%86%D9%85%D8%A7%D8%B0%D8%AC-forms-%D9%81%D9%8A-html5-r370/" rel="">استمارة HTML</a> مجموعةً مكونةً من حقل واحد أو عنصر واجهة مستخدم <a href="https://docs.djangoproject.com/en/4.0/ref/forms/fields/#widget" rel="external nofollow">widget</a> واحدة أو أكثر على صفحة الويب، والتي يمكن استخدامها لجمع المعلومات من المستخدمين لإرسالها إلى الخادم، فالاستمارات هي آلية مرنة لتجميع دخل المستخدم، نظرًا لوجود عناصر واجهة مستخدم مناسبة لإدخال العديد من أنواع البيانات المختلفة، بما في ذلك مربعات النص ومربعات الاختيار وأزرار الاختيار ومنتقي التواريخ وما إلى ذلك. تُعَد الاستمارات طريقةً آمنة نسبيًا لمشاركة البيانات مع الخادم، لأنها تسمح بإرسال البيانات في طلبات <code>POST</code> مع الحماية من هجمات تزوير الطلبات عبر المواقع.
</p>

<p>
	لم ننشِئ أيّ استمارات في هذه السلسلة من المقالات حتى الآن، ولكننا صادفناها في <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D9%85%D9%88%D9%82%D8%B9-%D9%85%D8%AF%D9%8A%D8%B1-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-admin-r2105/" rel="">موقع مدير جانغو</a>، فمثلًا تُظهِر لقطة الشاشة التالية استمارةً لتعديل أحد <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%86%D9%85%D8%A7%D8%B0%D8%AC-models-r2092/" rel="">نماذج الكتاب Book</a>، والذي يتكون من عدد من قوائم الاختيار ومحرّري النصوص.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135468" href="https://academy.hsoub.com/uploads/monthly_2023_09/01_admin_book_add.png.f98762a8b1e86a68617cecee2094c576.png" rel=""><img alt="01_admin_book_add.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135468" data-unique="a8qc75plf" src="https://academy.hsoub.com/uploads/monthly_2023_09/01_admin_book_add.thumb.png.eb7dd2984c4549a8f235287103c46c63.png"> </a>
</p>

<p>
	يمكن أن يكون العمل مع الاستمارات معقدًا، إذ يجب أن يكتب المطورون شيفرة HTML للاستمارة، ويجب التحقق من صحة البيانات المدخلة و<a href="https://academy.hsoub.com/programming/php/%D8%A7%D9%84%D9%85%D8%B1%D8%B4%D8%AD%D8%A7%D8%AA-%D9%88%D8%AF%D9%88%D8%A7%D9%84-%D8%A7%D9%84%D9%85%D8%B1%D8%B4%D8%AD-filter-%D8%A7%D9%84%D9%85%D8%B9%D9%82%D9%85%D8%A9-%D9%81%D9%8A-php-r1126/" rel="">تعقيمها Sanitization</a> (أي التخلص من البيانات الحساسة أو غير الضرورية) على الخادم وربما في المتصفح أيضًا، وإعادة نشر الاستمارة مع رسائل خطأ لإبلاغ المستخدمين بأيّ حقول غير صالحة، والتعامل مع البيانات عند إرسالها بنجاح، وأخيرًا الرد على المستخدم بطريقة ما للإشارة إلى النجاح.
</p>

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

<p>
	سنعرض في هذا المقال بعض الطرق التي يمكنك من خلالها إنشاء الاستمارات والعمل معها، وخاصة كيف يمكن لعروض التعديل المُعمَّمة أن تقلل من حجم العمل الواجب تطبيقه لإنشاء الاستمارات بهدف معالجة <a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D9%84%D9%86%D9%85%D8%A7%D8%B0%D8%AC-models-%D9%88%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D8%B9%D9%84%D8%A7%D9%85-%D8%B9%D9%86-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%81%D9%8A-django-r405/" rel="">النماذج Models</a>، إذ سنوسّع تطبيق المكتبة المحلية LocalLibrary من خلال إضافة استمارة للسماح لأمناء المكتبة بتجديد الكتب، وسننشئ صفحات لإنشاء وتعديل وحذف الكتب والمؤلفين، أو إعادة إنتاج نسخة أساسية من الاستمارة التي وضحناها سابقًا لتعديل الكتب.
</p>

<p>
	تتألف هذه السلسلة الفرعية من السلسلة الأشمل <a href="https://academy.hsoub.com/tags/%D8%AA%D8%B9%D9%84%D9%85%20%D8%AA%D8%B7%D9%88%D9%8A%D8%B1%20%D8%A7%D9%84%D9%88%D9%8A%D8%A8/" rel="">تعلم تطوير الويب</a> من المقالات التالية:
</p>

<ul>
	<li>
		<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>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%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%AC%D8%A7%D9%86%D8%BA%D9%88-django-r2049/" rel="">إعداد بيئة تطوير تطبيقات جانغو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2090/" rel="">تطبيق عملي لتعلم جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%86%D9%85%D8%A7%D8%B0%D8%AC-models-r2092/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D9%85%D9%88%D9%82%D8%B9-%D9%85%D8%AF%D9%8A%D8%B1-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-admin-r2105/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B1%D8%A7%D8%A8%D8%B9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B5%D9%81%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%A7%D9%84%D8%B1%D8%A6%D9%8A%D8%B3%D9%8A%D8%A9-r2106/" rel="">تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AE%D8%A7%D9%85%D8%B3-%D8%A7%D9%84%D8%B9%D8%B1%D9%88%D8%B6-views-%D8%A7%D9%84%D8%B9%D8%A7%D9%85%D8%A9-%D9%88%D8%A7%D9%84%D8%AA%D9%81%D8%B5%D9%8A%D9%84%D9%8A%D8%A9-r2107/" rel="">تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B3%D8%A7%D8%AF%D8%B3-%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D8%AC%D9%84%D8%B3%D8%A7%D8%AA-sessions-r2118/" rel="">تطبيق عملي لتعلم جانغو - الجزء السادس: إدارة الجلسات Sessions</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B3%D8%A7%D8%A8%D8%B9-%D8%A7%D8%B3%D8%AA%D9%8A%D8%AB%D8%A7%D9%82-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D9%8A%D9%86-%D9%88%D8%A3%D8%B0%D9%88%D9%86%D8%A7%D8%AA%D9%87%D9%85-r2119/" rel="">تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم</a>
	</li>
	<li>
		<span ipsnoautolink="true">تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms</span>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AA%D8%A7%D8%B3%D8%B9-%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2121/" rel="">تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-10-%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%81%D9%8A-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%A7%D8%AC-r2122/" rel="">تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A3%D9%85%D8%A7%D9%86-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2123/" rel="">تعرف على أمان تطبيقات جانغو</a>
	</li>
</ul>

<h2>
	استمارة HTML
</h2>

<p>
	أولًا، اطّلع على مفهوم <a href="https://academy.hsoub.com/programming/html/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%86%D9%85%D8%A7%D8%B0%D8%AC-html5-%D9%88%D8%AE%D8%B5%D8%A7%D8%A6%D8%B5%D9%87%D8%A7-%D8%A7%D9%84%D8%AC%D8%AF%D9%8A%D8%AF%D8%A9-r4/" rel="">استمارات HTML</a>، ثم أنشئ استمارة HTML بسيطة، مع حقل نصي واحد لإدخال اسم الفريق والتسمية Label المرتبطة به كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135467" href="https://academy.hsoub.com/uploads/monthly_2023_09/02_form_example_name_field.png.c699666626bd54f3a760aa78ed2704dc.png" rel=""><img alt="02_form_example_name_field.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135467" data-unique="p5ufyn45z" src="https://academy.hsoub.com/uploads/monthly_2023_09/02_form_example_name_field.png.c699666626bd54f3a760aa78ed2704dc.png"> </a>
</p>

<p>
	تُعرَّف الاستمارة Form في HTML بوصفها مجموعة من العناصر ضمن وسوم <a href="https://wiki.hsoub.com/HTML/form" rel="external"><code>&lt;form&gt;…&lt;/form&gt;</code></a> التي تحتوي على عنصر إدخال <code>input</code> واحد على الأقل من النوع <code>type="submit"‎</code>.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1677_8" style=""><span class="tag">&lt;form</span><span class="pln"> </span><span class="atn">action</span><span class="pun">=</span><span class="atv">"/team_name_url/"</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;label</span><span class="pln"> </span><span class="atn">for</span><span class="pun">=</span><span class="atv">"team_name"</span><span class="tag">&gt;</span><span class="pln">Enter name: </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">id</span><span class="pun">=</span><span class="atv">"team_name"</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">"name_field"</span><span class="pln">
    </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"Default name for team."</span><span class="pln"> </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">"OK"</span><span class="pln"> </span><span class="tag">/&gt;</span><span class="pln">
</span><span class="tag">&lt;/form&gt;</span></pre>

<p>
	لدينا حقل نصي واحد فقط لإدخال اسم الفريق في مثالنا، ولكن يمكن أن تحتوي الاستمارة على أيّ عدد من عناصر الإدخال الأخرى والتسميات المرتبطة بها. تحدّد السمة <code>type</code> الخاصة بالحقل نوع عنصر واجهة المستخدم الذي سيُعرض، ويُستخدَم اسم <code>name</code> ومعرّف <code>id</code> الحقل لتحديد الحقل في شيفرة JavaScript/CSS/HTML، بينما يحدد <code>value</code> القيمة الأولية للحقل عند عرضه لأول مرة. تُحدَّد تسمية الفريق المطابق باستخدام الوسم <code>label</code> (لاحظ التسمية "أدخِل اسمًا Enter name") مع الحقل <code>for</code> الذي يحتوي على قيمة معرّف <code>id</code> حقل الإدخال <code>input</code> المرتبط به.
</p>

<p>
	سيُعرَض حقل الإدخال <code>submit</code> بوصفه زرًا افتراضيًا، ويمكن الضغط عليه لتحميل البيانات من جميع عناصر الإدخال الأخرى في الاستمارة إلى الخادم (وهي في حالتنا حقل اسم الفريق <code>team_name</code> فقط). تحدّد سمات الاستمارة <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-http-%D8%B4%D8%B1%D8%AD-%D8%A7%D9%84%D8%AA%D8%AE%D8%A7%D8%B7%D8%A8-%D8%A8%D9%8A%D9%86-%D8%A7%D9%84%D8%B9%D9%85%D9%8A%D9%84-%D9%88%D8%A7%D9%84%D8%AE%D8%A7%D8%AF%D9%85-r74/" rel="">نوع طلب HTTP</a> المُستخدَم لإرسال البيانات (عبر <code>method</code>) ووِجهة البيانات على الخادم (<code>action</code>) كما يلي:
</p>

<ul>
	<li>
		<code>action</code>: المورد أو عنوان URL لمكان إرسال البيانات لمعالجتها عند إرسال الاستمارة. إذا لم تُضبَط هذه السمة، أو ضُبطت على أنها سلسلة فارغة)، ستُعاد الاستمارة إلى عنوان URL للصفحة الحالية.
	</li>
	<li>
		<code>method</code>: نوع طلب HTTP المُستخدَم لإرسال البيانات وهو إما من النوع <code>POST</code> أو <code>GET</code>.
		<ul>
			<li>
				يجب دائمًا استخدام النوع <code>POST</code> إذا كانت البيانات ستؤدي إلى تغيير في قاعدة بيانات الخادم، لأنه يمكن جعلها أكثر مقاومة لهجمات تزوير الطلبات عبر المواقع.
			</li>
			<li>
				يجب استخدام النوع <code>GET</code> فقط للاستمارات التي لا تغير بيانات المستخدم (مثل استمارة البحث)، ويوصَى به عندما تريد أن تكون قادرًا على وضع إشارة مرجعية على <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> أو مشاركته.
			</li>
		</ul>
	</li>
</ul>

<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="">الخادم server</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> والتحقق من صحة البيانات المُعادة وإعادة عرض البيانات المُدخَلة مع تقارير الأخطاء إن لزم الأمر وتطبيق العملية المطلوبة على البيانات الصالحة أمرًا متعبًا جدًا ليكون صحيحًا، ويجعل جانغو هذا الأمر أسهل بكثير من خلال التخلص من بعض الشيفرة البرمجية الثقيلة والمتكررة.
</p>

<h2>
	عملية معالجة استمارة جانغو
</h2>

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

<p>
	يوضَح المخطط التالي كيفية معالجة جانغو لطلبات الاستمارة، بدءًا من طلب صفحة تحتوي على استمارة والموضح باللون الأخضر:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135466" href="https://academy.hsoub.com/uploads/monthly_2023_09/03_form_handling_-_standard.png.a11c1aae5c76f7957ba459ec8f6622db.png" rel=""><img alt="03_form_handling_-_standard.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135466" data-unique="ojpcdlzri" src="https://academy.hsoub.com/uploads/monthly_2023_09/03_form_handling_-_standard.png.a11c1aae5c76f7957ba459ec8f6622db.png"> </a>
</p>

<p>
	تطبّق عملية معالجة استمارة جانغو الأمور الرئيسية التالية بناءً على المخطط السابق:
</p>

<ol>
	<li>
		عرض الاستمارة الافتراضية في المرة الأولى التي يطلبها المستخدم.
		<ul>
			<li>
				يمكن أن تحتوي الاستمارة على حقول فارغة إذا أردتَ إنشاء سجل جديد، أو يمكن ملؤها مسبقًا بالقيم الأولية إذا غيّرت سجلًا أو كان لديك قيم أولية افتراضية مفيدة مثلًا.
			</li>
			<li>
				يُشار إلى الاستمارة إلى أنها غير مرتبطة Unbound في هذه المرحلة، لأنها غير مرتبطة بأيّ بيانات أدخلها المستخدم، بالرغم من أنه يمكن أن تحتوي على قيم أولية.
			</li>
		</ul>
	</li>
	<li>
		تلقي البيانات من طلب الإرسال وربطها بالاستمارة.
		<ul>
			<li>
				يعني ربط Binding البيانات بالاستمارة أن البيانات التي أدخلها المستخدم والأخطاء تكون متاحة عندما نحتاج إلى إعادة عرض الاستمارة.
			</li>
		</ul>
	</li>
	<li>
		تنظيف البيانات والتحقق من صحتها.
		<ul>
			<li>
				يؤدي تنظيف البيانات إلى تعقيم حقول الإدخال مثل إزالة المحارف غير الصالحة التي يمكن استخدامها لإرسال محتوًى ضار إلى الخادم، وتحويلها إلى أنواع بايثون متناسقة.
			</li>
			<li>
				تفحص عملية التحقق من صحة البيانات أن القيم مناسبة للحقل، مثل أن تكون موجودة في مجال البيانات الصحيح، أو أنها ليست قصيرة جدًا أو طويلة جدًا وما إلى ذلك.
			</li>
		</ul>
	</li>
	<li>
		إذا وُجدت بيانات غير صالحة، فأعِد عرض الاستمارة، ولكن هذه المرة مع أيّ قيمٍ يملؤها المستخدم ورسائل خطأ لحقول المشكلة.
	</li>
	<li>
		إذا كانت جميع البيانات صالحة، فطبّق الإجراءات المطلوبة، مثل حفظ البيانات وإرسال بريد إلكتروني وإعادة نتيجة البحث وتحميل ملف وغير ذلك.
	</li>
	<li>
		أعِد توجيه المستخدم إلى صفحة أخرى عند اكتمال جميع الإجراءات.
	</li>
</ol>

<p>
	يوفر جانغو عددًا من الأدوات والأساليب لمساعدتك في المهام السابقة، والأكثر أهمية هو الصنف <code>Form</code> الذي يبسّط توليد شيفرة HTML للاستمارة وتنظيف أو التحقق من صحة البيانات. سنشرح في القسم التالي كيفية عمل الاستمارات باستخدام تطبيق عملي لصفحة بهدف السماح لأمناء المكتبة بتجديد الكتب.
</p>

<p>
	<strong>ملاحظة</strong>: سيساعدك فهم كيفية استخدام الصنف <code>Form</code> عندما نناقش أصناف إطار عمل الاستمارة عالية المستوى الخاصة بجانغو.
</p>

<h2>
	استمارة تجديد الكتب Renew-book باستخدام الصنف Form والعرض المستند إلى الدوال
</h2>

<p>
	سنضيف صفحةً للسماح لأمناء المكتبة بتجديد الكتب المستعارة من خلال إنشاء استمارة تسمح للمستخدمين بإدخال قيمة تاريخ، إذ سنعطي الحقل قيمة أولية هي 3 أسابيع من التاريخ الحالي (فترة الاستعارة العادية)، وسنضيف تحققًا من صحة البيانات للتأكد من أنّ أمين المكتبة لا يمكنه إدخال تاريخ في الماضي، أو تاريخ بعيد جدًا في المستقبل. إذا أُدخِل تاريخ صالح، فسنكتبه في الحقل <code>BookInstance.due_back</code> الخاص بالسجل الحالي.
</p>

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

<h3>
	صنف الاستمارة Form
</h3>

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

<h4>
	التصريح عن الصنف Form
</h4>

<p>
	تُعَد صياغة التصريح عن الصنف <code>Form</code> مشابهة جدًا للصياغة المُستخدَمة في التصريح عن الصنف <code>Model</code>، وتتشاركان في أنواع الحقول نفسها وبعض المعاملات المماثلة، وهذا أمرٌ منطقي لأنه في كلتا الحالتين يجب التأكد من أن كل حقل يتعامل مع <a href="https://academy.hsoub.com/programming/general/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%A3%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-r1726/" rel="">الأنواع الصحيحة من البيانات</a>، وهو مقيدٌ بالبيانات الصالحة وله وصف description للعرض أو التوثيق.
</p>

<p>
	تُخزَّن بيانات الاستمارة في الملف "forms.py" الخاص بالتطبيق ضمن مجلد التطبيق، لذا أنشئ وافتح الملف locallibrary/catalog/forms.py. يمكن إنشاء <code>Form</code> من خلال استيراد المكتبة <code>forms</code>، واشتقاق الصنف <code>Form</code>، والتصريح عن حقول الاستمارة. يوضح المثال البسيط التالي صنف استمارة تجديد كتب المكتبة، لذا أضفه إلى ملفك الجديد:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1677_10" style=""><span class="kwd">from</span><span class="pln"> django </span><span class="kwd">import</span><span class="pln"> forms

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">RenewBookForm</span><span class="pun">(</span><span class="pln">forms</span><span class="pun">.</span><span class="typ">Form</span><span class="pun">):</span><span class="pln">
    renewal_date </span><span class="pun">=</span><span class="pln"> forms</span><span class="pun">.</span><span class="typ">DateField</span><span class="pun">(</span><span class="pln">help_text</span><span class="pun">=</span><span class="str">"Enter a date between now and 4 weeks (default 3)."</span><span class="pun">)</span></pre>

<h4>
	حقول الاستمارة
</h4>

<p>
	لدينا في مثالنا حقل تاريخ واحد <code>DateField</code> لإدخال تاريخ التجديد الذي سيُعرَض في <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="">صفحة HTML</a> بقيمة فارغة، والتسمية الافتراضية ":Renewal date"، وبعض نصوص الاستخدام المفيدة مثل: "أدخِل تاريخًا بين الوقت الحالي و4 أسابيع (القيمة الافتراضية 3 أسابيع).".
</p>

<p>
	سيقبل الحقل التواريخ باستخدام تنسيق <a href="https://docs.djangoproject.com/en/4.0/ref/forms/fields/#django.forms.DateField.input_formats" rel="external nofollow">input_formats</a> مثل:
</p>

<ul>
	<li>
		YYYY-MM-DD (2016-11-06)‎
	</li>
	<li>
		MM/DD/YYYY (02/26/2016)‎
	</li>
	<li>
		MM/DD/YY (10/25/16)‎
	</li>
</ul>

<p>
	وذلك نظرًا لعدم تحديد أي من الوسطاء الاختيارية الأخرى، وستُعرَض باستخدام عنصر واجهة المستخدم الافتراضية: <a href="https://docs.djangoproject.com/en/4.0/ref/forms/widgets/#django.forms.DateInput" rel="external nofollow"><code>DateInput</code></a>.
</p>

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

<ul>
	<li>
		<code>BooleanField</code>
	</li>
	<li>
		<code>CharField</code>
	</li>
	<li>
		<code>ChoiceField</code>
	</li>
	<li>
		<code>TypedChoiceField</code>
	</li>
	<li>
		<code>DateField</code>
	</li>
	<li>
		<code>DateTimeField</code>
	</li>
	<li>
		<code>DecimalField</code>
	</li>
	<li>
		<code>DurationField</code>
	</li>
	<li>
		<code>EmailField</code>
	</li>
	<li>
		<code>FileField</code>
	</li>
	<li>
		<code>FilePathField</code>
	</li>
	<li>
		<code>FloatField</code>
	</li>
	<li>
		<code>ImageField</code>
	</li>
	<li>
		<code>IntegerField</code>
	</li>
	<li>
		<code>GenericIPAddressField</code>
	</li>
	<li>
		<code>MultipleChoiceField</code>
	</li>
	<li>
		<code>TypedMultipleChoiceField</code>
	</li>
	<li>
		<code>NullBooleanField</code>
	</li>
	<li>
		<code>RegexField</code>
	</li>
	<li>
		<code>SlugField</code>
	</li>
	<li>
		<code>TimeField</code>
	</li>
	<li>
		<code>URLField</code>
	</li>
	<li>
		<code>UUIDField</code>
	</li>
	<li>
		<code>ComboField</code>
	</li>
	<li>
		<code>MultiValueField</code>
	</li>
	<li>
		<code>SplitDateTimeField</code>
	</li>
	<li>
		<code>ModelMultipleChoiceField</code>
	</li>
	<li>
		<code>ModelChoiceField</code>
	</li>
</ul>

<p>
	إليك الوسائط الشائعة في معظم الحقول مع قيمها الافتراضية:
</p>

<ul>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/ref/forms/fields/#required" rel="external nofollow"><code>required</code></a>: إذا كانت قيمته <code>True</code>، فلا يجوز ترك الحقل فارغًا أو إعطاءه قيمة <code>None</code>. تكون الحقول مطلوبة افتراضيًا، لذلك يجب أن تضبط <code>required=False</code> للسماح بالقيم الفارغة في الاستمارة.
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/ref/forms/fields/#label" rel="external nofollow"><code>label</code></a>: التسمية التي ستُستخدَم عند عرض الحقل بتنسيق HTML. إذا لم تُحدَّد تسمية، فسينشئ جانغو تسمية من اسم الحقل من خلال كتابة الحرف الأول بأحرف كبيرة ووضع مسافات مكان الشرطات السفلية، مثل Renewal date.
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/ref/forms/fields/#label-suffix" rel="external nofollow"><code>label_suffix</code></a>: تُعرَض نقطتان بعد التسمية افتراضيًا، مثل Renewal date:‎، إذ يسمح لك هذا الوسيط بتحديد لاحقة مختلفة تحتوي على محرف أو محارف أخرى.
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/ref/forms/fields/#initial" rel="external nofollow"><code>initial</code></a>: قيمة الحقل الأولية عند عرض الاستمارة.
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/ref/forms/fields/#widget" rel="external nofollow"><code>widget</code></a>: عنصر واجهة المستخدم للعرض المُراد استخدامه.
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/ref/forms/fields/#help-text" rel="external nofollow"><code>help_text</code></a>: نص إضافي يمكن عرضه في الاستمارة لشرح كيفية استخدام الحقل.
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/ref/forms/fields/#error-messages" rel="external nofollow"><code>error_messages</code></a>: قائمة برسائل الخطأ الخاصة بالحقل، ويمكنك تعديلها لتحتوي على رسائلك الخاصة إن لزم الأمر.
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/ref/forms/fields/#validators" rel="external nofollow"><code>validators</code></a>: قائمة بالدوال التي ستُستدعَى في الحقل عند التحقق من صحتها.
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/ref/forms/fields/#localize" rel="external nofollow"><code>localize</code></a>: يفعّل توطين Localization إدخال بيانات الاستمارة.
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/ref/forms/fields/#disabled" rel="external nofollow"><code>disabled</code></a>: يُعرَض الحقل ولكن لا يمكن تعديل قيمته إذا كان هذا الوسيط <code>True</code>، والقيمة الافتراضية هي <code>False</code>.
	</li>
</ul>

<h4>
	التحقق من صحة البيانات
</h4>

<p>
	يوفر جانغو العديد من الأماكن التي يمكنك من خلالها التحقق من صحة بياناتك، ولكن أسهل طريقة للتحقق من صحة حقل واحد هي تعديل التابع <code>clean_&lt;fieldname&gt;()‎</code> للحقل الذي تريد التحقق منه، لذلك مثلًا يمكننا التحقق من أن قيم <code>renewal_date</code> المُدخلة تتراوح بين الآن (الوقت الحالي) و 4 أسابيع من خلال تقديم التابع <code>clean_renewal_date()‎</code>، لذا حدّث الملف "forms.py" ليبدو كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1677_12" style=""><span class="kwd">import</span><span class="pln"> datetime

</span><span class="kwd">from</span><span class="pln"> django </span><span class="kwd">import</span><span class="pln"> forms

</span><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">core</span><span class="pun">.</span><span class="pln">exceptions </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">ValidationError</span><span class="pln">
</span><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">utils</span><span class="pun">.</span><span class="pln">translation </span><span class="kwd">import</span><span class="pln"> gettext_lazy </span><span class="kwd">as</span><span class="pln"> _

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">RenewBookForm</span><span class="pun">(</span><span class="pln">forms</span><span class="pun">.</span><span class="typ">Form</span><span class="pun">):</span><span class="pln">
    renewal_date </span><span class="pun">=</span><span class="pln"> forms</span><span class="pun">.</span><span class="typ">DateField</span><span class="pun">(</span><span class="pln">help_text</span><span class="pun">=</span><span class="str">"Enter a date between now and 4 weeks (default 3)."</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> clean_renewal_date</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        data </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">cleaned_data</span><span class="pun">[</span><span class="str">'renewal_date'</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"> data </span><span class="pun">&lt;</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">date</span><span class="pun">.</span><span class="pln">today</span><span class="pun">():</span><span class="pln">
            </span><span class="kwd">raise</span><span class="pln"> </span><span class="typ">ValidationError</span><span class="pun">(</span><span class="pln">_</span><span class="pun">(</span><span class="str">'Invalid date - renewal in past'</span><span class="pun">))</span><span class="pln">

        </span><span class="com"># ‫تحقق مما إذا كان التاريخ يقع ضمن النطاق المسموح به (‎+4 أسابيع من اليوم)</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> data </span><span class="pun">&gt;</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">date</span><span class="pun">.</span><span class="pln">today</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">timedelta</span><span class="pun">(</span><span class="pln">weeks</span><span class="pun">=</span><span class="lit">4</span><span class="pun">):</span><span class="pln">
            </span><span class="kwd">raise</span><span class="pln"> </span><span class="typ">ValidationError</span><span class="pun">(</span><span class="pln">_</span><span class="pun">(</span><span class="str">'Invalid date - renewal more than 4 weeks ahead'</span><span class="pun">))</span><span class="pln">

        </span><span class="com"># تذكّر دائمًا أن تعيد البيانات النظيفة</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> data</span></pre>

<p>
	هناك شيئان مهمان يجب ملاحظتهما، الأول هو أننا نحصل على بياناتنا باستخدام <code>self.cleaned_data['renewal_date']‎</code> وأننا نعيد هذه البيانات سواءً عدّلناها أم لا في نهاية الدالة، إذ تمنحنا هذه الخطوة بيانات "نظيفة" ومُعقمة من المدخلات التي يمكن أن تكون غير آمنة باستخدام أدوات التحقق الافتراضية، ومُحوَّلة إلى النوع المعياري الصحيح للبيانات، وهو في حالتنا كائن بايثون <code>datetime.datetime</code>.
</p>

<p>
	النقطة الثانية هي أنه إذا كانت القيمة واقعةً خارج نطاقنا، فسنصدّر خطأ <code>ValidationError</code> مع تحديد نص الخطأ الذي نريد عرضه في الاستمارة إذا أُدخِلت قيمة غير صالحة، إذ يغلّف المثال السابق هذا النص في إحدى دوال الترجمة Translation Functions الخاصة بجانغو هي <code>gettext_lazy()‎</code> (مستوردة بالشكل <code>()_</code>)، وهي ممارسة جيدة إذا أردتَ ترجمة موقعك لاحقًا.
</p>

<p>
	<strong>ملاحظة</strong>: هناك العديد من التوابع والأمثلة الأخرى للتحقق من صحة الاستمارات في <a href="https://docs.djangoproject.com/en/4.0/ref/forms/validation/" rel="external nofollow">توثيق جانغو</a>، فمثلًا يمكنك تعديل الدالة <a href="https://docs.djangoproject.com/en/4.0/ref/forms/api/#django.forms.Form.clean" rel="external nofollow"><code>Form.clean()‎</code></a> وإصدار خطأ <code>ValidationError</code> مرةً أخرى في الحالات التي يكون فيها لديك حقول متعددة تعتمد على بعضها بعضًا.
</p>

<p>
	وهذا كل ما نحتاجه للاستمارات في مثالنا.
</p>

<h3>
	ضبط عناوين URL
</h3>

<p>
	لنضِف ضبط عنوان URL لصفحة تجديد الكتب قبل إنشاء العرض، لذا انسخ الضبط التالي وضعه في نهاية الملف locallibrary/catalog/urls.py:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1677_14" style=""><span class="pln">urlpatterns </span><span class="pun">+=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    path</span><span class="pun">(</span><span class="str">'book/&lt;uuid:pk&gt;/renew/'</span><span class="pun">,</span><span class="pln"> views</span><span class="pun">.</span><span class="pln">renew_book_librarian</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">=</span><span class="str">'renew-book-librarian'</span><span class="pun">),</span><span class="pln">
</span><span class="pun">]</span></pre>

<p>
	سيعيد ضبط URL توجيه عناوين URL بالتنسيق "/catalog/book/&lt;‎bookinstance_id&gt;/renew/" <bookinstance_id>إلى الدالة <code>renew_book_librarian()‎</code> في الملف views.py، ويرسل معرّف نسخة الكتاب <code>BookInstance</code> بوصفه معاملًا بالاسم <code>pk</code>، إذ يتطابق النمط فقط إذا كان <code>pk</code> مُنسَّقًا بتنسيق <code>uuid</code> بصورة صحيحة.</bookinstance_id>
</p>

<p>
	<strong>ملاحظة</strong>: يمكننا تسمية بيانات URL المأخوذة "<code>pk</code>" بأيّ شيء نريده، لأن لدينا تحكمًا كاملًا بدالة العرض فنحن لا نستخدم صنف عرض تفصيلي مُعمَّم يتوقع معاملات باسم معين، ولكن يُعَد <code>pk</code> اختصارًا للمفتاح الرئيسي "primary key"، وهو اصطلاح مناسب لاستخدامه.
</p>

<h3>
	العرض
</h3>

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

<p>
	النمط الأكثر شيوعًا بالنسبة للاستمارات التي تستخدم طلب <code>POST</code> لإرسال معلومات إلى الخادم هو اختبار العرض للطلب من النوع <code>POST</code>، أي <code>if request.method == 'POST':‎</code>، لتحديد طلبات التحقق من صحة الاستمارة، والنوع <code>GET</code> (باستخدام تعليمة <code>else</code>) لتحديد طلب إنشاء الاستمارة الأولية؛ فإذا أدرتَ إرسال بياناتك باستخدام طلب <code>GET</code>، فيمكنك تحديد ما إذا كان هذا الاستدعاء هو استدعاء العرض الأول أو التالي من خلال قراءة بيانات الاستمارة، مثل قراءة قيمة مخفية في الاستمارة.
</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>، لذلك نستخدم أسلوب طلب <code>POST</code> اصطلاحيًا، إذ يُظهِر جزء الشيفرة البرمجية التالي النمط المعياري لهذا النوع من العرض المستند إلى الدوال:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1677_16" style=""><span class="kwd">import</span><span class="pln"> datetime

</span><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">shortcuts </span><span class="kwd">import</span><span class="pln"> render</span><span class="pun">,</span><span class="pln"> get_object_or_404
</span><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">http </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">HttpResponseRedirect</span><span class="pln">
</span><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">urls </span><span class="kwd">import</span><span class="pln"> reverse

</span><span class="kwd">from</span><span class="pln"> catalog</span><span class="pun">.</span><span class="pln">forms </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">RenewBookForm</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> renew_book_librarian</span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> pk</span><span class="pun">):</span><span class="pln">
    book_instance </span><span class="pun">=</span><span class="pln"> get_object_or_404</span><span class="pun">(</span><span class="typ">BookInstance</span><span class="pun">,</span><span class="pln"> pk</span><span class="pun">=</span><span class="pln">pk</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># ‫إذا كان هذا الطلب من النوع POST، فعالج بيانات الاستمارة</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">

        </span><span class="com"># أنشئ نسخة من الاستمارة واملأها ببيانات من الطلب (الربط‫ Binding):</span><span class="pln">
        form </span><span class="pun">=</span><span class="pln"> </span><span class="typ">RenewBookForm</span><span class="pun">(</span><span class="pln">request</span><span class="pun">.</span><span class="pln">POST</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"> form</span><span class="pun">.</span><span class="pln">is_valid</span><span class="pun">():</span><span class="pln">
            </span><span class="com"># ‫معالجة البيانات في form.cleaned_data كما هو مطلوب (نكتبها هنا فقط في الحقل due_back الخاص بالنموذج)</span><span class="pln">
            book_instance</span><span class="pun">.</span><span class="pln">due_back </span><span class="pun">=</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">cleaned_data</span><span class="pun">[</span><span class="str">'renewal_date'</span><span class="pun">]</span><span class="pln">
            book_instance</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">

            </span><span class="com"># ‫إعادة التوجيه إلى عنوان URL جديد:</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> </span><span class="typ">HttpResponseRedirect</span><span class="pun">(</span><span class="pln">reverse</span><span class="pun">(</span><span class="str">'all-borrowed'</span><span class="pun">))</span><span class="pln">

    </span><span class="com"># ‫إذا كان تابع GET (أو أي تابع آخر)، فأنشئ الاستمارة الافتراضية</span><span class="pln">
    </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
        proposed_renewal_date </span><span class="pun">=</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">date</span><span class="pun">.</span><span class="pln">today</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">timedelta</span><span class="pun">(</span><span class="pln">weeks</span><span class="pun">=</span><span class="lit">3</span><span class="pun">)</span><span class="pln">
        form </span><span class="pun">=</span><span class="pln"> </span><span class="typ">RenewBookForm</span><span class="pun">(</span><span class="pln">initial</span><span class="pun">={</span><span class="str">'renewal_date'</span><span class="pun">:</span><span class="pln"> proposed_renewal_date</span><span class="pun">})</span><span class="pln">

    context </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="str">'form'</span><span class="pun">:</span><span class="pln"> form</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'book_instance'</span><span class="pun">:</span><span class="pln"> book_instance</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="kwd">return</span><span class="pln"> render</span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> </span><span class="str">'catalog/book_renew_librarian.html'</span><span class="pun">,</span><span class="pln"> context</span><span class="pun">)</span></pre>

<p>
	أولًا، نستورد الاستمارة <code>RenewBookForm</code> وعددًا من الكائنات والتوابع المفيدة الأخرى المُستخدَمة في متن دالة العرض وهي:
</p>

<ul>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/topics/http/shortcuts/#get-object-or-404" rel="external nofollow"><code>get_object_or_404()‎</code></a>: يعيد كائنًا محددًا من نموذج بناءً على قيمة مفتاحه الرئيسي، ويرفع استثناء <code>Http404</code> (غير موجود) إذا كان السجل غير موجود.
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/ref/request-response/#django.http.HttpResponseRedirect" rel="external nofollow"><code>HttpResponseRedirect</code></a>: ينشئ إعادة توجيه إلى عنوان URL محدد (<a href="https://academy.hsoub.com/questions/18357-%D9%85%D8%A7%D8%B0%D8%A7-%D8%AA%D8%B9%D9%86%D9%8A-%D8%AD%D8%A7%D9%84%D8%A9-http-status-codes-3xx/" rel="">رمز حالة HTTP هو 302</a>).
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/ref/urlresolvers/#django.urls.reverse" rel="external nofollow"><code>reverse()‎</code></a>: يولّد عنوان URL من اسم ضبط URL ومجموعة من الوسائط، وهو مكافئ لغة بايثون للوسم <code>url</code> الذي استخدمناه في قوالبنا.
	</li>
	<li>
		<a href="https://docs.python.org/3/library/datetime.html" rel="external nofollow"><code>datetime</code></a>: مكتبة بايثون لمعالجة التواريخ والأوقات.
	</li>
</ul>

<p>
	نستخدم أولًا في العرض الوسيط <code>pk</code> ضمن التابع <code>get_object_or_404()‎</code> للحصول على نسخة الكتاب <code>BookInstance</code> الحالية، وإذا لم تكن موجودة، فسيُنهَى العرض مباشرةً وستعرض الصفحة خطأ "غير موجود". إذا لم يكن هذا الطلب من النوع <code>POST</code> (تعالجه تعليمة <code>else</code>)، فسننشئ الاستمارة الافتراضية التي تمرّر القيمة الأولية <code>initial</code> لحقل <code>renewal_date</code>، وهي 3 أسابيع من التاريخ الحالي.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1677_18" style=""><span class="pln">book_instance </span><span class="pun">=</span><span class="pln"> get_object_or_404</span><span class="pun">(</span><span class="typ">BookInstance</span><span class="pun">,</span><span class="pln"> pk</span><span class="pun">=</span><span class="pln">pk</span><span class="pun">)</span><span class="pln">

</span><span class="com"># ‫إذا كان تابع GET (أو أي تابع آخر)، فأنشئ الاستمارة الافتراضية</span><span class="pln">
</span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
    proposed_renewal_date </span><span class="pun">=</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">date</span><span class="pun">.</span><span class="pln">today</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">timedelta</span><span class="pun">(</span><span class="pln">weeks</span><span class="pun">=</span><span class="lit">3</span><span class="pun">)</span><span class="pln">
    form </span><span class="pun">=</span><span class="pln"> </span><span class="typ">RenewBookForm</span><span class="pun">(</span><span class="pln">initial</span><span class="pun">={</span><span class="str">'renewal_date'</span><span class="pun">:</span><span class="pln"> proposed_renewal_date</span><span class="pun">})</span><span class="pln">

context </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">'form'</span><span class="pun">:</span><span class="pln"> form</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'book_instance'</span><span class="pun">:</span><span class="pln"> book_instance</span><span class="pun">,</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">return</span><span class="pln"> render</span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> </span><span class="str">'catalog/book_renew_librarian.html'</span><span class="pun">,</span><span class="pln"> context</span><span class="pun">)</span></pre>

<p>
	نستدعي الدالة <code>render()‎</code> بعد إنشاء الاستمارة لإنشاء صفحة HTML، مما يؤدي إلى تحديد القالب والسياق الذي يحتوي على الاستمارة، إذ يحتوي السياق في هذه الحالة على نسخة الكتاب <code>BookInstance</code> التي سنستخدمها في القالب لتقديم معلومات حول الكتاب الذي نجدّده، لكن إذا كان هذا الطلب من النوع <code>POST</code>، فسننشئ كائن <code>form</code> ونملؤه ببيانات من الطلب، وتسمَّى هذه العملية "بالربط Binding" وتسمح لنا بالتحقق من صحة الاستمارة.
</p>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1677_20" style=""><span class="pln">book_instance </span><span class="pun">=</span><span class="pln"> get_object_or_404</span><span class="pun">(</span><span class="typ">BookInstance</span><span class="pun">,</span><span class="pln"> pk</span><span class="pun">=</span><span class="pln">pk</span><span class="pun">)</span><span class="pln">

</span><span class="com"># ‫إذا كان هذا الطلب من النوع POST، فعالج بيانات الاستمارة</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">

    </span><span class="com"># أنشئ نسخة من الاستمارة واملأها ببيانات من الطلب (الربط‫ Binding):</span><span class="pln">
    form </span><span class="pun">=</span><span class="pln"> </span><span class="typ">RenewBookForm</span><span class="pun">(</span><span class="pln">request</span><span class="pun">.</span><span class="pln">POST</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"> form</span><span class="pun">.</span><span class="pln">is_valid</span><span class="pun">():</span><span class="pln">
        </span><span class="com"># ‫معالجة البيانات في form.cleaned_data كما هو مطلوب (نكتبها هنا فقط في الحقل due_back الخاص بالنموذج)</span><span class="pln">
        book_instance</span><span class="pun">.</span><span class="pln">due_back </span><span class="pun">=</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">cleaned_data</span><span class="pun">[</span><span class="str">'renewal_date'</span><span class="pun">]</span><span class="pln">
        book_instance</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">

        </span><span class="com"># إعادة التوجيه إلى عنوان‫ URL جديد:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="typ">HttpResponseRedirect</span><span class="pun">(</span><span class="pln">reverse</span><span class="pun">(</span><span class="str">'all-borrowed'</span><span class="pun">))</span><span class="pln">

context </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">'form'</span><span class="pun">:</span><span class="pln"> form</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'book_instance'</span><span class="pun">:</span><span class="pln"> book_instance</span><span class="pun">,</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">return</span><span class="pln"> render</span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> </span><span class="str">'catalog/book_renew_librarian.html'</span><span class="pun">,</span><span class="pln"> context</span><span class="pun">)</span></pre>

<p>
	نستدعي الدالة <code>render()‎</code> مرةً أخرى إذا لم تكن الاستمارة صالحة، ولكن ستتضمن هذه المرة قيمة الاستمارة المُمرَّرة في السياق رسائل خطأ؛ أما إذا كانت الاستمارة صالحة، فيمكننا البدء في استخدام البيانات والوصول إليها من خلال السمة <code>form.cleaned_data</code>، مثل <code>data = form.cleaned_data['renewal_date']‎</code>، إذ نحتفظ في مثالنا فقط بالبيانات ضمن قيمة <code>due_back</code> الخاصة بالكائن <code>BookInstance</code> المرتبط بها.
</p>

<p>
	<strong>تحذير</strong>: يمكنك الوصول إلى بيانات الاستمارة مباشرةً من خلال الطلب، مثل <code>request.POST['renewal_date']‎</code> أو <code>request.GET['renewal_date']‎</code> إذا استخدمتَ طلبًا من النوع <code>GET</code>، ولكن لا ينصح بذلك، إذ تُطهَّر البيانات المُنظَّفة ويُتحقَّق من صحتها وتُحوَّل إلى أنواع متوافقة مع لغة بايثون.
</p>

<p>
	تتمثل الخطوة الأخيرة في جزء معالجة الاستمارة ضمن العرض في إعادة التوجيه إلى صفحةٍ أخرى وهي صفحة "النجاح" عادةً، إذ نستخدم في هذه الحالة <code>HttpResponseRedirect</code> و <code>reverse()‎</code> لإعادة التوجيه إلى العرض الذي اسمه <code>'all-borrowed'</code> (وهو تحدي <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B3%D8%A7%D8%A8%D8%B9-%D8%A7%D8%B3%D8%AA%D9%8A%D8%AB%D8%A7%D9%82-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D9%8A%D9%86-%D9%88%D8%A3%D8%B0%D9%88%D9%86%D8%A7%D8%AA%D9%87%D9%85-r2119/" rel="">المقال السابق</a>)؛ فإذا لم تنشئ هذه الصفحة، ففكّر في إعادة التوجيه إلى الصفحة الرئيسية ذات العنوان <code>'/'</code>
</p>

<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="">المزخرف decorator</a>‏ <code>‎@login_required</code> لمتطلب تسجيل دخول المستخدم، ومزخرف الدالة <code>‎@permission_required</code> مع الإذن <code>can_mark_returned</code> الحالي للسماح بالوصول. تُعالَج المزخرفات بالترتيب.
</p>

<p>
	لاحظ أنه ربما كان يجب إنشاء إعداد إذن جديد في <code>BookInstance</code> هو "<code>can_renew</code>"، لكننا سنعيد استخدام الإعداد الحالي لتبسيط مثالنا.
</p>

<p>
	يكون العرض النهائي كما هو موضح فيما يلي، لذا انسخ الشيفرة التالية في نهاية الملف locallibrary/catalog/views.py:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1677_23" style=""><span class="kwd">import</span><span class="pln"> datetime

</span><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">contrib</span><span class="pun">.</span><span class="pln">auth</span><span class="pun">.</span><span class="pln">decorators </span><span class="kwd">import</span><span class="pln"> login_required</span><span class="pun">,</span><span class="pln"> permission_required
</span><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">shortcuts </span><span class="kwd">import</span><span class="pln"> get_object_or_404
</span><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">http </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">HttpResponseRedirect</span><span class="pln">
</span><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">urls </span><span class="kwd">import</span><span class="pln"> reverse

</span><span class="kwd">from</span><span class="pln"> catalog</span><span class="pun">.</span><span class="pln">forms </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">RenewBookForm</span><span class="pln">

</span><span class="lit">@login_required</span><span class="pln">
</span><span class="lit">@permission_required</span><span class="pun">(</span><span class="str">'catalog.can_mark_returned'</span><span class="pun">,</span><span class="pln"> raise_exception</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> renew_book_librarian</span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> pk</span><span class="pun">):</span><span class="pln">
    </span><span class="str">"""View function for renewing a specific BookInstance by librarian."""</span><span class="pln">
    book_instance </span><span class="pun">=</span><span class="pln"> get_object_or_404</span><span class="pun">(</span><span class="typ">BookInstance</span><span class="pun">,</span><span class="pln"> pk</span><span class="pun">=</span><span class="pln">pk</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># إذا كان هذا الطلب من النوع‫ POST، فعالج بيانات الاستمارة</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">

        </span><span class="com"># أنشئ نسخة من الاستمارة واملأها ببيانات من الطلب (الربط‫ Binding):</span><span class="pln">
        form </span><span class="pun">=</span><span class="pln"> </span><span class="typ">RenewBookForm</span><span class="pun">(</span><span class="pln">request</span><span class="pun">.</span><span class="pln">POST</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"> form</span><span class="pun">.</span><span class="pln">is_valid</span><span class="pun">():</span><span class="pln">
            </span><span class="com"># ‫معالجة البيانات في form.cleaned_data كما هو مطلوب (نكتبها هنا فقط في الحقل due_back الخاص بالنموذج)</span><span class="pln">
            book_instance</span><span class="pun">.</span><span class="pln">due_back </span><span class="pun">=</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">cleaned_data</span><span class="pun">[</span><span class="str">'renewal_date'</span><span class="pun">]</span><span class="pln">
            book_instance</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">

            </span><span class="com"># إعادة التوجيه إلى عنوان‫ URL جديد:</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> </span><span class="typ">HttpResponseRedirect</span><span class="pun">(</span><span class="pln">reverse</span><span class="pun">(</span><span class="str">'all-borrowed'</span><span class="pun">))</span><span class="pln">

    </span><span class="com"># إذا كان تابع‫ GET (أو أي تابع آخر)، فأنشئ الاستمارة الافتراضية</span><span class="pln">
    </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
        proposed_renewal_date </span><span class="pun">=</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">date</span><span class="pun">.</span><span class="pln">today</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">timedelta</span><span class="pun">(</span><span class="pln">weeks</span><span class="pun">=</span><span class="lit">3</span><span class="pun">)</span><span class="pln">
        form </span><span class="pun">=</span><span class="pln"> </span><span class="typ">RenewBookForm</span><span class="pun">(</span><span class="pln">initial</span><span class="pun">={</span><span class="str">'renewal_date'</span><span class="pun">:</span><span class="pln"> proposed_renewal_date</span><span class="pun">})</span><span class="pln">

    context </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="str">'form'</span><span class="pun">:</span><span class="pln"> form</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'book_instance'</span><span class="pun">:</span><span class="pln"> book_instance</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="kwd">return</span><span class="pln"> render</span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> </span><span class="str">'catalog/book_renew_librarian.html'</span><span class="pun">,</span><span class="pln"> context</span><span class="pun">)</span></pre>

<h3>
	القالب
</h3>

<p>
	أنشئ القالب المُشار إليه في العرض "‎/catalog/templates/catalog/book_renew_librarian.html" وانسخ الشيفرة التالية إليه:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1677_25" style=""><span class="pun">{%</span><span class="pln"> extends </span><span class="str">"base_generic.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="typ">Renew</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{{</span><span class="pln"> book_instance</span><span class="pun">.</span><span class="pln">book</span><span class="pun">.</span><span class="pln">title </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="typ">Borrower</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{{</span><span class="pln"> book_instance</span><span class="pun">.</span><span class="pln">borrower </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">{%</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> book_instance</span><span class="pun">.</span><span class="pln">is_overdue </span><span class="pun">%}</span><span class="pln"> </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"text-danger"</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="typ">Due</span><span class="pln"> date</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{{</span><span class="pln"> book_instance</span><span class="pun">.</span><span class="pln">due_back </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 action</span><span class="pun">=</span><span class="str">""</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">{%</span><span class="pln"> csrf_token </span><span class="pun">%}</span><span class="pln">
    </span><span class="pun">&lt;</span><span class="pln">table</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">as_table </span><span class="pun">}}</span><span class="pln">
    </span><span class="pun">&lt;/</span><span class="pln">table</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">"Submit"</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>
	ستكون غالبية هذه الشيفرة البرمجية مألوفةً من المقالات السابقة، إذ نوسّع القالب الأساسي ثم نعيد تعريف كتلة المحتوى <code>content</code>، ويمكننا الإشارة إلى <code>{{ book_instance }}</code> ومتغيراته لأنه مُمرَّر إلى كائن السياق في الدالة <code>render()‎</code>، ونستخدمه لسرد عنوان الكتاب والمستعير، وتاريخ الاسترجاع الأصلي.
</p>

<p>
	شيفرة الاستمارة بسيطة نسبيًا، إذ نصرّح أولًا عن وسوم <code>form</code>، التي تحدّد مكان إرسال الاستمارة <code>action</code> وتابع <code>method</code> لإرسال البيانات (في هذه الحالة هو "<code>POST</code>")، إذ يعني الإجراء <code>action</code> الفارغ إرسال بيانات الاستمارة إلى عنوان URL الحالي للصفحة وهو ما نريده. نعرّف ضمن الوسوم عنصرَ الإدخال <code>submit</code> الذي يمكن للمستخدم الضغط عليه لإرسال البيانات، ويُعَد <code>{% csrf_token %}</code> المُضَاف ضمن وسوم الاستمارة جزءًا من حماية جانغو من هجمات التزوير عبر المواقع، انظر مقال <a href="https://academy.hsoub.com/programming/python/django/%D8%B1%D9%81%D8%B9-%D9%85%D8%B3%D8%AA%D9%88%D9%89-%D8%A3%D9%85%D8%A7%D9%86-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%81%D9%8A-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%A7%D8%AC-r1717/" rel="">رفع مستوى أمان تطبيقات جانغو في بيئة الإنتاج</a>.
</p>

<p>
	<strong>ملاحظة</strong>: ضِف <code>{% csrf_token %}</code> إلى كل قالب جانغو تنشئه والذي يستخدم طلب <code>POST</code> لإرسال البيانات، لان ذلك سيؤدي إلى تقليل فرصة اختطاف المستخدمين الضارين للاستمارات.
</p>

<p>
	بقي متغير القالب <code>{{ form }}</code> الذي مرّرناه إلى القالب في قاموس السياق، وليس مستغربًا أنه يوفر الإخراج الافتراضي لجميع حقول الاستمارة عند استخدامه، بما في ذلك التسميات وعناصر واجهة المستخدم ونص التعليمات، ويكون الإخراج كما هو موضح فيما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1677_27" style=""><span class="tag">&lt;tr&gt;</span><span class="pln">
  </span><span class="tag">&lt;th&gt;&lt;label</span><span class="pln"> </span><span class="atn">for</span><span class="pun">=</span><span class="atv">"id_renewal_date"</span><span class="tag">&gt;</span><span class="pln">Renewal date:</span><span class="tag">&lt;/label&gt;&lt;/th&gt;</span><span class="pln">
  </span><span class="tag">&lt;td&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">"id_renewal_date"</span><span class="pln">
      </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"renewal_date"</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">value</span><span class="pun">=</span><span class="atv">"2016-11-08"</span><span class="pln">
      </span><span class="atn">required</span><span class="pln"> </span><span class="tag">/&gt;</span><span class="pln">
    </span><span class="tag">&lt;br</span><span class="pln"> </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">"helptext"</span><span class="tag">&gt;</span><span class="pln">
      Enter date between now and 4 weeks (default 3 weeks).
    </span><span class="tag">&lt;/span&gt;</span><span class="pln">
  </span><span class="tag">&lt;/td&gt;</span><span class="pln">
</span><span class="tag">&lt;/tr&gt;</span></pre>

<p>
	<strong>ملاحظة</strong>: يمكن ألّا يكون الأمر واضحًا لأن لدينا حقلًا واحدًا فقط، ولكن يُعرَّف كل حقل في صف الجدول الخاص به افتراضيًا، ويمكن توفير الإخراج نفسه إذا أشرت إلى متغير القالب <code>{{ form.as_table }}</code>.
</p>

<p>
	إذا أدخلتَ تاريخًا غير صالح، فستحصل على قائمة بالأخطاء المعروضة على الصفحة، مثل <code>errorlist</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1677_29" style=""><span class="tag">&lt;tr&gt;</span><span class="pln">
  </span><span class="tag">&lt;th&gt;&lt;label</span><span class="pln"> </span><span class="atn">for</span><span class="pun">=</span><span class="atv">"id_renewal_date"</span><span class="tag">&gt;</span><span class="pln">Renewal date:</span><span class="tag">&lt;/label&gt;&lt;/th&gt;</span><span class="pln">
  </span><span class="tag">&lt;td&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">"errorlist"</span><span class="tag">&gt;</span><span class="pln">
      </span><span class="tag">&lt;li&gt;</span><span class="pln">Invalid date - renewal in past</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;input</span><span class="pln">
      </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"id_renewal_date"</span><span class="pln">
      </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"renewal_date"</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">value</span><span class="pun">=</span><span class="atv">"2015-11-08"</span><span class="pln">
      </span><span class="atn">required</span><span class="pln"> </span><span class="tag">/&gt;</span><span class="pln">
    </span><span class="tag">&lt;br</span><span class="pln"> </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">"helptext"</span><span class="tag">&gt;</span><span class="pln">
      Enter date between now and 4 weeks (default 3 weeks).
    </span><span class="tag">&lt;/span&gt;</span><span class="pln">
  </span><span class="tag">&lt;/td&gt;</span><span class="pln">
</span><span class="tag">&lt;/tr&gt;</span></pre>

<h4>
	طرق أخرى لاستخدام متغير قالب الاستمارة
</h4>

<p>
	يُخرج كل حقل بوصفه صفًا في جدول باستخدام <code>{{ form.as_table }}</code>، ويمكنك إخراج كل حقل بوصفه عنصر قائمة باستخدام <code>{{ form.as_ul }}</code>، أو فقرة باستخدام <code>{{ form.as_p }}</code>.
</p>

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

<ul>
	<li>
		<code>‎{{ form.renewal_date }}‎</code>: كامل الحقل.
	</li>
	<li>
		<code>{{ form.renewal_date.errors }}</code>: قائمة الأخطاء.
	</li>
	<li>
		<code>{{ form.renewal_date.id_for_label }}</code>: معرّف التسمية.
	</li>
	<li>
		<code>{{ form.renewal_date.help_text }}</code>: حقل نص التعليمات.
	</li>
</ul>

<h3>
	اختبار الصفحة
</h3>

<p>
	إذا قبلت التحدي الموجود في <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B3%D8%A7%D8%A8%D8%B9-%D8%A7%D8%B3%D8%AA%D9%8A%D8%AB%D8%A7%D9%82-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D9%8A%D9%86-%D9%88%D8%A3%D8%B0%D9%88%D9%86%D8%A7%D8%AA%D9%87%D9%85-r2119/" rel="">المقال السابق</a>، فستحصل على قائمة بجميع الكتب المُعارة في المكتبة، والتي تكون مرئية فقط لموظفي المكتبة، إذ سيكون العرض مشابهًا لما يلي:
</p>

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

{% block content %}
    </span><span class="tag">&lt;h1&gt;</span><span class="pln">All Borrowed Books</span><span class="tag">&lt;/h1&gt;</span><span class="pln">

    {% if bookinstance_list %}
    </span><span class="tag">&lt;ul&gt;</span><span class="pln">

      {% for bookinst in bookinstance_list %}
      </span><span class="tag">&lt;li</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"{% if bookinst.is_overdue %}text-danger{% endif %}"</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 'book-detail' bookinst.book.pk %}"</span><span class="tag">&gt;</span><span class="pln">{{ bookinst.book.title }}</span><span class="tag">&lt;/a&gt;</span><span class="pln"> ({{ bookinst.due_back }}) {% if user.is_staff %}- {{ bookinst.borrower }}{% endif %}
      </span><span class="tag">&lt;/li&gt;</span><span class="pln">
      {% endfor %}
    </span><span class="tag">&lt;/ul&gt;</span><span class="pln">

    {% else %}
      </span><span class="tag">&lt;p&gt;</span><span class="pln">There are no books borrowed.</span><span class="tag">&lt;/p&gt;</span><span class="pln">
    {% endif %}
{% endblock %}</span></pre>

<p>
	يمكننا إضافة رابط إلى صفحة تجديد الكتاب بجانب كل عنصر من خلال إلحاق شيفرة القالب التالية بنص عنصر القائمة السابق. لاحظ أنه لا يمكن تشغيل شيفرة القالب هذه إلا داخل حلقة <code>{% for %}</code>، لأنه مكان تعريف قيمة <code>bookinst</code>.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1677_33" style=""><span class="pun">{%</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> perms</span><span class="pun">.</span><span class="pln">catalog</span><span class="pun">.</span><span class="pln">can_mark_returned </span><span class="pun">%}-</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln">a href</span><span class="pun">=</span><span class="str">"{% url 'renew-book-librarian' bookinst.id %}"</span><span class="pun">&gt;</span><span class="typ">Renew</span><span class="pun">&lt;/</span><span class="pln">a</span><span class="pun">&gt;{%</span><span class="pln"> endif </span><span class="pun">%}</span></pre>

<p>
	<strong>ملاحظة</strong>: تذكّر أن تسجيل الدخول التجريبي سيحتاج الحصول على الإذن "<code>catalog.can_mark_returned</code>" لرؤية رابط "التجديد Renew" الجديد والوصول إلى الصفحة المرتبطة بهذا الارتباط، وذلك ربما باستخدام حساب مستخدمك المميز.
</p>

<p>
	يمكنك بدلًا من ذلك بناء عنوان URL تجريبي يدويًا مثل "http://127.0.0.1:8000/catalog/book/&lt;‎bookinstance_id&gt;/renew/‎"<bookinstance_id>، إذ يمكن الحصول على معرّف <code>bookinstance_id</code> صالح بالانتقال إلى صفحة تفاصيل الكتاب في مكتبتك، ونسخ الحقل <code>id</code>.</bookinstance_id>
</p>

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

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135465" href="https://academy.hsoub.com/uploads/monthly_2023_09/04_forms_example_renew_default.png.5358a21d189c1d7fca9ba8eca434830b.png" rel=""><img alt="04_forms_example_renew_default.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135465" data-unique="8t5rnbhz4" src="https://academy.hsoub.com/uploads/monthly_2023_09/04_forms_example_renew_default.png.5358a21d189c1d7fca9ba8eca434830b.png"> </a>
</p>

<p>
	وستبدو الاستمارة التي تحتوي على قيمة مُدخَلة غير صالحة كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135464" href="https://academy.hsoub.com/uploads/monthly_2023_09/05_forms_example_renew_invalid.png.6b6ed3834f49b4d5c3ba56a0c904f823.png" rel=""><img alt="05_forms_example_renew_invalid.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135464" data-unique="tze5853z9" src="https://academy.hsoub.com/uploads/monthly_2023_09/05_forms_example_renew_invalid.png.6b6ed3834f49b4d5c3ba56a0c904f823.png"> </a>
</p>

<p>
	وستبدو قائمة جميع الكتب التي تحتوي على روابط التجديد كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135463" href="https://academy.hsoub.com/uploads/monthly_2023_09/06_forms_example_renew_allbooks.png.f7449f31789a434701bf3e6c8d4f3731.png" rel=""><img alt="06_forms_example_renew_allbooks.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135463" data-unique="lhyxiyisy" src="https://academy.hsoub.com/uploads/monthly_2023_09/06_forms_example_renew_allbooks.png.f7449f31789a434701bf3e6c8d4f3731.png"> </a>
</p>

<h2>
	الصنف ModelForm
</h2>

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

<p>
	يُعَد استخدام الصنف المساعد <a href="https://docs.djangoproject.com/en/4.0/topics/forms/modelforms/" rel="external nofollow"><code>ModelForm</code></a> لإنشاء الاستمارة من نموذجك أسهل من إعادة إنشاء تعريفات النموذج في استمارتك، ثم يمكن استخدام الصنف <code>ModelForm</code> ضمن <a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D9%84%D8%B9%D8%B1%D9%88%D8%B6-%D9%88%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-%D9%81%D9%8A-django-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-r436/" rel="">العروض Views</a> بطريقة الصنف <code>Form</code> نفسها.
</p>

<p>
	إليك فيما يلي صنف <code>ModelForm</code> يحتوي على حقل الصنف <code>RenewBookForm</code> الأصلي نفسه، وكل ما عليك تطبيقه لإنشاء الاستمارة هو إضافة <code>class Meta</code> مع النموذج <code>model</code> المرتبط به (<code>BookInstance</code>) وقائمة بحقول <code>fields</code> النموذج المُراد تضمينها في الاستمارة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1677_35" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">forms </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">ModelForm</span><span class="pln">

</span><span class="kwd">from</span><span class="pln"> catalog</span><span class="pun">.</span><span class="pln">models </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">BookInstance</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">RenewBookModelForm</span><span class="pun">(</span><span class="typ">ModelForm</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Meta</span><span class="pun">:</span><span class="pln">
        model </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BookInstance</span><span class="pln">
        fields </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">'due_back'</span><span class="pun">]</span></pre>

<p>
	<strong>ملاحظة</strong>: يمكنك أيضًا تضمين جميع الحقول في الاستمارة باستخدام <code>fields = '__all__'‎</code>، أو يمكنك استخدام <code>exclude</code> بدلًا من <code>fields</code> لتحديد الحقول التي لا يجب تضمينها من النموذج. لا يوصَى بأيٍّ من الأسلوبين لأنه ستُضمَّن بعد ذلك الحقول الجديدة المُضافة إلى النموذج في الاستمارة تلقائيًا دون أن يأخذ المطور بالضرورة في حساباته الآثار الأمنية المحتملة.
</p>

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

<p>
	تأتي بقية المعلومات من تعريفات حقول النموذج، مثل التسميات وعناصر واجهة المستخدم ونص التعليمات ورسائل الخطأ، فإن لم تكن هذه المعلومات صحيحة، فيمكننا تعديلها في <code>class Meta</code>، وتحديد قاموس يحتوي على الحقل المطلوب تغييره وقيمته الجديدة.
</p>

<p>
	يمكن أن نرغب في هذه الاستمارة مثلًا باستخدام تسميةٍ لحقلنا هي "تاريخ التجديد Renewal date" بدلًا من التسمية الافتراضية التي تعتمد على اسم الحقل "Due Back"، ويمكن أن نرغب أيضًا في أن يكون نص التعليمات محددًا لحالة الاستخدام هذه. يوضح الصنف <code>Meta</code> التالي كيفية تعديل هذه الحقول، ويمكنك ضبط عناصر واجهة المستخدم <code>widgets</code> ورسائل الخطأ <code>error_messages</code> إن لم تكن الإعدادات الافتراضية كافية.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1677_37" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Meta</span><span class="pun">:</span><span class="pln">
    model </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BookInstance</span><span class="pln">
    fields </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">'due_back'</span><span class="pun">]</span><span class="pln">
    labels </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="str">'due_back'</span><span class="pun">:</span><span class="pln"> _</span><span class="pun">(</span><span class="str">'New renewal date'</span><span class="pun">)}</span><span class="pln">
    help_texts </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="str">'due_back'</span><span class="pun">:</span><span class="pln"> _</span><span class="pun">(</span><span class="str">'Enter a date between now and 4 weeks (default 3).'</span><span class="pun">)}</span></pre>

<p>
	يمكن إضافة التحقق من صحة البيانات من خلال استخدام الأسلوب المتبع نفسه في الصنف <code>Form</code> العادي، إذ يمكنك تعريف دالة بالاسم <code>clean_&lt;field_name&gt;()‎</code> ورفع استثناءات <code>ValidationError</code> للقيم غير الصالحة. الاختلاف الوحيد فيما يتعلق بالاستمارة الأصلية هو أن حقل النموذج يسمى <code>due_back</code> وليس "<code>renewal_date</code>"، وهذا التغيير ضروري لأن الحقل المقابل في <code>BookInstance</code> يسمى <code>due_back</code>.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1677_39" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">forms </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">ModelForm</span><span class="pln">

</span><span class="kwd">from</span><span class="pln"> catalog</span><span class="pun">.</span><span class="pln">models </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">BookInstance</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">RenewBookModelForm</span><span class="pun">(</span><span class="typ">ModelForm</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> clean_due_back</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
       data </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">cleaned_data</span><span class="pun">[</span><span class="str">'due_back'</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"> data </span><span class="pun">&lt;</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">date</span><span class="pun">.</span><span class="pln">today</span><span class="pun">():</span><span class="pln">
            </span><span class="kwd">raise</span><span class="pln"> </span><span class="typ">ValidationError</span><span class="pun">(</span><span class="pln">_</span><span class="pun">(</span><span class="str">'Invalid date - renewal in past'</span><span class="pun">))</span><span class="pln">

        </span><span class="com"># تحقق مما إذا كان التاريخ يقع ضمن النطاق المسموح به‫ (‎+4 أسابيع من اليوم)</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> data </span><span class="pun">&gt;</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">date</span><span class="pun">.</span><span class="pln">today</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">timedelta</span><span class="pun">(</span><span class="pln">weeks</span><span class="pun">=</span><span class="lit">4</span><span class="pun">):</span><span class="pln">
            </span><span class="kwd">raise</span><span class="pln"> </span><span class="typ">ValidationError</span><span class="pun">(</span><span class="pln">_</span><span class="pun">(</span><span class="str">'Invalid date - renewal more than 4 weeks ahead'</span><span class="pun">))</span><span class="pln">

        </span><span class="com"># تذكّر دائمًا أن تعيد البيانات النظيفة</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> data

    </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Meta</span><span class="pun">:</span><span class="pln">
        model </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BookInstance</span><span class="pln">
        fields </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">'due_back'</span><span class="pun">]</span><span class="pln">
        labels </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="str">'due_back'</span><span class="pun">:</span><span class="pln"> _</span><span class="pun">(</span><span class="str">'Renewal date'</span><span class="pun">)}</span><span class="pln">
        help_texts </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="str">'due_back'</span><span class="pun">:</span><span class="pln"> _</span><span class="pun">(</span><span class="str">'Enter a date between now and 4 weeks (default 3).'</span><span class="pun">)}</span></pre>

<p>
	يُعَد الصنف <code>RenewBookModelForm</code> السابق مكافئًا وظيفيًا للصنف <code>RenewBookForm</code> الأصلي، ويمكنك استيراده واستخدامه في أيّ مكان تستخدم فيه الصنف <code>RenewBookForm</code> حاليًا طالما أنك تحدّث اسم متغير الاستمارة المقابل من <code>renewal_date</code> إلى <code>due_back</code> كما في التصريح عن الاستمارة الثانية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1677_41" style=""><span class="typ">RenewBookModelForm</span><span class="pun">(</span><span class="pln">initial</span><span class="pun">={</span><span class="str">'due_back'</span><span class="pun">:</span><span class="pln"> proposed_renewal_date</span><span class="pun">}‎</span></pre>

<h2>
	عروض التعديل المعممة Generic Editing Views
</h2>

<p>
	تمثل خوارزمية معالجة الاستمارة التي استخدمناها في مثال العرض المستند إلى الدوال السابق نمطًا شائعًا جدًا في عروض تعديل الاستمارة، إذ يجرّد جانغو كثيرًا من هذه "الشيفرة المتداولة Boilerplate" من خلال إنشاء <a href="https://docs.djangoproject.com/en/4.0/ref/class-based-views/generic-editing/" rel="external nofollow">عروض التعديل المُعمَّمة</a> لإنشاء وتعديل وحذف العروض بناءً على النماذج، فهي لا تعالج سلوك "العرض View" فحسب، بل تنشئ تلقائيًا صنف الاستمارة <code>ModelForm</code> من النموذج.
</p>

<p>
	<strong>ملاحظة</strong>: هناك أيضًا -إضافةً إلى عروض التعديل التي وضحناها سابقًا- الصنف <a href="https://docs.djangoproject.com/en/4.0/ref/class-based-views/generic-editing/#formview" rel="external nofollow"><code>FormView</code></a> الذي يقع في مكانٍ ما بين العرض المستند إلى الدوال والعروض المُعمَّمة الأخرى من حيث المرونة والجهد المبذول لكتابة الشيفرة البرمجية. ما زلت بحاجة إلى إنشاء الصنف <code>Form</code> لاستخدام الصنف <code>FormView</code>، ولكن لا يتوجّب عليك تقديم جميع أنماط معالجة الاستمارة المعيارية، بل يجب فقط توفير تقديم للدالة التي ستُستدعَى بمجرد معرفة أن الإرسال صالح.
</p>

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

<h3>
	العروض
</h3>

<p>
	افتح ملف العروض "locallibrary/catalog/views.py" وضع كتلة الشيفرة البرمجية التالية في نهايته:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1677_43" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">views</span><span class="pun">.</span><span class="pln">generic</span><span class="pun">.</span><span class="pln">edit </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">CreateView</span><span class="pun">,</span><span class="pln"> </span><span class="typ">UpdateView</span><span class="pun">,</span><span class="pln"> </span><span class="typ">DeleteView</span><span class="pln">
</span><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">urls </span><span class="kwd">import</span><span class="pln"> reverse_lazy

</span><span class="kwd">from</span><span class="pln"> catalog</span><span class="pun">.</span><span class="pln">models </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Author</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">AuthorCreate</span><span class="pun">(</span><span class="typ">CreateView</span><span class="pun">):</span><span class="pln">
    model </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Author</span><span class="pln">
    fields </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">'first_name'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'last_name'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'date_of_birth'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'date_of_death'</span><span class="pun">]</span><span class="pln">
    initial </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="str">'date_of_death'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'11/06/2020'</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">AuthorUpdate</span><span class="pun">(</span><span class="typ">UpdateView</span><span class="pun">):</span><span class="pln">
    model </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Author</span><span class="pln">
    fields </span><span class="pun">=</span><span class="pln"> </span><span class="str">'__all__'</span><span class="pln"> </span><span class="com"># غير موصَى به، إذ يمكن أن يسبب مشكلة أمنية محتملة في حالة إضافة مزيد من الحقول</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">AuthorDelete</span><span class="pun">(</span><span class="typ">DeleteView</span><span class="pun">):</span><span class="pln">
    model </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Author</span><span class="pln">
    success_url </span><span class="pun">=</span><span class="pln"> reverse_lazy</span><span class="pun">(</span><span class="str">'authors'</span><span class="pun">)</span></pre>

<p>
	يمكنك إنشاء أو تحديث أو حذف العروض التي تريد اشتقاقها من <code>CreateView</code> و <code>UpdateView</code> و <code>DeleteView</code> على التوالي، ثم تعريف النموذج المرتبط بها.
</p>

<p>
	يجب أيضًا -بالنسبة لحالات "الإنشاء create" و"التحديث update"- تحديد الحقول المراد عرضها في الاستمارة باستخدام صياغة <code>ModelForm</code> نفسها، لذا نظهر في هذه الحالة كيفية سرد الحقول بصورة فردية وكيفية صياغة سردها جميعًا. يمكنك تحديد القيم الأولية لكل حقل باستخدام قاموس أزواج اسم الحقل/قيمته field_name/value، وقد ضبطنا هنا تاريخ نهايتها عشوائيًا للتوضيح، إذ يمكن أن ترغب في حذفها. ستعيد هذه العروض توجيهك افتراضيًا عند النجاح إلى صفحة تعرض عنصر النموذج المُنشَأ أو المُعدَّل، والذي سيكون في حالتنا عرض تفاصيل المؤلف الذي أنشأناه في مقال سابق، ويمكنك تحديد موقع إعادة توجيه بديل من خلال التصريح عن المعامل <code>success_url</code> كما حدث للصنف <code>AuthorDelete</code>.
</p>

<p>
	لا يحتاج الصنف <code>AuthorDelete</code> إلى عرض أيٍّ من الحقول، فلا حاجة لتحديدها، ولكن يجب تحديد المعامل <code>success_url</code>، لأنه لا توجد قيمة افتراضية واضحة ليستخدمها جانغو، لذا نستخدم في هذه الحالة الدالة <a href="https://docs.djangoproject.com/en/4.0/ref/urlresolvers/#reverse-lazy" rel="external nofollow"><code>reverse_lazy()‎</code></a> لإعادة التوجيه إلى قائمة المؤلفين بعد حذف المؤلف، إذ تُعَد الدالة <code>reverse_lazy()‎</code> نسخةً بطيئة من الدالة <code>reverse()‎</code>، واستخدمناها هنا لأننا نقدم عنوان URL لسمة عرض مستند إلى الأصناف.
</p>

<h3>
	القوالب
</h3>

<p>
	تستخدم عروض "الإنشاء create" و"التحديث update" القالب نفسه افتراضيًا، والذي سيُسمَّى بعد نموذجك وفقًا التنسيق: <code>model_name_form.html</code>. يمكنك تغيير اللاحقة إلى شيء آخر غير "‎"_form باستخدام الحقل <code>template_name_suffix</code> في عرضك مثل <code>template_name_suffix = '_other_suffix'‎</code>.
</p>

<p>
	أنشئ ملف القالب "locallibrary/catalog/templates/catalog/author_form.html" وانسخ النص التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1677_45" style=""><span class="pun">{%</span><span class="pln"> extends </span><span class="str">"base_generic.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">form action</span><span class="pun">=</span><span class="str">""</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">{%</span><span class="pln"> csrf_token </span><span class="pun">%}</span><span class="pln">
  </span><span class="pun">&lt;</span><span class="pln">table</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">as_table </span><span class="pun">}}</span><span class="pln">
  </span><span class="pun">&lt;/</span><span class="pln">table</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">"Submit"</span><span class="pln"> </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>
	يُعَد ذلك مشابهًا للاستمارات السابقة ويعرض الحقول باستخدام جدول. لاحظ أيضًا كيف نصرّح مرةً أخرى عن <code>{% csrf_token %}</code> للتأكد من أن الاستمارات مقاومة <a href="https://academy.hsoub.com/programming/advanced/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A3%D9%85%D8%A7%D9%86-%D9%85%D9%88%D8%A7%D9%82%D8%B9-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r2020/" rel="">لهجمات CSRF</a>.
</p>

<p>
	يتوقع عرض "الحذف delete" العثور على قالب مسمًّى بالتنسيق"_" مثل <code>model_name_confirm_delete.html</code>. يمكنك تغيير اللاحقة باستخدام <code>template_name_suffix</code> في عرضك.
</p>

<p>
	أنشئ ملف القالب التالي وانسخ النص التالي:
</p>

<ul>
	<li>
		الملف "locallibrary/catalog/templates/catalog/author_confirm_delete.html":
	</li>
</ul>

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

{% block content %}

</span><span class="tag">&lt;h1&gt;</span><span class="pln">Delete Author</span><span class="tag">&lt;/h1&gt;</span><span class="pln">

</span><span class="tag">&lt;p&gt;</span><span class="pln">Are you sure you want to delete the author: {{ author }}?</span><span class="tag">&lt;/p&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">""</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">
  {% csrf_token %}
  </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">"Yes, delete."</span><span class="pln"> </span><span class="tag">/&gt;</span><span class="pln">
</span><span class="tag">&lt;/form&gt;</span><span class="pln">

{% endblock %}</span></pre>

<h3>
	ضبط عناوين URL
</h3>

<p>
	افتح ملف ضبط عناوين URL الخاص بك "locallibrary/catalog/urls.py" وضِف الضبط التالي إلى نهاية الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1677_51" style=""><span class="pln">urlpatterns </span><span class="pun">+=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    path</span><span class="pun">(</span><span class="str">'author/create/'</span><span class="pun">,</span><span class="pln"> views</span><span class="pun">.</span><span class="typ">AuthorCreate</span><span class="pun">.</span><span class="pln">as_view</span><span class="pun">(),</span><span class="pln"> name</span><span class="pun">=</span><span class="str">'author-create'</span><span class="pun">),</span><span class="pln">
    path</span><span class="pun">(</span><span class="str">'author/&lt;int:pk&gt;/update/'</span><span class="pun">,</span><span class="pln"> views</span><span class="pun">.</span><span class="typ">AuthorUpdate</span><span class="pun">.</span><span class="pln">as_view</span><span class="pun">(),</span><span class="pln"> name</span><span class="pun">=</span><span class="str">'author-update'</span><span class="pun">),</span><span class="pln">
    path</span><span class="pun">(</span><span class="str">'author/&lt;int:pk&gt;/delete/'</span><span class="pun">,</span><span class="pln"> views</span><span class="pun">.</span><span class="typ">AuthorDelete</span><span class="pun">.</span><span class="pln">as_view</span><span class="pun">(),</span><span class="pln"> name</span><span class="pun">=</span><span class="str">'author-delete'</span><span class="pun">),</span><span class="pln">
</span><span class="pun">]</span></pre>

<p>
	لا يوجد شيء جديد هنا، إذ يمكنك أن ترى أن العروض ما هي إلا أصناف، وبالتالي يجب استدعاؤها باستخدام <code>‎.as_view()‎</code>، ويجب أن تكون قادرًا على التعرّف على أنماط URL في كل حالة، ويجب أن نستخدم <code>pk</code> بوصفه اسمًا لقيمة المفتاح الرئيسي الملتقطة، لأنه اسم المعامل الذي تتوقعه أصناف العرض.
</p>

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

<p>
	<strong>ملاحظة</strong>: سيلاحظ المستخدمون شديدو الانتباه أننا لم نفعل أيّ شيء لمنع المستخدمين غير المُصرَّح لهم من الوصول إلى الصفحات، إذ سنترك ذلك بمثابة تمرين لك (تلميح: يمكنك استخدام <code>PermissionRequiredMixin</code> وإنشاء إذن جديد أو إعادة استخدام الإذن <code>can_mark_returned</code>).
</p>

<h3>
	اختبار الصفحة
</h3>

<p>
	أولًا، سجّل الدخول إلى الموقع بحساب لديه أيّ أذونات قررت أنها ضرورية للوصول إلى صفحات تعديل المؤلف، ثم انتقل إلى صفحة إنشاء المؤلف "http://127.0.0.1:8000/catalog/author/create/‎"، والتي يجب أن تبدو كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135462" href="https://academy.hsoub.com/uploads/monthly_2023_09/07_forms_example_create_author.png.3e53ed8912821640109b0230dbab299f.png" rel=""><img alt="07_forms_example_create_author.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135462" data-unique="2vdzw2acf" src="https://academy.hsoub.com/uploads/monthly_2023_09/07_forms_example_create_author.png.3e53ed8912821640109b0230dbab299f.png"> </a>
</p>

<p>
	أدخِل قيم الحقول ثم اضغط على إرسال Submit لحفظ سجل المؤلف، ويجب أن تُنقَل الآن إلى عرض تفصيلي لمؤلفك الجديد، مع عنوان URL مثل العنوان "http://127.0.0.1:8000/catalog/author/10".
</p>

<p>
	يمكنك اختبار تعديل السجلات من خلال إلحاق "/update/" بنهاية عنوان URL للعرض التفصيلي، مثل "http://127.0.0.1:8000/catalog/author/10/update/‎". لن نعرض لقطة شاشة لأنها تبدو تمامًا مثل صفحة "الإنشاء create".
</p>

<p>
	أخيرًا، يمكننا حذف الصفحة من خلال إلحاق "delete" بنهاية عنوان URL لعرض المؤلف التفصيلي، مثل "http://127.0.0.1:8000/catalog/author/10/delete/‎"، ويجب أن يعرض جانغو صفحة الحذف الآتية. اضغط على "Yes, delete.‎" لإزالة السجل والانتقال إلى قائمة جميع المؤلفين.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135461" href="https://academy.hsoub.com/uploads/monthly_2023_09/08_forms_example_delete_author.png.57767eaf55b85f1d5a0d0715d9bcffb8.png" rel=""><img alt="08_forms_example_delete_author.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135461" data-unique="0e51kqhii" src="https://academy.hsoub.com/uploads/monthly_2023_09/08_forms_example_delete_author.png.57767eaf55b85f1d5a0d0715d9bcffb8.png"> </a>
</p>

<h2>
	تحدى نفسك
</h2>

<p>
	أنشئ بعض الاستمارات لإنشاء وتعديل وحذف سجلات <code>Book</code>، إذ يمكنك استخدام بنية <code>Authors</code> نفسها تمامًا. إذا كان القالب book_form.html مجرد نسخة مُعاد تسميتها من القالب author_form.html، فستظهر صفحة "إنشاء كتاب" الجديدة مثل لقطة الشاشة التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135460" href="https://academy.hsoub.com/uploads/monthly_2023_09/09_forms_example_create_book.png.e0524f4149cfdad4a723206d53c6ab35.png" rel=""><img alt="09_forms_example_create_book.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135460" data-unique="sh9ihbbmp" src="https://academy.hsoub.com/uploads/monthly_2023_09/09_forms_example_create_book.png.e0524f4149cfdad4a723206d53c6ab35.png"> </a>
</p>

<h2>
	الخلاصة
</h2>

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

<p>
	هناك الكثير من الأمور التي يمكن إنجازها باستخدام الاستمارات، ولكن يجب أن تفهم كيفية إضافة الاستمارات الأساسية وشيفرة معالجتها إلى موقعك.
</p>

<p>
	ترجمة -وبتصرُّف- للمقال <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Forms" rel="external nofollow">Django Tutorial Part 9: Working with forms</a>.
</p>

<h2>
	اقرأ المزيد
</h2>

<ul>
	<li>
		المقال التالي: <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AA%D8%A7%D8%B3%D8%B9-%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2121/" rel="">تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو</a>
	</li>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B3%D8%A7%D8%A8%D8%B9-%D8%A7%D8%B3%D8%AA%D9%8A%D8%AB%D8%A7%D9%82-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D9%8A%D9%86-%D9%88%D8%A3%D8%B0%D9%88%D9%86%D8%A7%D8%AA%D9%87%D9%85-r2119/" rel="">تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-forms-%D9%88%D8%A7%D9%84%D8%B9%D8%B1%D9%88%D8%B6-%D8%A7%D9%84%D8%B9%D8%A7%D9%85%D8%A9-%D9%88%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D8%A7%D9%84%D8%B3%D8%A7%D9%83%D9%86%D8%A9-%D9%81%D9%8A-django-r476/" rel="">التعامل مع الاستمارات</a>
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/intro/tutorial04/#write-a-simple-form" rel="external nofollow">كتابة أول تطبيق دانغو - الجزء الرابع: كتابة استمارة بسيطة</a> (توثيق جانغو)
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/ref/forms/api/" rel="external nofollow">واجهة برمجة تطبيقات الاستمارة</a> (توثيق جانغو)
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/ref/forms/fields/" rel="external nofollow">حقول الاستمارة</a> (توثيق جانغو)
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/ref/forms/validation/" rel="external nofollow">التحقق من صحة الاستمارة والحقل</a> (توثيق جانغو)
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/topics/class-based-views/generic-editing/" rel="external nofollow">معالجة الاستمارة باستخدام العروض المستندة إلى الأصناف</a> (توثيق جانغو)
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/topics/forms/modelforms/" rel="external nofollow">إنشاء الاستمارات من النماذج</a> (توثيق جانغو)
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/ref/class-based-views/generic-editing/" rel="external nofollow">عروض التعديل المعمَّمة</a> (توثيق جانغو)
	</li>
</ul>
]]></description><guid isPermaLink="false">2120</guid><pubDate>Tue, 03 Oct 2023 13:06:05 +0000</pubDate></item><item><title>&#x62A;&#x637;&#x628;&#x64A;&#x642; &#x639;&#x645;&#x644;&#x64A; &#x644;&#x62A;&#x639;&#x644;&#x645; &#x62C;&#x627;&#x646;&#x63A;&#x648; - &#x627;&#x644;&#x62C;&#x632;&#x621; &#x627;&#x644;&#x633;&#x627;&#x628;&#x639;: &#x627;&#x633;&#x62A;&#x64A;&#x62B;&#x627;&#x642; &#x627;&#x644;&#x645;&#x633;&#x62A;&#x62E;&#x62F;&#x645;&#x64A;&#x646; &#x648;&#x623;&#x630;&#x648;&#x646;&#x627;&#x62A;&#x647;&#x645;</title><link>https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B3%D8%A7%D8%A8%D8%B9-%D8%A7%D8%B3%D8%AA%D9%8A%D8%AB%D8%A7%D9%82-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D9%8A%D9%86-%D9%88%D8%A3%D8%B0%D9%88%D9%86%D8%A7%D8%AA%D9%87%D9%85-r2119/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_09/--.png.f95c634ee46cf024576c68f437ce6fc6.png" /></p>
<p>
	سنوضح في هذا المقال كيفية السماح للمستخدمين بتسجيل الدخول إلى موقعك باستخدام حساباتهم الخاصة، وكيفية التحكم بما يمكنهم فعله ورؤيته بالاعتماد على ما إذا سجّلوا الدخول أم لا وبالاعتماد على أذوناتهم، إذ سنوسع موقع <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2090/" rel="">المكتبة المحلية LocalLibrary</a>، ونضيف صفحات تسجيل الدخول والخروج وصفحات خاصة بالمستخدمين وأخرى خاصة بالموظفين لعرض الكتب المستعارة.
</p>

<ul>
	<li>
		<strong>المتطلبات الأساسية</strong>: اطّلع على جميع المقالات السابقة من هذه السلسلة، بما في ذلك المقال الخاص <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B3%D8%A7%D8%AF%D8%B3-%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D8%AC%D9%84%D8%B3%D8%A7%D8%AA-sessions-r2118/" rel="">إدارة الجلسات</a>.
	</li>
	<li>
		<strong>الهدف</strong>: فهم كيفية إعداد واستخدام استيثاق Authentication المستخدمين وأذوناتهم Permissions.
	</li>
</ul>

<p>
	يوفر <a href="https://academy.hsoub.com/programming/python/django/" rel="">جانغو Django</a> نظام استيثاق وترخيص Authorization ("إذن permission") مبني على إطار عمل الجلسة (ناقشناه في المقال السابق)، والذي يسمح بالتحقق من اعتماديات Credentials المستخدم وتحديد الإجراءات التي يُسمَح لكل مستخدم بتطبيقها.
</p>

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

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

<p>
	سنوضّح في هذا المقال كيفية تفعيل استيثاق المستخدم في <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2090/" rel="">المكتبة المحلية LocalLibrary</a>، وإنشاء صفحات تسجيل الدخول والخروج، وإضافة أذونات إلى نماذجك، والتحكم في الوصول إلى الصفحات. سنستخدم الاستيثاق أو الأذونات لعرض قوائم الكتب المُستعارة لكل من المستخدمين وأمناء المكتبة.
</p>

<p>
	يُعَد نظام الاستيثاق مرنًا جدًا، إذ يمكنك إنشاء عناوين URL والاستمارات والعروض والقوالب الخاصة بك من البداية إن أردتَ ذلك، فما عليك سوى استدعاء <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-api-r1314/" rel="">واجهة برمجة التطبيقات <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr></a> المتوفرة لتسجيل دخول المستخدم. سنستخدم في هذا المقال عروض واستمارات استيثاق خاصة بجانغو لصفحات تسجيل الدخول والخروج، ولكن يجب إنشاء بعض القوالب، ويُعَد ذلك سهلًا جدًا، وسنوضح أيضًا كيفية إنشاء الأذونات، والتحقق من حالة تسجيل الدخول والأذونات في كلِّ من <a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D9%84%D8%B9%D8%B1%D9%88%D8%B6-%D9%88%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-%D9%81%D9%8A-django-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-r436/" rel="">العروض والقوالب</a>.
</p>

<p>
	تتألف هذه السلسلة الفرعية من السلسلة الأشمل <a href="https://academy.hsoub.com/tags/%D8%AA%D8%B9%D9%84%D9%85%20%D8%AA%D8%B7%D9%88%D9%8A%D8%B1%20%D8%A7%D9%84%D9%88%D9%8A%D8%A8/" rel="">تعلم تطوير الويب</a> من المقالات التالية:
</p>

<ul>
	<li>
		<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>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%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%AC%D8%A7%D9%86%D8%BA%D9%88-django-r2049/" rel="">إعداد بيئة تطوير تطبيقات جانغو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2090/" rel="">تطبيق عملي لتعلم جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%86%D9%85%D8%A7%D8%B0%D8%AC-models-r2092/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D9%85%D9%88%D9%82%D8%B9-%D9%85%D8%AF%D9%8A%D8%B1-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-admin-r2105/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B1%D8%A7%D8%A8%D8%B9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B5%D9%81%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%A7%D9%84%D8%B1%D8%A6%D9%8A%D8%B3%D9%8A%D8%A9-r2106/" rel="">تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AE%D8%A7%D9%85%D8%B3-%D8%A7%D9%84%D8%B9%D8%B1%D9%88%D8%B6-views-%D8%A7%D9%84%D8%B9%D8%A7%D9%85%D8%A9-%D9%88%D8%A7%D9%84%D8%AA%D9%81%D8%B5%D9%8A%D9%84%D9%8A%D8%A9-r2107/" rel="">تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B3%D8%A7%D8%AF%D8%B3-%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D8%AC%D9%84%D8%B3%D8%A7%D8%AA-sessions-r2118/" rel="">تطبيق عملي لتعلم جانغو - الجزء السادس: إدارة الجلسات Sessions</a>
	</li>
	<li>
		<span ipsnoautolink="true">تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم</span>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%85%D9%86-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-forms-r2120/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AA%D8%A7%D8%B3%D8%B9-%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2121/" rel="">تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-10-%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%81%D9%8A-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%A7%D8%AC-r2122/" rel="">تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A3%D9%85%D8%A7%D9%86-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2123/" rel="">تعرف على أمان تطبيقات جانغو</a>
	</li>
</ul>

<h2>
	تفعيل الاستيثاق
</h2>

<p>
	جرى تفعيل الاستيثاق تلقائيًا عندما أنشأنا <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2090/" rel="">موقع الويب الهيكلي</a> سابقًا لذلك لست بحاجة تطبيق أيّ شيء آخر حاليًا.
</p>

<p>
	<strong>ملاحظة</strong>: أُنجِز الضبط Configuration الضروري عندما أنشأنا التطبيق باستخدام الأمر <code>django-admin startproject</code>، وأُنشِئت جداول قاعدة البيانات للمستخدمين وأذونات النموذج عندما استدعينا الأمر <code>python manage.py migrate</code> لأول مرة.
</p>

<p>
	يُجرَى إعداد الضبط في الأقسام <code>INSTALLED_APPS</code> و <code>MIDDLEWARE</code> من ملف المشروع "locallibrary/locallibrary/settings.py" كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3368_8" style=""><span class="pln">INSTALLED_APPS </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    </span><span class="com"># …</span><span class="pln">
    </span><span class="str">'django.contrib.auth'</span><span class="pun">,</span><span class="pln">  </span><span class="com"># إطار عمل الاستيثاق الأساسي ونماذجه الافتراضية</span><span class="pln">
    </span><span class="str">'django.contrib.contenttypes'</span><span class="pun">,</span><span class="pln">  </span><span class="com"># نظام نوع محتوى جانغو ‫(يسمح بربط الأذونات بالنماذج)</span><span class="pln">
    </span><span class="com"># …</span><span class="pln">

MIDDLEWARE </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    </span><span class="com"># …</span><span class="pln">
    </span><span class="str">'django.contrib.sessions.middleware.SessionMiddleware'</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="str">'django.contrib.auth.middleware.AuthenticationMiddleware'</span><span class="pun">,</span><span class="pln">  </span><span class="com"># يربط المستخدمين بالطلبات باستخدام الجلسات</span><span class="pln">
    </span><span class="com"># …</span></pre>

<h2>
	إنشاء المستخدمين والمجموعات
</h2>

<p>
	أنشأنا مسبقًا أول مستخدم عندما تعرّفنا على <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D9%85%D9%88%D9%82%D8%B9-%D9%85%D8%AF%D9%8A%D8%B1-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-admin-r2105/" rel="">موقع مدير جانغو</a>، وهو مستخدم مميز Superuser أُنشِئ باستخدام الأمر:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2924_7" style=""><span class="pln">python manage</span><span class="pun">.</span><span class="pln">py createsuperuser</span></pre>

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

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3368_10" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">contrib</span><span class="pun">.</span><span class="pln">auth</span><span class="pun">.</span><span class="pln">models </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">User</span><span class="pln">

</span><span class="com"># إنشاء مستخدم وحفظه في قاعدة البيانات</span><span class="pln">
user </span><span class="pun">=</span><span class="pln"> </span><span class="typ">User</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">create_user</span><span class="pun">(</span><span class="str">'myusername'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'myemail@crazymail.com'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'mypassword'</span><span class="pun">)</span><span class="pln">

</span><span class="com"># عدّل الحقول ثم احفظها مرةً أخرى</span><span class="pln">
user</span><span class="pun">.</span><span class="pln">first_name </span><span class="pun">=</span><span class="pln"> </span><span class="str">'Tyrone'</span><span class="pln">
user</span><span class="pun">.</span><span class="pln">last_name </span><span class="pun">=</span><span class="pln"> </span><span class="str">'Citizen'</span><span class="pln">
user</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span></pre>

<p>
	يوصَى بشدة بإعداد نموذج model مستخدم مخصص عند بدء مشروع فعلي، إذ ستتمكّن من تخصيصه بسهولة مستقبلًا إن دعت الحاجة. اطّلع على <a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%86%D9%85%D8%A7%D8%B0%D8%AC-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-models-%D9%88%D8%B1%D8%A8%D8%B7%D9%87%D8%A7-%D8%A8%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-r1627/" rel="">إنشاء نماذج جانغو Django Models وربطها بقاعدة البيانات</a> على أكاديمية حسوب لمزيد من المعلومات.
</p>

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

<p>
	شغّل خادم التطوير وانتقل إلى موقع المدير في متصفح الويب المحلي "http://127.0.0.1:8000/admin/‎"، ثم سجّل الدخول إلى الموقع باستخدام اعتماديات حساب مستخدمك المميز.
</p>

<p>
	يعرض القسم العلوي من موقع المدير جميع نماذجك التي يرتبها تطبيق جانغو. يمكنك النقر على ارتباطات المستخدمين Users أو المجموعات Groups من قسم الاستيثاق والترخيص Authentication and Authorization لرؤية سجلاتها الحالية.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135451" href="https://academy.hsoub.com/uploads/monthly_2023_09/01_admin_authentication_add.png.8d1b7f4fa1775349db66c93734737324.png" rel=""><img alt="01_admin_authentication_add.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135451" data-unique="1swmvmwph" src="https://academy.hsoub.com/uploads/monthly_2023_09/01_admin_authentication_add.png.8d1b7f4fa1775349db66c93734737324.png"> </a>
</p>

<p>
	لننشئ مجموعةً جديدةً لأعضاء مكتبتنا: انقر على زر "الإضافة Add" (بجانب المجموعة Group) لإنشاء مجموعة جديدة، ثم أدخِل الاسم Name "أعضاء المكتبة Library Members" لهذه المجموعة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135450" href="https://academy.hsoub.com/uploads/monthly_2023_09/02_admin_authentication_add_group.png.3e03f8f3807b1087aff40aa7f33584f2.png" rel=""><img alt="02_admin_authentication_add_group.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135450" data-unique="le4128v88" src="https://academy.hsoub.com/uploads/monthly_2023_09/02_admin_authentication_add_group.png.3e03f8f3807b1087aff40aa7f33584f2.png"> </a>
</p>

<p>
	لا نحتاج أيّ أذونات للمجموعة، لذا اضغط على حفظ SAVE فقط، وستنتقل إلى قائمة المجموعات.
</p>

<p>
	لننشئ الآن مستخدمًا، لذا انتقل أولًا إلى الصفحة الرئيسية لموقع المدير.
</p>

<p>
	ثانيًا، انقر على زر "الإضافة Add" الموجود بجانب "المستخدمين Users" لفتح نافذة "إضافة مستخدم Add user".
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135449" href="https://academy.hsoub.com/uploads/monthly_2023_09/03_admin_authentication_add_user_prt1.png.c0834f7d08cbbad3ee5f5cda1f658259.png" rel=""><img alt="03_admin_authentication_add_user_prt1.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135449" data-unique="6ckh15ejd" src="https://academy.hsoub.com/uploads/monthly_2023_09/03_admin_authentication_add_user_prt1.png.c0834f7d08cbbad3ee5f5cda1f658259.png"> </a>
</p>

<p>
	ثالثًا، أدخِل اسم مستخدم <strong>Username</strong> مناسب وكلمة مرور <strong>Password</strong> وتأكيدها <strong>Password confirmation</strong> للمستخدم التجريبي.
</p>

<p>
	رابعًا، اضغط على <strong>حفظ SAVE</strong> لإنشاء المستخدم. سينشئ موقع المدير المستخدم الجديد وينقلك مباشرةً إلى شاشة تغيير المستخدم Change user، إذ يمكنك تغيير اسم المستخدم <strong>username</strong> وإضافة معلومات للحقول الاختيارية لنموذج المستخدم، والتي تتضمن الاسم الأول واسم العائلة وعنوان البريد الإلكتروني وحالة المستخدم وأذوناته (يجب ضبط الراية <strong>Active</strong> فقط). يمكنك تحديد مجموعات المستخدم وأذوناته، والاطلاع على التواريخ المهمة المتعلقة بالمستخدم، مثل تاريخ انضمامه وتاريخ آخر تسجيل دخول.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135448" href="https://academy.hsoub.com/uploads/monthly_2023_09/04_admin_authentication_add_user_prt2.png.cb7a5944c6a360983afcbc0d8a647bc2.png" rel=""><img alt="04_admin_authentication_add_user_prt2.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135448" data-unique="tpd0v5q14" src="https://academy.hsoub.com/uploads/monthly_2023_09/04_admin_authentication_add_user_prt2.thumb.png.ba6658c0b95d1ec8446bcbedbff6c468.png"> </a>
</p>

<p>
	خامسًا، حدّد المجموعة <strong>Library Member</strong> من قائمة المجموعات المتاحة Available groups في قسم المجموعات Groups، ثم اضغط على السهم الأيمن بين المربعين لنقل هذه المجموعة إلى مربع المجموعات المختارة Chosen groups.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135447" href="https://academy.hsoub.com/uploads/monthly_2023_09/05_admin_authentication_user_add_group.png.f8951aaedef82f08d7dd307167ac861e.png" rel=""><img alt="05_admin_authentication_user_add_group.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135447" data-unique="2hiih8fs2" src="https://academy.hsoub.com/uploads/monthly_2023_09/05_admin_authentication_user_add_group.png.f8951aaedef82f08d7dd307167ac861e.png"> </a>
</p>

<p>
	أخيرًا، لا تحتاج تطبيق شيء آخر، لذا اضغط على حفظ <strong>SAVE</strong> مرةً أخرى للانتقال إلى قائمة المستخدمين.
</p>

<p>
	هذا كل شيء. أصبح لديك الآن حساب "عضو مكتبة عادي" يمكنك استخدامه للاختبار (بعد أن نقدّم الصفحات لتمكين هذا العضو من تسجيل الدخول).
</p>

<p>
	<strong>ملاحظة</strong>: يجب أن تحاول إنشاء مستخدم عضو مكتبة آخر، وأن تنشئ مجموعة لأمناء المكتبة، وتضيف مستخدمًا إليها.
</p>

<h2>
	إعداد عروض الاستيثاق
</h2>

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

<p>
	سنوضح في هذا القسم كيفية دمج النظام الافتراضي في موقع المكتبة المحلية LocalLibrary وإنشاء القوالب التي سنضعها في <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> الرئيسية للمشروع.
</p>

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

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

<h3>
	عناوين URL الخاصة بالمشروع
</h3>

<p>
	أضف ما يلي إلى نهاية الملف urls.py الخاص بالمشروع "locallibrary/locallibrary/urls.py":
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3368_12" style=""><span class="com"># ‫أضف عناوين url لاستيثاق موقع جانغو (لتسجيل الدخول والخروج وإدارة كلمات المرور)</span><span class="pln">

urlpatterns </span><span class="pun">+=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    path</span><span class="pun">(</span><span class="str">'accounts/'</span><span class="pun">,</span><span class="pln"> include</span><span class="pun">(</span><span class="str">'django.contrib.auth.urls'</span><span class="pun">)),</span><span class="pln">
</span><span class="pun">]</span></pre>

<p>
	انتقل إلى العنوان "http://127.0.0.1:8000/accounts/‎" (لاحظ الشرطة المائلة للأمام في نهاية العنوان)، وسيُظِهر جانغو خطأً مفاده أنه لم يتمكن من العثور على عنوان URL، ويسرد جميع عناوين URL التي جربها، والتي يمكن منها رؤية عناوين URL التي ستعمل.
</p>

<p>
	<strong>ملاحظة</strong>: يؤدي استخدام الطريقة السابقة إلى إضافة عناوين URL التالية مع أسماء موضوعة بين أقواس معقوفة square bracket، والتي يمكن استخدامها لعكس روابط عناوين URL. لست مضطرًا إلى تطبيق أيّ شيء آخر، إذ تربط عملية ربط عنوان URL السابقة تلقائيًا عناوين URL التالية:
</p>

<pre class="ipsCode">accounts/ login/ [name='login']
accounts/ logout/ [name='logout']
accounts/ password_change/ [name='password_change']
accounts/ password_change/done/ [name='password_change_done']
accounts/ password_reset/ [name='password_reset']
accounts/ password_reset/done/ [name='password_reset_done']
accounts/ reset/&lt;uidb64&gt;/&lt;token&gt;/ [name='password_reset_confirm']
accounts/ reset/done/ [name='password_reset_complete']
</pre>

<p>
	حاول الآن الانتقال إلى عنوان URL لتسجيل الدخول "http://127.0.0.1:8000/accounts/login/‎"، الذي سيفشل مرةً أخرى مع وجود خطأ يخبرك بعدم وجود القالب المطلوب "registration/login.html" في مسار بحث القالب، وسترى الأسطر التالية في القسم الأصفر في الأعلى:
</p>

<pre class="ipsCode">Exception Type:    TemplateDoesNotExist
Exception Value:    registration/login.html
</pre>

<p>
	الخطوة التالية هي إنشاء المجلد registration على مسار البحث ثم إضافة الملف login.html.
</p>

<h3>
	مجلد القوالب Template
</h3>

<p>
	تتوقع عناوين URL (والعروض ضمنيًا) التي أضفناها مسبقًا العثورَ على القوالب المرتبطة بها في المجلد "/registration/" في مكان ما في مسار بحث القوالب.
</p>

<p>
	سنضع في موقعنا صفحات HTML في المجلد "templates/registration/‎"، إذ يجب أن يكون هذا المجلد في المجلد الجذر لمشروعك، أي المجلد نفسه للمجلدات catalog و locallibrary. إذًا، لننشئ هذه المجلدات الآن.
</p>

<p>
	<strong>ملاحظة</strong>: يجب أن تبدو بنية مجلدك الآن كما يلي:
</p>

<pre class="ipsCode">locallibrary/   # مجلد مشروع جانغو
  catalog/
  locallibrary/
  templates/
    registration/
</pre>

<p>
	يمكن جعل مجلد القوالب "templates" مرئيًا لمحمِّل القوالب template loader من خلال إضافته في مسار بحث القوالب، لذا افتح إعدادات المشروع "‎/locallibrary/locallibrary/settings.py"، ثم استورد بعد ذلك الوحدة <code>os</code> (أضف السطر التالي بالقرب من بداية الملف):
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3368_16" style=""><span class="kwd">import</span><span class="pln"> os </span><span class="com"># تحتاجه الشيفرة البرمجية الآتية </span></pre>

<p>
	عدّل السطر <code>'DIRS'</code> الخاص بالقسم <code>TEMPLATES</code> كما يلي:
</p>

<pre class="ipsCode">    # …
    TEMPLATES = [
      {
       # …
       'DIRS': [os.path.join(BASE_DIR, 'templates')],
       'APP_DIRS': True,
       # …
</pre>

<h3>
	قالب تسجيل الدخول Login
</h3>

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

<p>
	أنشِئ <a href="https://academy.hsoub.com/programming/html/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AA%D8%B9%D9%84%D9%8A%D9%85%D8%A7%D8%AA-html-r1894/" rel="">ملف HTML</a> جديد بالاسم "‎/locallibrary/templates/registration/login.html" وضع فيه المحتويات التالية:
</p>

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

{% block content %}

  {% if form.errors %}
    </span><span class="tag">&lt;p&gt;</span><span class="pln">Your username and password didn't match. Please try again.</span><span class="tag">&lt;/p&gt;</span><span class="pln">
  {% endif %}

  {% if next %}
    {% if user.is_authenticated %}
      </span><span class="tag">&lt;p&gt;</span><span class="pln">Your account doesn't have access to this page. To proceed,
      please login with an account that has access.</span><span class="tag">&lt;/p&gt;</span><span class="pln">
    {% else %}
      </span><span class="tag">&lt;p&gt;</span><span class="pln">Please login to see this page.</span><span class="tag">&lt;/p&gt;</span><span class="pln">
    {% endif %}
  {% endif %}

  </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="pln"> </span><span class="atn">action</span><span class="pun">=</span><span class="atv">"{% url 'login' %}"</span><span class="tag">&gt;</span><span class="pln">
    {% csrf_token %}
    </span><span class="tag">&lt;table&gt;</span><span class="pln">
      </span><span class="tag">&lt;tr&gt;</span><span class="pln">
        </span><span class="tag">&lt;td&gt;</span><span class="pln">{{ form.username.label_tag }}</span><span class="tag">&lt;/td&gt;</span><span class="pln">
        </span><span class="tag">&lt;td&gt;</span><span class="pln">{{ form.username }}</span><span class="tag">&lt;/td&gt;</span><span class="pln">
      </span><span class="tag">&lt;/tr&gt;</span><span class="pln">
      </span><span class="tag">&lt;tr&gt;</span><span class="pln">
        </span><span class="tag">&lt;td&gt;</span><span class="pln">{{ form.password.label_tag }}</span><span class="tag">&lt;/td&gt;</span><span class="pln">
        </span><span class="tag">&lt;td&gt;</span><span class="pln">{{ form.password }}</span><span class="tag">&lt;/td&gt;</span><span class="pln">
      </span><span class="tag">&lt;/tr&gt;</span><span class="pln">
    </span><span class="tag">&lt;/table&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">"login"</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">"hidden"</span><span class="pln"> </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"next"</span><span class="pln"> </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"{{ next }}"</span><span class="tag">&gt;</span><span class="pln">
  </span><span class="tag">&lt;/form&gt;</span><span class="pln">

  {# Assumes you setup the password_reset view in your URLconf #}
  </span><span class="tag">&lt;p&gt;&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{% url 'password_reset' %}"</span><span class="tag">&gt;</span><span class="pln">Lost password?</span><span class="tag">&lt;/a&gt;&lt;/p&gt;</span><span class="pln">

{% endblock %}</span></pre>

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

<p>
	انتقل إلى صفحة تسجيل الدخول "http://127.0.0.1:8000/accounts/login/‎" بعد حفظ قالبك، وسترى شيئًا يشبه ما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135446" href="https://academy.hsoub.com/uploads/monthly_2023_09/06_library_login.png.0bed70b4bcf087ffeee01eafa841221f.png" rel=""><img alt="06_library_login.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135446" data-unique="co7kfffzj" src="https://academy.hsoub.com/uploads/monthly_2023_09/06_library_login.png.0bed70b4bcf087ffeee01eafa841221f.png"> </a>
</p>

<p>
	إذا سجّلت الدخول باستخدام اعتماديات صالحة، فسيعاد توجيهك إلى صفحة أخرى (هي "http://127.0.0.1:8000/accounts/profile/‎" افتراضيًا). تكمن المشكلة في أن جانغو يتوقع افتراضيًا أنه عند تسجيل الدخول سترغب في نقلك إلى الصفحة الشخصية profile، ويمكن ألّا تكون راغبًا في ذلك، وبما أنك لم تعرّف هذه الصفحة بعد، فستتلقى خطأ آخر.
</p>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3368_20" style=""><span class="com"># إعادة التوجيه إلى عنوان‫ URL للصفحة الرئيسية بعد تسجيل الدخول </span><span class="pln">
</span><span class="com"># ‫تكون إعادة التوجيه الافتراضية إلى عنوان /accounts/profile/</span><span class="pln">
LOGIN_REDIRECT_URL </span><span class="pun">=</span><span class="pln"> </span><span class="str">'/'</span></pre>

<h3>
	قالب تسجيل الخروج Logout
</h3>

<p>
	إذا انتقلتَ إلى عنوان URL لتسجيل الخروج "http://127.0.0.1:8000/accounts/logout/‎"، فسترى بعض السلوكيات الغريبة، إذ سيُسجَّل خروج مستخدمك بالتاكيد، ولكن ستُنقَل إلى صفحة تسجيل خروج المدير Admin، وهذا ما لا تريده، لأن رابط تسجيل الدخول login link في تلك الصفحة ينقلك إلى شاشة تسجيل دخول المدير وهذا متاح فقط للمستخدمين الذين لديهم الإذن <code>is_staff</code>.
</p>

<p>
	أنشئ الملف التالي وافتحه، ثم انسخ الشيفرة التالية:
</p>

<ul>
	<li>
		الملف "‎/locallibrary/templates/registration/logged_out.html":
	</li>
</ul>

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

{% block content %}
  </span><span class="tag">&lt;p&gt;</span><span class="pln">Logged out!</span><span class="tag">&lt;/p&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 'login'%}"</span><span class="tag">&gt;</span><span class="pln">Click here to login again.</span><span class="tag">&lt;/a&gt;</span><span class="pln">
{% endblock %}</span></pre>

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

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135445" href="https://academy.hsoub.com/uploads/monthly_2023_09/07_library_logout.png.bfe2e3e39e30276cb75e044ca434b352.png" rel=""><img alt="07_library_logout.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135445" data-unique="pmew6j3vb" src="https://academy.hsoub.com/uploads/monthly_2023_09/07_library_logout.png.bfe2e3e39e30276cb75e044ca434b352.png"> </a>
</p>

<h3>
	قوالب إعادة تعيين كلمة المرور
</h3>

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

<h4>
	استمارة إعادة تعيين كلمة المرور
</h4>

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

<p>
	أنشئ الملف التالي، وضع فيه المحتويات التالية:
</p>

<ul>
	<li>
		الملف "‎/locallibrary/templates/registration/password_reset_form.html":
	</li>
</ul>

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

{% block content %}
  </span><span class="tag">&lt;form</span><span class="pln"> </span><span class="atn">action</span><span class="pun">=</span><span class="atv">""</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">
  {% csrf_token %}
  {% if form.email.errors %}
    {{ form.email.errors }}
  {% endif %}
      </span><span class="tag">&lt;p&gt;</span><span class="pln">{{ form.email }}</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">"submit"</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"btn btn-default btn-lg"</span><span class="pln"> </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"Reset password"</span><span class="tag">&gt;</span><span class="pln">
  </span><span class="tag">&lt;/form&gt;</span><span class="pln">
{% endblock %}</span></pre>

<h4>
	اكتمال إعادة تعيين كلمة المرور
</h4>

<p>
	تُعرَض هذه الاستمارة بعد جمع عنوان بريدك الإلكتروني. أنشئ الملف "‎/locallibrary/templates/registration/password_reset_done.html"، وضع فيه المحتويات التالية:
</p>

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

{% block content %}
  </span><span class="tag">&lt;p&gt;</span><span class="pln">We've emailed you instructions for setting your password. If they haven't arrived in a few minutes, check your spam folder.</span><span class="tag">&lt;/p&gt;</span><span class="pln">
{% endblock %}</span></pre>

<h4>
	البريد الإلكتروني لإعادة تعيين كلمة المرور
</h4>

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

<p>
	أنشئ الملف التالي وضع فيه المحتويات التالية:
</p>

<ul>
	<li>
		الملف "‎"/locallibrary/templates/registration/password_reset_email.html:
	</li>
</ul>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3368_31" style=""><span class="typ">Someone</span><span class="pln"> asked </span><span class="kwd">for</span><span class="pln"> password reset </span><span class="kwd">for</span><span class="pln"> email </span><span class="pun">{{</span><span class="pln"> email </span><span class="pun">}}.</span><span class="pln"> </span><span class="typ">Follow</span><span class="pln"> the link below</span><span class="pun">:</span><span class="pln">
</span><span class="pun">{{</span><span class="pln"> protocol </span><span class="pun">}}://{{</span><span class="pln"> domain </span><span class="pun">}}{%</span><span class="pln"> url </span><span class="str">'password_reset_confirm'</span><span class="pln"> uidb64</span><span class="pun">=</span><span class="pln">uid token</span><span class="pun">=</span><span class="pln">token </span><span class="pun">%}</span></pre>

<h4>
	تأكيد إعادة تعيين كلمة المرور
</h4>

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

<p>
	أنشئ الملف التالي، وضع فيه المحتويات التالية:
</p>

<ul>
	<li>
		الملف "‎/locallibrary/templates/registration/password_reset_confirm.html":
	</li>
</ul>

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

{% block content %}
    {% if validlink %}
        </span><span class="tag">&lt;p&gt;</span><span class="pln">Please enter (and confirm) your new password.</span><span class="tag">&lt;/p&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">""</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">
        {% csrf_token %}
            </span><span class="tag">&lt;table&gt;</span><span class="pln">
                </span><span class="tag">&lt;tr&gt;</span><span class="pln">
                    </span><span class="tag">&lt;td&gt;</span><span class="pln">{{ form.new_password1.errors }}
                        </span><span class="tag">&lt;label</span><span class="pln"> </span><span class="atn">for</span><span class="pun">=</span><span class="atv">"id_new_password1"</span><span class="tag">&gt;</span><span class="pln">New password:</span><span class="tag">&lt;/label&gt;&lt;/td&gt;</span><span class="pln">
                    </span><span class="tag">&lt;td&gt;</span><span class="pln">{{ form.new_password1 }}</span><span class="tag">&lt;/td&gt;</span><span class="pln">
                </span><span class="tag">&lt;/tr&gt;</span><span class="pln">
                </span><span class="tag">&lt;tr&gt;</span><span class="pln">
                    </span><span class="tag">&lt;td&gt;</span><span class="pln">{{ form.new_password2.errors }}
                        </span><span class="tag">&lt;label</span><span class="pln"> </span><span class="atn">for</span><span class="pun">=</span><span class="atv">"id_new_password2"</span><span class="tag">&gt;</span><span class="pln">Confirm password:</span><span class="tag">&lt;/label&gt;&lt;/td&gt;</span><span class="pln">
                    </span><span class="tag">&lt;td&gt;</span><span class="pln">{{ form.new_password2 }}</span><span class="tag">&lt;/td&gt;</span><span class="pln">
                </span><span class="tag">&lt;/tr&gt;</span><span class="pln">
                </span><span class="tag">&lt;tr&gt;</span><span class="pln">
                    </span><span class="tag">&lt;td&gt;&lt;/td&gt;</span><span class="pln">
                    </span><span class="tag">&lt;td&gt;&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">"Change my password"</span><span class="tag">&gt;&lt;/td&gt;</span><span class="pln">
                </span><span class="tag">&lt;/tr&gt;</span><span class="pln">
            </span><span class="tag">&lt;/table&gt;</span><span class="pln">
        </span><span class="tag">&lt;/form&gt;</span><span class="pln">
    {% else %}
        </span><span class="tag">&lt;h1&gt;</span><span class="pln">Password reset failed</span><span class="tag">&lt;/h1&gt;</span><span class="pln">
        </span><span class="tag">&lt;p&gt;</span><span class="pln">The password reset link was invalid, possibly because it has already been used. Please request a new password reset.</span><span class="tag">&lt;/p&gt;</span><span class="pln">
    {% endif %}
{% endblock %}</span></pre>

<h4>
	اكتمال إعادة تعيين كلمة المرور
</h4>

<p>
	هذا هو آخر قالب لإعادة تعيين كلمة المرور، إذ يُعرَض لإعلامك بنجاح إعادة تعيين كلمة المرور.
</p>

<p>
	أنشئ الملف التالي، وضع فيه المحتويات التالية:
</p>

<ul>
	<li>
		الملف "‎/locallibrary/templates/registration/password_reset_complete.html":
	</li>
</ul>

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

{% block content %}
  </span><span class="tag">&lt;h1&gt;</span><span class="pln">The password has been changed!</span><span class="tag">&lt;/h1&gt;</span><span class="pln">
  </span><span class="tag">&lt;p&gt;&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{% url 'login' %}"</span><span class="tag">&gt;</span><span class="pln">log in again?</span><span class="tag">&lt;/a&gt;&lt;/p&gt;</span><span class="pln">
{% endblock %}</span></pre>

<h3>
	اختبار صفحات الاستيثاق الجديدة
</h3>

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

<ul>
	<li>
		<code>/http://127.0.0.1:8000/accounts/login</code>
	</li>
	<li>
		<code>/http://127.0.0.1:8000/accounts/logout</code>
	</li>
</ul>

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

<p>
	<strong>ملاحظة</strong>: يتطلب نظام إعادة تعيين كلمة المرور دعم موقعك للبريد الإلكتروني، وهو ما يتجاوز نطاق هذا المقال، وبالتالي لن يعمل هذا الجزء بعد، لذا ضع السطر التالي في نهاية الملف settings.py للسماح بالاختبار، إذ يسجل هذا السطر أيّ رسائل بريد إلكتروني مُرسَلة إلى الطرفية Console، بحيث يمكنك نسخ رابط إعادة تعيين كلمة المرور من الطرفية.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3368_37" style=""><span class="pln">EMAIL_BACKEND </span><span class="pun">=</span><span class="pln"> </span><span class="str">'django.core.mail.backends.console.EmailBackend'</span></pre>

<p>
	اطلع على صفحة <a href="https://docs.djangoproject.com/en/4.0/topics/email/" rel="external nofollow">إرسال بريد إلكتروني</a> في توثيق جانغو لمزيد من المعلومات.
</p>

<h2>
	اختبار المستخدمين المستوثقين
</h2>

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

<h3>
	اختبار القوالب
</h3>

<p>
	يمكنك الحصول على معلومات حول المستخدم الذي سجّل الدخول حاليًا في القوالب باستخدام متغير القالب <code>{{ user }}</code>، إذ يُضاف هذا المتغير إلى سياق القالب افتراضيًا عند إعداد المشروع كما فعلنا سابقًا في الموقع الهيكلي.
</p>

<p>
	ستختبر أولًا متغير القالب <code>{{ user.is_authenticated }}</code> لتحديد ما إذا كان المستخدم مؤهلًا لمشاهدة محتوى معين، لذلك سنحدّث الشريط الجانبي لعرض رابط "تسجيل الدخول Login" إذا كان المستخدم قد سجّل الخروج، ورابط "تسجيل الخروج Logout" إذا سجّل الدخول.
</p>

<p>
	افتح القالب الأساسي "‎base_generic.html" من المسار "/locallibrary/catalog/templates/" وانسخ النص التالي في الكتلة <code>sidebar</code> قبل وسم القالب <code>endblock</code> مباشرةً:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_3368_39" style=""><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">"sidebar-nav"</span><span class="tag">&gt;</span><span class="pln">

    …

   {% if user.is_authenticated %}
     </span><span class="tag">&lt;li&gt;</span><span class="pln">User: {{ user.get_username }}</span><span class="tag">&lt;/li&gt;</span><span class="pln">
     </span><span class="tag">&lt;li&gt;&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{% url 'logout' %}?next={{ request.path }}"</span><span class="tag">&gt;</span><span class="pln">Logout</span><span class="tag">&lt;/a&gt;&lt;/li&gt;</span><span class="pln">
   {% else %}
     </span><span class="tag">&lt;li&gt;&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{% url 'login' %}?next={{ request.path }}"</span><span class="tag">&gt;</span><span class="pln">Login</span><span class="tag">&lt;/a&gt;&lt;/li&gt;</span><span class="pln">
   {% endif %}
  </span><span class="tag">&lt;/ul&gt;</span></pre>

<p>
	نستخدم الوسوم <code>if</code> و <code>else</code> و <code>endif</code> لعرض نص بطريقة شرطية بناءً على ما إذا كان المتغير <code>{{ user.is_authenticated }}</code> صحيحًا أم لا؛ فإذا كان المستخدم مستوثقًا، فسنعلم أن لدينا مستخدمًا صالحًا، لذلك نستدعي <code>{{ user.get_username }}</code> لعرض اسمه.
</p>

<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> لروابط تسجيل الدخول والخروج باستخدام وسم القالب <code>url</code> وأسماء عمليات ضبط عناوين URL ذات الصلة. لاحظ كيف ألحقنا <code>‎?next={{ request.path }}‎</code> بنهاية عناوين URL، مما يؤدي إلى إضافة معامل URL وهو <code>next</code> (الذي يحتوي على عنوان URL للصفحة الحالية) إلى نهاية عنوان URL المتعلق بالرابط. ستستخدم العروض قيمة "<code>next</code>" هذه بعد أن يسجّل المستخدم الدخول أو الخروج بنجاح لإعادة توجيه المستخدم إلى الصفحة التي نقر فيها أولًا على ارتباط تسجيل الدخول أو الخروج.
</p>

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

<h3>
	اختبار العروض Views
</h3>

<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="">المزخرف decorator</a> الذي يدعى <code>login_required</code> على دالة عرضك؛ فإذا سجّل المستخدم دخوله، فستُنفَّذ شيفرة العرض كما هو معتاد؛ وإذا لم يسجّل المستخدم دخوله، فسيُعاد توجيهه إلى عنوان URL لتسجيل الدخول المُعرَّف في إعدادات المشروع <code>settings.LOGIN_URL</code>، مع تمرير المسار المطلق الحالي بوصفه معامل URL الذي هو <code>next</code>. إذا نجح المستخدم في تسجيل الدخول، فسيُعاد إلى هذه الصفحة، ولكن سيكون مستوثقًا هذه المرة.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3368_41" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">contrib</span><span class="pun">.</span><span class="pln">auth</span><span class="pun">.</span><span class="pln">decorators </span><span class="kwd">import</span><span class="pln"> login_required

</span><span class="lit">@login_required</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> my_view</span><span class="pun">(</span><span class="pln">request</span><span class="pun">):</span><span class="pln">
    </span><span class="com"># …</span></pre>

<p>
	<strong>ملاحظة</strong>: يمكنك تطبيق الشيء نفسه يدويًا من خلال اختبار <code>request.user.is_authenticated</code>، ولكن استخدام المزخرف يُعَد ملائمًا أكثر.
</p>

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

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_3368_43" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">contrib</span><span class="pun">.</span><span class="pln">auth</span><span class="pun">.</span><span class="pln">mixins </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">LoginRequiredMixin</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">MyView</span><span class="pun">(</span><span class="typ">LoginRequiredMixin</span><span class="pun">,</span><span class="pln"> </span><span class="typ">View</span><span class="pun">):</span><span class="pln">
    </span><span class="com"># …</span></pre>

<p>
	وهذا له سلوك إعادة التوجيه نفسه للمزخرف <code>login_required</code>. يمكنك أيضًا تحديد موقع بديل لإعادة توجيه المستخدم إليه إن لم يكون مستوثقًا <code>login_url</code>، وتحديد اسم معامل URL بدلًا من المعامل "<code>next</code>" لإدخال المسار المطلق الحالي <code>redirect_field_name</code>.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3368_45" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">MyView</span><span class="pun">(</span><span class="typ">LoginRequiredMixin</span><span class="pun">,</span><span class="pln"> </span><span class="typ">View</span><span class="pun">):</span><span class="pln">
    login_url </span><span class="pun">=</span><span class="pln"> </span><span class="str">'/login/'</span><span class="pln">
    redirect_field_name </span><span class="pun">=</span><span class="pln"> </span><span class="str">'redirect_to'</span></pre>

<p>
	اطلع على <a href="https://docs.djangoproject.com/en/4.0/topics/auth/default/#limiting-access-to-logged-in-users" rel="external nofollow">توثيق جانغو</a> لمزيد من التفاصيل.
</p>

<h2>
	تطبيق عملي: سرد كتب المستخدم الحالي
</h2>

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

<h3>
	النماذج Models
</h3>

<p>
	أولًا، يجب أن نجعل من الممكن للمستخدمين الحصول على نسخة كتاب <code>BookInstance</code> على سبيل الإعارة. لدينا فعليًا حالة <code>status</code> وتاريخ الاسترجاع <code>due_back</code>، ولكن ليس لدينا حتى الآن أي ارتباط association بين هذا النموذج والمستخدم، لذا سننشئ هذا الارتباط باستخدام حقل <code>ForeignKey</code> (علاقة واحد إلى متعدد). نحتاج أيضًا إلى آلية سهلة لاختبار ما إذا كان الكتاب المُعار متأخرًا عن تاريخ استرجاعه أم لا.
</p>

<p>
	افتح الملف catalog/models.py، واستورد نموذج المستخدم <code>User</code> من <code>django.contrib.auth.models</code>، وضِف ذلك مباشرةً بعد سطر الاستيراد السابق في بداية الملف، بحيث يكون <code>User</code> متاحًا للشيفرة البرمجية اللاحقة التي تستخدمه:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3368_47" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">contrib</span><span class="pun">.</span><span class="pln">auth</span><span class="pun">.</span><span class="pln">models </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">User</span></pre>

<p>
	أضف بعد ذلك الحقل <code>borrower</code> إلى النموذج <code>BookInstance</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3368_49" style=""><span class="pln">borrower </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">ForeignKey</span><span class="pun">(</span><span class="typ">User</span><span class="pun">,</span><span class="pln"> on_delete</span><span class="pun">=</span><span class="pln">models</span><span class="pun">.</span><span class="pln">SET_NULL</span><span class="pun">,</span><span class="pln"> null</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">,</span><span class="pln"> blank</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span></pre>

<p>
	لنضِف أيضًا خاصية يمكننا استدعاؤها من قوالبنا لمعرفة ما إذا كان هناك نسخة لكتاب معين متأخرة عن تاريخ استرجاعها، إذ يمكننا حساب ذلك في القالب نفسه، ولكن يُعَد استخدام <a href="https://wiki.hsoub.com/Python/property" rel="external">خاصية property</a> كما هو موضح لاحقًا أكثر كفاءة، لذلك أضف ما يلي في مكان ما بالقرب من بداية الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3368_51" style=""><span class="kwd">from</span><span class="pln"> datetime </span><span class="kwd">import</span><span class="pln"> date</span></pre>

<p>
	ضِف بعد ذلك تعريف الخاصية الآتي إلى الصنف <code>BookInstance</code>.
</p>

<p>
	<strong>ملاحظة</strong>: تستخدم الشيفرة التالية دالة بايثون <a href="https://wiki.hsoub.com/Python/bool" rel="external"><code>bool()‎</code></a> التي تقيّم كائنًا أو كائنًا ناتجًا عن تعبيرٍ ما، وتعيد القيمة <code>True</code> إلّا إذا كانت النتيجة "خاطئة"، فتعيد في هذه الحالة القيمة <code>False</code>. يكون الكائن خاطئًا في بايثون (يُقيَّم على أنه <code>False</code>) إذا كان فارغًا (مثل <code>[]</code> و <code>()</code> و <code>{}</code>) أو <code>0</code> أو <code>None</code> أو إذا كان <code>False</code>.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3368_53" style=""><span class="lit">@property</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> is_overdue</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
    </span><span class="str">"""Determines if the book is overdue based on due date and current date."""</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> bool</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">due_back </span><span class="kwd">and</span><span class="pln"> date</span><span class="pun">.</span><span class="pln">today</span><span class="pun">()</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">due_back</span><span class="pun">)</span></pre>

<p>
	<strong>ملاحظة</strong>: نتحقق أولًا ما إذا كان <code>due_back</code> فارغًا قبل إجراء موازنة، إذ يمكن أن يتسبب حقل <code>due_back</code> الفارغ في أن يعطي جانغو خطأً بدلًا من إظهار الصفحة، فالقيم الفارغة غير قابلة للموازنة، ولا نريد أن يظهر ذلك الخطأ لمستخدمينا.
</p>

<p>
	حدّثنا نماذجنا، ويجب الآن إجراء عمليات تهجير migrations جديدة في المشروع ثم تطبيقها كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3368_55" style=""><span class="pln">python3 manage</span><span class="pun">.</span><span class="pln">py makemigrations
python3 manage</span><span class="pun">.</span><span class="pln">py migrate</span></pre>

<h3>
	المدير Admin
</h3>

<p>
	افتح الملف "catalog/admin.py"، وضِف الحقل <code>borrower</code> إلى الصنف <code>BookInstanceAdmin</code> في كلٍ من <code>list_display</code> و <code>fieldsets</code> كما هو موضح فيما يلي، وسيجعل هذا الحقل مرئيًا في قسم المدير Admin، وبالتالي سيُسمَح لنا بإسناد مستخدم <code>User</code> إلى نسخة كتاب <code>BookInstance</code> عند الحاجة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3368_57" style=""><span class="lit">@admin</span><span class="pun">.</span><span class="pln">register</span><span class="pun">(</span><span class="typ">BookInstance</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">BookInstanceAdmin</span><span class="pun">(</span><span class="pln">admin</span><span class="pun">.</span><span class="typ">ModelAdmin</span><span class="pun">):</span><span class="pln">
    list_display </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="str">'book'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'status'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'borrower'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'due_back'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'id'</span><span class="pun">)</span><span class="pln">
    list_filter </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="str">'status'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'due_back'</span><span class="pun">)</span><span class="pln">

    fieldsets </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
        </span><span class="pun">(</span><span class="kwd">None</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="str">'fields'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">(</span><span class="str">'book'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'imprint'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'id'</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}),</span><span class="pln">
        </span><span class="pun">(</span><span class="str">'Availability'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="str">'fields'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">(</span><span class="str">'status'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'due_back'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'borrower'</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}),</span><span class="pln">
    </span><span class="pun">)</span></pre>

<h3>
	إعارة بعض الكتب
</h3>

<p>
	أصبح من الممكن إعارة الكتب إلى مستخدم معين، لذا جرّب إعارة عددٍ من سجلات <code>BookInstance</code>. اضبط الحقل <code>borrowed</code> لمستخدمك التجريبي، واجعل الحالة <code>status</code> مُعار "On loan"، واضبط تواريخ الاسترجاع اللاحقة والسابقة.
</p>

<p>
	<strong>ملاحظة</strong>: لن نشرح العملية بالتفصيل، لأنك تعرف مسبقًا كيفية استخدام <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D9%85%D9%88%D9%82%D8%B9-%D9%85%D8%AF%D9%8A%D8%B1-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-admin-r2105/" rel="">موقع المدير</a>.
</p>

<h3>
	عرض الإعارة On loan
</h3>

<p>
	سنضيف الآن عرضًا للحصول على قائمة بجميع الكتب المُعارة للمستخدم الحالي، إذ سنستخدم <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AE%D8%A7%D9%85%D8%B3-%D8%A7%D9%84%D8%B9%D8%B1%D9%88%D8%B6-views-%D8%A7%D9%84%D8%B9%D8%A7%D9%85%D8%A9-%D9%88%D8%A7%D9%84%D8%AA%D9%81%D8%B5%D9%8A%D9%84%D9%8A%D8%A9-r2107/" rel="">عالعروض Views العامة والتفصيلية</a> نفسه الذي نعرفه، ولكن سنستورد ونشتق <code>LoginRequiredMixin</code> هذه المرة، بحيث لا يتمكن سوى المستخدم الذي سجّل الدخول من استدعاء هذا العرض. سنصرّح عن اسم القالب <code>template_name</code> بدلًا من استخدام القيمة الافتراضية، لأنه يمكن أن يكون لدينا عدة قوائم مختلفة من سجلات <code>BookInstance</code> مع عروض وقوالب مختلفة.
</p>

<p>
	أضف ما يلي إلى الملف catalog/views.py:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3368_59" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">contrib</span><span class="pun">.</span><span class="pln">auth</span><span class="pun">.</span><span class="pln">mixins </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">LoginRequiredMixin</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">LoanedBooksByUserListView</span><span class="pun">(</span><span class="typ">LoginRequiredMixin</span><span class="pun">,</span><span class="pln">generic</span><span class="pun">.</span><span class="typ">ListView</span><span class="pun">):</span><span class="pln">
    </span><span class="str">"""Generic class-based view listing books on loan to current user."""</span><span class="pln">
    model </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BookInstance</span><span class="pln">
    template_name </span><span class="pun">=</span><span class="pln"> </span><span class="str">'catalog/bookinstance_list_borrowed_user.html'</span><span class="pln">
    paginate_by </span><span class="pun">=</span><span class="pln"> </span><span class="lit">10</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> get_queryset</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"> </span><span class="pun">(</span><span class="pln">
            </span><span class="typ">BookInstance</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">borrower</span><span class="pun">=</span><span class="pln">self</span><span class="pun">.</span><span class="pln">request</span><span class="pun">.</span><span class="pln">user</span><span class="pun">)</span><span class="pln">
            </span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">status__exact</span><span class="pun">=</span><span class="str">'o'</span><span class="pun">)</span><span class="pln">
            </span><span class="pun">.</span><span class="pln">order_by</span><span class="pun">(</span><span class="str">'due_back'</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">)</span></pre>

<p>
	يمكننا تقييد استعلامنا عن كائنات <code>BookInstance</code> للمستخدم الحالي فقط من خلال إعادة تقديم التابع <code>get_queryset()‎</code> كما هو موضح في الشيفرة السابقة. لاحظ أن الرمز "o" هو الرمز المُخزَّن الذي يمثل الإعارة "on loan" وتُرتب هذه الكتب المُعارة حسب تاريخ استرجاعها <code>due_back</code> بحيث تُعرَض العناصر الأقدم أولًا.
</p>

<h3>
	ضبط عناوين URL للكتب المعارة
</h3>

<p>
	افتح الملف "‎/catalog/urls.py" وأضِف التابع <code>path()‎</code> الذي يشير إلى العرض السابق. يمكنك نسخ النص التالي إلى نهاية الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3368_61" style=""><span class="pln">urlpatterns </span><span class="pun">+=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    path</span><span class="pun">(</span><span class="str">'mybooks/'</span><span class="pun">,</span><span class="pln"> views</span><span class="pun">.</span><span class="typ">LoanedBooksByUserListView</span><span class="pun">.</span><span class="pln">as_view</span><span class="pun">(),</span><span class="pln"> name</span><span class="pun">=</span><span class="str">'my-borrowed'</span><span class="pun">),</span><span class="pln">
</span><span class="pun">]</span></pre>

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

<p>
	ما نحتاجه الآن هو إضافة قالب لهذه الصفحة، لذا أنشئ أولًا ملف القالب وضع فيه المحتويات التالية:
</p>

<ul>
	<li>
		الملف "‎/catalog/templates/catalog/bookinstance_list_borrowed_user.html":
	</li>
</ul>

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

{% block content %}
    </span><span class="tag">&lt;h1&gt;</span><span class="pln">Borrowed books</span><span class="tag">&lt;/h1&gt;</span><span class="pln">

    {% if bookinstance_list %}
    </span><span class="tag">&lt;ul&gt;</span><span class="pln">

      {% for bookinst in bookinstance_list %}
      </span><span class="tag">&lt;li</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"{% if bookinst.is_overdue %}text-danger{% endif %}"</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 'book-detail' bookinst.book.pk %}"</span><span class="tag">&gt;</span><span class="pln">{{ bookinst.book.title }}</span><span class="tag">&lt;/a&gt;</span><span class="pln"> ({{ bookinst.due_back }})
      </span><span class="tag">&lt;/li&gt;</span><span class="pln">
      {% endfor %}
    </span><span class="tag">&lt;/ul&gt;</span><span class="pln">

    {% else %}
      </span><span class="tag">&lt;p&gt;</span><span class="pln">There are no books borrowed.</span><span class="tag">&lt;/p&gt;</span><span class="pln">
    {% endif %}
{% endblock %}</span></pre>

<p>
	يشبه هذا القالب إلى حدٍ كبير القوالب التي أنشأناها مسبقًا لكائنات <code>Book</code> و <code>Author</code>، ولكن الشيء الوحيد المختلف هنا هو أننا نتحقق من التابع الذي أضفناها في النموذج (<code>bookinst.is_overdue</code>) ونستخدمه لتغيير لون العناصر المتأخرة.
</p>

<p>
	يجب أن تكون الآن قادرًا على عرض القائمة الخاصة بالمستخدم الذي سجّل الدخول في متصفحك على العنوان "http://127.0.0.1:8000/catalog/mybooks/‎" عند تشغيل خادم التطوير. جرّب ذلك عندما يسجّل المستخدم الدخول وعندما يسجّل خروجه، إذ يجب إعادة توجيهك إلى صفحة تسجيل الدخول في الحالة الثانية.
</p>

<h3>
	إضافة القائمة إلى الشريط الجانبي
</h3>

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

<p>
	افتح القالب الأساسي "‎/locallibrary/catalog/templates/base_generic.html" وضِف سطر "My Borrowed" الذي يمثل الكتب التي استعارها المستخدم إلى الشريط الجانبي في الموضع الموضح فيما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_3368_65" style=""><span class="tag">&lt;ul</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"sidebar-nav"</span><span class="tag">&gt;</span><span class="pln">
   {% if user.is_authenticated %}
   </span><span class="tag">&lt;li&gt;</span><span class="pln">User: {{ user.get_username }}</span><span class="tag">&lt;/li&gt;</span><span class="pln">

   </span><span class="tag">&lt;li&gt;&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{% url 'my-borrowed' %}"</span><span class="tag">&gt;</span><span class="pln">My Borrowed</span><span class="tag">&lt;/a&gt;&lt;/li&gt;</span><span class="pln">

   </span><span class="tag">&lt;li&gt;&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{% url 'logout' %}?next={{ request.path }}"</span><span class="tag">&gt;</span><span class="pln">Logout</span><span class="tag">&lt;/a&gt;&lt;/li&gt;</span><span class="pln">
   {% else %}
   </span><span class="tag">&lt;li&gt;&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{% url 'login' %}?next={{ request.path }}"</span><span class="tag">&gt;</span><span class="pln">Login</span><span class="tag">&lt;/a&gt;&lt;/li&gt;</span><span class="pln">
   {% endif %}
 </span><span class="tag">&lt;/ul&gt;</span></pre>

<p>
	إذا سجل أيّ مستخدم الدخول، فسيرى رابط "My Borrowed" في الشريط الجانبي، وقائمة الكتب المعروضة على النحو التالي (الكتاب الأول ليس له تاريخ استرجاع، وهو خطأ يجب إصلاحه في مقال لاحق):
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135444" href="https://academy.hsoub.com/uploads/monthly_2023_09/08_library_borrowed_by_user.png.7d1264cf2eb57a8c197de47627b91537.png" rel=""><img alt="08_library_borrowed_by_user.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135444" data-unique="elbtvmkpp" src="https://academy.hsoub.com/uploads/monthly_2023_09/08_library_borrowed_by_user.png.7d1264cf2eb57a8c197de47627b91537.png"> </a>
</p>

<h2>
	الأذونات
</h2>

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

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

<h3>
	النماذج models
</h3>

<p>
	تُعرَّف الأذونات في قسم النموذج "<code>class Meta</code>" باستخدام الحقل <code>permissions</code>، ويمكنك تحديد العديد من الأذونات التي تريدها ضمن <a href="https://academy.hsoub.com/programming/python/%D9%81%D9%87%D9%85-%D9%86%D9%88%D8%B9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-tuples-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-r507/" rel="">صف tuple</a>، إذ يُعرَّف كل إذن في صف متداخل nested tuple يحتوي على اسم الإذن وقيمة عرضه، فمثلًا يمكن أن نعرّف إذنًا للسماح للمستخدم بتمييز الكتاب بوصفه مُعادًا كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3368_67" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">BookInstance</span><span class="pun">(</span><span class="pln">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
    </span><span class="com"># …</span><span class="pln">
    </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Meta</span><span class="pun">:</span><span class="pln">
        </span><span class="com"># …</span><span class="pln">
        permissions </span><span class="pun">=</span><span class="pln"> </span><span class="pun">((</span><span class="str">"can_mark_returned"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Set book as returned"</span><span class="pun">),)</span></pre>

<p>
	يمكننا بعد ذلك إسناد الإذن لمجموعة "أمناء المكتبة Librarian" في موقع المدير.
</p>

<p>
	افتح الملف catalog/models.py، وضِف الإذن كما هو موضح سابقًا. ينبغي كذلك إعادة تشغيل عمليات التهجير (استدعِ الأمرين التاليين لتحديث قاعدة البيانات بصورة مناسبة:
</p>

<pre class="ipsCode">python3 manage.py makemigrations
python3 manage.py migrate
</pre>

<h3>
	القوالب Templates
</h3>

<p>
	تُخزَّن أذونات المستخدم الحالي في متغير قالب يسمى <code>{{ perms }}</code>، ويمكنك التحقق مما إذا كان المستخدم الحالي لديه إذن معين باستخدام اسم المتغير المحدد ضمن تطبيق جانغو المرتبط به، فمثلًا ستكون قيمة <code>{{ perms.catalog.can_mark_returned }}</code> هي <code>True</code> إذا كان المستخدم لديه هذا الإذن، و <code>False</code> في الحالات الأخرى.
</p>

<p>
	نختبر عادةً الإذن باستخدام وسم القالب <code>{% if %}</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_3368_70" style=""><span class="pln">{% if perms.catalog.can_mark_returned %}
    </span><span class="com">&lt;!-- We can mark a BookInstance as returned. --&gt;</span><span class="pln">
    </span><span class="com">&lt;!-- Perhaps add code to link to a "book return" view here. --&gt;</span><span class="pln">
{% endif %}</span></pre>

<h3>
	العروض Views
</h3>

<p>
	يمكن اختبار الأذونات في عرض مستند إلى الدوال باستخدام المزخرف <code>permission_required</code> أو في عرض مستند إلى الأصناف باستخدام الصنف <code>PermissionRequiredMixin</code>. يكون النمط هنا مماثلًا لاستيثاق تسجيل الدخول، بالرغم من أنه يمكن أن تضطر إلى إضافة عدة أذونات.
</p>

<p>
	إليك مزخرف عرض مستند إلى الدوال:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3368_72" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">contrib</span><span class="pun">.</span><span class="pln">auth</span><span class="pun">.</span><span class="pln">decorators </span><span class="kwd">import</span><span class="pln"> permission_required

</span><span class="lit">@permission_required</span><span class="pun">(</span><span class="str">'catalog.can_mark_returned'</span><span class="pun">)</span><span class="pln">
</span><span class="lit">@permission_required</span><span class="pun">(</span><span class="str">'catalog.can_edit'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> my_view</span><span class="pun">(</span><span class="pln">request</span><span class="pun">):</span><span class="pln">
    </span><span class="com"># …</span></pre>

<p>
	إليك <code>PermissionRequiredMixin</code> للعروض المستندة إلى الأصناف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3368_74" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">contrib</span><span class="pun">.</span><span class="pln">auth</span><span class="pun">.</span><span class="pln">mixins </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">PermissionRequiredMixin</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">MyView</span><span class="pun">(</span><span class="typ">PermissionRequiredMixin</span><span class="pun">,</span><span class="pln"> </span><span class="typ">View</span><span class="pun">):</span><span class="pln">
    permission_required </span><span class="pun">=</span><span class="pln"> </span><span class="str">'catalog.can_mark_returned'</span><span class="pln">
    </span><span class="com"># أو أذونات متعددة</span><span class="pln">
    permission_required </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="str">'catalog.can_mark_returned'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'catalog.can_edit'</span><span class="pun">)</span><span class="pln">
    </span><span class="com"># ‫لاحظ أن 'catalog.can_edit' هو مجرد مثال</span><span class="pln">
    </span><span class="com"># ‫تطبيق الدليل catalog ليس لديه هذا الإذن</span></pre>

<p>
	<strong>ملاحظة</strong>: يوجد اختلاف افتراضي بسيط في السلوك السابق، إذ يكون لمستخدم سجّل الدخول ولكنه انتهك إذنًا ما افتراضيًا ما يلي:
</p>

<ul>
	<li>
		<code>‎@permission_required</code>: الذي يعيد التوجيه إلى شاشة تسجيل الدخول (<a href="https://academy.hsoub.com/questions/18357-%D9%85%D8%A7%D8%B0%D8%A7-%D8%AA%D8%B9%D9%86%D9%8A-%D8%AD%D8%A7%D9%84%D8%A9-http-status-codes-3xx/" rel="">حالة HTTP التي هي 302</a>).
	</li>
	<li>
		<code>PermissionRequiredMixin</code>: الذي يعيد قيمة <a href="https://academy.hsoub.com/questions/18358-%D9%85%D8%A7%D8%B0%D8%A7-%D8%AA%D8%B9%D9%86%D9%8A-%D8%AD%D8%A7%D9%84%D8%A9-http-status-codes-4xx/" rel="">الحالة 403</a> (وهي حالة HTTP التي تمثل أن الوصول ممنوع Forbidden).
	</li>
</ul>

<p>
	ستحتاج عادةً إلى استخدام سلوك <code>PermissionRequiredMixin</code> الذي يعيد القيمة 403 إذا سجّل المستخدم الدخول ولكنه لا يمتلك الإذن الصحيح، ويمكن تحقيق ذلك للعرض المستند إلى الدوال من خلال استخدام <code>‎@login_required</code> و <code>‎@permission_required</code> مع <code>raise_exception=True</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3368_76" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">contrib</span><span class="pun">.</span><span class="pln">auth</span><span class="pun">.</span><span class="pln">decorators </span><span class="kwd">import</span><span class="pln"> login_required</span><span class="pun">,</span><span class="pln"> permission_required

</span><span class="lit">@login_required</span><span class="pln">
</span><span class="lit">@permission_required</span><span class="pun">(</span><span class="str">'catalog.can_mark_returned'</span><span class="pun">,</span><span class="pln"> raise_exception</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> my_view</span><span class="pun">(</span><span class="pln">request</span><span class="pun">):</span><span class="pln">
    </span><span class="com"># …</span></pre>

<h2>
	تحدى نفسك
</h2>

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

<p>
	يجب أن تكون قادرًا على اتباع النمط نفسه المُتبَع في العرض السابق الذي نفّذناه، ولكن الاختلاف الرئيسي هو أنك ستحتاج إلى تقييد العرض لأمناء المكتبة فقط، ويمكنك تحقيق ذلك بناءً على ما إذا كان المستخدم موظفًا (مزخرف الدالة هو <code>staff_member_required</code> ومتغير القالب هو <code>user.is_staff</code>)، ولكننا نوصي باستخدام الإذن <code>can_mark_returned</code> و <code>PermissionRequiredMixin</code> كما هو موضح في القسم السابق.
</p>

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

<p>
	يجب أن تبدو صفحتك كما يلي عند الانتهاء:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="135443" href="https://academy.hsoub.com/uploads/monthly_2023_09/09_library_borrowed_all.png.55ae2b659d505313295b9201a92dee56.png" rel=""><img alt="09_library_borrowed_all.png" class="ipsImage ipsImage_thumbnailed" data-fileid="135443" data-unique="wede9mmr9" src="https://academy.hsoub.com/uploads/monthly_2023_09/09_library_borrowed_all.png.55ae2b659d505313295b9201a92dee56.png"> </a>
</p>

<h2>
	الخلاصة
</h2>

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

<p>
	سنتعرّف في المقال التالي على كيفية استخدام استمارات جانغو لجمع مدخلات المستخدم، ثم البدء في تعديل بعض بياناتنا المُخزَّنة.
</p>

<p>
	ترجمة -وبتصرُّف- للمقال <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Authentication" rel="external nofollow">Django Tutorial Part 8: User authentication and permissions</a>.
</p>

<h2>
	اقرأ المزيد
</h2>

<ul>
	<li>
		المقال التالي: <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%85%D9%86-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-forms-r2120/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms</a>
	</li>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B3%D8%A7%D8%AF%D8%B3-%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D8%AC%D9%84%D8%B3%D8%A7%D8%AA-sessions-r2118/" rel="">تطبيق عملي لتعلم جانغو Django - الجزء السادس: إدارة الجلسات Sessions</a>
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/topics/auth/" rel="external nofollow">استيثاق المستخدم في جانغو</a> (توثيق جانغو)
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/topics/auth/default/" rel="external nofollow">استخدام نظام استيثاق جانغو الافتراضي</a> (توثيق جانغو)
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/topics/class-based-views/intro/#decorating-class-based-views" rel="external nofollow">زخرفة العروض المستندة إلى الأصناف</a> (توثيق جانغو)
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%8A%D8%B9%D8%B1%D8%B6-%D8%A3%D8%AD%D9%88%D8%A7%D9%84-%D8%A7%D9%84%D8%B7%D9%82%D8%B3-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-r1775/" rel="">بناء تطبيق يعرض أحوال الطقس باستخدام جانغو Django</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2119</guid><pubDate>Wed, 27 Sep 2023 13:02:00 +0000</pubDate></item><item><title>&#x62A;&#x637;&#x628;&#x64A;&#x642; &#x639;&#x645;&#x644;&#x64A; &#x644;&#x62A;&#x639;&#x644;&#x645; &#x62C;&#x627;&#x646;&#x63A;&#x648; - &#x627;&#x644;&#x62C;&#x632;&#x621; &#x627;&#x644;&#x633;&#x627;&#x62F;&#x633;: &#x625;&#x62F;&#x627;&#x631;&#x629; &#x627;&#x644;&#x62C;&#x644;&#x633;&#x627;&#x62A; Sessions</title><link>https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B3%D8%A7%D8%AF%D8%B3-%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D8%AC%D9%84%D8%B3%D8%A7%D8%AA-sessions-r2118/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_09/--.png.b9b6e870257cce8ba4e7c078ce883ebe.png" /></p>
<p>
	سنوسّع في هذا المقال <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2090/" rel="">موقع المكتبة المحلية LocalLibrary</a> من خلال إضافة عدّاد زيارات يعتمد على الجلسات إلى الصفحة الرئيسية. يُعَد هذا مثالًا بسيطًا نسبيًا، ولكنه يوضح كيفية استخدام إطار عمل الجلسة لتوفير سلوك دائم للمستخدمين المجهولين في مواقعك الخاصة.
</p>

<ul>
	<li>
		<strong>المتطلبات الأساسية</strong>: اطّلع على جميع المقالات السابقة من هذه السلسلة، بما في ذلك مقال <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AE%D8%A7%D9%85%D8%B3-%D8%A7%D9%84%D8%B9%D8%B1%D9%88%D8%B6-views-%D8%A7%D9%84%D8%B9%D8%A7%D9%85%D8%A9-%D9%88%D8%A7%D9%84%D8%AA%D9%81%D8%B5%D9%8A%D9%84%D9%8A%D8%A9-r2107/" rel="">العروض Views العامة والتفصيلية</a>.
	</li>
	<li>
		<strong>الهدف</strong>: فهم كيفية استخدام الجلسات.
	</li>
</ul>

<p>
	يتيح موقع المكتبة المحلية LocalLibrary الذي أنشأناه سابقًا للمستخدمين تصفح الكتب والمؤلفين في صفحة الدليل Catalog، إذ يمكن لكل مستخدم الوصول إلى الصفحات وأنواع المعلومات نفسها عند استخدام الموقع أثناء توليد المحتوى ديناميكيًا من قاعدة البيانات.
</p>

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

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

<p>
	تتألف هذه السلسلة الفرعية من السلسلة الأشمل <a href="https://academy.hsoub.com/tags/%D8%AA%D8%B9%D9%84%D9%85%20%D8%AA%D8%B7%D9%88%D9%8A%D8%B1%20%D8%A7%D9%84%D9%88%D9%8A%D8%A8/" rel="">تعلم تطوير الويب</a> من المقالات التالية:
</p>

<ul>
	<li>
		<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>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%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%AC%D8%A7%D9%86%D8%BA%D9%88-django-r2049/" rel="">إعداد بيئة تطوير تطبيقات جانغو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2090/" rel="">تطبيق عملي لتعلم جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%86%D9%85%D8%A7%D8%B0%D8%AC-models-r2092/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D9%85%D9%88%D9%82%D8%B9-%D9%85%D8%AF%D9%8A%D8%B1-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-admin-r2105/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B1%D8%A7%D8%A8%D8%B9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B5%D9%81%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%A7%D9%84%D8%B1%D8%A6%D9%8A%D8%B3%D9%8A%D8%A9-r2106/" rel="">تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AE%D8%A7%D9%85%D8%B3-%D8%A7%D9%84%D8%B9%D8%B1%D9%88%D8%B6-views-%D8%A7%D9%84%D8%B9%D8%A7%D9%85%D8%A9-%D9%88%D8%A7%D9%84%D8%AA%D9%81%D8%B5%D9%8A%D9%84%D9%8A%D8%A9-r2107/" rel="">تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية</a>
	</li>
	<li>
		<span ipsnoautolink="true">تطبيق عملي لتعلم جانغو - الجزء السادس: إدارة الجلسات Sessions</span>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B3%D8%A7%D8%A8%D8%B9-%D8%A7%D8%B3%D8%AA%D9%8A%D8%AB%D8%A7%D9%82-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D9%8A%D9%86-%D9%88%D8%A3%D8%B0%D9%88%D9%86%D8%A7%D8%AA%D9%87%D9%85-r2119/" rel="">تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%85%D9%86-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-forms-r2120/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AA%D8%A7%D8%B3%D8%B9-%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2121/" rel="">تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-10-%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%81%D9%8A-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%A7%D8%AC-r2122/" rel="">تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A3%D9%85%D8%A7%D9%86-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2123/" rel="">تعرف على أمان تطبيقات جانغو</a>
	</li>
</ul>

<h2>
	ما هي الجلسات؟
</h2>

<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>، وهو بروتوكول عديم الحالة Stateless، والذي يعني أن الرسائل بين العميل والخادم مستقلة تمامًا عن بعضها بعضًا، إذ لا توجد فكرة عن التسلسل أو السلوك المعتمد على الرسائل السابقة، لذا إذا أردتَ أن يكون لديك موقع يتعقّب العلاقات الجارية مع العميل، فيجب تطبيق ذلك بنفسك.
</p>

<p>
	تُعَد الجلسات آليةًَ يستخدمها إطار عمل <a href="https://academy.hsoub.com/programming/python/django/" rel="">جانغو Django</a> ومعظم شبكة الإنترنت لتعقّب الحالة بين الموقع ومتصفحٍ معين، وتتيح الجلسات تخزين البيانات العشوائية لكل متصفح، وإتاحة هذه البيانات للموقع كلما اتصل المتصفح، ويُشَار بعد ذلك إلى عناصر البيانات الفردية المرتبطة بالجلسة باستخدام "مفتاح" يُستخدم لتخزين البيانات واستردادها.
</p>

<p>
	يستخدم جانغو ملف تعريف ارتباط Cookie يحتوي على معرّف جلسة خاص id لتحديد كل متصفح وجلسته المرتبطة بالموقع، ولكن تُخزَّن بيانات الجلسة الفعلية في قاعدة بيانات الموقع افتراضيًا، ويُعَد ذلك أكثر أمانًا من تخزين البيانات في ملف تعريف ارتباط، والتي تكون فيها أكثر عرضةً للمستخدمين الضارين. يمكنك ضبط جانغو لتخزين بيانات الجلسة في أماكن أخرى، مثل <a href="https://academy.hsoub.com/programming/os-embedded-systems/%D9%86%D8%B8%D8%B1%D8%A9-%D8%B9%D9%85%D9%8A%D9%82%D8%A9-%D8%B9%D9%84%D9%89-%D8%AA%D8%B3%D9%84%D8%B3%D9%84-%D8%A7%D9%84%D8%B0%D9%88%D8%A7%D9%83%D8%B1-%D8%A7%D9%84%D9%87%D8%B1%D9%85%D9%8A-%D9%88%D8%A7%D9%84%D8%B0%D8%A7%D9%83%D8%B1%D8%A9-%D8%A7%D9%84%D9%85%D8%AE%D8%A8%D8%A6%D9%8A%D8%A9-%D9%81%D9%8A-%D9%85%D8%B9%D9%85%D8%A7%D8%B1%D9%8A%D8%A9-%D8%A7%D9%84%D8%AD%D8%A7%D8%B3%D9%88%D8%A8-r1720/" rel="">الذاكرة المخبئية cache</a> والملفات وملفات تعريف الارتباط "الآمنة"، ولكن يُعَد الموقع الافتراضي خيارًا جيدًا وآمنًا نسبيًا.
</p>

<h2>
	تفعيل الجلسات
</h2>

<p>
	جرى تفعيل الجلسات تلقائيًا عندما أنشأنا <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2090/" rel="">موقع الويب الهيكلي</a>.
</p>

<p>
	يجري إعداد الضبط Configuration في الأقسام <code>INSTALLED_APPS</code> و <code>MIDDLEWARE</code> من ملف المشروع "locallibrary/locallibrary/settings.py" كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6064_8" style=""><span class="pln">INSTALLED_APPS </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    </span><span class="com"># …</span><span class="pln">
    </span><span class="str">'django.contrib.sessions'</span><span class="pun">,</span><span class="pln">
    </span><span class="com"># …</span><span class="pln">

MIDDLEWARE </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    </span><span class="com"># …</span><span class="pln">
    </span><span class="str">'django.contrib.sessions.middleware.SessionMiddleware'</span><span class="pun">,</span><span class="pln">
    </span><span class="com"># …</span></pre>

<h2>
	استخدام الجلسات
</h2>

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

<p>
	تُعَد سمة <code>session</code> كائنًا يشبه القاموس dictionary يمكنك قراءته وكتابته عدة مرات في عرضك، مع تعديله بالطريقة التي تريدها، كما يمكنك تطبيق جميع عمليات القاموس العادية، بما في ذلك مسح جميع البيانات واختبار ما إذا كان المفتاح موجودًا والتكرار عبر البيانات وغير ذلك، ولكن ستستخدم غالبًا واجهة برمجة تطبيقات "القاموس" المعيارية فقط للحصول على القيم وضبطها.
</p>

<p>
	توضح أجزاء الشيفرة البرمجية الآتية كيفية الحصول على بعض البيانات وضبطها وحذفها باستخدام المفتاح "<code>my_car</code>" المرتبط بالجلسة الحالية (المتصفح).
</p>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6064_10" style=""><span class="com"># الحصول على قيمة الجلسة باستخدام مفتاحها (مثل المفتاح‫ 'my_car')، إذ سيحدث خطأ KeyError إن لم يكن المفتاح موجودًا</span><span class="pln">
my_car </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">session</span><span class="pun">[</span><span class="str">'my_car'</span><span class="pun">]</span><span class="pln">

</span><span class="com"># الحصول على قيمة الجلسة، إذ تُضبَط القيمة الافتراضية إن لم تكن موجودة‫ ('mini')</span><span class="pln">
my_car </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="str">'my_car'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'mini'</span><span class="pun">)</span><span class="pln">

</span><span class="com"># ضبط قيمة الجلسة</span><span class="pln">
request</span><span class="pun">.</span><span class="pln">session</span><span class="pun">[</span><span class="str">'my_car'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'mini'</span><span class="pln">

</span><span class="com"># حذف قيمة الجلسة</span><span class="pln">
</span><span class="kwd">del</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">session</span><span class="pun">[</span><span class="str">'my_car'</span><span class="pun">]</span></pre>

<p>
	تقدّم <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-api-r1314/" rel="">واجهة برمجة التطبيقات <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr></a> عددًا من التوابع الأخرى التي تُستخدَم غالبًا لإدارة ملف تعريف ارتباط الجلسة المرتبط، فمثلًا هناك توابع لاختبار أن ملفات تعريف الارتباط مدعومة في متصفح العميل، ولضبط والتحقق من تواريخ انتهاء صلاحية ملف تعريف الارتباط، ولمسح الجلسات منتهية الصلاحية من مخزن البيانات. يمكنك التعرف على واجهة برمجة التطبيقات الكاملة في <a href="https://docs.djangoproject.com/en/4.0/topics/http/sessions/" rel="external nofollow">توثيق جانغو الرسمي</a>.
</p>

<h2>
	حفظ بيانات الجلسة
</h2>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6064_12" style=""><span class="com"># ‫اُكتشِف السطر التالي بوصفه تحديثًا للجلسة، لذلك تُحفَظ بيانات الجلسة.</span><span class="pln">
request</span><span class="pun">.</span><span class="pln">session</span><span class="pun">[</span><span class="str">'my_car'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'mini'</span></pre>

<p>
	إذا حدّثتَ بعض المعلومات في بيانات الجلسة، فلن يدرك جانغو أنك أجريت تغييرًا على الجلسة ولن يحفظ البيانات، فإذا أردتَ مثلًا تغيير بيانات "<code>wheels</code>" في بيانات "<code>my_car</code>" كما يلي، فيجب تمييز الجلسة على أنها مُعدَّلة صراحةً:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6064_14" style=""><span class="com"># لم يُعدَّل كائن الجلسة مباشرة، إذ عُدِّلت البيانات ضمن الجلسة فقط، إذًا لن تُحفَظ تغييرات الجلسة</span><span class="pln">
request</span><span class="pun">.</span><span class="pln">session</span><span class="pun">[</span><span class="str">'my_car'</span><span class="pun">][</span><span class="str">'wheels'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'alloy'</span><span class="pln">

</span><span class="com"># ضبط الجلسة بوصفها مُعدَّلة لفرض حفظ تحديثات البيانات أو ملفات تعريف الارتباط</span><span class="pln">
request</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">modified </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">True</span></pre>

<p>
	<strong>ملاحظة</strong>: يمكنك تغيير السلوك بحيث يحدّث الموقع قاعدة بيانات أو يرسل ملف تعريف الارتباط في كل طلب من خلال إضافة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6064_16" style=""><span class="pln">SESSION_SAVE_EVERY_REQUEST </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">True</span></pre>

<p>
	في إعدادات مشروعك "locallibrary/locallibrary/settings.py".
</p>

<h2>
	تطبيق عملي: الحصول على عدد الزيارات
</h2>

<p>
	سنحدّث مكتبتنا لإخبار المستخدم الحالي بعدد المرات التي زار فيها الصفحة الرئيسية لموقع المكتبة المحلية ليكون بمثابة مثالٍ بسيط واقعي.
</p>

<p>
	افتح الملف ‎/locallibrary/catalog/views.py وضِف الأسطر التي تحتوي على <code>num_visits</code> في التابع <code>index()‎</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6064_18" style=""><span class="kwd">def</span><span class="pln"> index</span><span class="pun">(</span><span class="pln">request</span><span class="pun">):</span><span class="pln">
    </span><span class="com"># …</span><span class="pln">

    num_authors </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Author</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">count</span><span class="pun">()</span><span class="pln">  </span><span class="com"># ‫'all()‎' مُضمَّنة افتراضيًا</span><span class="pln">

    </span><span class="com"># عدد الزيارات إلى هذا العرض، كما هو محسوب في متغير الجلسة</span><span class="pln">
    num_visits </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="str">'num_visits'</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln">
    request</span><span class="pun">.</span><span class="pln">session</span><span class="pun">[</span><span class="str">'num_visits'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> num_visits </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pln">

    context </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="str">'num_books'</span><span class="pun">:</span><span class="pln"> num_books</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'num_instances'</span><span class="pun">:</span><span class="pln"> num_instances</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'num_instances_available'</span><span class="pln">
</span><span class="pun">:</span><span class="pln"> num_instances_available</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'num_authors'</span><span class="pun">:</span><span class="pln"> num_authors</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'num_visits'</span><span class="pun">:</span><span class="pln"> num_visits</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="com"># ‫عرض قالب index.html مع البيانات الموجودة في متغير السياق</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render</span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> </span><span class="str">'index.html'</span><span class="pun">,</span><span class="pln"> context</span><span class="pun">=</span><span class="pln">context</span><span class="pun">)</span></pre>

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

<p>
	<strong>ملاحظة</strong>: يمكن أن نختبر أيضًا ما إذا كانت ملفات تعريف الارتباط مدعومةً في المتصفح (اطّلع على <a href="https://docs.djangoproject.com/en/4.0/topics/http/sessions/" rel="external nofollow">كيفية استخدام الجلسات في توثيق جانغو</a> للحصول على أمثلة)، أو أن نصمم واجهة المستخدم بحيث لا يهم ما إذا كانت ملفات تعريف الارتباط مدعومةً أم لا.
</p>

<p>
	أضف السطر الموضّح في نهاية الكتلة التالية إلى قالب HTML الرئيسي "‎/locallibrary/catalog/templates/index.html" في نهاية قسم "المحتوى الديناميكي Dynamic content" لعرض متغير السياق <code>num_visits</code>:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_6064_20" style=""><span class="tag">&lt;h2&gt;</span><span class="pln">Dynamic content</span><span class="tag">&lt;/h2&gt;</span><span class="pln">

</span><span class="tag">&lt;p&gt;</span><span class="pln">The library has the following record counts:</span><span class="tag">&lt;/p&gt;</span><span class="pln">
</span><span class="tag">&lt;ul&gt;</span><span class="pln">
  </span><span class="tag">&lt;li&gt;&lt;strong&gt;</span><span class="pln">Books:</span><span class="tag">&lt;/strong&gt;</span><span class="pln"> {{ num_books }}</span><span class="tag">&lt;/li&gt;</span><span class="pln">
  </span><span class="tag">&lt;li&gt;&lt;strong&gt;</span><span class="pln">Copies:</span><span class="tag">&lt;/strong&gt;</span><span class="pln"> {{ num_instances }}</span><span class="tag">&lt;/li&gt;</span><span class="pln">
  </span><span class="tag">&lt;li&gt;&lt;strong&gt;</span><span class="pln">Copies available:</span><span class="tag">&lt;/strong&gt;</span><span class="pln"> {{ num_instances_available }}</span><span class="tag">&lt;/li&gt;</span><span class="pln">
  </span><span class="tag">&lt;li&gt;&lt;strong&gt;</span><span class="pln">Authors:</span><span class="tag">&lt;/strong&gt;</span><span class="pln"> {{ num_authors }}</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;p&gt;</span><span class="pln">
  You have visited this page {{ num_visits }} time{{ num_visits|pluralize }}.
</span><span class="tag">&lt;/p&gt;</span></pre>

<p>
	لاحظ أننا نستخدم وسم القالب <code>pluralize</code> المبني مسبقًا في جانغو لإضافة "s" (التي تمثل صيغة الجمع في اللغة الانجليزية) عند زيارة الصفحة مرات متعددة.
</p>

<p>
	احفظ التغييرات وأعِد تشغيل خادم الاختبار، إذ يجب تحديث عدد الزيارات في كل مرة تحدّث فيها الصفحة.
</p>

<h2>
	الخلاصة
</h2>

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

<p>
	ترجمة -وبتصرُّف- للمقال <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Sessions" rel="external nofollow">Django Tutorial Part 7: Sessions framework</a>.
</p>

<h2>
	اقرأ المزيد
</h2>

<ul>
	<li>
		المقال التالي: <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B3%D8%A7%D8%A8%D8%B9-%D8%A7%D8%B3%D8%AA%D9%8A%D8%AB%D8%A7%D9%82-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D9%8A%D9%86-%D9%88%D8%A3%D8%B0%D9%88%D9%86%D8%A7%D8%AA%D9%87%D9%85-r2119/" rel="">تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم</a>
	</li>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AE%D8%A7%D9%85%D8%B3-%D8%A7%D9%84%D8%B9%D8%B1%D9%88%D8%B6-views-%D8%A7%D9%84%D8%B9%D8%A7%D9%85%D8%A9-%D9%88%D8%A7%D9%84%D8%AA%D9%81%D8%B5%D9%8A%D9%84%D9%8A%D8%A9-r2107/" rel="">تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%88%D9%8A%D8%A8-%D9%84%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D9%85%D8%B9%D9%84%D9%88%D9%85%D8%A7%D8%AA-%D8%A7%D9%84%D8%B9%D9%85%D9%84%D8%A7%D8%A1-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-%D9%88%D8%B1%D9%8A%D8%A2%D9%83%D8%AA-react-r1776/" rel="">بناء تطبيق ويب لإدارة معلومات العملاء باستخدام جانغو Django وريآكت React</a>
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/topics/http/sessions/" rel="external nofollow">كيفية استخدام الجلسات في توثيق جانغو</a> (توثيق جانغو)
	</li>
</ul>
]]></description><guid isPermaLink="false">2118</guid><pubDate>Thu, 21 Sep 2023 13:00:00 +0000</pubDate></item><item><title>&#x62A;&#x637;&#x628;&#x64A;&#x642; &#x639;&#x645;&#x644;&#x64A; &#x644;&#x62A;&#x639;&#x644;&#x645; &#x62C;&#x627;&#x646;&#x63A;&#x648; - &#x627;&#x644;&#x62C;&#x632;&#x621; &#x627;&#x644;&#x62E;&#x627;&#x645;&#x633;: &#x627;&#x644;&#x639;&#x631;&#x648;&#x636; Views &#x627;&#x644;&#x639;&#x627;&#x645;&#x629; &#x648;&#x627;&#x644;&#x62A;&#x641;&#x635;&#x64A;&#x644;&#x64A;&#x629;</title><link>https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AE%D8%A7%D9%85%D8%B3-%D8%A7%D9%84%D8%B9%D8%B1%D9%88%D8%B6-views-%D8%A7%D9%84%D8%B9%D8%A7%D9%85%D8%A9-%D9%88%D8%A7%D9%84%D8%AA%D9%81%D8%B5%D9%8A%D9%84%D9%8A%D8%A9-r2107/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_09/-----------.png.376fafdb41861c054790479e56ae0e5f.png" /></p>
<p>
	سنوسّع في هذا المقال موقع المكتبة المحلية من خلال إضافة صفحات قائمة وتفاصيل للكتب والمؤلفين، إذ سنتعرف على العروض المُعمَّمة المستندة إلى الأصناف Generic Class-based Views، ونوضح كيف يمكنها تقليل كمية الشيفرة البرمجية التي يجب عليك كتابتها لحالات الاستخدام الشائعة. سننتقل أيضًا إلى معالجة عناوين URLs بمزيد من التفصيل، ونوضح كيفية إجراء مطابقة الأنماط الأساسية.
</p>

<ul>
	<li>
		<strong>المتطلبات الأساسية</strong>: أكمل جميع هذه السلسلة من المقالات بما في ذلك المقال السابق <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B1%D8%A7%D8%A8%D8%B9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B5%D9%81%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%A7%D9%84%D8%B1%D8%A6%D9%8A%D8%B3%D9%8A%D8%A9-r2106/" rel="">تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية</a> لإنشاء الصفحة الرئيسية.
	</li>
	<li>
		<strong>الهدف</strong>: فهم مكان وكيفية استخدام العروض المعمَّمة المستندة إلى الأصناف، وكيفية استخراج الأنماط من عناوين URLs وتمرير المعلومات إلى العروض.
	</li>
</ul>

<p>
	سنكمل في هذا المقال النسخة الأولى من <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2090/" rel="">موقع المكتبة المحلية LocalLibrary</a> من خلال إضافة صفحات قائمة وصفحات تفصيلية للكتب والمؤلفين، إذ سنعرض كيفية تقديم صفحات الكتاب، ونجعلك تنشئ صفحات المؤلفين بنفسك. تشبه هذه العملية إنشاء صفحة الفهرس التي تعلّمناها في المقال السابق، إذ سنحتاج إلى إنشاء روابط Maps لعناوين URL والعروض والقوالب. يتمثل الاختلاف الرئيسي في أننا سنواجه تحديًا إضافيًا بالنسبة لصفحات التفاصيل يتمثل في استخراج المعلومات من الأنماط الموجودة في عنوان URL وتمريرها إلى العرض، إذ سنعرض بالنسبة لهذه الصفحات نوعًا مختلفًا تمامًا من العروض هو عروض القائمة المُعمَّمة المستندة إلى الأصناف والعروض التفصيلية التي يمكن أن تقلل بصورة كبيرة من مقدار الشيفرة البرمجية للعرض المطلوب، مما يسهل كتابته وصيانته.
</p>

<p>
	سيوضح الجزء الأخير من هذا المقال كيفية ترقيم صفحات Pagination بياناتك عند استخدام عروض القائمة المُعمَّمة المستندة إلى الأصناف.
</p>

<p>
	تتألف هذه السلسلة الفرعية من السلسلة الأشمل <a href="https://academy.hsoub.com/tags/%D8%AA%D8%B9%D9%84%D9%85%20%D8%AA%D8%B7%D9%88%D9%8A%D8%B1%20%D8%A7%D9%84%D9%88%D9%8A%D8%A8/" rel="">تعلم تطوير الويب</a> من المقالات التالية:
</p>

<ul>
	<li>
		<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>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%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%AC%D8%A7%D9%86%D8%BA%D9%88-django-r2049/" rel="">إعداد بيئة تطوير تطبيقات جانغو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2090/" rel="">تطبيق عملي لتعلم جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%86%D9%85%D8%A7%D8%B0%D8%AC-models-r2092/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D9%85%D9%88%D9%82%D8%B9-%D9%85%D8%AF%D9%8A%D8%B1-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-admin-r2105/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B1%D8%A7%D8%A8%D8%B9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B5%D9%81%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%A7%D9%84%D8%B1%D8%A6%D9%8A%D8%B3%D9%8A%D8%A9-r2106/" rel="">تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية</a>
	</li>
	<li>
		<span ipsnoautolink="true">تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية</span>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B3%D8%A7%D8%AF%D8%B3-%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D8%AC%D9%84%D8%B3%D8%A7%D8%AA-sessions-r2118/" rel="">تطبيق عملي لتعلم جانغو - الجزء السادس: إدارة الجلسات Sessions</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B3%D8%A7%D8%A8%D8%B9-%D8%A7%D8%B3%D8%AA%D9%8A%D8%AB%D8%A7%D9%82-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D9%8A%D9%86-%D9%88%D8%A3%D8%B0%D9%88%D9%86%D8%A7%D8%AA%D9%87%D9%85-r2119/" rel="">تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%85%D9%86-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-forms-r2120/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AA%D8%A7%D8%B3%D8%B9-%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2121/" rel="">تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-10-%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%81%D9%8A-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%A7%D8%AC-r2122/" rel="">تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A3%D9%85%D8%A7%D9%86-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2123/" rel="">تعرف على أمان تطبيقات جانغو</a>
	</li>
</ul>

<h2>
	صفحة قائمة الكتب
</h2>

<p>
	ستعرض صفحة قائمة الكتب قائمةً بجميع سجلات الكتب المتاحة في الصفحة، والتي يمكن الوصول إليها باستخدام عنوان URL هو "catalog/books/‎"، وستعرض الصفحة عنوانًا ومؤلفًا لكل سجل على أن يكون العنوان رابطًا تشعبيًا لصفحة تفاصيل الكتاب المرتبطة به. سيكون للصفحة البنية والتنقل نفسه لجميع الصفحات الأخرى في الموقع، وبالتالي يمكننا توسيع القالب الأساسي base_generic.html الذي أنشأناه سابقًا.
</p>

<h3>
	ربط عناوين URLs
</h3>

<p>
	افتح الملف "‎/catalog/urls.py" وانسخ فيه السطر الذي يضبط مسار <code>'books/‎'</code>. تعرّف الدالة <code>path()‎</code> -كما هو الحال بالنسبة لصفحة الفهرس Index- نمطًا لمطابقة عنوان URL الذي هو 'books/‎'، ودالة عرض ستُستدعَى عند التطابق مع عنوان URL وهي <code>views.BookListView.as_view()‎</code>، واسمًا لهذا الربط المحدَّد كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1409_6" style=""><span class="pln">urlpatterns </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    path</span><span class="pun">(</span><span class="str">''</span><span class="pun">,</span><span class="pln"> views</span><span class="pun">.</span><span class="pln">index</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">=</span><span class="str">'index'</span><span class="pun">),</span><span class="pln">
    path</span><span class="pun">(</span><span class="str">'books/'</span><span class="pun">,</span><span class="pln"> views</span><span class="pun">.</span><span class="typ">BookListView</span><span class="pun">.</span><span class="pln">as_view</span><span class="pun">(),</span><span class="pln"> name</span><span class="pun">=</span><span class="str">'books'</span><span class="pun">),</span><span class="pln">
</span><span class="pun">]</span></pre>

<p>
	يجب أن يطابق عنوان URL عنوان "‎/catalog" كما ناقشنا سابقًا، لذلك سيُستدعَى العرض لعنوان URL هو "/catalog/books/".
</p>

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

<h3>
	العرض المستند إلى الأصناف
</h3>

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

<p>
	افتح الملف catalog/views.py، وانسخ الشيفرة البرمجية التالية في نهاية الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1409_8" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">views </span><span class="kwd">import</span><span class="pln"> generic

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">BookListView</span><span class="pun">(</span><span class="pln">generic</span><span class="pun">.</span><span class="typ">ListView</span><span class="pun">):</span><span class="pln">
    model </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Book</span></pre>

<p>
	سيستعلم العرض المُعمَّم في قاعدة البيانات للحصول على جميع السجلات للنموذج المحدد <code>Book</code>، ثم يعرض قالبًا موجودًا في الملف "‎/locallibrary/catalog/templates/catalog/book_list.html"، الذي سننشئه لاحقًا. يمكنك ضمن القالب الوصول إلى قائمة الكتب باستخدام متغير القالب المسمى <code>object_list</code> أو <code>book_list</code>؛ أي "<code>‎list_&lt;اسم النموذج&gt;</code>" عمومًا.
</p>

<p>
	<strong>ملاحظة</strong>: ليس هذا المسار الغريب لموقع القالب خطأ مطبعيًا، إذ تبحث العروض المعمَّمة عن القوالب في الملف "‎/application_name/the_model_name_list.html" (في حالتنا "catalog/book_list.html") في المجلد "/application_name/templates/" الخاص بالتطبيق ("/catalog/templates/").
</p>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1409_10" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">BookListView</span><span class="pun">(</span><span class="pln">generic</span><span class="pun">.</span><span class="typ">ListView</span><span class="pun">):</span><span class="pln">
    model </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Book</span><span class="pln">
    context_object_name </span><span class="pun">=</span><span class="pln"> </span><span class="str">'book_list'</span><span class="pln">   </span><span class="com"># اسمك الخاص للقائمة بوصفه متغير قالب</span><span class="pln">
    queryset </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Book</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">title__icontains</span><span class="pun">=</span><span class="str">'war'</span><span class="pun">)[:</span><span class="lit">5</span><span class="pun">]</span><span class="pln"> </span><span class="com"># احص‫ل على 5 كتب تحتوي على العنوان war</span><span class="pln">
    template_name </span><span class="pun">=</span><span class="pln"> </span><span class="str">'books/my_arbitrary_template_name_list.html'</span><span class="pln">  </span><span class="com"># حدد اسم/موقع قالبك</span></pre>

<h4>
	تعديل التوابع في العروض المستندة إلى الأصناف
</h4>

<p>
	يمكنك تعديل بعض توابع الصنف بالرغم من أننا لا نحتاج إلى ذلك حاليًا، فمثلًا يمكننا تعديل التابع <code>get_queryset()‎</code> لتغيير قائمة السجلات المُعادة، إذ يُعَد ذلك أكثر مرونةً من ضبط السمة <code>queryset</code> كما فعلنا في جزء الشيفرة البرمجية السابق، بالرغم من عدم وجود فائدة حقيقية في هذه الحالة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1409_12" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">BookListView</span><span class="pun">(</span><span class="pln">generic</span><span class="pun">.</span><span class="typ">ListView</span><span class="pun">):</span><span class="pln">
    model </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Book</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> get_queryset</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># ا‫حصل على 5 كتب تحتوي على العنوان war</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="typ">Book</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">title__icontains</span><span class="pun">=</span><span class="str">'war'</span><span class="pun">)[:</span><span class="lit">5</span><span class="pun">]</span><span class="pln"> </span></pre>

<p>
	يمكننا تعديل التابع <code>get_context_data()‎</code> لتمرير متغيرات سياق إضافية إلى القالب، مثل تمرير قائمة الكتب افتراضيًا. يوضح جزء الشيفرة التالي كيفية إضافة متغير بالاسم "<code>some_data</code>" إلى السياق، وسيكون متاحًا بعد ذلك بوصفه متغير قالب:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1409_14" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">BookListView</span><span class="pun">(</span><span class="pln">generic</span><span class="pun">.</span><span class="typ">ListView</span><span class="pun">):</span><span class="pln">
    model </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Book</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> get_context_data</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> </span><span class="pun">**</span><span class="pln">kwargs</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># استدعِ التقديم الأساسي أولًا للحصول على السياق</span><span class="pln">
        context </span><span class="pun">=</span><span class="pln"> super</span><span class="pun">(</span><span class="typ">BookListView</span><span class="pun">,</span><span class="pln"> self</span><span class="pun">).</span><span class="pln">get_context_data</span><span class="pun">(**</span><span class="pln">kwargs</span><span class="pun">)</span><span class="pln">
        </span><span class="com"># أنشئ بيانات وأضِفها إلى السياق</span><span class="pln">
        context</span><span class="pun">[</span><span class="str">'some_data'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'This is just some data'</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> context</span></pre>

<p>
	من المهم اتباع النمط المستخدم السابق عند تطبيق ذلك كما يلي:
</p>

<ul>
	<li>
		احصل أولًا على السياق الحالي من الصنف الأب.
	</li>
	<li>
		ضِف بعد ذلك معلومات السياق الجديدة.
	</li>
	<li>
		ثم أعِد السياق الجديد (المُحدَّث).
	</li>
</ul>

<p>
	<strong>ملاحظة</strong>: اطلع على <a href="https://docs.djangoproject.com/en/4.0/topics/class-based-views/generic-display/" rel="external nofollow">العروض المعمَّمة المستندة إلى الأصناف المبنية مسبقًا</a> في توثيق جانغو للحصول على مزيد من الأمثلة لما يمكنك فعله باستخدامها.
</p>

<h3>
	إنشاء قالب عرض القائمة
</h3>

<p>
	أنشئ ملف HTML بالاسم التالي:
</p>

<pre class="ipsCode">/locallibrary/catalog/templates/catalog/book_list.html
</pre>

<p>
	وانسخ فيه النص الآتي، وهو ملف القالب الافتراضي الذي يتوقعه عرض القائمة المُعمَّمة المستند إلى الأصناف (للنموذج <code>Book</code> في التطبيق <code>catalog</code>).
</p>

<p>
	تتشابه قوالب العروض المُعمَّمة مع أيّ قوالب أخرى بالرغم من اختلاف السياق أو المعلومات الممررة إلى القالب. سنوسّع القالب الأساسي في السطر الأول، ثم سنستبدل كتلة المحتوى <code>content</code> كما هو الحال في قالب الفهرس index.
</p>

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

{% block content %}
  </span><span class="tag">&lt;h1&gt;</span><span class="pln">Book List</span><span class="tag">&lt;/h1&gt;</span><span class="pln">
  {% if book_list %}
    </span><span class="tag">&lt;ul&gt;</span><span class="pln">
      {% for book in book_list %}
      </span><span class="tag">&lt;li&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">"{{ book.get_absolute_url }}"</span><span class="tag">&gt;</span><span class="pln">{{ book.title }}</span><span class="tag">&lt;/a&gt;</span><span class="pln">
        ({{book.author}})
      </span><span class="tag">&lt;/li&gt;</span><span class="pln">
      {% endfor %}
    </span><span class="tag">&lt;/ul&gt;</span><span class="pln">
  {% else %}
    </span><span class="tag">&lt;p&gt;</span><span class="pln">There are no books in the library.</span><span class="tag">&lt;/p&gt;</span><span class="pln">
  {% endif %}
{% endblock %}</span></pre>

<p>
	يمرر العرضُ السياقَ (قائمة الكتب) افتراضيًا بوصفه اسمًا بديلًا لمتغير القالب <code>object_list</code> و <code>book_list</code> (سيلبي أيّ منهما الغرض).
</p>

<h4>
	التنفيذ المشروط
</h4>

<p>
	نستخدم وسوم القالب <code>if</code> و <code>else</code> و <code>endif</code> للتحقق من تعريف المتغير <code>book_list</code> وأنه ليس فارغًا. إذا كان <code>book_list</code> فارغًا، فستعرض تعليمة <code>else</code> نصًا يوضح عدم وجود كتب لسردها؛ وإذا لم يكن فارغًا، سنكرر المرور على قائمة الكتب.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1409_18" style=""><span class="pln">{% if book_list %}
  </span><span class="com">&lt;!-- تسرد هذه الشيفرة البرمجية الكتب --&gt;</span><span class="pln">
{% else %}
  </span><span class="tag">&lt;p&gt;</span><span class="pln">There are no books in the library.</span><span class="tag">&lt;/p&gt;</span><span class="pln">
{% endif %}</span></pre>

<p>
	يتحقق الشرط السابق من حالة واحدة فقط، ولكن يمكنك اختبار شروط إضافية باستخدام وسم القالب <code>elif</code>، مثل <code>{% elif var2 %}</code>. اطلع على <a href="https://docs.djangoproject.com/en/4.0/ref/templates/builtins/#if" rel="external nofollow">if</a> و <a href="https://docs.djangoproject.com/en/4.0/ref/templates/builtins/#ifequal-and-ifnotequal" rel="external nofollow">ifequal/ifnotequal</a> و <a href="https://docs.djangoproject.com/en/4.0/ref/templates/builtins/#ifchanged" rel="external nofollow">ifchanged</a> في وسوم ومرشحات القوالب المبنية مسبقًا في توثيق جانغو لمزيد من المعلومات حول المعاملات الشرطية.
</p>

<h4>
	حلقات for
</h4>

<p>
	يستخدم القالب وسمي القالب <code>for</code> و <code>endfor</code> للتكرار عبر قائمة الكتب كما هو موضح في المثال التالي، إذ يملأ كل تكرار متغير القالب <code>book</code> بمعلومات عن عنصر القائمة الحالية:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1409_20" style=""><span class="pln">{% for book in book_list %}
  </span><span class="tag">&lt;li&gt;</span><span class="pln"> </span><span class="com">&lt;!-- تحصل هذه الشيفرة البرمجية على المعلومات من كل عنصر كتاب --&gt;</span><span class="pln"> </span><span class="tag">&lt;/li&gt;</span><span class="pln">
{% endfor %}</span></pre>

<p>
	يمكنك أيضًا استخدام وسم القالب <code>{% empty %}</code> لتحديد ما يحدث إذا كانت قائمة الكتب فارغة (بالرغم من أن قالبنا يختار استخدام شرط بدلًا من ذلك):
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1409_22" style=""><span class="tag">&lt;ul&gt;</span><span class="pln">
  {% for book in book_list %}
    </span><span class="tag">&lt;li&gt;</span><span class="com">&lt;!-- تحصل هذه الشيفرة البرمجية على المعلومات من كل عنصر كتاب --&gt;</span><span class="tag">&lt;/li&gt;</span><span class="pln">
  {% empty %}
    </span><span class="tag">&lt;p&gt;</span><span class="pln">There are no books in the library.</span><span class="tag">&lt;/p&gt;</span><span class="pln">
  {% endfor %}
</span><span class="tag">&lt;/ul&gt;</span></pre>

<p>
	سينشئ جانغو أيضًا ضمن الحلقة متغيرات أخرى يمكنك استخدامها لتتبع التكرار بالرغم من عدم استخدامه هنا، فمثلًا يمكنك اختبار المتغير <code>forloop.last</code> لإجراء معالجة شرطية لآخر مرة لتشغيل الحلقة.
</p>

<h4>
	الوصول إلى المتغيرات
</h4>

<p>
	تنشئ الشيفرة البرمجية الموجودة ضمن الحلقة عنصر قائمة لكل كتاب يُظهِر المؤلف والعنوان كأنه رابط للعرض التفصيلي الذي لم ننشئه بعد كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1409_24" style=""><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ book.get_absolute_url }}"</span><span class="tag">&gt;</span><span class="pln">{{ book.title }}</span><span class="tag">&lt;/a&gt;</span><span class="pln"> ({{book.author}})</span></pre>

<p>
	يمكن الوصول إلى حقول سجل الكتاب المرتبط بها باستخدام "الصيغة النقطية Dot Notation" مثل <code>book.title</code> و <code>book.author</code>، إذ يكون النص الذي يأتي بعد العنصر <code>book</code> هو اسم الحقل كما هو مُحدَّد في النموذج.
</p>

<p>
	يمكننا أيضًا استدعاء دوال في النموذج من القالب، إذ نستدعي في هذه الحالة الدالة <code>Book.get_absolute_url()‎</code> للحصول على عنوان URL الذي يمكنك استخدامه لعرض سجل التفاصيل المرتبط به. تعمل هذه الدالة بشرط ألّا تحتوي على أي وسطاء، إذ لا توجد طريقة لتمريرها.
</p>

<p>
	<strong>ملاحظة</strong>: يجب أن نكون حذرين بعض الشيء من "الآثار الجانبية" لاستدعاء الدوال في القوالب، إذ حصلنا في مثالنا فقط على عنوان URL للعرض، ولكن الدالة يمكنها فعل أي شيء، فلا نريد حذف قاعدة بياناتنا عند عرض النموذج مثلًا.
</p>

<h4>
	تحديث القالب الأساسي
</h4>

<p>
	افتح النموذج الأساسي التالي وأدخل <code>{% url 'books'‎ %}</code> في ارتباط عنوان URL لجميع الكتب <strong>All books</strong>، مما يؤدي إلى تفعيل هذا الارتباط في جميع الصفحات، ويمكننا وضع ذلك في مكانه الصحيح بنجاح الآن بعد أن أنشأنا رابط Mapper عنوان URL للكتب.
</p>

<ul>
	<li>
		الملف ‎/locallibrary/catalog/templates/base_generic.html:
	</li>
</ul>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1409_26" style=""><span class="tag">&lt;li&gt;&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{% url 'index' %}"</span><span class="tag">&gt;</span><span class="pln">Home</span><span class="tag">&lt;/a&gt;&lt;/li&gt;</span><span class="pln">
</span><span class="tag">&lt;li&gt;&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{% url 'books' %}"</span><span class="tag">&gt;</span><span class="pln">All books</span><span class="tag">&lt;/a&gt;&lt;/li&gt;</span><span class="pln">
</span><span class="tag">&lt;li&gt;&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">All authors</span><span class="tag">&lt;/a&gt;&lt;/li&gt;</span></pre>

<p>
	لن تتمكن من إنشاء قائمة الكتب بعد، لأننا ما زلنا نفتقد اعتمادية هي ربط عنوان URL لصفحات تفاصيل الكتب، والتي تُعَد ضروريةً لإنشاء ارتباطات تشعبية لكل كتاب. سنعرض كلًا من عروض القائمة والعروض التفصيلية بعد القسم التالي.
</p>

<h2>
	صفحة تفاصيل الكتاب
</h2>

<p>
	ستعرض صفحة تفاصيل الكتاب معلومات حول كتاب معين يمكن الوصول إليه باستخدام عنوان URL هو <code>catalog/book/&lt;id&gt;‎</code>، إذ يمثل <code>&lt;id&gt;</code> المفتاح الرئيسي للكتاب. سندرج أيضًا تفاصيل النسخ المتاحة <code>BookInstances</code> بما في ذلك الحالة وموعد الإعادة المتوقع والناشر والمعرّف، إضافةً إلى الحقول الموجودة في النموذج <code>Book</code> (المؤلف والملخص ورقم ISBN واللغة والنوع)، مما يسمح لقرائنا ليس فقط بالتعرف على الكتاب، ولكن لتأكيد ما إذا كان متوفرًا أو موعد توفره.
</p>

<h3>
	ربط عناوين URLs
</h3>

<p>
	افتح الملف ‎/catalog/urls.py وضِف المسار المُسمَّى 'book-detail'، إذ تعرّف الدالة <code>path()‎</code> نمطًا، وعرضًا تفصيليًا معمَّمًا مستندًا إلى الأصناف مرتبطًا به، واسمًا.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1409_28" style=""><span class="pln">urlpatterns </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    path</span><span class="pun">(</span><span class="str">''</span><span class="pun">,</span><span class="pln"> views</span><span class="pun">.</span><span class="pln">index</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">=</span><span class="str">'index'</span><span class="pun">),</span><span class="pln">
    path</span><span class="pun">(</span><span class="str">'books/'</span><span class="pun">,</span><span class="pln"> views</span><span class="pun">.</span><span class="typ">BookListView</span><span class="pun">.</span><span class="pln">as_view</span><span class="pun">(),</span><span class="pln"> name</span><span class="pun">=</span><span class="str">'books'</span><span class="pun">),</span><span class="pln">
    path</span><span class="pun">(</span><span class="str">'book/&lt;int:pk&gt;'</span><span class="pun">,</span><span class="pln"> views</span><span class="pun">.</span><span class="typ">BookDetailView</span><span class="pun">.</span><span class="pln">as_view</span><span class="pun">(),</span><span class="pln"> name</span><span class="pun">=</span><span class="str">'book-detail'</span><span class="pun">),</span><span class="pln">
</span><span class="pun">]</span></pre>

<p>
	يستخدم نمط عنوان URL -بالنسبة للمسار book-detail- صيغةً خاصةً لالتقاط المعرّف المحدد للكتاب الذي نريد رؤيته، إذ تكون هذه الصيغة بسيطةً جدًا، إذ تحدّد أقواس الزاوية جزء عنوان URL الذي سيُلتقط، مع تضمين اسم المتغير الذي يمكن أن يستخدمه العرض للوصول إلى البيانات الملتقطة، فمثلًا سيلتقط <strong><something></something></strong> النمط المحدد ويمرر القيمة إلى العرض بوصفه المتغير "something". يمكنك اختياريًا أن تسبق اسم المتغير <a href="https://docs.djangoproject.com/en/4.0/topics/http/urls/#path-converters" rel="external nofollow">بمواصفات المحوّل Converter Specification</a> التي تحدد نوع البيانات (int و str و slug و uuid و path).
</p>

<p>
	نستخدم في حالتنا <code>'&lt;int:pk&gt;'</code> لالتقاط معرّف الكتاب، والذي يجب أن يكون سلسلةً نصيةً منسقةً بصورة خاصة ويجب تمريره إلى العرض بوصفه معاملًا بالاسم <code>pk</code> (اختصار المفتاح الرئيسي Primary Key)، وهذا هو المعرّف الذي يُستخدَم لتخزين الكتاب بصورة فريدة في <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> كما هو مُحدَّد في النموذج Book.
</p>

<p>
	<strong>ملاحظة</strong>: عنوان URL المطابق هو "catalog/book/<digits>‎"، لأننا في التطبيق catalog أو <code>/catalog/</code> كما يُفترض.</digits>
</p>

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

<h4>
	مطابقة المسار أو التعبير النمطي المتقدم
</h4>

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

<p>
	مطابقة النمط التي يوفرها التابع <code>path()‎</code> بسيطة ومفيدة للحالات الشائعة جدًا إذ تريد فقط التقاط أيّ سلسلة أو عدد صحيح، ولكن إذا كنت بحاجة إلى مزيدٍ من الترشيح المُحسَّن مثل ترشيح السلاسل النصية التي تحتوي على عدد معين من المحارف فقط، فيمكنك استخدام التابع <a href="https://docs.djangoproject.com/en/4.0/ref/urls/#django.urls.re_path" rel="external nofollow"><code>re_path()‎</code></a> الذي يُستخدَم تمامًا مثل التابع <code>path()‎</code> باستثناء أنه يسمح لك بتحديد نمط باستخدام تعبير نمطي Regular Expression، فمثلًا يمكن كتابة المسار السابق كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1409_30" style=""><span class="pln">re_path</span><span class="pun">(</span><span class="pln">r</span><span class="str">'^book/(?P&lt;pk&gt;\d+)$'</span><span class="pun">,</span><span class="pln"> views</span><span class="pun">.</span><span class="typ">BookDetailView</span><span class="pun">.</span><span class="pln">as_view</span><span class="pun">(),</span><span class="pln"> name</span><span class="pun">=</span><span class="str">'book-detail'</span><span class="pun">),</span></pre>

<p>
	تُعَد التعابير النمطية أداةً فعالة جدًا لربط الأنماط، وهي غير بسيطة ويمكن أن تكون مخيفة للمبتدئين. سنوضّح فيما يلي مقدمةً تمهيديةً عنها، فأول شيء يجب معرفته هو أنه يجب التصريح عن التعابير النمطية باستخدام صياغة سلسلة نصية مجردة (أي تُضمَّن بالشكل: '&lt;يظهر نص تعبيرك النمطي هنا&gt;'r‎).
</p>

<p>
	الأجزاء الرئيسية من الصياغة التي ستحتاج إلى معرفتها للتصريح عن تطابقات الأنماط هي:
</p>

<table>
	<thead>
		<tr>
			<th>
				الرمز
			</th>
			<th>
				معناه
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				^
			</td>
			<td>
				يطابق بداية النص.
			</td>
		</tr>
		<tr>
			<td>
				$
			</td>
			<td>
				يطابق نهاية النص.
			</td>
		</tr>
		<tr>
			<td>
				‎\d
			</td>
			<td>
				يطابق رقمًا (0 و 1 و 2 و ... 9).
			</td>
		</tr>
		<tr>
			<td>
				‎\w
			</td>
			<td>
				يطابق محرفًا بطول كلمة مثل أي حرف كبير أو صغير في الأبجدية أو رقمًا أو محرف الشرطة السفلية (_).
			</td>
		</tr>
		<tr>
			<td>
				+
			</td>
			<td>
				يطابق محرفًا أو أكثر من المحارف السابقة، فمثلًا يمكنك استخدام <code>‎\d+‎</code> لمطابقة رقم واحد أو أكثر، ويمكنك استخدام <code>a+‎</code> لمطابقة حرف "a" واحد أو أكثر.
			</td>
		</tr>
		<tr>
			<td>
				*
			</td>
			<td>
				يطابق صفرًا أو أكثر من المحارف السابقة، فمثلًا يمكنك مطابقة لا شيء أو كلمة من خلال استخدام <code>‎\w*‎</code>
			</td>
		</tr>
		<tr>
			<td>
				( )
			</td>
			<td>
				يلتقط جزء النمط الموجود بين الأقواس، وتُمرَّر أيّ قيم مُلتقَطة إلى العرض بوصفات معاملات دون اسم، فإن اُلتقِطت أنماطٌ متعددة، فستُوفَّر المعاملات المرتبطة بها بترتيب التصريح عن هذه الالتقاطات.
			</td>
		</tr>
		<tr>
			<td>
				‎(?P<name>...)‎</name>
			</td>
			<td>
				التقاط النمط (الذي تشير إليه ...) بوصفه متغيرًا مُسمًّى (في هذه الحالة "name")، وتُمرَّر القيم المُلتقَطة إلى العرض باستخدام الاسم المُحدَّد، لذلك يجب أن يصرّح العرض عن معامل بالاسم نفسه.
			</td>
		</tr>
		<tr>
			<td>
				[ ]
			</td>
			<td>
				يطابق محرفًا واحدًا في المجموعة، فمثلًا سيطابَق [abc] مع 'a' أو 'b' أو 'c'، وسيطابَق [‎-\w] مع المحرف '-' أو أي محرف بحجم كلمة.
			</td>
		</tr>
	</tbody>
</table>

<p>
	يمكن أن تؤخَذ معظم المحارف الأخرى حرفيًا.
</p>

<p>
	لنطّلع الآن على بعض الأمثلة الحقيقية عن الأنماط:
</p>

<table>
	<thead>
		<tr>
			<th>
				النمط
			</th>
			<th>
				الوصف
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				r'^book/(?P<code>&lt;pk&gt;</code>\d+)$'‎
			</td>
			<td>
				هذا هو التعبير النمطي RE المُستخدَم في رابط عنوان URL الخاص بنا، حيث يجري مطابقة مع سلسلة نصية تحتوي على <code>book/‎</code> في بداية السطر (<code>‎^book/‎</code>)، ثم يحتوي على رقم واحد أو أكثر (<code>‎\d+‎</code>)، ثم ينتهي (بمحارف غير رقمية قبل علامة نهاية السطر). يلتقط هذا النمط أيضًا جميع الأرقام (‎?P<code>&lt;pk&gt;</code>\d+‎) ويمررها إلى العرض ضمن معامل يسمى 'pk'، إذ تُمرَّر دائمًا القيم المُلتقَطة بوصفها سلسلة نصية، فمثلًا سيطابق هذا النمط <code>book/1234</code>، ويرسل المتغير <code>pk='1234'‎</code> إلى العرض.
			</td>
		</tr>
		<tr>
			<td>
				r'^book/(\d+)$'‎
			</td>
			<td>
				يجري هذا النمط مطابقة مع عناوين URL للحالة السابقة نفسها، وستُرسَل المعلومات المُتقَطة بوصفها وسيطًا غير مُسمَّى إلى العرض.
			</td>
		</tr>
		<tr>
			<td>
				r'^book/(?P<code>&lt;stub&gt;</code>[-\w]+)$'‎
			</td>
			<td>
				يجري هذا النمط مطابقة مع سلسلة نصية تحتوي على <code>book/‎</code> في بداية السطر (‎^book/‎)، ثم يحتوي على حرف واحد أو أكثر يكون إما '-' أو محرف بحجم كلمة (‎[‎-\w]+‎)، ثم ينتهي النمط، إذ يلتقط هذه المجموعة من المحارف ويمرّرها إلى العرض ضمن معامل بالاسم 'stub'. يُعَد هذا النمط نموذجيًا إلى حد ما لمفاتيح ‎"Stub"‎، التي تُعَد مفاتيحًا رئيسية للبيانات وتعتمد على الكلمات ومناسبة لعناوين URL، إذ يمكنك استخدامها إذا أردتَ أن يكون عنوان URL للكتاب يتضمن معلومات أكثر مثل استخدام <code>‎/catalog/book/the-secret-garden</code> بدلًا من <code>‎/catalog/book/33</code>.
			</td>
		</tr>
	</tbody>
</table>

<p>
	يمكنك التقاط أنماط متعددة في تطابق واحد، مما يؤدي إلى تشفير الكثير من المعلومات المختلفة في عنوان URL.
</p>

<p>
	<strong>ملاحظة</strong>: ضع في حساباتك كتحدٍ لك كيفية تشفير عنوان URL لسرد جميع الكتب الصادرة في سنة وشهر ويوم محددين والتعبير النمطي RE الذي يمكن استخدامه لمطابقتها.
</p>

<h4>
	تمرير خيارات إضافية في روابط URL
</h4>

<p>
	إحدى الميزات التي لم نستخدمها حتى الآن -لكنها تُعَد قيّمة- هي أنه يمكنك تمرير قاموس Dictionary يحتوي على خيارات إضافية إلى العرض باستخدام الوسيط الثالث غير المسمَّى للدالة <code>path()‎</code>. يمكن أن يكون هذا الأسلوب مفيدًا إذا أردت استخدام العرض نفسه لموارد متعددة، وتمرير البيانات لإعداد سلوكها في كل حالة، فمثلًا سيستدعي جانغو:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1409_32" style=""><span class="pln">views</span><span class="pun">.</span><span class="pln">my_view</span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> fish</span><span class="pun">=</span><span class="pln">halibut</span><span class="pun">,</span><span class="pln"> my_template_name</span><span class="pun">=</span><span class="str">'some_path'</span><span class="pun">)‎</span></pre>

<p>
	بالنسبة إلى المسار التالي لطلب <code>/myurl/halibut/</code>:
</p>

<pre class="ipsCode">path('myurl/&lt;int:fish&gt;', views.my_view, {'my_template_name': 'some_path'}, name='aurl'),
</pre>

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

<h3>
	العرض المستند إلى الأصناف
</h3>

<p>
	افتح "catalog/views.py"، وانسخ الشيفرة البرمجية التالية في أسفل الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1409_35" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">BookDetailView</span><span class="pun">(</span><span class="pln">generic</span><span class="pun">.</span><span class="typ">DetailView</span><span class="pun">):</span><span class="pln">
    model </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Book</span></pre>

<p>
	هذا كل شيء، وكل ما عليك فعله الآن هو إنشاء قالب يُسمَّى:
</p>

<pre class="ipsCode">‎/locallibrary/catalog/templates/catalog/book_detail.html
</pre>

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

<h4>
	ماذا يحدث إذا كان السجل غير موجود؟
</h4>

<p>
	إذا لم يكن السجل المطلوب موجودًا، فسيرفع العرض التفصيلي المُعمَّم المستند إلى الأصناف استثناءَ <code>Http404</code> تلقائيًا، وسيعرض في مرحلة الإنتاج صفحة مناسبة تحتوي على النص "لم يُعثَر على المورد resource not found"، والتي يمكنك تخصيصها إذا رغبت في ذلك.
</p>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1409_37" style=""><span class="kwd">def</span><span class="pln"> book_detail_view</span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> primary_key</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">try</span><span class="pun">:</span><span class="pln">
        book </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Book</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">pk</span><span class="pun">=</span><span class="pln">primary_key</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">except</span><span class="pln"> </span><span class="typ">Book</span><span class="pun">.</span><span class="typ">DoesNotExist</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">raise</span><span class="pln"> </span><span class="typ">Http404</span><span class="pun">(</span><span class="str">'Book does not exist'</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">return</span><span class="pln"> render</span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> </span><span class="str">'catalog/book_detail.html'</span><span class="pun">,</span><span class="pln"> context</span><span class="pun">={</span><span class="str">'book'</span><span class="pun">:</span><span class="pln"> book</span><span class="pun">})</span></pre>

<p>
	يحاول العرض أولًا الحصول على سجل الكتاب المحدد من النموذج، وإذا فشل في ذلك، فيجب أن يرفع العرض استثناء "Http404" للإشارة إلى أن الكتاب غير موجود، والخطوة الأخيرة هي كالعادة استدعاء الدالة <code>render()‎</code> مع اسم القالب وبيانات الكتاب في المعامل <code>context</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>.
</p>

<p>
	يمكننا بدلًا من ذلك استخدام الدالة <code>get_object_or_404()‎</code> بوصفها اختصارًا لرفع استثناء "Http404" إذا لم يُعثَر على السجل كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1409_39" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">shortcuts </span><span class="kwd">import</span><span class="pln"> get_object_or_404

</span><span class="kwd">def</span><span class="pln"> book_detail_view</span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> primary_key</span><span class="pun">):</span><span class="pln">
    book </span><span class="pun">=</span><span class="pln"> get_object_or_404</span><span class="pun">(</span><span class="typ">Book</span><span class="pun">,</span><span class="pln"> pk</span><span class="pun">=</span><span class="pln">primary_key</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render</span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> </span><span class="str">'catalog/book_detail.html'</span><span class="pun">,</span><span class="pln"> context</span><span class="pun">={</span><span class="str">'book'</span><span class="pun">:</span><span class="pln"> book</span><span class="pun">})</span></pre>

<h3>
	إنشاء نموذج العرض التفصيلي
</h3>

<p>
	أنشئ ملف HTML بالاسم:
</p>

<pre class="ipsCode">‎/locallibrary/catalog/templates/catalog/book_detail.html 
</pre>

<p>
	وضع فيه المحتوى التالي، وهذا هو اسم ملف القالب الافتراضي الذي يتوقعه العرض التفصيلي المُعمَّم المستند إلى الأصناف (للنموذج <code>Book</code> في التطبيق المُسمى <code>catalog</code>).
</p>

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

{% block content %}
  </span><span class="tag">&lt;h1&gt;</span><span class="pln">Title: {{ book.title }}</span><span class="tag">&lt;/h1&gt;</span><span class="pln">

  </span><span class="tag">&lt;p&gt;&lt;strong&gt;</span><span class="pln">Author:</span><span class="tag">&lt;/strong&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">{{ book.author }}</span><span class="tag">&lt;/a&gt;&lt;/p&gt;</span><span class="pln">
  </span><span class="com">&lt;!-- ارتباط تفاصيل المؤلف لم يُعرَّف بعد --&gt;</span><span class="pln">
  </span><span class="tag">&lt;p&gt;&lt;strong&gt;</span><span class="pln">Summary:</span><span class="tag">&lt;/strong&gt;</span><span class="pln"> {{ book.summary }}</span><span class="tag">&lt;/p&gt;</span><span class="pln">
  </span><span class="tag">&lt;p&gt;&lt;strong&gt;</span><span class="pln">ISBN:</span><span class="tag">&lt;/strong&gt;</span><span class="pln"> {{ book.isbn }}</span><span class="tag">&lt;/p&gt;</span><span class="pln">
  </span><span class="tag">&lt;p&gt;&lt;strong&gt;</span><span class="pln">Language:</span><span class="tag">&lt;/strong&gt;</span><span class="pln"> {{ book.language }}</span><span class="tag">&lt;/p&gt;</span><span class="pln">
  </span><span class="tag">&lt;p&gt;&lt;strong&gt;</span><span class="pln">Genre:</span><span class="tag">&lt;/strong&gt;</span><span class="pln"> {{ book.genre.all|join:", " }}</span><span class="tag">&lt;/p&gt;</span><span class="pln">

  </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">style</span><span class="pun">=</span><span class="atv">"</span><span class="kwd">margin-left</span><span class="pun">:</span><span class="lit">20px</span><span class="pun">;</span><span class="kwd">margin-top</span><span class="pun">:</span><span class="lit">20px</span><span class="atv">"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;h4&gt;</span><span class="pln">Copies</span><span class="tag">&lt;/h4&gt;</span><span class="pln">

    {% for copy in book.bookinstance_set.all %}
      </span><span class="tag">&lt;hr</span><span class="pln"> </span><span class="tag">/&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">"{% if copy.status == 'a' %}text-success{% elif copy.status == 'm' %}text-danger{% else %}text-warning{% endif %}"</span><span class="tag">&gt;</span><span class="pln">
        {{ copy.get_status_display }}
      </span><span class="tag">&lt;/p&gt;</span><span class="pln">
      {% if copy.status != 'a' %}
        </span><span class="tag">&lt;p&gt;&lt;strong&gt;</span><span class="pln">Due to be returned:</span><span class="tag">&lt;/strong&gt;</span><span class="pln"> {{ copy.due_back }}</span><span class="tag">&lt;/p&gt;</span><span class="pln">
      {% endif %}
      </span><span class="tag">&lt;p&gt;&lt;strong&gt;</span><span class="pln">Imprint:</span><span class="tag">&lt;/strong&gt;</span><span class="pln"> {{ copy.imprint }}</span><span class="tag">&lt;/p&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">"text-muted"</span><span class="tag">&gt;&lt;strong&gt;</span><span class="pln">Id:</span><span class="tag">&lt;/strong&gt;</span><span class="pln"> {{ copy.id }}</span><span class="tag">&lt;/p&gt;</span><span class="pln">
    {% endfor %}
  </span><span class="tag">&lt;/div&gt;</span><span class="pln">
{% endblock %}</span></pre>

<p>
	<strong>ملاحظة</strong>: لارتباط المؤلف في النموذج السابق <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 الخاص بصفحة التفاصيل بمجرد وجودها باستخدام أيٍّ من الطريقتين التاليتين:
</p>

<ul>
	<li>
		أولًا، استخدم وسم القالب <code>url</code> لعكس عنوان URL الخاص بتفاصيل المؤلف 'author-detail' المُعرَّف في رابط عنوان URL، ومرّره إلى نسخة المؤلف الخاص بالكتاب:
	</li>
</ul>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1409_45" style=""><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{% url 'author-detail' book.author.pk %}"</span><span class="tag">&gt;</span><span class="pln">{{ book.author }}</span><span class="tag">&lt;/a&gt;</span></pre>

<ul>
	<li>
		استدعِ التابع <code>get_absolute_url()‎</code> الخاص بنموذج المؤلف، إذ يجري هذا التابع عملية الانعكاس نفسها:
	</li>
</ul>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1409_47" style=""><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ book.author.get_absolute_url }}"</span><span class="tag">&gt;</span><span class="pln">{{ book.author }}</span><span class="tag">&lt;/a&gt;</span></pre>

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

<p>
	شرحنا مسبقًا كل شيء تقريبًا في هذا القالب بالرغم من أنه أكبر قليلًا الآن:
</p>

<ul>
	<li>
		نوسّع القالب الأساسي ونعدّل كتلة "المحتوى content".
	</li>
	<li>
		نستخدم المعالجة المشروطة لتحديد عرض محتوًى معين أم لا.
	</li>
	<li>
		نستخدم حلقات for للتكرار عبر قوائم الكائنات.
	</li>
	<li>
		نصل إلى حقول السياق باستخدام الصيغة النقطية، إذ سُمِّي السياق بالاسم <code>book</code> لأننا استخدمنا العرض المُعمَّم التفصيلي، ولكن يمكننا أيضًا استخدام الاسم "<code>object</code>".
	</li>
</ul>

<p>
	أول شيء مهم لم نره من قبل هو التابع <code>book.bookinstance_set.all()‎</code> الذي ينشئه جانغو تلقائيًا لإعادة مجموعة سجلات <code>BookInstance</code> المرتبطة بكتاب <code>Book</code> معين:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1409_49" style=""><span class="pln">{% for copy in book.bookinstance_set.all %}
  </span><span class="com">&lt;!-- شيفرة للتكرار على كل نسخة من الكتاب --&gt;</span><span class="pln">
{% endfor %}</span></pre>

<p>
	يُعَد هذا التابع ضروريًا لأنك تصرّح عن حقل <code>ForeignKey</code> (واحد إلى متعدد) فقط في جانب "المتعدد" من العلاقة (جانب <code>BookInstance</code>). بما أنك لا تفعل أي شيء للتصريح عن العلاقة في النموذج الآخر ("واحد")، فلن يحتوي هذا الجانب (النموذج <code>Book</code>) على أيّ حقل للحصول على مجموعة السجلات المرتبطة به. يتغلب جانغو على هذه المشكلة من خلال بناء دالة "بحث عكسي" مسمّاة بطريقة مناسبة لاستخدامها، إذ يُبنَى اسم الدالة من خلال جعل حروف اسم النموذج حروفًا صغيرة في مكان التصريح عن <code>ForeignKey</code> ويتبعه <code>‎_set</code>، فمثلًا يكون اسم الدالة المُنشَأة في <code>Book</code> هي <code>bookinstance_set()‎</code>.
</p>

<p>
	<strong>ملاحظة</strong>: استخدمنا هنا الدالة <code>all()‎</code> للحصول على جميع السجلات افتراضيًا، ويمكنك استخدام الدالة <code>filter()‎</code> للحصول على مجموعة فرعية من السجلات في الشيفرة البرمجية، ولكن لا يمكنك ذلك مباشرةً في القوالب لأنه لا يمكن تحديد وسطاء للدوال.
</p>

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

<pre class="ipsCode">[29/May/2017 18:37:53] "GET /catalog/books/?page=1 HTTP/1.1" 200 1637
/foo/local_library/venv/lib/python3.5/site-packages/django/views/generic/list.py:99: UnorderedObjectListWarning: Pagination may yield inconsistent results with an unordered object_list: &lt;QuerySet [&lt;Author: Ortiz, David&gt;, &lt;Author: H. McRaven, William&gt;, &lt;Author: Leigh, Melinda&gt;]&gt;
  allow_empty_first_page=allow_empty_first_page, **kwargs)
</pre>

<p>
	يحدث هذا الخطأ لأن كائن ترقيم الصفحات يتوقع أن يرى تنفيذ تعليمة الترتيب <code>ORDER BY</code> على قاعدة بياناتك الأساسية، فبدونها لا يمكن التأكد من أن السجلات المُعادة بالترتيب الصحيح.
</p>

<p>
	لم يغطي هذا المقال ترقيم الصفحات Pagination حتى الآن، ولكن بما أنه لا يمكنك استخدام الدالة <code>sort_by()‎</code> وتمرير معامل كما هو الحال مع الدالة <code>filter()‎</code> سابقًا، فيجب عليك الاختيار من بين ثلاثة خيارات هي:
</p>

<ol>
	<li>
		أضف السمة <code>ordering</code> ضمن التصريح عن الصنف <code>class Meta</code> في نموذجك.
	</li>
	<li>
		أضف السمة <code>queryset</code> في عرضك المخصص المستند إلى الأصناف مع تحديد الدالة <code>order_by()‎</code>.
	</li>
	<li>
		أضف التابع <code>get_queryset</code> في عرضك المخصص المستند إلى الأصناف مع تحديد الدالة <code>order_by()‎</code>.
	</li>
</ol>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1409_51" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Author</span><span class="pun">(</span><span class="pln">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
    first_name </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">100</span><span class="pun">)</span><span class="pln">
    last_name </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">100</span><span class="pun">)</span><span class="pln">
    date_of_birth </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">DateField</span><span class="pun">(</span><span class="pln">null</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">,</span><span class="pln"> blank</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
    date_of_death </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">DateField</span><span class="pun">(</span><span class="str">'Died'</span><span class="pun">,</span><span class="pln"> null</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">,</span><span class="pln"> blank</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> get_absolute_url</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"> reverse</span><span class="pun">(</span><span class="str">'author-detail'</span><span class="pun">,</span><span class="pln"> args</span><span class="pun">=[</span><span class="pln">str</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">id</span><span class="pun">)])</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> __str__</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">'{self.last_name}, {self.first_name}'</span><span class="pln">

    </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Meta</span><span class="pun">:</span><span class="pln">
        ordering </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">'last_name'</span><span class="pun">]</span></pre>

<p>
	ليس ضروريًا أن يكون الحقل هو <code>last_name</code>، إذ يمكن أن يكون أيّ حقل آخر.
</p>

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

<p>
	الشيء الثاني المهم وغير الواضح في القالب هو المكان الذي نعرض فيه نص الحالة لكل نسخة كتاب ("متاح Available" و"في الصيانة Maintenance" …إلخ.). سيلاحظ القراء المتمرسون أن التابع <code>BookInstance.get_status_display()‎</code> الذي نستخدمه للحصول على نص الحالة لا يظهر في أي مكان آخر من <a href="https://academy.hsoub.com/programming/javascript/%D9%85%D8%AD%D8%B1%D8%B1%D8%A7%D8%AA-%D8%A7%D9%84%D8%B4%D9%8A%D9%81%D8%B1%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r666/" rel="">الشيفرة البرمجية</a>.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1409_53" style=""><span class="tag">&lt;p</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"{% if copy.status == 'a' %}text-success{% elif copy.status == 'm' %}text-danger{% else %}text-warning{% endif %}"</span><span class="tag">&gt;</span><span class="pln">
 {{ copy.get_status_display }} </span><span class="tag">&lt;/p&gt;</span></pre>

<p>
	تُنشَأ هذه الدالة تلقائيًا لأن الحقل <code>BookInstance.status</code> هو <a href="https://docs.djangoproject.com/en/4.0/ref/models/fields/#choices" rel="external nofollow">حقل اختيارات choices</a>، إذ ينشئ جانغو تلقائيًا التابع <code>get_FOO_display()‎</code> لكل حقل اختيارات "<code>Foo</code>" في النموذج، والذي يمكن استخدامه للحصول على قيمة الحقل الحالية.
</p>

<p>
	أنشأنا حتى الآن كل ما هو مطلوب لعرض كلِّ من صفحات قائمة الكتب وتفاصيل الكتاب. شغّل الخادم باستخدام الأمر <code>python3 manage.py runserver</code> وافتح المتصفح على العنوان "http://127.0.0.1:8000/‎".
</p>

<p>
	<strong>ملاحظة</strong>: لا تنقر على أيّ ارتباطات للمؤلف أو تفاصيله حتى الآن، إذ ستنشِئ هذه الارتباطات في التحدي الذي سنوكله إليك في نهاية المقال.
</p>

<p>
	انقر على ارتباط جميع الكتب All books لعرض قائمة الكتب.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="134914" href="https://academy.hsoub.com/uploads/monthly_2023_09/01_book_list_page_no_pagination.png.3b8e38d7bde47173014043806ba6cee0.png" rel=""><img alt="01_book_list_page_no_pagination.png" class="ipsImage ipsImage_thumbnailed" data-fileid="134914" data-unique="giin0rytc" src="https://academy.hsoub.com/uploads/monthly_2023_09/01_book_list_page_no_pagination.png.3b8e38d7bde47173014043806ba6cee0.png"> </a>
</p>

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

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="134913" href="https://academy.hsoub.com/uploads/monthly_2023_09/02_book_detail_page_no_pagination.png.55d20615fcbfd74a73090683b794ee59.png" rel=""><img alt="02_book_detail_page_no_pagination.png" class="ipsImage ipsImage_thumbnailed" data-fileid="134913" data-unique="ckqceg58p" src="https://academy.hsoub.com/uploads/monthly_2023_09/02_book_detail_page_no_pagination.thumb.png.1beb4f3c8b4ce3e38957403e2ca01a0b.png"> </a>
</p>

<h2>
	ترقيم الصفحات Pagination
</h2>

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

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

<h3>
	العروض Views
</h3>

<p>
	افتح الملف catalog/views.py، وأضِف سطر <code>paginate_by</code> التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1409_55" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">BookListView</span><span class="pun">(</span><span class="pln">generic</span><span class="pun">.</span><span class="typ">ListView</span><span class="pun">):</span><span class="pln">
    model </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Book</span><span class="pln">
    paginate_by </span><span class="pun">=</span><span class="pln"> </span><span class="lit">10</span></pre>

<p>
	سيبدأ العرض مع هذه الإضافة بترقيم صفحات البيانات التي يرسلها إلى القالب بمجرد أن يكون لديك أكثر من 10 سجلات. يمكن الوصول إلى الصفحات المختلفة باستخدام معاملات GET، فمثلًا ستستخدم <code>‎/catalog/books/?page=2</code> للوصول إلى الصفحة 2.
</p>

<h3>
	القوالب Templates
</h3>

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

<p>
	افتح الملف التالي وابحث عن "كتلة المحتوى content block" كما يلي:
</p>

<ul>
	<li>
		الملف ‎/locallibrary/catalog/templates/base_generic.html:
	</li>
</ul>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1409_57" style=""><span class="pln">{% block content %}{% endblock %}</span></pre>

<p>
	انسخ فيه كتلة ترقيم الصفحات التالية بعد <code>{% endblock %}</code> مباشرةً، إذ تتحقق هذه الشيفرة البرمجية أولًا من تفعيل ترقيم الصفحات في الصفحة الحالية. إذا كان الأمر كذلك، فستضيف ارتباطات التالي next والسابق previous كما هو مناسب (ورقم الصفحة الحالية).
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1409_59" style=""><span class="pln">{% block pagination %}
    {% if is_paginated %}
        </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">
            </span><span class="tag">&lt;span</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"page-links"</span><span class="tag">&gt;</span><span class="pln">
                {% if page_obj.has_previous %}
                    </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ request.path }}?page={{ page_obj.previous_page_number }}"</span><span class="tag">&gt;</span><span class="pln">previous</span><span class="tag">&lt;/a&gt;</span><span class="pln">
                {% endif %}
                </span><span class="tag">&lt;span</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"page-current"</span><span class="tag">&gt;</span><span class="pln">
                    Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
                </span><span class="tag">&lt;/span&gt;</span><span class="pln">
                {% if page_obj.has_next %}
                    </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{{ request.path }}?page={{ page_obj.next_page_number }}"</span><span class="tag">&gt;</span><span class="pln">next</span><span class="tag">&lt;/a&gt;</span><span class="pln">
                {% endif %}
            </span><span class="tag">&lt;/span&gt;</span><span class="pln">
        </span><span class="tag">&lt;/div&gt;</span><span class="pln">
    {% endif %}
  {% endblock %}</span></pre>

<p>
	يُعَد الكائن <code>page_obj</code> كائن ترقيم <a href="https://docs.djangoproject.com/en/4.0/topics/pagination/#paginator-objects" rel="external nofollow">Paginator</a>، والذي يكون موجودًا في حالة استخدام ترقيم الصفحات في الصفحة الحالية، ويسمح لك بالحصول على جميع المعلومات حول الصفحة الحالية والصفحات السابقة وعدد الصفحات الموجودة وغير ذلك.
</p>

<p>
	نستخدم <code>{{ request.path }}</code> للحصول على عنوان URL للصفحة الحالية لإنشاء ارتباطات ترقيم الصفحات، ويُعَد ذلك مفيدًا لأنه مستقل عن الكائن الذي نرقّم صفحاته.
</p>

<p>
	توضح لقطة الشاشة الآتية كيف يبدو ترقيم الصفحات؛ فإذا لم تدخِل أكثر من 10 عناوين في قاعدة بياناتك، فيمكنك اختبارها بسهولة أكبر من خلال تقليل العدد المُحدَّد في السطر <code>paginate_by</code> في الملف "catalog/views.py"، فمثلًا يمكنك الحصول على النتيجة التالية من خلال التغيير إلى <code>paginate_by = 2</code>، وتُعرَض ارتباطات ترقيم الصفحات في الجزء السفلي مع عرض ارتباطات next أو previous بناءً على الصفحة الموجود فيها:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="134912" href="https://academy.hsoub.com/uploads/monthly_2023_09/03_book_list_paginated.png.e00f29c768c61876f83d5d0ce1041639.png" rel=""><img alt="03_book_list_paginated.png" class="ipsImage ipsImage_thumbnailed" data-fileid="134912" data-unique="cx9arur42" src="https://academy.hsoub.com/uploads/monthly_2023_09/03_book_list_paginated.png.e00f29c768c61876f83d5d0ce1041639.png"> </a>
</p>

<h2>
	تحدى نفسك
</h2>

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

<ul>
	<li>
		<code>catalog/authors/‎</code>: قائمة جميع المؤلفين.
	</li>
	<li>
		<code>catalog/author/&lt;id&gt;</code>‎: العرض التفصيلي للمؤلف المُحدَّد باستخدام حقل المفتاح الرئيسي الذي يُسمَّى <code>&lt;id&gt;</code>.
	</li>
</ul>

<p>
	يجب أن تكون الشيفرة البرمجية المطلوبة لروابط عناوين URL والعروض متطابقة تقريبًا مع العروض التفصيلية وعروض قائمة <code>Book</code> التي أنشأناها سابقًا، وستكون القوالب مختلفة ولكنها ستشترك في سلوك مماثل.
</p>

<p>
	<strong>ملاحظة</strong>: ستحتاج إلى تحديث ارتباط جميع المؤلفين All authors في القالب الأساسي بعد إنشاء رابط عنوان URL لصفحة قائمة المؤلفين، لذا اتبع العملية نفسها عندما حدّثنا ارتباط جميع الكتب All books. يجب أيضًا تحديث قالب عرض تفاصيل الكتاب ‎/locallibrary/catalog/templates/catalog/book_detail.html بعد إنشاء رابط عنوان URL لصفحة تفاصيل المؤلف، بحيث يؤشّر ارتباط المؤلف إلى صفحة تفاصيل المؤلف الجديدة بدلًا من أن يكون عنوان URL فارغ، والطريقة الموصى بها لذلك هي استدعاء التابع <code>get_absolute_url()‎</code> في نموذج المؤلف كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1409_61" style=""><span class="tag">&lt;p&gt;</span><span class="pln">
  </span><span class="tag">&lt;strong&gt;</span><span class="pln">Author:</span><span class="tag">&lt;/strong&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">"{{ book.author.get_absolute_url }}"</span><span class="tag">&gt;</span><span class="pln">{{ book.author }}</span><span class="tag">&lt;/a&gt;</span><span class="pln">
</span><span class="tag">&lt;/p&gt;</span></pre>

<p>
	يجب بعد ذلك أن تبدو صفحاتك كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="134911" href="https://academy.hsoub.com/uploads/monthly_2023_09/04_author_list_page_no_pagination.png.5dd591f7d87025fc6fe6f23644ea32c1.png" rel=""><img alt="04_author_list_page_no_pagination.png" class="ipsImage ipsImage_thumbnailed" data-fileid="134911" data-unique="vyfxeu9di" src="https://academy.hsoub.com/uploads/monthly_2023_09/04_author_list_page_no_pagination.png.5dd591f7d87025fc6fe6f23644ea32c1.png"> </a>
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="134910" href="https://academy.hsoub.com/uploads/monthly_2023_09/05_author_detail_page_no_pagination.png.8bcef1e8acde6b08093e81dfa382f58a.png" rel=""><img alt="05_author_detail_page_no_pagination.png" class="ipsImage ipsImage_thumbnailed" data-fileid="134910" data-unique="zdhvz021e" src="https://academy.hsoub.com/uploads/monthly_2023_09/05_author_detail_page_no_pagination.png.8bcef1e8acde6b08093e81dfa382f58a.png"> </a>
</p>

<h2>
	الخلاصة
</h2>

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

<p>
	سنوسّع في المقالات القادمة هذه المكتبة لدعم حسابات المستخدمين، وبالتالي سنوضح استيثاق Authentication المستخدمين والأذونات والجلسات والاستمارات.
</p>

<p>
	ترجمة -وبتصرُّف- للمقال <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Generic_views" rel="external nofollow">Django Tutorial Part 6: Generic list and detail views</a>.
</p>

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

<ul>
	<li>
		المقال التالي: <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B3%D8%A7%D8%AF%D8%B3-%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D8%AC%D9%84%D8%B3%D8%A7%D8%AA-sessions-r2118/" rel="">تطبيق عملي لتعلم جانغو - الجزء السادس: إدارة الجلسات Sessions</a>
	</li>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B1%D8%A7%D8%A8%D8%B9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B5%D9%81%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%A7%D9%84%D8%B1%D8%A6%D9%8A%D8%B3%D9%8A%D8%A9-r2106/" rel="">تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية</a>
	</li>
	<li>
		العروض والقوالب <a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D9%84%D8%B9%D8%B1%D9%88%D8%B6-%D9%88%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-%D9%81%D9%8A-django-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-r436/" rel="">الجزء الأول</a> و<a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D9%84%D8%B9%D8%B1%D9%88%D8%B6-%D9%88%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-%D9%81%D9%8A-django-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-r439/" rel="">الجزء الثاني</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%88%D9%8A%D8%A8-%D9%84%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D9%85%D8%B9%D9%84%D9%88%D9%85%D8%A7%D8%AA-%D8%A7%D9%84%D8%B9%D9%85%D9%84%D8%A7%D8%A1-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-%D9%88%D8%B1%D9%8A%D8%A2%D9%83%D8%AA-react-r1776/" rel="">بناء تطبيق ويب لإدارة معلومات العملاء باستخدام جانغو Django وريآكت React</a>
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/topics/class-based-views/generic-display/" rel="external nofollow">العروض المُضمَّنة المُعمَّمة والقائمة على الأصناف</a> (توثيق جانغو)
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/ref/class-based-views/generic-display/" rel="external nofollow">العروض المُعمَّمة</a> (توثيق جانغو)
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/topics/class-based-views/intro/" rel="external nofollow">مدخل إلى العروض المستندة إلى الأصناف</a> (توثيق جانغو)
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/ref/templates/builtins/" rel="external nofollow">وسوم ومرشحات القوالب المُضمَّنة</a> (توثيق جانغو)
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/topics/pagination/" rel="external nofollow">ترقيم الصفحات</a> (توثيق جانغو)
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/topics/db/queries/#related-objects" rel="external nofollow">الكائنات ذات الصلة بإجراء الاستعلامات</a> (توثيق جانغو)
	</li>
</ul>
]]></description><guid isPermaLink="false">2107</guid><pubDate>Sat, 16 Sep 2023 13:05:00 +0000</pubDate></item><item><title>&#x62A;&#x637;&#x628;&#x64A;&#x642; &#x639;&#x645;&#x644;&#x64A; &#x644;&#x62A;&#x639;&#x644;&#x645; &#x62C;&#x627;&#x646;&#x63A;&#x648; - &#x627;&#x644;&#x62C;&#x632;&#x621; &#x627;&#x644;&#x631;&#x627;&#x628;&#x639;: &#x625;&#x646;&#x634;&#x627;&#x621; &#x635;&#x641;&#x62D;&#x629; &#x627;&#x644;&#x645;&#x643;&#x62A;&#x628;&#x629; &#x627;&#x644;&#x631;&#x626;&#x64A;&#x633;&#x64A;&#x629;</title><link>https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B1%D8%A7%D8%A8%D8%B9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B5%D9%81%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%A7%D9%84%D8%B1%D8%A6%D9%8A%D8%B3%D9%8A%D8%A9-r2106/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_09/------------.png.713825e2d211aad8bfb9286e6addcba7.png" /></p>
<p>
	أصبحنا الآن جاهزين لإضافة الشيفرة البرمجية التي تعرض أول صفحة كاملة، وهي الصفحة الرئيسية <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2090/" rel="">لموقع المكتبة المحلية LocalLibrary</a>. ستعرض الصفحة الرئيسية عددًا من سجلاتنا لكل نوع نموذج وتوفر روابط التنقّل الجانبية إلى صفحاتنا الأخرى. سنكتسب في هذا المقال خبرةً عمليةً في كتابة روابط Maps وعروض Views عناوين URL الأساسية للحصول على السجلات من قاعدة البيانات واستخدام القوالب.
</p>

<ul>
	<li>
		<strong>المتطلبات الأساسية</strong>: الاطلاع على مقال <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="">مدخل إلى إطار عمل الويب جانغو</a>، وأكمل موضوعات المقالات السابقة (بما في ذلك مقال <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D9%85%D9%88%D9%82%D8%B9-%D9%85%D8%AF%D9%8A%D8%B1-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-admin-r2105/" rel="">موقع مدير جانغو</a>).
	</li>
	<li>
		<strong>الهدف</strong>: تعلم كيفية إنشاء روابط وعروض بسيطة لعناوين URL (إذ لا توجد بيانات مُشفَّرة في عنوان URL) والحصول على البيانات من النماذج وإنشاء القوالب.
	</li>
</ul>

<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> المراد استخدامها لإعادة تلك الموارد، ثم سننشئ رابط Mapper عنوان URL والعروض والقوالب لعرض الصفحات.
</p>

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

<ul>
	<li>
		روابط عناوين URL لتوجيه عناوين URL المدعومة (وأي معلومات مُشفَّرة في عناوين URL) إلى دوال العرض المناسبة.
	</li>
	<li>
		دوال العرض للحصول على البيانات المطلوبة من النماذج وإنشاء صفحات HTML التي تعرض البيانات وإعادة الصفحات إلى المستخدم لعرضها في المتصفح.
	</li>
	<li>
		القوالب المراد استخدامها عند عرض البيانات في العروض.
	</li>
</ul>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="134909" href="https://academy.hsoub.com/uploads/monthly_2023_09/01_basic-django.png.a270ec5b3225490f8c689aa8bd9f1fd0.png" rel=""><img alt="01_basic-django.png" class="ipsImage ipsImage_thumbnailed" data-fileid="134909" data-unique="nav9f9sc1" src="https://academy.hsoub.com/uploads/monthly_2023_09/01_basic-django.png.a270ec5b3225490f8c689aa8bd9f1fd0.png"> </a>
</p>

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

<p>
	تتألف هذه السلسلة الفرعية من السلسلة الأشمل <a href="https://academy.hsoub.com/tags/%D8%AA%D8%B9%D9%84%D9%85%20%D8%AA%D8%B7%D9%88%D9%8A%D8%B1%20%D8%A7%D9%84%D9%88%D9%8A%D8%A8/" rel="">تعلم تطوير الويب</a> من المقالات التالية:
</p>

<ul>
	<li>
		<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>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%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%AC%D8%A7%D9%86%D8%BA%D9%88-django-r2049/" rel="">إعداد بيئة تطوير تطبيقات جانغو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2090/" rel="">تطبيق عملي لتعلم جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%86%D9%85%D8%A7%D8%B0%D8%AC-models-r2092/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D9%85%D9%88%D9%82%D8%B9-%D9%85%D8%AF%D9%8A%D8%B1-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-admin-r2105/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin</a>
	</li>
	<li>
		<span ipsnoautolink="true">تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية</span>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AE%D8%A7%D9%85%D8%B3-%D8%A7%D9%84%D8%B9%D8%B1%D9%88%D8%B6-views-%D8%A7%D9%84%D8%B9%D8%A7%D9%85%D8%A9-%D9%88%D8%A7%D9%84%D8%AA%D9%81%D8%B5%D9%8A%D9%84%D9%8A%D8%A9-r2107/" rel="">تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B3%D8%A7%D8%AF%D8%B3-%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D8%AC%D9%84%D8%B3%D8%A7%D8%AA-sessions-r2118/" rel="">تطبيق عملي لتعلم جانغو - الجزء السادس: إدارة الجلسات Sessions</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B3%D8%A7%D8%A8%D8%B9-%D8%A7%D8%B3%D8%AA%D9%8A%D8%AB%D8%A7%D9%82-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D9%8A%D9%86-%D9%88%D8%A3%D8%B0%D9%88%D9%86%D8%A7%D8%AA%D9%87%D9%85-r2119/" rel="">تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%85%D9%86-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-forms-r2120/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AA%D8%A7%D8%B3%D8%B9-%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2121/" rel="">تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-10-%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%81%D9%8A-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%A7%D8%AC-r2122/" rel="">تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A3%D9%85%D8%A7%D9%86-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2123/" rel="">تعرف على أمان تطبيقات جانغو</a>
	</li>
</ul>

<h2>
	تعريف مورد عناوين URLs
</h2>

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

<p>
	عناوين URLs التي سنحتاجها لصفحاتنا، هي:
</p>

<ul>
	<li>
		<code>catalog/‎</code>: الصفحة الرئيسية (الفهرس index).
	</li>
	<li>
		<code>catalog/books/‎</code>: قائمة بجميع الكتب.
	</li>
	<li>
		<code>catalog/authors/‎</code>: قائمة بجميع المؤلفين.
	</li>
	<li>
		<code>catalog/book/&lt;id&gt;‎</code>: العرض التفصيلي لكتاب معين مع حقل المفتاح الرئيسي <code>&lt;id&gt;</code> (الافتراضي)، فمثلًا سيكون عنوان URL للكتاب الثالث المُضاف إلى القائمة هو <code>‎/catalog/book/3</code>.
	</li>
	<li>
		<code>catalog/author/&lt;id&gt;‎</code>: العرض التفصيلي للمؤلف المحدد مع حقل المفتاح الرئيسي <code>&lt;id&gt;</code>، فمثلًا سيكون عنوان URL الخاص بالمؤلف الحادي عشر المُضاف إلى القائمة هو <code>‎/catalog/author/11</code>.
	</li>
</ul>

<p>
	ستعرض عناوين URLs الثلاثة الأولى صفحة الفهرس وقائمة الكتب وقائمة المؤلفين. لا تشفّر عناوين URLs هذه أيّ معلومات إضافية، وستبقى الاستعلامات التي تجلب البيانات من <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>
	بينما سيعرض عنوانا URL الأخيرين معلومات مُفصَّلة حول كتاب أو مؤلف معين. تشفّر عناوين URLs هذه هوية العنصر المُراد عرضه (يمثله <code>&lt;id&gt;</code>). سيستخرج رابط عنوان URL المعلومات المُشفَّرة ويمررها إلى العرض، وسيحدد العرض ديناميكيًا المعلومات التي سنحصل عليها من قاعدة البيانات، وسنستخدم مجموعة واحدة من ربط عنوان URL والعرض والقالب للتعامل مع جميع الكتب (أو المؤلفين) من خلال تشفير المعلومات في عنوان URL.
</p>

<p>
	<strong>ملاحظة</strong>: يمكنك باستخدام جانغو بناء عناوين URLs كما تشاء، إذ يمكنك تشفير المعلومات في متن عنوان URL كما هو موضح سابقًا، أو تضمين معاملات <code>GET</code> في عنوان URL مثل <code>‎/book/?id=6</code>. يجب أن تبقى عناوين URLs نظيفة ومنطقية وقابلة للقراءة على النحو الذي <a href="https://www.w3.org/Provider/Style/URI" rel="external nofollow">توصي به منظمة W3C</a> بغض النظر عن الطريقة التي تستخدمها. يوصي توثيق جانغو بتشفير المعلومات في متن عنوان URL لتحقيق تصميم أفضل لعنوان URL.
</p>

<p>
	سنوضح في باقي هذا المقال كيفية إنشاء صفحة الفهرس أو الصفحة الرئيسية.
</p>

<h2>
	إنشاء صفحة الفهرس
</h2>

<p>
	الصفحة الأولى التي سننشئها هي صفحة الفهرس (<code>catalog/‎</code>)، إذ ستتضمن صفحة الفهرس بعض شيفرة HTML الثابتة إلى جانب "عدادات" ناتجة عن السجلات المختلفة في قاعدة البيانات. يمكن إنجاز ذلك من خلال إنشاء ربطٍ لعناوين URLs وعرض وقالب.
</p>

<p>
	<strong>ملاحظة</strong>: يستحق الأمر مزيدًا من الاهتمام في هذا القسم، إذ تنطبق معظم هذه المعلومات على الصفحات الأخرى التي سننشئها.
</p>

<h3>
	ربط Mapping عناوين URLs
</h3>

<p>
	حدّثنا الملف locallibrary/urls.py عندما أنشأنا <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2090/" rel="">موقع الويب الهيكلي</a> للتأكد من أنه كلما اُستلِم عنوان URL له البادئة <code>catalog/‎</code>، فسيعالج الوحدة <code>catalog.urls</code> من النوع URLConf السلسلة الفرعية المتبقية.
</p>

<p>
	يتضمن جزء الشيفرة البرمجية التالي من الملف locallibrary/urls.py الوحدة <code>catalog.urls</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9239_6" style=""><span class="pln">urlpatterns </span><span class="pun">+=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    path</span><span class="pun">(</span><span class="str">'catalog/'</span><span class="pun">,</span><span class="pln"> include</span><span class="pun">(</span><span class="str">'catalog.urls'</span><span class="pun">)),</span><span class="pln">
</span><span class="pun">]</span></pre>

<p>
	<strong>ملاحظة</strong>: إذا قابل جانغو دالة الاستيراد <code>django.urls.include()‎</code>، فسيقسم سلسلة عنوان URL عند المحرف النهائي المحدد ويرسل السلسلة الفرعية المتبقية إلى وحدة URLconf المُضمَّنة لمزيدٍ من المعالجة.
</p>

<p>
	أنشأنا أيضًا ملفًا بديلًا لوحدة URLConf بالاسم <code>‎/catalog/urls.py</code>، لذا أضِف الأسطر التالية إلى هذا الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9239_8" style=""><span class="pln">urlpatterns </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    path</span><span class="pun">(</span><span class="str">''</span><span class="pun">,</span><span class="pln"> views</span><span class="pun">.</span><span class="pln">index</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">=</span><span class="str">'index'</span><span class="pun">),</span><span class="pln">
</span><span class="pun">]</span></pre>

<p>
	تعرّف الدالة <code>path()‎</code> ما يلي:
</p>

<ul>
	<li>
		نمط عنوان URL وهو سلسلة نصية فارغة: <code>''</code> (سنناقش أنماط URL بالتفصيل عند العمل على العروض Views الأخرى).
	</li>
	<li>
		دالة عرض ستُستدعَى عند اكتشاف نمط URL: هي الدالة <code>views.index</code> التي تكون في الملف views.py بالاسم <code>index()‎</code>.
	</li>
</ul>

<p>
	تحدد الدالة <code>path()‎</code> أيضًا المعامل <code>name</code>، وهو معرّف فريد لهذا الربط لعنوان URL. يمكنك استخدام الاسم لعكس الرابط Mapper، أي لإنشاء عنوان URL ديناميكيًا الذي يؤشّر إلى المورد الذي صُمِّم الرابط للتعامل معه، فمثلًا يمكننا استخدام معامل الاسم للربط بصفحتنا الرئيسية من أيّ صفحة أخرى من خلال إضافة الارتباط التالي في القالب:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9239_10" style=""><span class="pun">&lt;</span><span class="pln">a href</span><span class="pun">=</span><span class="str">"{% url 'index' %}"</span><span class="pun">&gt;</span><span class="typ">Home</span><span class="pun">&lt;/</span><span class="pln">a</span><span class="pun">&gt;.</span></pre>

<p>
	<strong>ملاحظة</strong>: يمكننا كتابة الارتباط بصورة ثابتة كما هو الحال في <code>&lt;a href="/catalog/"&gt;Home&lt;/a&gt;</code>، ولكن إذا غيّرنا نمط صفحتنا الرئيسية إلى <code>‎/catalog/index</code> مثلًا، فلن تُربَط القوالب بصورة صحيحة، لذا يُعَد استخدام ربط عنوان URL المعكوس أفضل.
</p>

<h3>
	العرض القائم على الدوال
</h3>

<p>
	يُعَد <a href="https://academy.hsoub.com/programming/python/django/%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%A9-%D8%B7%D9%84%D8%A8%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D8%B9%D8%A8%D8%B1-%D8%A7%D9%84%D8%B9%D8%B1%D9%88%D8%B6-views-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r1629/" rel="">العرض View</a> دالةً تعالج طلب HTTP، وتجلب البيانات المطلوبة من قاعدة البيانات، وتعرض البيانات في صفحة HTML باستخدام قالب HTML، ثم تعيد صفحة HTML المُنشَأة في <a href="https://academy.hsoub.com/programming/general/%D8%B1%D9%85%D9%88%D8%B2-%D8%A7%D9%84%D8%A5%D8%AC%D8%A7%D8%A8%D8%A9-%D9%81%D9%8A-http-r75/" rel="">استجابة HTTP</a> لعرض الصفحة للمستخدم. يتّبع عرض الفهرس هذا النموذج، إذ يجلب معلومات حول عدد سجلات <code>Book</code> و <code>BookInstance</code> و <code>Author</code> سجلات <code>BookInstance</code> المتوفرة في قاعدة البيانات، وتمرر هذه المعلومات إلى قالب لعرضها.
</p>

<p>
	افتح الملف catalog/views.py ولاحظ أن الملف يستورد دالة <a href="https://docs.djangoproject.com/en/4.0/topics/http/shortcuts/#django.shortcuts.render" rel="external nofollow"><code>render()‎</code></a> المُختصَرة لإنشاء ملف HTML باستخدام قالب وبيانات:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9239_12" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">shortcuts </span><span class="kwd">import</span><span class="pln"> render

</span><span class="com"># أنشئ عروضك هنا</span></pre>

<p>
	الصق الأسطر التالية في نهاية الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9239_14" style=""><span class="kwd">from</span><span class="pln"> </span><span class="pun">.</span><span class="pln">models </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Book</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Author</span><span class="pun">,</span><span class="pln"> </span><span class="typ">BookInstance</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Genre</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> index</span><span class="pun">(</span><span class="pln">request</span><span class="pun">):</span><span class="pln">
    </span><span class="str">"""View function for home page of site."""</span><span class="pln">

    </span><span class="com"># توليد عدّادات من بعض الكائنات الرئيسية</span><span class="pln">
    num_books </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Book</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">all</span><span class="pun">().</span><span class="pln">count</span><span class="pun">()</span><span class="pln">
    num_instances </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BookInstance</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">all</span><span class="pun">().</span><span class="pln">count</span><span class="pun">()</span><span class="pln">

    </span><span class="com"># ‫الكتب المتوفرة (status = 'a'‎)</span><span class="pln">
    num_instances_available </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BookInstance</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">status__exact</span><span class="pun">=</span><span class="str">'a'</span><span class="pun">).</span><span class="pln">count</span><span class="pun">()</span><span class="pln">

    </span><span class="com"># تُضمَّن‫ 'all()‎' افتراضيًا</span><span class="pln">
    num_authors </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Author</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">count</span><span class="pun">()</span><span class="pln">

    context </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="str">'num_books'</span><span class="pun">:</span><span class="pln"> num_books</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'num_instances'</span><span class="pun">:</span><span class="pln"> num_instances</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'num_instances_available'</span><span class="pun">:</span><span class="pln"> num_instances_available</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'num_authors'</span><span class="pun">:</span><span class="pln"> num_authors</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="com"># ‫عرض قالب HTML وهو index.html مع البيانات الموجودة في متغير السياق</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> render</span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> </span><span class="str">'index.html'</span><span class="pun">,</span><span class="pln"> context</span><span class="pun">=</span><span class="pln">context</span><span class="pun">)</span></pre>

<p>
	يستورد السطر الأول أصناف النموذج التي سنستخدمها للوصول إلى البيانات في جميع عروضنا، إذ يجلب الجزء الأول من دالة العرض عدد السجلات باستخدام السمة <code>objects.all()‎</code> في أصناف النموذج، ويحصل على قائمة بكائنات <code>BookInstance</code> التي لها القيمة "a" (متاح Available) في حقل الحالة status. يمكنك العثور على مزيد من المعلومات حول كيفية الوصول إلى بيانات النموذج في قسم البحث عن السجلات من مقال <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%86%D9%85%D8%A7%D8%B0%D8%AC-models-r2092/" rel="">استخدام النماذج</a>.
</p>

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

<ul>
	<li>
		كائن الطلب <code>request</code> الأصلي وهو <code>HttpRequest</code>.
	</li>
	<li>
		قالب HTML مع عناصر بديلة للبيانات.
	</li>
	<li>
		متغير <code>context</code> وهو قاموس بايثون الذي يحتوي على البيانات المطلوب إدخالها في العناصر البديلة.
	</li>
</ul>

<p>
	سنتحدث أكثر عن القوالب ومتغير <code>context</code> في القسم التالي، ولنبدأ الآن في إنشاء قالبنا لنتمكّن من عرض شيء ما للمستخدم.
</p>

<h3>
	القالب Template
</h3>

<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="">صفحة HTML</a>، ويستخدم عناصر بديلة لتمثيل المحتوى الفعلي. سيبحث تطبيق جانغو الذي أنشأه تطبيق البدء <strong>Startapp</strong> (مثل التطبيق الهيكلي) عن قوالب في مجلد فرعي له الاسم <strong>'templates'</strong> لتطبيقاتك، فمثلًا ستتوقّع الدالة <code>render()‎</code> في عرض الفهرس الذي أضفناه للتو العثور على الملف index.html في المجلد /catalog/templates/ وستصدِر خطأً إذا لم يكن الملف موجودًا.
</p>

<p>
	يمكنك التحقق من ذلك من خلال حفظ التغييرات السابقة والوصول إلى العنوان "127.0.0.1:8000" في متصفحك، حيث ستظهر رسالة خطأ بديهية إلى حدٍ ما هي "TemplateDoesNotExist at /catalog/‎" وتفاصيل أخرى.
</p>

<p>
	<strong>ملاحظة</strong>: سيبحث جانغو عن قوالب في عدد من الأماكن بناءً على ملف إعدادات مشروعك، وسيبحث في التطبيقات المُثبَّتة افتراضيًا. يمكنك معرفة المزيد حول كيفية عثور جانغو على القوالب وتنسيقات القوالب التي يدعمها في <a href="https://docs.djangoproject.com/en/4.0/topics/templates/" rel="external nofollow">قسم القوالب من توثيق جانغو</a>.
</p>

<h4>
	توسيع القوالب
</h4>

<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> لقسم الرأس والمتن مع أقسام التنقل للربط بالصفحات الأخرى من الموقع (التي لم ننشئها بعد) والأقسام التي تعرض نصًا تقديميًا وبيانات الكتاب.
</p>

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

<p>
	يُعَد جزء الشيفرة البرمجية الآتية نموذجًا للقالب الأساسي من الملف base_generic.html، وسننشئ قالبًا لموقع المكتبة المحلية LocalLibrary قريبًا. يشتمل النموذج الآتي على شيفرة HTML مشتركة لأقسام العنوان والشريط الجانبي والمحتويات الرئيسية المميزة بوسوم القالب المُسمَّاة <code>block</code> و <code>endblock</code>. يمكنك ترك الكتل Blocks فارغة أو تضمين محتوًى افتراضي لاستخدامه عند عرض الصفحات المشتقة من القالب.
</p>

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

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_9239_16" 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">
  {% block title %}</span><span class="tag">&lt;title&gt;</span><span class="pln">Local Library</span><span class="tag">&lt;/title&gt;</span><span class="pln">{% endblock %}
</span><span class="tag">&lt;/head&gt;</span><span class="pln">
</span><span class="tag">&lt;body&gt;</span><span class="pln">
  {% block sidebar %}</span><span class="com">&lt;!-- أدخِل نص التنقل الافتراضي لكل صفحة --&gt;</span><span class="pln">{% endblock %}
  {% block content %}</span><span class="com">&lt;!-- نص المحتوى الافتراضي، ويكون فارغ عادة --&gt;</span><span class="pln">{% endblock %}
</span><span class="tag">&lt;/body&gt;</span><span class="pln">
</span><span class="tag">&lt;/html&gt;</span></pre>

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

<p>
	يوضح جزء الشيفرة البرمجية الآتي كيفية استخدام وسم القالب <code>extends</code> وتعديل الكتلة <code>content</code>. ستتضمن شيفرة HTML الشيفرة البرمجية والبنية المُعرَّفة في القالب الأساسي بما في ذلك المحتوى الافتراضي الذي عرّفته في الكتلة <code>title</code>، ولكن توضَع الكتلة <code>content</code> الجديدة مكان الكتلة الافتراضية.
</p>

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

{% block content %}
  </span><span class="tag">&lt;h1&gt;</span><span class="pln">Local Library Home</span><span class="tag">&lt;/h1&gt;</span><span class="pln">
  </span><span class="tag">&lt;p&gt;</span><span class="pln">Welcome to LocalLibrary, a website developed by </span><span class="tag">&lt;em&gt;</span><span class="pln">Mozilla Developer Network</span><span class="tag">&lt;/em&gt;</span><span class="pln">!</span><span class="tag">&lt;/p&gt;</span><span class="pln">
{% endblock %}</span></pre>

<h4>
	القالب الأساسي لموقع المكتبة المحلية LocalLibrary
</h4>

<p>
	سنستخدم جزء الشيفرة الآتي بمثابة قالب أساسي لموقع المكتبة المحلية LocalLibrary، إذ يحتوي هذا الجزء على بعض شيفرة HTML ويعرّف كتل <code>title</code> و <code>sidebar</code> و <code>content</code>. لدينا عنوان افتراضي وشريط جانبي افتراضي يحتوي روابطًا لقوائم جميع الكتب والمؤلفين، وكلاهما مُضمَّن في كتل يمكن تغييرها بسهولة لاحقًا.
</p>

<p>
	<strong>ملاحظة</strong>: سنشرح أيضًا وسمي قالب إضافيين هما: <code>url</code> و <code>load static</code> في الأقسام اللاحقة.
</p>

<p>
	أنشئ ملفًا جديدًا هو base_generic.html في المجلد /locallibrary/catalog/templates/ والصق الشيفرة البرمجية التالية في هذا الملف:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_9239_20" 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">
    {% block title %}
      </span><span class="tag">&lt;title&gt;</span><span class="pln">Local Library</span><span class="tag">&lt;/title&gt;</span><span class="pln">
    {% endblock %}
    </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="pln"> </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"</span><span class="pln"> </span><span class="tag">/&gt;</span><span class="pln">
    </span><span class="tag">&lt;link</span><span class="pln">
      </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"</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">integrity</span><span class="pun">=</span><span class="atv">"sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"</span><span class="pln">
      </span><span class="atn">crossorigin</span><span class="pun">=</span><span class="atv">"anonymous"</span><span class="pln"> </span><span class="tag">/&gt;</span><span class="pln">
  ‏&lt;-- ضِف شيفرة‫ CSS إضافية في ملف ثابت --!&gt;
   {% load static %}
    </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">"{% static 'css/styles.css' %}"</span><span class="pln"> </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;body&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-fluid"</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">"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-sm-2"</span><span class="tag">&gt;</span><span class="pln">
          {% block sidebar %}
            </span><span class="tag">&lt;ul</span><span class="pln"> </span><span class="atn">class</span><span class="pun">=</span><span class="atv">"sidebar-nav"</span><span class="tag">&gt;</span><span class="pln">
              </span><span class="tag">&lt;li&gt;&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{% url 'index' %}"</span><span class="tag">&gt;</span><span class="pln">Home</span><span class="tag">&lt;/a&gt;&lt;/li&gt;</span><span class="pln">
              </span><span class="tag">&lt;li&gt;&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">All books</span><span class="tag">&lt;/a&gt;&lt;/li&gt;</span><span class="pln">
              </span><span class="tag">&lt;li&gt;&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">All authors</span><span class="tag">&lt;/a&gt;&lt;/li&gt;</span><span class="pln">
            </span><span class="tag">&lt;/ul&gt;</span><span class="pln">
          {% endblock %}
        </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-sm-10 "</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;/div&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>
	يتضمن القالب <a href="https://academy.hsoub.com/programming/css/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A8%D9%86%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-css-r1879/" rel="">شيفرة CSS</a> من إطار عمل بوتستراب <a href="https://wiki.hsoub.com/Bootstrap" rel="external">Bootstrap</a> لتحسين تخطيط وعرض صفحة HTML؛ إذ يُعَد استخدام بوتستراب -أو إطار عمل ويب آخر من طرف العميل- طريقةً سريعةً لإنشاء صفحة جذابة تُعرَض بصورة جيدة على أحجام الشاشات المختلفة.
</p>

<p>
	يشير القالب الأساسي إلى ملف CSS محلي (styles.css) يوفر تنسيقًا إضافيًا. أنشئ ملف styles.css في المجلد /locallibrary/catalog/static/css/ والصق الشيفرة البرمجية التالية في الملف:
</p>

<pre class="ipsCode prettyprint lang-css prettyprinted" id="ips_uid_9239_22" style=""><span class="pun">.</span><span class="pln">sidebar-nav </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">margin-top</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">padding</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">list-style</span><span class="pun">:</span><span class="pln"> none</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<h4>
	قالب الفهرس
</h4>

<p>
	أنشئ ملف HTML جديد بالاسم index.html في المجلد /locallibrary/catalog/templates/ والصق الشيفرة البرمجية التالية فيه، إذ توسّع هذه الشيفرة البرمجية القالب الأساسي في السطر الأول، ثم تستبدل كتلة <code>content</code> الافتراضية في هذا القالب:
</p>

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

{% block content %}
  </span><span class="tag">&lt;h1&gt;</span><span class="pln">Local Library Home</span><span class="tag">&lt;/h1&gt;</span><span class="pln">
  </span><span class="tag">&lt;p&gt;</span><span class="pln">
    Welcome to LocalLibrary, a website developed by
    </span><span class="tag">&lt;em&gt;</span><span class="pln">Mozilla Developer Network</span><span class="tag">&lt;/em&gt;</span><span class="pln">!
  </span><span class="tag">&lt;/p&gt;</span><span class="pln">
  </span><span class="tag">&lt;h2&gt;</span><span class="pln">Dynamic content</span><span class="tag">&lt;/h2&gt;</span><span class="pln">
  </span><span class="tag">&lt;p&gt;</span><span class="pln">The library has the following record counts:</span><span class="tag">&lt;/p&gt;</span><span class="pln">
  </span><span class="tag">&lt;ul&gt;</span><span class="pln">
    </span><span class="tag">&lt;li&gt;&lt;strong&gt;</span><span class="pln">Books:</span><span class="tag">&lt;/strong&gt;</span><span class="pln"> {{ num_books }}</span><span class="tag">&lt;/li&gt;</span><span class="pln">
    </span><span class="tag">&lt;li&gt;&lt;strong&gt;</span><span class="pln">Copies:</span><span class="tag">&lt;/strong&gt;</span><span class="pln"> {{ num_instances }}</span><span class="tag">&lt;/li&gt;</span><span class="pln">
    </span><span class="tag">&lt;li&gt;&lt;strong&gt;</span><span class="pln">Copies available:</span><span class="tag">&lt;/strong&gt;</span><span class="pln"> {{ num_instances_available }}</span><span class="tag">&lt;/li&gt;</span><span class="pln">
    </span><span class="tag">&lt;li&gt;&lt;strong&gt;</span><span class="pln">Authors:</span><span class="tag">&lt;/strong&gt;</span><span class="pln"> {{ num_authors }}</span><span class="tag">&lt;/li&gt;</span><span class="pln">
  </span><span class="tag">&lt;/ul&gt;</span><span class="pln">
{% endblock %}</span></pre>

<p>
	نصرّح في قسم المحتوى الديناميكي Dynamic content عن العناصر البديلة، أي متغيرات القالب للمعلومات الواردة من العرض الذي نريد تضمينها، إذ تكون المتغيرات محاطة بأقواس معقوصة مزدوجة Handlebars.
</p>

<p>
	<strong>ملاحظة</strong>: يمكنك التعرف بسهولة على متغيرات القالب ووسوم القالب (الدوال)، إذ تكون المتغيرات محاطةً بأقواس معقوصة مزدوجة <code>{{ num_books }}</code>، وتكون الوسوم محاطة بأقواس معقوصة مفردة مع إشارات النسبة المئوية <code>{% extends "base_generic.html" %}</code>.
</p>

<p>
	الشيء المهم الذي يجب ملاحظته هنا هو تسمية المتغيرات بالمفاتيح Keys التي نمررها في قاموس <code>context</code> ضمن الدالة <code>render()‎</code> الخاصة بالعرض (اطلع على النموذج التالي)، وسنستبدل المتغيرات بالقيم المرتبطة بها عند عرض القالب:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9239_26" style=""><span class="pln">context </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">'num_books'</span><span class="pun">:</span><span class="pln"> num_books</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'num_instances'</span><span class="pun">:</span><span class="pln"> num_instances</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'num_instances_available'</span><span class="pun">:</span><span class="pln"> num_instances_available</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'num_authors'</span><span class="pun">:</span><span class="pln"> num_authors</span><span class="pun">,</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">return</span><span class="pln"> render</span><span class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> </span><span class="str">'index.html'</span><span class="pun">,</span><span class="pln"> context</span><span class="pun">=</span><span class="pln">context</span><span class="pun">)</span></pre>

<h4>
	الإشارة إلى الملفات الثابتة في القوالب
</h4>

<p>
	يُحتمَل أن يستخدم مشروعك مواردًا ثابتة مثل <a href="https://academy.hsoub.com/questions/24727-%D8%B1%D8%A8%D8%B7-%D9%85%D9%84%D9%81-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-%D9%81%D9%8A-html-%D9%88%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D8%A7%D9%84%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-%D8%A8%D8%B4%D9%83%D9%84-%D8%B5%D8%AD%D9%8A%D8%AD/" rel="">ملفات جافاسكربت</a> و CSS والصور، ويمكن أن يكون موقع هذه الملفات غير معروف (أو يمكن أن يتغير)، لذا يتيح لك جانغو تحديد الموقع في قوالبك المتعلقة بالإعداد العام <code>STATIC_URL</code>. يضبط موقع الويب الهيكلي الافتراضي الإعداد <code>STATIC_URL</code> على القيمة <code>/static/</code>، ولكن يمكنك اختيار استضافتها على شبكة توصيل المحتوى أو أيّ مكان آخر.
</p>

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

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_9239_28" style=""><span class="pln">‏&lt;-- إضافة شيفرة‫ CSS إضافية في ملف ثابت --!&gt;
{% load static %}
</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">"{% static 'css/styles.css' %}"</span><span class="pln"> </span><span class="tag">/&gt;</span></pre>

<p>
	يمكنك إضافة صورة إلى الصفحة بطريقة مماثلة كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_9239_30" style=""><span class="pln">{% load static %}
</span><span class="tag">&lt;img</span><span class="pln">
  </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"{% static 'catalog/images/local_library_model_uml.png' %}"</span><span class="pln">
  </span><span class="atn">alt</span><span class="pun">=</span><span class="atv">"UML diagram"</span><span class="pln">
  </span><span class="atn">style</span><span class="pun">=</span><span class="atv">"</span><span class="kwd">width</span><span class="pun">:</span><span class="lit">555px</span><span class="pun">;</span><span class="kwd">height</span><span class="pun">:</span><span class="lit">540px</span><span class="pun">;</span><span class="atv">"</span><span class="pln"> </span><span class="tag">/&gt;</span></pre>

<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> الخاص بالتطوير لتخديم الملفات من خلال تعديل رابط عنوان URL العام (‎/locallibrary/locallibrary/urls.py) عندما أنشأنا موقع الويب الهيكلي، ولكننا ما زلنا بحاجة إلى تفعيل تخديم الملفات في بيئة الإنتاج، إذ سنوضح ذلك لاحقًا.
</p>

<p>
	اطّلع على <a href="https://docs.djangoproject.com/en/4.0/howto/static-files/" rel="external nofollow">إدارة الملفات الثابتة</a> في توثيق جانغو لمزيد من المعلومات.
</p>

<h4>
	الارتباط بعناوين URLs
</h4>

<p>
	قدّم القالب الأساسي السابق وسم القالب <code>url</code>.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_9239_32" style=""><span class="tag">&lt;li&gt;&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{% url 'index' %}"</span><span class="tag">&gt;</span><span class="pln">Home</span><span class="tag">&lt;/a&gt;&lt;/li&gt;</span></pre>

<p>
	يقبل هذا الوسم اسم الدالة <code>path()‎</code> المُستدعاة في الملف urls.py وقيم أيّ وسائط يتلقّاها العرض من هذه الدالة، ويعيد عنوان URL الذي يمكنك استخدامه للارتباط بالمورد.
</p>

<h4>
	ضبط مكان العثور على القوالب
</h4>

<p>
	يُحدَّد الموقع الذي يبحث فيه جانغو عن القوالب في الكائن <code>TEMPLATES</code> ضمن الملف settings.py، إذ يبدو هذا الملف افتراضيًا (كما أُنشِئ لهذه السلسلة من المقالات) كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9239_34" style=""><span class="pln">TEMPLATES </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="str">'BACKEND'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'django.template.backends.django.DjangoTemplates'</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'DIRS'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[],</span><span class="pln">
        </span><span class="str">'APP_DIRS'</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">'OPTIONS'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="str">'context_processors'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
                </span><span class="str">'django.template.context_processors.debug'</span><span class="pun">,</span><span class="pln">
                </span><span class="str">'django.template.context_processors.request'</span><span class="pun">,</span><span class="pln">
                </span><span class="str">'django.contrib.auth.context_processors.auth'</span><span class="pun">,</span><span class="pln">
                </span><span class="str">'django.contrib.messages.context_processors.messages'</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">
</span><span class="pun">]</span></pre>

<p>
	يُعَد إعداد <code>‎'APP_DIRS': True</code> الأكثر أهمية من بين القوالب المُسمَّاة، لأنه يخبر جانغو بالبحث عن قوالب في مجلد فرعي لكل تطبيق في المشروع، مما يسهّل تجميع القوالب مع التطبيق المرتبط بها لسهولة إعادة الاستخدام. يمكننا أيضًا تحديد مواقع محددة لجانغو للبحث عن المجلدات باستخدام <code>‎'DIRS': []‎</code> لكنه ليس مطلوبًا حاليًا.
</p>

<p>
	<strong>ملاحظة</strong>: يمكنك معرفة المزيد حول كيفية عثور جانغو على القوالب وتنسيقات القوالب التي يدعمها في <a href="https://docs.djangoproject.com/en/4.0/topics/templates/" rel="external nofollow">قسم القوالب ضمن توثيق جانغو</a>.
</p>

<h2>
	كيف تبدو صفحة موقعنا الرئيسية؟
</h2>

<p>
	أنشأنا حتى الآن جميع الموارد المطلوبة لعرض صفحة الفهرس. شغّل الخادم باستخدام الأمر <code>python3 manage.py runserver</code> وافتح العنوان "http://127.0.0.1:8000/‎" في متصفحك. إذا جرى ضبط كل شيء بصورة صحيحة، فيجب أن يبدو موقعك كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="134908" href="https://academy.hsoub.com/uploads/monthly_2023_09/02_index_page_ok.png.fdcb837026d225437ceca22597a26e0f.png" rel=""><img alt="02_index_page_ok.png" class="ipsImage ipsImage_thumbnailed" data-fileid="134908" data-unique="oss0jsz8s" src="https://academy.hsoub.com/uploads/monthly_2023_09/02_index_page_ok.png.fdcb837026d225437ceca22597a26e0f.png"> </a>
</p>

<p>
	<strong>ملاحظة</strong>: لن تعمل ارتباطات جميع الكتب <strong>All books</strong> وجميع المؤلفين <strong>All authors</strong> حاليًا، لأن المسارات والعروض والقوالب الخاصة بهذه الصفحات لم تُعرَّف بعد، فقد أدخلنا فقط عناصرًا بديلة لتلك الارتباطات في القالب <code>base_generic.html</code>.
</p>

<h2>
	تحدى نفسك
</h2>

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

<p>
	أولًا، يتضمن القالب الأساسي لموقع المكتبة المحلية LocalLibrary الكتلة <code>title</code>. عدّل هذه الكتلة في قالب الفهرس وأنشئ عنوانًا جديدًا للصفحة.
</p>

<p>
	<strong>ملاحظة</strong>: يوضح القسم "توسيع القوالب" في هذا المقال كيفية إنشاء كتل وتوسيعها في قالب آخر.
</p>

<p>
	ثانيًا، عدّل العرض لتوليد أعداد من أنواع الكتب genres والكتب books التي تحتوي على كلمة معينة (غير حساسة لحالة الأحرف)، ومرّر النتائج إلى <code>context</code>. يمكنك تحقيق ذلك بطريقة مماثلة لإنشاء واستخدام المتغيرات <code>num_books</code> و<code>num_instances_available</code>، ثم حدّث قالب الفهرس لتضمين هذه المتغيرات.
</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-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AA%D8%B9%D9%84%D9%8A%D9%85%D8%A7%D8%AA-html-r1894/" rel="">صفحة HTML</a> تعرض عددًا من السجلات من قاعدة البيانات وارتباطات لصفحات أخرى لم تُنشَأ بعد. تعلّمنا المعلومات الأساسية حول روابط Mappers عناوين URLs والعروض، والاستعلام في قاعدة البيانات باستخدام النماذج، وتمرير المعلومات إلى القالب من العرض، وإنشاء القوالب وتوسيعها. سننشئ في المقال التالي الصفحات الأربع المتبقية من موقعنا بناءً على هذه المعرفة.
</p>

<p>
	ترجمة -وبتصرُّف- للمقال <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Home_page" rel="external nofollow">Django Tutorial Part 5: Creating our home page</a>.
</p>

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

<ul>
	<li>
		المقال التالي: <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AE%D8%A7%D9%85%D8%B3-%D8%A7%D9%84%D8%B9%D8%B1%D9%88%D8%B6-views-%D8%A7%D9%84%D8%B9%D8%A7%D9%85%D8%A9-%D9%88%D8%A7%D9%84%D8%AA%D9%81%D8%B5%D9%8A%D9%84%D9%8A%D8%A9-r2107/" rel="">تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية</a>
	</li>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D9%85%D9%88%D9%82%D8%B9-%D9%85%D8%AF%D9%8A%D8%B1-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-admin-r2105/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin</a>
	</li>
	<li>
		العروض والقوالب <a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D9%84%D8%B9%D8%B1%D9%88%D8%B6-%D9%88%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-%D9%81%D9%8A-django-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-r436/" rel="">الجزء الأول</a> و<a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D9%84%D8%B9%D8%B1%D9%88%D8%B6-%D9%88%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-%D9%81%D9%8A-django-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-r439/" rel="">الجزء الثاني</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D9%84%D8%A8%D8%AF%D8%A1-%D9%85%D8%B9-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%84%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-r1625/" rel="">البدء مع إطار العمل جانغو لإنشاء تطبيق ويب</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%85%D9%87%D8%A7%D9%85-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-%D9%88%D8%B1%D9%8A%D8%A2%D9%83%D8%AA-react-r1773/" rel="">بناء تطبيق مهام باستخدام جانغو Django وريآكت React</a>
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/topics/http/urls/" rel="external nofollow">مرسل Dispatcher عناوين URL</a> (توثيق جانغو)
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/topics/http/views/" rel="external nofollow">دوال العرض</a> (توثيق جانغو)
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/topics/templates/" rel="external nofollow">القوالب</a> (توثيق جانغو)
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/howto/static-files/" rel="external nofollow">إدارة الملفات الثابتة</a> (توثيق جانغو)
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/topics/http/shortcuts/#django.shortcuts.render" rel="external nofollow">دوال جانغو المُختصَرة</a> (توثيق جانغو)
	</li>
</ul>
]]></description><guid isPermaLink="false">2106</guid><pubDate>Fri, 08 Sep 2023 13:00:00 +0000</pubDate></item><item><title>&#x62A;&#x637;&#x628;&#x64A;&#x642; &#x639;&#x645;&#x644;&#x64A; &#x644;&#x62A;&#x639;&#x644;&#x645; &#x62C;&#x627;&#x646;&#x63A;&#x648; - &#x627;&#x644;&#x62C;&#x632;&#x621; &#x627;&#x644;&#x62B;&#x627;&#x644;&#x62B;: &#x645;&#x648;&#x642;&#x639; &#x645;&#x62F;&#x64A;&#x631; &#x62C;&#x627;&#x646;&#x63A;&#x648; Django Admin</title><link>https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D9%85%D9%88%D9%82%D8%B9-%D9%85%D8%AF%D9%8A%D8%B1-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-admin-r2105/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_09/5.png.9aeb7e693c743e992b69d8f35c8cedbd.png" /></p>
<p>
	أنشأنا نماذجًا لموقع المكتبة المحلية LocalLibrary، وسنستخدم الآن موقع مدير جانغو Django Admin لإضافة بعض بيانات الكتب الحقيقية. سنوضّح أولًا كيفية تسجيل النماذج في موقع المدير، ثم سنوضّح كيفية تسجيل الدخول وإنشاء بعض البيانات، وسنعرض في نهاية المقال بعض الطرق التي يمكنك من خلالها تحسين عرض موقع المدير.
</p>

<ul>
	<li>
		<p>
			<strong>المتطلبات الأساسية</strong>: أكمل أولًا مقال <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%86%D9%85%D8%A7%D8%B0%D8%AC-models-r2092/" rel="">استخدام النماذج Models</a>.
		</p>
	</li>
	<li>
		<p>
			<strong>الهدف</strong>: فهم فوائد وقيود موقع مدير جانغو واستخدامه لإنشاء بعض السجلات للنماذج.
		</p>
	</li>
</ul>

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

<p>
	أُجرِي كل الإعداد المطلوب لتضمين تطبيق المدير في موقع الويب تلقائيًا عندما <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2090/" rel="">أنشأتَ المشروع الهيكلي</a> (اطلع على <a href="https://docs.djangoproject.com/en/4.0/ref/contrib/admin/" rel="external nofollow">توثيق جانغو</a> للحصول على معلومات حول الاعتماديات Dependencies الفعلية المطلوبة)، فكل ما <strong>يجب عليك فعله</strong> لإضافة نماذجك إلى تطبيق المدير هو تسجيلها فقط. سنقدم في نهاية هذا المقال عرضًا موجزًا لكيفية إعداد منطقة المدير لعرض بيانات نموذجنا بصورة أفضل.
</p>

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

<p>
	تتألف هذه السلسلة الفرعية من السلسلة الأشمل <a href="https://academy.hsoub.com/tags/%D8%AA%D8%B9%D9%84%D9%85%20%D8%AA%D8%B7%D9%88%D9%8A%D8%B1%20%D8%A7%D9%84%D9%88%D9%8A%D8%A8/" rel="">تعلم تطوير الويب</a> من المقالات التالية:
</p>

<ul>
	<li>
		<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>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%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%AC%D8%A7%D9%86%D8%BA%D9%88-django-r2049/" rel="">إعداد بيئة تطوير تطبيقات جانغو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2090/" rel="">تطبيق عملي لتعلم جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%86%D9%85%D8%A7%D8%B0%D8%AC-models-r2092/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models</a>
	</li>
	<li>
		<span ipsnoautolink="true">تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin</span>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B1%D8%A7%D8%A8%D8%B9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B5%D9%81%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%A7%D9%84%D8%B1%D8%A6%D9%8A%D8%B3%D9%8A%D8%A9-r2106/" rel="">تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AE%D8%A7%D9%85%D8%B3-%D8%A7%D9%84%D8%B9%D8%B1%D9%88%D8%B6-views-%D8%A7%D9%84%D8%B9%D8%A7%D9%85%D8%A9-%D9%88%D8%A7%D9%84%D8%AA%D9%81%D8%B5%D9%8A%D9%84%D9%8A%D8%A9-r2107/" rel="">تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B3%D8%A7%D8%AF%D8%B3-%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D8%AC%D9%84%D8%B3%D8%A7%D8%AA-sessions-r2118/" rel="">تطبيق عملي لتعلم جانغو - الجزء السادس: إدارة الجلسات Sessions</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B3%D8%A7%D8%A8%D8%B9-%D8%A7%D8%B3%D8%AA%D9%8A%D8%AB%D8%A7%D9%82-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D9%8A%D9%86-%D9%88%D8%A3%D8%B0%D9%88%D9%86%D8%A7%D8%AA%D9%87%D9%85-r2119/" rel="">تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%85%D9%86-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-forms-r2120/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AA%D8%A7%D8%B3%D8%B9-%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2121/" rel="">تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-10-%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%81%D9%8A-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%A7%D8%AC-r2122/" rel="">تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A3%D9%85%D8%A7%D9%86-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2123/" rel="">تعرف على أمان تطبيقات جانغو</a>
	</li>
</ul>

<h2>
	تسجيل النماذج
</h2>

<p>
	أولًا، افتح الملف admin.py في التطبيق catalog ضمن ‎/locallibrary/catalog/admin.py، إذ يبدو هذا الملف حاليًا كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6453_6" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">contrib </span><span class="kwd">import</span><span class="pln"> admin

</span><span class="com"># سجّل نماذجك هنا</span></pre>

<p>
	لاحظ أنه يستورد <code>django.contrib.admin</code>. سجّل النماذج من خلال نسخ النص التالي في نهاية الملف، إذ تستورد هذه الشيفرة البرمجية النماذج وتستدعي <code>admin.site.register</code> لتسجيل كلٍّ منها:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6453_8" style=""><span class="kwd">from</span><span class="pln"> </span><span class="pun">.</span><span class="pln">models </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Author</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Genre</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Book</span><span class="pun">,</span><span class="pln"> </span><span class="typ">BookInstance</span><span class="pln">

admin</span><span class="pun">.</span><span class="pln">site</span><span class="pun">.</span><span class="pln">register</span><span class="pun">(</span><span class="typ">Book</span><span class="pun">)</span><span class="pln">
admin</span><span class="pun">.</span><span class="pln">site</span><span class="pun">.</span><span class="pln">register</span><span class="pun">(</span><span class="typ">Author</span><span class="pun">)</span><span class="pln">
admin</span><span class="pun">.</span><span class="pln">site</span><span class="pun">.</span><span class="pln">register</span><span class="pun">(</span><span class="typ">Genre</span><span class="pun">)</span><span class="pln">
admin</span><span class="pun">.</span><span class="pln">site</span><span class="pun">.</span><span class="pln">register</span><span class="pun">(</span><span class="typ">BookInstance</span><span class="pun">)</span></pre>

<p>
	<strong>ملاحظة</strong>: إذا قبلت التحدي لإنشاء نموذج يمثل اللغة الطبيعية للكتاب في <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%86%D9%85%D8%A7%D8%B0%D8%AC-models-r2092/" rel="">المقال السابق</a>، فاستورد ذلك النموذج وسجّله.
</p>

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

<h2>
	إنشاء مستخدم مسؤول Superuser
</h2>

<p>
	تحتاج حساب مستخدم مع تفعيل الحالة Staff (أي أن المستخدم من الموظفين) لتسجيل الدخول إلى موقع المدير، ويجب أن يكون لدى هذا المستخدم أذونات لإدارة جميع الكائنات لعرض السجلات وإنشائها. يمكنك إنشاء حساب مستخدم مميز يتمتع بوصول كامل إلى الموقع وجميع الأذونات المطلوبة باستخدام الملف manage.py.
</p>

<p>
	استدعِ الأمر التالي في مجلد الملف manage.py لإنشاء المستخدم المميز، إذ سيُطلَب منك إدخال اسم مستخدم وعنوان بريد إلكتروني وكلمة مرور قوية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6453_10" style=""><span class="pln">python3 manage</span><span class="pun">.</span><span class="pln">py createsuperuser</span></pre>

<p>
	سيُضاف مستخدم مميز جديد إلى قاعدة البيانات بعد اكتمال الأمر السابق. أعِد تشغيل خادم التطوير لتتمكن من اختبار تسجيل الدخول كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6453_12" style=""><span class="pln">python3 manage</span><span class="pun">.</span><span class="pln">py runserver</span></pre>

<h2>
	تسجيل الدخول واستخدام الموقع
</h2>

<p>
	يمكنك تسجيل الدخول إلى الموقع من خلال فتح عنوان URL للمدير "‎/admin"، مثل "http://127.0.0.1:8000/admin" وإدخال بيانات اعتماد المستخدم المميز وكلمة المرور الجديدة، إذ سيُعاد توجيهك إلى صفحة تسجيل الدخول ثم ستعود إلى عنوان URL للمدير "‎/admin" بعد إدخال تفاصيلك.
</p>

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

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="134907" href="https://academy.hsoub.com/uploads/monthly_2023_09/01_admin_home.png.588a956644c9168e4260a7acc4cea2b0.png" rel=""><img alt="01_admin_home.png" class="ipsImage ipsImage_thumbnailed" data-fileid="134907" data-unique="xtd56e3d2" src="https://academy.hsoub.com/uploads/monthly_2023_09/01_admin_home.png.588a956644c9168e4260a7acc4cea2b0.png"> </a>
</p>

<p>
	انقر على رابط <strong>الإضافة Add</strong> الموجود على يمين النموذج Books لإنشاء كتاب جديد، إذ سيظهر مربع حوار يشبه إلى حد كبير الشكل الآتي. لاحظ كيف تتطابق عناوين كل حقل ونوع عنصر الواجهة المُستخدَم ونص التعليمات <code>help_text</code> -إن وجد- مع القيم التي حدّدتها في النموذج.
</p>

<p>
	أدخِل قيم الحقول، ويمكنك إنشاء مؤلفين Authors أو أنواع Genres جديدة بالضغط على الزر <strong>+</strong> بجانب الحقول أو تحديد قيم الموجودة من القوائم إذا أنشأتها مسبقًا. يمكنك عند الانتهاء الضغط على زر <strong>حفظ SAVE أو حفظ وإضافة آخر Save and add another أو حفظ ومتابعة التعديل Save and continue editing</strong> لحفظ السجل.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="134906" href="https://academy.hsoub.com/uploads/monthly_2023_09/02_admin_book_add.png.188218912c0b8e338f166bfdd17db1f5.png" rel=""><img alt="02_admin_book_add.png" class="ipsImage ipsImage_thumbnailed" data-fileid="134906" data-unique="jzk7p8p3p" src="https://academy.hsoub.com/uploads/monthly_2023_09/02_admin_book_add.thumb.png.f6f0ca06488a936aacefd8818fe3db45.png"> </a>
</p>

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

<p>
	انقر على رابط الصفحة الرئيسية Home link في الأعلى للرجوع إلى صفحة المدير الرئيسية عند الانتهاء من إضافة الكتب، ثم انقر على رابط الكتب Books لعرض قائمة الكتب الحالية، أو انقر على أحد الروابط الأخرى لمشاهدة قوائم النماذج الأخرى. يمكن أن تبدو القائمة الآن مشابهة للقطة الشاشة التالية بعد أن أضفت بعض الكتب، إذ سيُعرَض عنوان كل كتاب، وهي القيمة المُعادة في التابع <code>‎__str__()‎</code> الخاص بالنموذج Book الذي حدّدناه في المقال السابق.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="134905" href="https://academy.hsoub.com/uploads/monthly_2023_09/03_admin_book_list.png.c5d57d6e53da494bfe34c1651a6583aa.png" rel=""><img alt="03_admin_book_list.png" class="ipsImage ipsImage_thumbnailed" data-fileid="134905" data-unique="u6n15h3hg" src="https://academy.hsoub.com/uploads/monthly_2023_09/03_admin_book_list.png.c5d57d6e53da494bfe34c1651a6583aa.png"> </a>
</p>

<p>
	يمكنك حذف الكتب من هذه القائمة من خلال تحديد مربع الاختيار بجانب الكتاب الذي لا تريده وتحديد إجراء الحذف delete…‎ من القائمة المنسدلة Action، ثم الضغط على الزر <strong>Go</strong>. يمكنك إضافة كتب جديدة بالضغط على زر إضافة كتاب <strong>ADD BOOK</strong>.
</p>

<p>
	يمكنك تعديل كتاب من خلال اختيار اسمه في الارتباط، فصفحة تعديل الكتاب الموضَّحة في الشكل الآتي مطابقة تقريبًا لصفحة الإضافة، ولكن الاختلافات الرئيسية بينهما هي عنوان الصفحة "Change book" وإضافة الأزرار <strong>حذف Delete والسجل HISTORY وعرض على الموقع VIEW ON SITE</strong>، إذ يظهر هذا الزر الأخير لأننا عرّفنا التابع <code>get_absolute_url()‎</code> في نموذجنا.
</p>

<p>
	<strong>ملاحظة:</strong> يؤدي النقر فوق الزر <strong>VIEW ON SITE</strong> إلى ظهور استثناء <code>NoReverseMatch</code> بسبب محاولة التابع <code>get_absolute_url()‎</code> لعكس <code>()reverse</code> رابط عنوان URL المُسمى ('book-detail') غير المُعرّف بعد. سنعرّف ربط العنوان URL والعرض المرتبط به في <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%86%D9%85%D8%A7%D8%B0%D8%AC-models-r2092/" rel="">المقال السابع من هذه السلسلة</a>.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="134904" href="https://academy.hsoub.com/uploads/monthly_2023_09/04_admin_book_modify.png.957f92b02a4b234e6317322332e711a6.png" rel=""><img alt="04_admin_book_modify.png" class="ipsImage ipsImage_thumbnailed" data-fileid="134904" data-unique="0l83dan0q" src="https://academy.hsoub.com/uploads/monthly_2023_09/04_admin_book_modify.thumb.png.19aa9ec91c1b5707245ef4e23262fe81.png"> </a>
</p>

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

<p>
	لن يكون لديك أيّ نسخ كتب Book Instances، لأنها لم تُنشَأ من الكتب Books، بالرغم من أنه يمكنك إنشاء كتاب <code>Book</code> من <code>BookInstance</code>، فهذه هي طبيعة الحقل من النوع <code>ForeignKey</code>. انتقل مرةً أخرى إلى الصفحة الرئيسية واضغط على زر الإضافة Add المرتبط بعرض شاشة إضافة نسخة كتاب Add book instance التالية. لاحظ المعرّف الكبير والفريد بصورة عامة، والذي يمكن استخدامه لتحديد نسخة من كتاب في المكتبة بصورة منفصلة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="134903" href="https://academy.hsoub.com/uploads/monthly_2023_09/05_admin_bookinstance_add.png.4007d919a129c16cc5b99f944c538fd3.png" rel=""><img alt="05_admin_bookinstance_add.png" class="ipsImage ipsImage_thumbnailed" data-fileid="134903" data-unique="rvs1quv41" src="https://academy.hsoub.com/uploads/monthly_2023_09/05_admin_bookinstance_add.png.4007d919a129c16cc5b99f944c538fd3.png"> </a>
</p>

<p>
	أنشئ عددًا من هذه السجلات لكل كتاب من كتبك، واضبط الحالة على أنها متوفرة Available لبعض السجلات على الأقل وأنها مُعارة On Loan لسجلات أخرى. إذا كانت الحالة <strong>غير</strong> متوفرة، فاضبط أيضًا تاريخ استرجاع الكتاب Due back مستقبلًا.
</p>

<p>
	لقد تعلمت كيفية إعداد واستخدام موقع المدير، وأنشأت سجلات للنماذج <code>Book</code> و <code>BookInstance</code> و <code>Genre</code> و <code>Author</code> التي سنتمكن من استخدامها بمجرد إنشاء العروض والقوالب.
</p>

<h2>
	الضبط المتقدم
</h2>

<p>
	يطبّق جانغو عملًا جيدًا جدًا في إنشاء موقع مدير أولي باستخدام معلومات النماذج المسجلة، بحيث:
</p>

<ul>
	<li>
		يحتوي كل نموذج على قائمة من السجلات الفردية التي تحدّدها <a href="https://academy.hsoub.com/programming/python/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%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-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-r407/" rel="">السلسلة النصية</a> الناتجة باستخدام التابع <code>‎__str__()‎</code> الخاص بالنموذج وترتبط باستمارات أو عروض تفصيلية للتعديل، إذ يحتوي هذا العرض view افتراضيًا على قائمة إجراءات في الأعلى يمكنك استخدامها لإجراء مجموعة عمليات حذف على السجلات.
	</li>
	<li>
		تحتوي استمارات السجلات التفصيلية الخاصة بالنموذج لتعديل السجلات وإضافتها على جميع الحقول الموجودة في النموذج والموضوعة عموديًا في ترتيب التصريح عنها.
	</li>
</ul>

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

<ul>
	<li>
		<p>
			عروض القائمة List Views:
		</p>

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

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

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

<p>
	يمكنك العثور على مرجع كامل لجميع خيارات <a href="https://docs.djangoproject.com/en/4.0/ref/contrib/admin/" rel="external nofollow">تخصيص موقع المدير</a> في توثيق جانغو، ويمكنك على الاطلاع على مقال <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D9%86%D8%B4%D9%8A%D8%B7-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D9%85%D8%AF%D9%8A%D8%B1-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%88%D8%A7%D9%84%D8%A7%D8%AA%D8%B5%D8%A7%D9%84-%D8%A8%D9%87%D8%A7-r1628/" rel="">تنشيط واجهة مدير جانغو والاتصال بها</a> لمعلومات أكثر.
</p>

<h3>
	تسجيل الصنف ModelAdmin
</h3>

<p>
	يمكنك تغيير كيفية عرض النموذج في واجهة المدير من خلال تعريف <a href="https://docs.djangoproject.com/en/4.0/ref/contrib/admin/#modeladmin-objects" rel="external nofollow">الصنف ModelAdmin</a> (الذي يصف التخطيط) وتسجيله مع النموذج.
</p>

<p>
	لنبدأ بالنموذج <code>Author</code>. افتح الملف admin.py في التطبيق catalog ضمن ‎/locallibrary/catalog/admin.py. ضع تعليقًا على التسجيل الأصلي (ابدأه بالرمز #) للنموذج <code>Author</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6453_14" style=""><span class="com"># admin.site.register(Author)</span></pre>

<p>
	أضف صنف <code>AuthorAdmin</code> جديد وسجّله كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6453_16" style=""><span class="com"># تعريف صنف المدير</span><span class="pln">
</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">AuthorAdmin</span><span class="pun">(</span><span class="pln">admin</span><span class="pun">.</span><span class="typ">ModelAdmin</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">pass</span><span class="pln">

</span><span class="com"># تسجيل صنف المدير في النموذج المرتبط به</span><span class="pln">
admin</span><span class="pun">.</span><span class="pln">site</span><span class="pun">.</span><span class="pln">register</span><span class="pun">(</span><span class="typ">Author</span><span class="pun">,</span><span class="pln"> </span><span class="typ">AuthorAdmin</span><span class="pun">)</span></pre>

<p>
	سنضيف الآن أصناف <code>ModelAdmin</code> للنموذجين <code>Book</code> و <code>BookInstance</code>. يجب التعليق على التسجيلات الأصلية مرةً أخرى كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6453_18" style=""><span class="com"># admin.site.register(Book)</span><span class="pln">
</span><span class="com"># admin.site.register(BookInstance)</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>‎@register</code> لتسجيل النماذج، والذي يفعل الشيء نفسه تمامًا الذي تفعله صيغة <code>admin.site.register()‎</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6453_20" style=""><span class="com"># ‫تسجيل أصناف المدير للنموذج Book باستخدام المزخرف</span><span class="pln">
</span><span class="lit">@admin</span><span class="pun">.</span><span class="pln">register</span><span class="pun">(</span><span class="typ">Book</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">BookAdmin</span><span class="pun">(</span><span class="pln">admin</span><span class="pun">.</span><span class="typ">ModelAdmin</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">pass</span><span class="pln">

</span><span class="com">#  BookInstance  تسجيل أصناف المدير للنموذج باستخدام المزخرف </span><span class="pln">
</span><span class="lit">@admin</span><span class="pun">.</span><span class="pln">register</span><span class="pun">(</span><span class="typ">BookInstance</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">BookInstanceAdmin</span><span class="pun">(</span><span class="pln">admin</span><span class="pun">.</span><span class="typ">ModelAdmin</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">pass</span></pre>

<p>
	جميع أصناف المدير فارغة حاليًا (لاحظ <code>pass</code>)، لذلك لن يتغير سلوك المدير، ويمكننا توسيعها لتحديد سلوك المدير الخاص بالنموذج.
</p>

<h3>
	ضبط عروض القائمة
</h3>

<p>
	تسرد المكتبة المحلية LocalLibrary حاليًا جميع المؤلفين الذين يستخدمون اسم الكائن الذي ينشئه التابع <code>‎__str__()‎</code> الخاص بالنموذج، وهذا جيد عندما يكون لديك عدد قليل من المؤلفين، ولكن يمكن أن ينتهي بك الأمر بالحصول على نسخ مكررة بمجرد أن يكون لديك العديد من المؤلفين. يمكنك التمييز بينها أو إظهار مزيد من المعلومات حول كل مؤلف من خلال استخدام السمة <a href="https://docs.djangoproject.com/en/4.0/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_display" rel="external nofollow"><code>list_display</code></a> لإضافة حقول إضافية إلى العرض.
</p>

<p>
	ضع الشيفرة البرمجية الآتية بدلًا من الصنف <code>AuthorAdmin</code>. يُصرَّح عن أسماء الحقول المعروضة في القائمة ضمن صف tuple بالترتيب المطلوب كما يلي (هذه هي الأسماء نفسها المُحدَّدة في نموذجك الأصلي):
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6453_22" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">AuthorAdmin</span><span class="pun">(</span><span class="pln">admin</span><span class="pun">.</span><span class="typ">ModelAdmin</span><span class="pun">):</span><span class="pln">
    list_display </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="str">'last_name'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'first_name'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'date_of_birth'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'date_of_death'</span><span class="pun">)</span></pre>

<p>
	انتقل إلى قائمة المؤلفين في موقع الويب، إذ يجب الآن عرض الحقول السابقة على النحو التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="134902" href="https://academy.hsoub.com/uploads/monthly_2023_09/06_admin_improved_author_list.png.963ca425757feb1e58537f05f107b6ec.png" rel=""><img alt="06_admin_improved_author_list.png" class="ipsImage ipsImage_thumbnailed" data-fileid="134902" data-unique="49ejnwibd" src="https://academy.hsoub.com/uploads/monthly_2023_09/06_admin_improved_author_list.png.963ca425757feb1e58537f05f107b6ec.png"> </a>
</p>

<p>
	سنعرض أيضًا حقول المؤلف <code>author</code> والنوع <code>genre</code> للنموذج <code>Book</code>، فالحقل <code>author</code> هو حقل من النوع <code>ForeignKey</code> للعلاقة (واحد إلى متعدد)، وبالتالي ستمثله قيمة التابع <code>‎__str__()‎</code> للسجل المرتبط به. ضع ما يلي بدلًا من الصنف <code>BookAdmin</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6453_24" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">BookAdmin</span><span class="pun">(</span><span class="pln">admin</span><span class="pun">.</span><span class="typ">ModelAdmin</span><span class="pun">):</span><span class="pln">
    list_display </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">'author'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'display_genre'</span><span class="pun">)</span></pre>

<p>
	لسوء الحظ، لا يمكننا تحديد الحقل <code>genre</code> مباشرةً في السمة <code>list_display</code>، لأنه حقل من النوع <code>ManyToManyField</code>، إذ يمنع جانغو هذا النوع من الحقول لأنه سيكون هناك تكلفة كبيرة للوصول إلى <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> عند تطبيقه. لذا سنعرّف الدالة <code>display_genre</code> للحصول على المعلومات بوصفها سلسلة نصية، وهي الدالة التي استدعيناها سابقًا وسنعرّفها لاحقًا.
</p>

<p>
	<strong>ملاحظة</strong>: يمكن ألّا يكون الحصول على الحقل <code>genre</code> فكرةً جيدة هنا، بسبب تكلفة عملية قاعدة البيانات، ولكن يمكن أن تكون استدعاء الدوال في نماذجك مفيدة جدًا لأسباب أخرى مثل إضافة ارتباط الحذف Delete بجانب كل عنصر في القائمة.
</p>

<p>
	أضف الشيفرة البرمجية الآتية إلى النموذج <code>Book</code> في الملف models.py، إذ يؤدي هذا التابع إلى إنشاء سلسلة نصية من القيم الثلاث الأولى للحقل <code>genre</code> (إن وجدت) وإنشاء وصف قصير <code>short_description</code> يمكن استخدامه في موقع المدير لهذا التابع:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6453_26" style=""><span class="kwd">def</span><span class="pln"> display_genre</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
    </span><span class="str">"""Create a string for the Genre. This is required to display genre in Admin."""</span><span class="pln">
    </span><span class="kwd">return</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">genre</span><span class="pun">.</span><span class="pln">name </span><span class="kwd">for</span><span class="pln"> genre </span><span class="kwd">in</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">genre</span><span class="pun">.</span><span class="pln">all</span><span class="pun">()[:</span><span class="lit">3</span><span class="pun">])</span><span class="pln">

display_genre</span><span class="pun">.</span><span class="pln">short_description </span><span class="pun">=</span><span class="pln"> </span><span class="str">'Genre'</span></pre>

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

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="134901" href="https://academy.hsoub.com/uploads/monthly_2023_09/07_admin_improved_book_list.png.10ee46db6bdc37c806414034eb6ff01e.png" rel=""><img alt="07_admin_improved_book_list.png" class="ipsImage ipsImage_thumbnailed" data-fileid="134901" data-unique="s5klofi2v" src="https://academy.hsoub.com/uploads/monthly_2023_09/07_admin_improved_book_list.png.10ee46db6bdc37c806414034eb6ff01e.png"> </a>
</p>

<p>
	النموذج <code>Genre</code> (والنموذج <code>Language</code> إذا عرّفته) لهما حقل واحد، لذلك لا جدوى من إنشاء نموذج إضافي لهما لعرض حقول إضافية.
</p>

<p>
	<strong>ملاحظة</strong>: يجب تحديث قائمة النموذج <code>BookInstance</code> لعرض الحالة وتاريخ الإعادة المتوقع على الأقل، إذ أضفنا ذلك في نهاية هذا المقال.
</p>

<h3>
	إضافة مرشحات القائمة
</h3>

<p>
	يمكن أن يكون من المفيد أن تكون قادرًا على ترشيح العناصر المعروضة بعد حصولك على الكثير من العناصر في القائمة، إذ يمكن تحقيق ذلك من خلال سرد الحقول في السمة <code>list_filter</code>. ضع جزء الشيفرة البرمجية التالية بدلًا من الصنف <code>BookInstanceAdmin</code> الحالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6453_28" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">BookInstanceAdmin</span><span class="pun">(</span><span class="pln">admin</span><span class="pun">.</span><span class="typ">ModelAdmin</span><span class="pun">):</span><span class="pln">
    list_filter </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="str">'status'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'due_back'</span><span class="pun">)</span></pre>

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

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="134900" href="https://academy.hsoub.com/uploads/monthly_2023_09/08_admin_improved_bookinstance_list_filters.png.2e331fccd3496b145f02d5bc531c76c8.png" rel=""><img alt="08_admin_improved_bookinstance_list_filters.png" class="ipsImage ipsImage_thumbnailed" data-fileid="134900" data-unique="761mwcr57" src="https://academy.hsoub.com/uploads/monthly_2023_09/08_admin_improved_bookinstance_list_filters.png.2e331fccd3496b145f02d5bc531c76c8.png"> </a>
</p>

<h3>
	تنظيم تخطيط العرض التفصيلي
</h3>

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

<p>
	<strong>ملاحظة</strong>: تُعَد نماذج موقع المكتبة المحلية LocalLibrary بسيطةً نسبيًا، لذا لا توجد حاجة كبيرة لتغيير التخطيط، ولكن سنجري بعض التغييرات على أية حال لنوضّح لك كيفية تطبيق ذلك.
</p>

<h4>
	التحكم في الحقول المعروضة وتنسيقها
</h4>

<p>
	حدّث الصنف <code>AuthorAdmin</code> لإضافة السطر <code>fields</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6453_30" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">AuthorAdmin</span><span class="pun">(</span><span class="pln">admin</span><span class="pun">.</span><span class="typ">ModelAdmin</span><span class="pun">):</span><span class="pln">
    list_display </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="str">'last_name'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'first_name'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'date_of_birth'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'date_of_death'</span><span class="pun">)</span><span class="pln">

    fields </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">'first_name'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'last_name'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="str">'date_of_birth'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'date_of_death'</span><span class="pun">)]</span></pre>

<p>
	تسرد السمة <code>fields</code> فقط الحقول التي ستُعرَض في الاستمارة وفق ترتيبها، إذ تُعرَض الحقول عموديًا افتراضيًا، ولكن ستُعرَض أفقيًا إذا جمّعتها ضمن صف كما هو موضح في حقول "date" السابقة.
</p>

<p>
	انتقل إلى عرض المؤلف التفصيلي في موقعك، إذ يجب أن يظهر الآن كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="134899" href="https://academy.hsoub.com/uploads/monthly_2023_09/09_admin_improved_author_detail.png.69df7b5be4c465751ddd86a9e335eb79.png" rel=""><img alt="09_admin_improved_author_detail.png" class="ipsImage ipsImage_thumbnailed" data-fileid="134899" data-unique="n14c09fgc" src="https://academy.hsoub.com/uploads/monthly_2023_09/09_admin_improved_author_detail.png.69df7b5be4c465751ddd86a9e335eb79.png"> </a>
</p>

<p>
	<strong>ملاحظة</strong>: يمكنك أيضًا استخدام السمة <code>exclude</code> للتصريح عن قائمة السمات التي ستُستبعَد من الاستمارة، إذ ستُعرَض جميع السمات الأخرى في النموذج.
</p>

<h4>
	تقسيم العرض التفصيلي
</h4>

<p>
	يمكنك إضافة أقسام Sections لتجميع معلومات النموذج ذات الصلة في الاستمارة التفصيلية باستخدام السمة <a href="https://docs.djangoproject.com/en/4.0/ref/contrib/admin/#django.contrib.admin.ModelAdmin.fieldsets" rel="external nofollow"><code>fieldsets</code></a>. لدينا في النموذج <code>BookInstance</code> معلومات تتعلق بما هو الكتاب (<code>name</code> و <code>imprint</code> و <code>id</code>) ومتى سيكون متاحًا (<code>status</code> و <code>due_back</code>)، إذ يمكننا إضافة هذه المعلومات إلى الصنف <code>BookInstanceAdmin</code> باستخدام الخاصية <code>fieldsets</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6453_32" style=""><span class="lit">@admin</span><span class="pun">.</span><span class="pln">register</span><span class="pun">(</span><span class="typ">BookInstance</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">BookInstanceAdmin</span><span class="pun">(</span><span class="pln">admin</span><span class="pun">.</span><span class="typ">ModelAdmin</span><span class="pun">):</span><span class="pln">
    list_filter </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="str">'status'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'due_back'</span><span class="pun">)</span><span class="pln">

    fieldsets </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
        </span><span class="pun">(</span><span class="kwd">None</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="str">'fields'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">(</span><span class="str">'book'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'imprint'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'id'</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}),</span><span class="pln">
        </span><span class="pun">(</span><span class="str">'Availability'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="str">'fields'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">(</span><span class="str">'status'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'due_back'</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}),</span><span class="pln">
    </span><span class="pun">)</span></pre>

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

<p>
	انتقل الآن إلى عرض نسخة الكتاب في موقعك، إذ يجب أن تظهر الاستمارة كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="134898" href="https://academy.hsoub.com/uploads/monthly_2023_09/10_admin_improved_bookinstance_detail_sections.png.0dfde0834a4d22bca854a7209fc1c3b1.png" rel=""><img alt="10_admin_improved_bookinstance_detail_sections.png" class="ipsImage ipsImage_thumbnailed" data-fileid="134898" data-unique="h6iiccx6j" src="https://academy.hsoub.com/uploads/monthly_2023_09/10_admin_improved_bookinstance_detail_sections.png.0dfde0834a4d22bca854a7209fc1c3b1.png"> </a>
</p>

<h3>
	تعديل السجلات المضمن
</h3>

<p>
	يكون في بعض الأحيان من المنطقي أن تكون قادرًا على إضافة سجلات في الوقت نفسه، فمن المنطقي مثلًا أن يكون لديك معلومات الكتاب ومعلومات نسخ محددة حصلت عليها في صفحة التفاصيل نفسها. يمكنك تطبيق ذلك من خلال التصريح عن السمة <a href="https://docs.djangoproject.com/en/4.0/ref/contrib/admin/#django.contrib.admin.ModelAdmin.inlines" rel="external nofollow"><code>inlines</code></a> من النوع <a href="https://docs.djangoproject.com/en/4.0/ref/contrib/admin/#django.contrib.admin.TabularInline" rel="external nofollow"><code>TabularInline</code></a> (تخطيط أفقي) أو <code>StackedInline</code> (تخطيط عمودي تمامًا مثل تخطيط النموذج الافتراضي). يمكنك إضافة معلومات نسخ الكتاب <code>BookInstance</code> مضمنة في تفاصيل الكتاب <code>Book</code> من خلال تحديد السمة <code>inlines</code> في الصنف <code>BookAdmin</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6453_34" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">BooksInstanceInline</span><span class="pun">(</span><span class="pln">admin</span><span class="pun">.</span><span class="typ">TabularInline</span><span class="pun">):</span><span class="pln">
    model </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BookInstance</span><span class="pln">

</span><span class="lit">@admin</span><span class="pun">.</span><span class="pln">register</span><span class="pun">(</span><span class="typ">Book</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">BookAdmin</span><span class="pun">(</span><span class="pln">admin</span><span class="pun">.</span><span class="typ">ModelAdmin</span><span class="pun">):</span><span class="pln">
    list_display </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">'author'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'display_genre'</span><span class="pun">)</span><span class="pln">

    inlines </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="typ">BooksInstanceInline</span><span class="pun">]</span></pre>

<p>
	انتقل الآن إلى عرض النموذج <code>Book</code> في موقعك، وسترى الآن في الأسفل نسخ الكتاب المتعلقة به أسفل حقول نوع الكتاب مباشرةً:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="134897" href="https://academy.hsoub.com/uploads/monthly_2023_09/11_admin_improved_book_detail_inlines.png.1563b2f1200f842dce4c47ea88a39d8a.png" rel=""><img alt="11_admin_improved_book_detail_inlines.png" class="ipsImage ipsImage_thumbnailed" data-fileid="134897" data-unique="q8a343iws" src="https://academy.hsoub.com/uploads/monthly_2023_09/11_admin_improved_book_detail_inlines.thumb.png.729704882eb22e0b7de853b5b9dd21a6.png"> </a>
</p>

<p>
	كل ما فعلناه في هذه الحالة هو التصريح عن الصنف <code>TabularInline</code> المُضمَّن الذي يضيف فقط جميع الحقول من النموذج المُضمَّن inlined. يمكنك تحديد جميع أنواع المعلومات الإضافية للتخطيط بما في ذلك الحقول المراد عرضها وترتيبها وما إذا كانت للقراءة فقط أم لا وغير ذلك (اطلع على <a href="https://docs.djangoproject.com/en/4.0/ref/contrib/admin/#django.contrib.admin.TabularInline" rel="external nofollow">TabularInline</a> لمزيد من المعلومات).
</p>

<p>
	<strong>ملاحظة</strong>: هناك بعض القيود في هذه الوظيفة، إذ لدينا في لقطة الشاشة السابقة ثلاث نسخ حالية من الكتاب متبوعة بثلاثة عناصر بديلة لنسخ كتب جديدة، والتي تبدو متشابهة جدًا. يُفضَّل عدم وجود نسخ كتب احتياطية افتراضيًا وإضافتها فقط باستخدام ارتباط إضافة نسخة كتاب أخرى Add another Book instance، أو أن تكون قادرًا فقط على سرد نماذج <code>BookInstance</code> بوصفها ارتباطات غير قابلة للقراءة من هنا. يمكن تحقيق الخيار الأول من خلال ضبط السمة <code>extra</code> على القيمة "0" في النموذج <code>BooksInstanceInline</code> (جرّبها بنفسك).
</p>

<h2>
	تحدى نفسك
</h2>

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

<ul>
	<li>
		أضف لعرض قائمة <code>BookInstance</code> شيفرة برمجية لعرض الكتاب والحالة وتاريخ الاسترجاع والمعرّف (بدلًا من النص الافتراضي للتابع <code>‎__str__()‎</code>).
	</li>
	<li>
		أضف قائمة مُضمَّنة لعناصر الكتاب <code>Book</code> إلى عرض <code>Author</code> التفصيلي باستخدام الأسلوب نفسه الذي استخدمناه مع <code>Book</code> و <code>BookInstance</code>.
	</li>
</ul>

<h2>
	الخلاصة
</h2>

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

<p>
	ترجمة -وبتصرُّف- للمقال <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Admin_site" rel="external nofollow">Django Tutorial Part 4: Django admin site</a>.
</p>

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

<ul>
	<li>
		المقال التالي: <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B1%D8%A7%D8%A8%D8%B9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B5%D9%81%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%A7%D9%84%D8%B1%D8%A6%D9%8A%D8%B3%D9%8A%D8%A9-r2106/" rel="">تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية</a>
	</li>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%86%D9%85%D8%A7%D8%B0%D8%AC-models-r2092/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D9%86%D8%B4%D9%8A%D8%B7-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D9%85%D8%AF%D9%8A%D8%B1-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%88%D8%A7%D9%84%D8%A7%D8%AA%D8%B5%D8%A7%D9%84-%D8%A8%D9%87%D8%A7-r1628/" rel="">تنشيط واجهة مدير جانغو والاتصال بها</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D9%84%D8%B9%D8%B1%D9%88%D8%B6-%D9%88%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-%D9%81%D9%8A-django-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-r436/" rel="">العروض والقوالب في Django</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AD%D8%B2%D9%85-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D8%A7%D9%84%D8%AB%D9%85%D8%A7%D9%86%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D9%8A-%D8%AA%D8%B3%D9%87%D9%84-%D8%AA%D8%B9%D8%A7%D9%85%D9%84%D9%83-%D9%85%D8%B9-django-r656/" rel="">حزم بايثون الثمانية التي تسهل تعاملك مع Django</a>
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/intro/tutorial02/#introducing-the-django-admin" rel="external nofollow">كتابة أول تطبيق جانغو - الجزء الثاني: مقدمة إلى مدير جانغو</a> (توثيق جانغو)
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/ref/contrib/admin/" rel="external nofollow">موقع مدير جانغو</a> (توثيق جانغو)
	</li>
</ul>
]]></description><guid isPermaLink="false">2105</guid><pubDate>Sat, 02 Sep 2023 13:00:00 +0000</pubDate></item><item><title>&#x62A;&#x637;&#x628;&#x64A;&#x642; &#x639;&#x645;&#x644;&#x64A; &#x644;&#x62A;&#x639;&#x644;&#x645; &#x62C;&#x627;&#x646;&#x63A;&#x648; - &#x627;&#x644;&#x62C;&#x632;&#x621; &#x627;&#x644;&#x62B;&#x627;&#x646;&#x64A;: &#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x627;&#x644;&#x646;&#x645;&#x627;&#x630;&#x62C; Models</title><link>https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%86%D9%85%D8%A7%D8%B0%D8%AC-models-r2092/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_08/-----------.png.0dcc437aa2b1722795459e337e0b65d8.png" /></p>
<p>
	يوضح هذا المقال كيفية تعريف النماذج Models لموقع المكتبة المحلية LocalLibrary، ويشرح ما هو النموذج وكيفية التصريح عنه وبعض أنواع الحقول الرئيسية، ويعرض بإيجاز بعض الطرق الرئيسية التي يمكنك من خلالها الوصول إلى بيانات النموذج.
</p>

<ul>
	<li>
		<strong>المتطلبات الأساسية</strong>: الاطلاع على مقال <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2090/" rel="">إنشاء موقع ويب هيكلي لمكتبة محلية</a>.
	</li>
	<li>
		<strong>الهدف</strong>: أن تكون قادرًا على تصميم وإنشاء نماذجك واختيار الحقول بصورة مناسبة.
	</li>
</ul>

<p>
	يمكن لتطبيقات <a href="https://academy.hsoub.com/programming/python/django/" rel="">جانغو Django</a> الوصول إلى البيانات وإدارتها من خلال كائنات لغة <a href="https://wiki.hsoub.com/Python" rel="external">بايثون Python</a> المُشار إليها بالنماذج Models، إذ تعرِّف النماذج بنية البيانات المخزنة بما في ذلك أنواع الحقول وربما حجمها الأقصى وقيمها الافتراضية وخيارات قائمة الاختيار ونص التعليمات للتوثيق ونص التسمية للاستمارات Forms وغير ذلك. يُعَد تعريف النموذج مستقلًا عن قاعدة البيانات الأساسية، إذ يمكنك اختيار قاعدة بيانات واحدة من عدة قواعد بيانات بوصفها جزءًا من إعدادات مشروعك. لن تحتاج أبدًا إلى التواصل مع قاعدة البيانات التي تريد استخدامها مباشرةً بمجرد اختيارها، فما عليك سوى كتابة بنية نموذجك وشيفرة برمجية أخرى، وسيتولى إطار عمل جانغو كل الأعمال الأخرى للتواصل مع قاعدة البيانات نيابةً عنك.
</p>

<p>
	يوضح هذا المقال كيفية تعريف النماذج والوصول إليها في مثال <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2090/" rel="">موقع المكتبة المحلية LocalLibrary</a>.
</p>

<p>
	تتألف هذه السلسلة الفرعية من السلسلة الأشمل <a href="https://academy.hsoub.com/tags/%D8%AA%D8%B9%D9%84%D9%85%20%D8%AA%D8%B7%D9%88%D9%8A%D8%B1%20%D8%A7%D9%84%D9%88%D9%8A%D8%A8/" rel="">تعلم تطوير الويب</a> من المقالات التالية:
</p>

<ul>
	<li>
		<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>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%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%AC%D8%A7%D9%86%D8%BA%D9%88-django-r2049/" rel="">إعداد بيئة تطوير تطبيقات جانغو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2090/" rel="">تطبيق عملي لتعلم جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية</a>
	</li>
	<li>
		<span ipsnoautolink="true">تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models</span>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D9%85%D9%88%D9%82%D8%B9-%D9%85%D8%AF%D9%8A%D8%B1-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-admin-r2105/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B1%D8%A7%D8%A8%D8%B9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B5%D9%81%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%A7%D9%84%D8%B1%D8%A6%D9%8A%D8%B3%D9%8A%D8%A9-r2106/" rel="">تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AE%D8%A7%D9%85%D8%B3-%D8%A7%D9%84%D8%B9%D8%B1%D9%88%D8%B6-views-%D8%A7%D9%84%D8%B9%D8%A7%D9%85%D8%A9-%D9%88%D8%A7%D9%84%D8%AA%D9%81%D8%B5%D9%8A%D9%84%D9%8A%D8%A9-r2107/" rel="">تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B3%D8%A7%D8%AF%D8%B3-%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D8%AC%D9%84%D8%B3%D8%A7%D8%AA-sessions-r2118/" rel="">تطبيق عملي لتعلم جانغو - الجزء السادس: إدارة الجلسات Sessions</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B3%D8%A7%D8%A8%D8%B9-%D8%A7%D8%B3%D8%AA%D9%8A%D8%AB%D8%A7%D9%82-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D9%8A%D9%86-%D9%88%D8%A3%D8%B0%D9%88%D9%86%D8%A7%D8%AA%D9%87%D9%85-r2119/" rel="">تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%85%D9%86-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-forms-r2120/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AA%D8%A7%D8%B3%D8%B9-%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2121/" rel="">تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-10-%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%81%D9%8A-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%A7%D8%AC-r2122/" rel="">تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A3%D9%85%D8%A7%D9%86-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2123/" rel="">تعرف على أمان تطبيقات جانغو</a>
	</li>
</ul>

<h2>
	تصميم نماذج المكتبة المحلية LocalLibrary
</h2>

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

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

<p>
	يجب التفكير في العلاقات بمجرد أن نقرر ما هي النماذج والحقول، إذ يسمح جانغو بتعريف العلاقات التي تكون من نوع واحد لواحد "OneToOneField" وواحد إلى متعدد "ForeignKey" ومتعدد إلى متعدد "ManyToManyField".
</p>

<p>
	يوضح مخطط الارتباط التالي باستخدام لغة UML النماذج التي سنعرّفها في هذه الحالة (على شكل مربعات):
</p>

<p style="text-align: center;">
	<img alt="01_local_library_model_uml.png" class="ipsImage ipsImage_thumbnailed" data-fileid="133063" data-ratio="63.50" data-unique="i8yqogus5" style="width: 600px; height: auto;" width="600" src="https://academy.hsoub.com/uploads/monthly_2023_08/01_local_library_model_uml.thumb.png.398c7ee055dc6035e481bac88fa20138.png">
</p>

<p>
	أنشأنا نماذجًا للكتاب (التفاصيل العامة للكتاب)، ونسخة الكتاب (حالة النسخ الحقيقية المُحدَّدة للكتاب المتاح في النظام)، والمؤلف، وقررنا أن يكون لدينا نموذج للنوع، بحيث يمكن إنشاء أو تحديد القيم من خلال واجهة المدير، وقررنا عدم وجود نموذج لحالة نسخة الكتاب <code>BookInstance:status</code>، إذ كتبنا شيفرة ثابتة للقيم (<code>LOAN_STATUS</code>) لأننا لا نتوقع تغييرها. يمكنك رؤية اسم النموذج وأسماء الحقول وأنواعها والتوابع وأنواع قيمها المُعادة ضمن كل مربع من المربعات.
</p>

<p>
	يوضح المخطط العلاقات بين النماذج بما في ذلك درجة تعدّدها Multiplicities، وهي الأعداد الموجودة على المخطط والتي توضح أعداد (الحد الأقصى والحد الأدنى) كل نموذج والتي يمكن أن تكون موجودةً في العلاقة، فمثلًا يوضّح الخط المتصل بين المربعات أن الكتاب Book والنوع Genre مرتبطان، وتوضح الأعداد القريبة من نموذج النوع أن الكتاب يجب أن يحتوي على نوع واحد أو أكثر (بقدر ما تريد)، بينما توضح الأعداد الموجودة على الطرف الآخر من الخط بجوار نموذج الكتاب أن النوع يمكن ألّا يكون له أيّ نوع مرتبط بالكتاب أو يمكن أن يكون له العديد من الأنواع المرتبطة بالكتب.
</p>

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

<h2>
	مدخل إلى النماذج
</h2>

<p>
	يقدم هذا القسم نظرةً عامة موجزة عن كيفية تعريف النموذج وبعضًا من أهم الحقول والوسطاء.
</p>

<h3>
	تعريف النموذج
</h3>

<p>
	تُعرَّف النماذج عادةً في الملف models.py الخاص بالتطبيق، وتُقدَّم بوصفها صنفًا فرعيًا من <code>django.db.models.Model</code>، ويمكن أن تشمل الحقول والتوابع والبيانات الوصفية. يُظهِر جزء الشيفرة البرمجية التالي نموذجًا قياسيًا بالاسم <code>MyModelName</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8284_7" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">db </span><span class="kwd">import</span><span class="pln"> models
</span><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">urls </span><span class="kwd">import</span><span class="pln"> reverse

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">MyModelName</span><span class="pun">(</span><span class="pln">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
    </span><span class="str">"""A typical class defining a model, derived from the Model class."""</span><span class="pln">

    </span><span class="com"># حقول</span><span class="pln">
    my_field_name </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">20</span><span class="pun">,</span><span class="pln"> help_text</span><span class="pun">=</span><span class="str">'Enter field documentation'</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="kwd">class</span><span class="pln"> </span><span class="typ">Meta</span><span class="pun">:</span><span class="pln">
        ordering </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">'-my_field_name'</span><span class="pun">]</span><span class="pln">

    </span><span class="com"># توابع</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> get_absolute_url</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="str">"""Returns the URL to access a particular instance of MyModelName."""</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> reverse</span><span class="pun">(</span><span class="str">'model-detail-view'</span><span class="pun">,</span><span class="pln"> args</span><span class="pun">=[</span><span class="pln">str</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">id</span><span class="pun">)])</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> __str__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="str">"""String for representing the MyModelName object (in Admin site etc.)."""</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">my_field_name</span></pre>

<p>
	سنستكشف في الأقسام التالية كل ميزة في النموذج بالتفصيل.
</p>

<h4>
	الحقول Fileds
</h4>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8284_9" style=""><span class="pln">my_field_name </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">20</span><span class="pun">,</span><span class="pln"> help_text</span><span class="pun">=</span><span class="str">'Enter field documentation'</span><span class="pun">)</span></pre>

<p>
	يحتوي هذا المثال على حقل واحد يسمى <code>my_field_name</code> من النوع <code>models.CharField</code>، مما يعني أن هذا الحقل سيحتوي على سلاسل نصية من المحارف الأبْجَعَددية alphanumeric. تُسنَد أنواع الحقول باستخدام أصناف <a href="https://wiki.hsoub.com/Python/class" rel="external">Classes</a> معينة تحدد نوع السجل المُستخدَم لتخزين البيانات في قاعدة البيانات مع معايير التحقق لاستخدامها عند استلام القيم من <a href="https://wiki.hsoub.com/HTML/form" rel="external">استمارة HTML</a> (أي ما يشكّل قيمة صالحة). يمكن أن تأخذ أنواع الحقول وسطاء تحدّد كيفية تخزين الحقل أو كيفية استخدامه، إذ سنعطي الحقل في حالتنا وسيطين، هما:
</p>

<ul>
	<li>
		<p>
			<code>max_length=20</code>، الذي يشير إلى أن أقصى طول لقيمةٍ ما في هذا الحقل وهو 20 محرفًا.
		</p>
	</li>
	<li>
		<p>
			<code>help_text='Enter field documentation'‎</code>، وهو نص مفيد يمكن عرضه في استمارة لمساعدة المستخدمين على فهم كيفية استخدام الحقل.
		</p>
	</li>
</ul>

<p>
	يُستخدَم اسم الحقل للإشارة إليه في الاستعلامات والقوالب، وتحتوي الحقول على تسمية Label يحدّدها الوسيط <code>verbose_name</code> (بقيمة افتراضية هي <code>None</code>). إذا لم يُضبَط الوسيط <code>verbose_name</code>، فستُنشَأ التسمية من اسم الحقل من خلال استبدال أيّ شرطات سفلية بمسافة وجعل الحرف الأول حرفًا كبيرًا، فمثلًا سيكون للحقل <code>my_field_name</code> تسمية افتراضية هي "My field name" عند استخدامه في الاستمارات.
</p>

<p>
	يؤثر ترتيب التصريح عن الحقول على ترتيبها الافتراضي إذا عُرِض نموذج ضمن استمارة (في موقع المدير مثلًا) بالرغم من أن هذا الترتيب يمكن تجاهله.
</p>

<p>
	يمكن استخدام الوسطاء الشائعة التالية عند التصريح عن أنواع الحقول:
</p>

<ul>
	<li>
		<p>
			<a href="https://docs.djangoproject.com/en/4.0/ref/models/fields/#help-text" rel="external nofollow">help_text</a>: يوفر هذا الوسيط تسميةً نصيةً لاستمارات HTML (في موقع المدير مثلًا) كما وضّحنا سابقًا.
		</p>
	</li>
	<li>
		<p>
			<a href="https://docs.djangoproject.com/en/4.0/ref/models/fields/#verbose-name" rel="external nofollow">verbose_name</a>: اسم يمكن أن يقرأه الإنسان للحقل المُستخدَم في تسميات الحقل، وإذا لم يُحدَّد، فسيستنتج جانغو الاسم المُطوَّل Verbose Name الافتراضي من اسم الحقل.
		</p>
	</li>
	<li>
		<p>
			<a href="https://docs.djangoproject.com/en/4.0/ref/models/fields/#default" rel="external nofollow">default</a>: القيمة الافتراضية للحقل، ويمكن أن يكون هذا الوسيط قيمةً أو كائنًا يمكن استدعاؤه، إذ سيُستدعَى الكائن في هذه الحالة في كل مرة يُنشَأ فيها سجل جديد.
		</p>
	</li>
	<li>
		<p>
			<a href="https://docs.djangoproject.com/en/4.0/ref/models/fields/#null" rel="external nofollow">null</a>: إذا كانت قيمة هذا الوسيط <code>True</code>، فسيخزّن جانغو القيم الفارغة على أنها <code>NULL</code> في قاعدة البيانات للحقول التي يكون ذلك مناسبًا لها، وسيخزّن الحقل من النوع <code>CharField</code> سلسلة نصية فارغة بدلًا من ذلك. القيمة الافتراضية لهذا الوسيط هي <code>False</code>.
		</p>
	</li>
	<li>
		<p>
			<a href="https://docs.djangoproject.com/en/4.0/ref/models/fields/#blank" rel="external nofollow">blank</a>: إذا كانت قيمة هذا الوسيط <code>True</code>، فسيُسمَح للحقل بأن يكون فارغًا في استماراتك. القيمة الافتراضية لهذا الوسيط هي <code>False</code>، أي سيجبرك التحقق من صحة استمارة جانغو على إدخال قيمة. يُستخدَم هذا الحقل عادةً مع القيمة <code>null=True</code>، لأنك إذا أردتَ السماح بقيم فارغة، فأنت تحتاج أيضًا أن تكون قاعدة البيانات قادرة على تمثيلها بصورة مناسبة.
		</p>
	</li>
	<li>
		<p>
			<a href="https://docs.djangoproject.com/en/4.0/ref/models/fields/#choices" rel="external nofollow">choices</a>: مجموعة من الاختيارات للحقل، بحيث إذا توفّرت هذه الاختيارات، فستكون أداة الاستمارة المقابلة الافتراضية هي مربع تحديد لهذه الاختيارات بدلًا من حقل النص المعياري.
		</p>
	</li>
	<li>
		<p>
			<a href="https://docs.djangoproject.com/en/4.0/ref/models/fields/#primary-key" rel="external nofollow">primary_key</a>: إذا كانت قيمة هذا الوسيط <code>True</code>، فسيُضبط الحقل الحالي بوصفه مفتاحًا رئيسيًا للنموذج؛ والمفتاح الرئيسي هو عمود خاص في قاعدة البيانات مخصَّص لتعريف جميع سجلات الجدول المختلفة بطريقة فريدة. إذا لم يُحدَّد أيّ حقل بوصفه مفتاحًا رئيسيًا، فسيضيف جانغو تلقائيًا حقلًا لهذا الغرض. يمكن تحديد نوع حقول المفاتيح الرئيسية المُنشَأة تلقائيًا لكل تطبيق في <a href="https://docs.djangoproject.com/en/4.0/ref/applications/#django.apps.AppConfig.default_auto_field" rel="external nofollow"><code>AppConfig.default_auto_field</code></a> أو بصورة عامة في إعداد <a href="https://docs.djangoproject.com/en/4.0/ref/settings/#std:setting-DEFAULT_AUTO_FIELD" rel="external nofollow"><code>DEFAULT_AUTO_FIELD</code></a>.
		</p>
	</li>
</ul>

<p>
	<strong>ملاحظة</strong>: تضبط التطبيقات المُنشَأة باستخدام manage.py نوعَ المفتاح الرئيسي على النوع <a href="https://docs.djangoproject.com/en/4.0/ref/models/fields/#bigautofield" rel="external nofollow">BigAutoField</a>. يمكنك رؤية ما يلي في الملف catalog/apps.py الخاص بموقع المكتبة المحلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8284_13" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">CatalogConfig</span><span class="pun">(</span><span class="typ">AppConfig</span><span class="pun">):</span><span class="pln">
  default_auto_field </span><span class="pun">=</span><span class="pln"> </span><span class="str">'django.db.models.BigAutoField'</span></pre>

<p>
	هناك العديد من الخيارات الأخرى، إذ يمكنك الاطلاع <a href="https://docs.djangoproject.com/en/4.0/ref/models/fields/#field-options" rel="external nofollow">القائمة الكاملة لخيارات الحقول</a> في توثيق جانغو.
</p>

<p>
	توضح القائمة التالية بعض أنواع الحقول الأكثر استخدامًا:
</p>

<ul>
	<li>
		<p>
			يُستخدَم النوع <a href="https://docs.djangoproject.com/en/4.0/ref/models/fields/#django.db.models.CharField" rel="external nofollow">CharField</a> لتعريف <a href="https://wiki.hsoub.com/Python/str" rel="external">سلاسل نصية</a> ذات طول ثابت قصير إلى متوسط الحجم. يجب عليك تحديد الطول الأقصى <code>max_length</code> للبيانات المراد تخزينها.
		</p>
	</li>
	<li>
		<p>
			يُستخدَم النوع <a href="https://docs.djangoproject.com/en/4.0/ref/models/fields/#django.db.models.TextField" rel="external nofollow">TextField</a> للسلاسل النصية الكبيرة ذات الطول العشوائي. يمكنك تحديد الطول الأقصى <code>max_length</code> للحقل، ولكنه لا يُستخدَم إلا عند عرض الحقل في استمارات (ليس إجباريًا على مستوى قاعدة البيانات).
		</p>
	</li>
	<li>
		<p>
			النوع <a href="https://docs.djangoproject.com/en/4.0/ref/models/fields/#django.db.models.IntegerField" rel="external nofollow">IntegerField</a> هو حقل لتخزين القيم الصحيحة (عدد صحيح)، وللتحقق من صحة القيم المدخَلة بوصفها أعدادًا صحيحة في الاستمارات.
		</p>
	</li>
	<li>
		<p>
			يُستخدَم النوعان <a href="https://docs.djangoproject.com/en/4.0/ref/models/fields/#datefield" rel="external nofollow">DateField</a> و <a href="https://docs.djangoproject.com/en/4.0/ref/models/fields/#datetimefield" rel="external nofollow">DateTimeField</a> لتخزين أو تمثيل التواريخ ومعلومات <a href="https://wiki.hsoub.com/Python/datetime" rel="external">التاريخ/الوقت</a>، مثل كائنات بايثون <code>datetime.date</code> و <code>datetime.datetime</code> على التوالي. يمكن أن تصرِّح هذه الحقول إضافةً لما سبق عن المعاملات (الحصرية فيما بينها) <code>auto_now=True</code> (لضبط الحقل على التاريخ الحالي في كل مرة يُحفَظ فيها النموذج) و <code>auto_now_add</code> (لضبط التاريخ عند إنشاء النموذج لأول مرة فقط) و <code>default</code> (لضبط تاريخ افتراضي يمكن للمستخدم تجاهله).
		</p>
	</li>
	<li>
		<p>
			يُستخدَم النوع <a href="https://docs.djangoproject.com/en/4.0/ref/models/fields/#emailfield" rel="external nofollow">EmailField</a> لتخزين عناوين البريد الإلكتروني والتحقق من صحتها.
		</p>
	</li>
	<li>
		<p>
			يُستخدَم النوعان <a href="https://docs.djangoproject.com/en/4.0/ref/models/fields/#filefield" rel="external nofollow">FileField</a> و <a href="https://docs.djangoproject.com/en/4.0/ref/models/fields/#imagefield" rel="external nofollow">ImageField</a> لتحميل الملفات والصور على التوالي، إذ يضيف الحقل <code>ImageField</code> تحققًا إضافيًا من أن الملف المُحمَّل هو صورة. يمتلك هذان النوعان من الحقول معاملات لتحديد كيفية ومكان تخزين الملفات التي جرى تحميلها.
		</p>
	</li>
	<li>
		<p>
			النوع <a href="https://docs.djangoproject.com/en/4.0/ref/models/fields/#autofield" rel="external nofollow">AutoField</a> هو نوع خاص من الحقل <code>IntegerField</code> الذي يزداد تلقائيًا. يُضاف مفتاح رئيسي من هذا النوع تلقائيًا إلى نموذجك إذا لم تحدده صراحةً.
		</p>
	</li>
	<li>
		<p>
			يُستخدَم النوع <a href="https://docs.djangoproject.com/en/4.0/ref/models/fields/#foreignkey" rel="external nofollow">ForeignKey</a> لتحديد علاقة واحد إلى متعدد بنموذج قاعدة بيانات آخر، فمثلًا للسيارة مُصنِّع واحد، ولكن يمكن للمصنِّع صنع العديد من السيارات. الجانب "واحد" من العلاقة هو النموذج الذي يحتوي على المفتاح، وتشير النماذج التي تحتوي على مفتاح خارجي Foreign Key إلى ذلك المفتاح، إذ تكون هذه النماذج في الجانب "متعدد" من هذه العلاقة.
		</p>
	</li>
	<li>
		<p>
			يُستخدَم النوع <a href="https://docs.djangoproject.com/en/4.0/ref/models/fields/#manytomanyfield" rel="external nofollow">ManyToManyField</a> لتحديد علاقة متعدد إلى متعدد، فمثلًا يمكن أن يكون للكتاب عدة أنواع ويمكن أن يحتوي كل نوع على عدة كتب. سنستخدم هذا النوع في تطبيق المكتبة بطريقة مشابهة جدًا للنوع <code>ForeignKeys</code>، ولكن يمكن استخدامه بطرق أكثر تعقيدًا لوصف العلاقات بين المجموعات. يحتوي هذا النوع على المعامل <code>on_delete</code> لتحديد ما يحدث عند حذف السجل المرتبط به مثل قيمة <code>models.SET_NULL</code> التي تضبط القيمة على <code>NULL</code>.
		</p>
	</li>
</ul>

<p>
	هناك العديد من الأنواع الأخرى من الحقول بما في ذلك حقول أنواع مختلفة من الأعداد (الأعداد الصحيحة الكبيرة والأعداد الصحيحة الصغيرة والأعداد العشرية) والقيم المنطقية وعناوين URL والعناوين الفرعية Slugs والمعرّفات الفريدة وغيرها من المعلومات المتعلقة بالوقت (المدة والوقت وغير ذلك)، ويمكنك الاطلاع على <a href="https://docs.djangoproject.com/en/4.0/ref/models/fields/#field-types" rel="external nofollow">القائمة الكاملة</a> في توثيق جانغو.
</p>

<h4>
	البيانات الوصفية Metadata
</h4>

<p>
	يمكنك التصريح عن البيانات الوصفية على مستوى النموذج من خلال التصريح عن الصنف <code>class Meta</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8284_17" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Meta</span><span class="pun">:</span><span class="pln">
    ordering </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">'-my_field_name'</span><span class="pun">]</span></pre>

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

<p>
	إذا اخترنا مثلًا فرز الكتب افتراضيًا كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8284_19" style=""><span class="pln">ordering </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">'-pubdate'</span><span class="pun">]</span></pre>

<p>
	فستُفرَز الكتب أبجديًا حسب العنوان من A إلى Z، ثم حسب تاريخ النشر ضمن كل عنوان من الأحدث إلى الأقدم.
</p>

<p>
	هناك سمة شائعة أخرى هي <code>verbose_name</code>، وهي اسم مطوَّل للصنف بصيغة المفرد والجمع:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_8284_21" style=""><span class="pln">verbose_name </span><span class="pun">=</span><span class="pln"> </span><span class="str">'BetterName'</span></pre>

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

<p>
	تتحكم العديد من خيارات البيانات الوصفية الأخرى في قاعدة البيانات التي يجب استخدامها مع النموذج وكيفية تخزين البيانات، وهي مفيدة حقًا إذا كنت بحاجة إلى ربط نموذج مع قاعدة بيانات موجودة مسبقًا. يمكنك الاطلاع على القائمة الكاملة لخيارات <a href="https://docs.djangoproject.com/en/4.0/ref/models/options/" rel="external nofollow">بيانات النموذج الوصفية</a> في توثيق جانغو.
</p>

<h4>
	التوابع Methods
</h4>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8284_23" style=""><span class="kwd">def</span><span class="pln"> __str__</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"> self</span><span class="pun">.</span><span class="pln">field_name</span></pre>

<p>
	هناك تابع آخر شائع لتضمينه في نماذج جانغو وهو التابع <code>get_absolute_url()‎</code> الذي يعيد عنوان URL لعرض سجلات النماذج على موقع الويب. إذا حدّدتَ هذا التابع، فسيضيف جانغو تلقائيًا زر "عرض على الموقع View on Site" إلى شاشات تعديل سجل النموذج في موقع المدير. يوضّح ما يلي نمطًا معياريًا للتابع <code>get_absolute_url()‎</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8284_25" style=""><span class="kwd">def</span><span class="pln"> get_absolute_url</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
    </span><span class="str">"""Returns the URL to access a particular instance of the model."""</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> reverse</span><span class="pun">(</span><span class="str">'model-detail-view'</span><span class="pun">,</span><span class="pln"> args</span><span class="pun">=[</span><span class="pln">str</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">id</span><span class="pun">)])</span></pre>

<p>
	<strong>ملاحظة</strong>: يجب إنشاء رابط Mapper عنوان URL لتمرير الاستجابة والمعرّف إلى "عرض النموذج التفصيلي" (الذي سينفّذ العمل المطلوب لعرض السجل) بافتراض أنك ستستخدم عناوين URL، مثل "‎/myapplication/mymodelname/2" لعرض سجلات نموذجك، إذ يمثل "2" معرّف <code>id</code> سجل معين. الدالة <code>reverse()‎</code> السابقة قادرة على عكس رابط عنوان URL (المسماة في الحالة المذكورة سابقًا باسم نموذج-تفاصيل-عرض 'model-detail-View') لإنشاء عنوان URL بالتنسيق الصحيح، ويجب عليك كتابة ربط عنوان URL والعرض والقالب.
</p>

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

<h3>
	إدارة النموذج
</h3>

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

<h4>
	إنشاء وتعديل السجلات
</h4>

<p>
	يمكنك إنشاء سجل من خلال تعريف نسخة من النموذج ثم استدعاء الدالة <code>save()‎</code>.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8284_27" style=""><span class="com"># ‫أنشِئ سجلًا جديدًا باستخدام باني النموذج</span><span class="pln">
record </span><span class="pun">=</span><span class="pln"> </span><span class="typ">MyModelName</span><span class="pun">(</span><span class="pln">my_field_name</span><span class="pun">=</span><span class="str">"Instance #1"</span><span class="pun">)</span><span class="pln">

</span><span class="com"># احفظ الكائن في قاعدة البيانات</span><span class="pln">
record</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span></pre>

<p>
	<strong>ملاحظة</strong>: إذا لم تصرّح عن أي حقل <code>primary_key</code>، فسيُمنَح السجل الجديد حقلًا من هذا النوع تلقائيًا مع معرّف <code>id</code> اسم الحقل، إذ يمكنك الاستعلام عن هذا الحقل بعد حفظ السجل السابق، وسيكون له القيمة 1. يمكنك الوصول إلى الحقول في هذا السجل الجديد باستخدام الصيغة النقطية وتغيير القيم، ويجب عليك استدعاء الدالة <code>save()‎</code> لتخزين القيم المُعدَّلة في قاعدة البيانات.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8284_29" style=""><span class="com"># الوصول إلى قيم حقل النموذج باستخدام سمات بايثون</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="pln">record</span><span class="pun">.</span><span class="pln">id</span><span class="pun">)</span><span class="pln"> </span><span class="com"># يجب أن تعيد القيمة 1 للسجل الأول</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="pln">record</span><span class="pun">.</span><span class="pln">my_field_name</span><span class="pun">)</span><span class="pln"> </span><span class="com"># ‫يجب أن تطبع 'Instance #1'</span><span class="pln">

</span><span class="com"># ‫غيّر السجل من خلال تعديل الحقول ثم استدعِ save()‎</span><span class="pln">
record</span><span class="pun">.</span><span class="pln">my_field_name </span><span class="pun">=</span><span class="pln"> </span><span class="str">"New Instance Name"</span><span class="pln">
record</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span></pre>

<h4>
	البحث عن السجلات
</h4>

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

<p>
	<strong>ملاحظة</strong>: يمكن أن يكون شرح كيفية البحث عن السجلات باستخدام أسماء الحقول والنماذج المجردة مربكًا قليلًا، إذ سنشير في المناقشة الآتية إلى النموذج <code>Book</code> باستخدام الحقلين <code>title</code> و <code>genre</code>، بحيث يكون النوع <code>genre</code> نموذجًا له حقل <code>name</code> واحد.
</p>

<p>
	يمكننا الحصول على جميع سجلات النموذج بوصفها كائن <code>QuerySet</code> باستخدام الدالة <code>objects.all()‎</code>، إذ يُعَد <code>QuerySet</code> كائنًا تكراريًا، مما يعني أنه يحتوي على عدد من الكائنات الممكن تكرارها.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8284_31" style=""><span class="pln">all_books </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Book</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">all</span><span class="pun">()</span></pre>

<p>
	تسمح لنا الدالة <code>filter()‎</code> من جانغو بترشيح الكائن <code>QuerySet</code> المُعاد لمطابقة <strong>نص محدّد</strong>، أو حقل <strong>عددي</strong> مع معايير معينة، فمثلًا يمكننا تطبيق ما يلي لترشيح الكتب التي تحتوي على الكلمة "wild" في عنوانها ثم عَدّها:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8284_33" style=""><span class="pln">wild_books </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Book</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">title__contains</span><span class="pun">=</span><span class="str">'wild'</span><span class="pun">)</span><span class="pln">
number_wild_books </span><span class="pun">=</span><span class="pln"> wild_books</span><span class="pun">.</span><span class="pln">count</span><span class="pun">()</span></pre>

<p>
	تُعرَّف الحقول المراد مطابقتها ونوع التطابق في اسم معامل الترشيح باستخدام التنسيق <code>field_name__match_type</code>، ويمكنك ملاحظة الشرطة السفلية المزدوجة بين <code>title</code> و <code>contains</code>. رشّحنا <code>title</code> باستخدام مطابقة حساسة لحالة الأحرف، وهناك العديد من أنواع التطابقات الأخرى الممكن إجراؤها، مثل <code>icontains</code> (غير حساسة لحالة الأحرف) و <code>iexact</code> (مطابقة تامة غير حساسة لحالة الأحرف) و <code>exact</code> (مطابقة تامة حساسة لحالة الأحرف) و <code>in</code> و <code>gt</code> (أكبر من) و <code>startswith</code> وغير ذلك. يمكنك الاطلاع على <a href="https://docs.djangoproject.com/en/4.0/ref/models/querysets/#field-lookups" rel="external nofollow">القائمة الكاملة</a> في توثيق جانغو.
</p>

<p>
	ستحتاج في بعض الحالات إلى ترشيح حقل يحدّد علاقة واحد إلى متعدد مع نموذج آخر، مثل <code>ForeignKey</code>، إذ يمكنك في هذه الحالة "فهرسة Index" الحقول ضمن النموذج المتعلق بها باستخدام شرطات سفلية مزدوجة إضافية، لذلك سيتعين عليك فهرسة الاسم <code>name</code> من خلال الحقل <code>genre</code> لترشيح كتب ذات نمط نوع معين كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8284_35" style=""><span class="com"># Will match on: Fiction, Science fiction, non-fiction etc.</span><span class="pln">
books_containing_genre </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Book</span><span class="pun">.</span><span class="pln">objects</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">genre__name__icontains</span><span class="pun">=</span><span class="str">'fiction'</span><span class="pun">)</span></pre>

<p>
	<strong>ملاحظة</strong>: يمكنك استخدام الشرطين السفليتين <code>__</code> للتنقل عبر العديد من مستويات العلاقات <code>ForeignKey</code>/<code>ManyToManyField</code> كما تريد، فمثلًا يمكن أن يكون للكتاب <code>Book</code> الذي له أنواع مختلفة ومُعرَّف باستخدام علاقة "cover" معامل اسمٍ هو <code>type__cover__name__exact='hard'‎</code>.
</p>

<p>
	هناك الكثير من الأشياء الممكن فعلها باستخدام الاستعلامات، مثل عمليات البحث العكسية من النماذج ذات الصلة وتسلسل المرشّحات وإعادة مجموعة أصغر من القيم وغير ذلك. اطلع على <a href="https://docs.djangoproject.com/en/4.0/topics/db/queries/" rel="external nofollow">إجراء الاستعلامات</a> في توثيق جانغو لمزيد من المعلومات.
</p>

<h2>
	تعريف نماذج المكتبة المحلية LocalLibrary
</h2>

<p>
	سنبدأ في هذا القسم بتعريف نماذج المكتبة، لذا افتح الملف "models.py" ضمن المجلد "/locallibrary/catalog/". تستورد الشيفرة البرمجية المتداولة الموجودة في أعلى الصفحة الوحدة <code>models</code> التي تحتوي على صنف النموذج الأساسي <code>models.Model</code> الذي سترثه نماذجنا.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8284_37" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">db </span><span class="kwd">import</span><span class="pln"> models

</span><span class="com"># أنشِئ نماذجك هنا</span></pre>

<h3>
	النموذج Genre
</h3>

<p>
	انسخ شيفرة النموذج <code>Genre</code> البرمجية التالية والصقه في نهاية الملف "models.py" الخاص بك. يُستخدَم هذا النموذج لتخزين المعلومات حول فئة الكتاب مثل كونه خياليًا أو غير خيالي، أو عاطفيًا أو تاريخًا وغير ذلك. أنشأنا النوع genre بوصفه نموذجًا وليس نصًا حرًا أو قائمة اختيار بحيث يمكن إدارة القيم الممكنة عبر قاعدة البيانات بدلًا من أن تكون شيفرة ثابتة.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8284_39" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Genre</span><span class="pun">(</span><span class="pln">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
    </span><span class="str">"""Model representing a book genre."""</span><span class="pln">
    name </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">200</span><span class="pun">,</span><span class="pln"> help_text</span><span class="pun">=</span><span class="str">'Enter a book genre (e.g. Science Fiction)'</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> __str__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="str">"""String for representing the Model object."""</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">name</span></pre>

<p>
	يحتوي النموذج على حقل واحد من النوع <code>CharField</code> هو <code>name</code>، بحيث يُستخدَم لوصف نوع الكتاب وهو مُحدَّد ليحتوي على 200 محرف ولديه بعض نصوص التعليمات <code>help_text</code>. صرّحنا في نهاية النموذج عن التابع <code>‎__str__()‎</code> الذي يعيد اسم النوع الذي يحدّده سجل معين. لم يُحدَّد أيّ اسم مطوَّل لذلك سيُسمَّى الحقل بالاسم <code>Name</code> في الاستمارات.
</p>

<h3>
	النموذج Book
</h3>

<p>
	انسخ النموذج <code>Book</code> التالي والصقه في نهاية ملفك، إذ يمثل هذا النموذج جميع المعلومات حول الكتاب المتاح بالمعنى العام، وليس مثيلًا أو نسخةً ماديةً معينة متاحة للإعارة. يستخدم النموذجُ النوعَ <code>CharField</code> لتمثيل الحقلين <code>title</code> و <code>isbn</code> الخاصَين بالكتاب، ولاحظ كيف يضبط المعامل الأول غير المُسمَّى للحقل <code>isbn</code> التسميةَ بصورة صريحة على القيمة "ISBN" وإلّا فستُضبَط افتراضيًا على القيمة "Isbn". ضبطنا المعامل <code>unique</code> على القيمة <code>true</code> لضمان حصول جميع الكتب على رقم ISBN فريد، إذ يجعل المعامل <code>unique</code> قيمة الحقل فريدةً بصورة عامة في الجدول. يستخدم النموذج النوع <code>TextField</code> للحقل <code>summary</code>، لأن هذا النص يمكن أن يكون طويلًا جدًا.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8284_41" style=""><span class="com"># يُستخدَم لإنشاء عناوين‫ URL من خلال عكس أنماط عنوان URL</span><span class="pln">
</span><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">urls </span><span class="kwd">import</span><span class="pln"> reverse 

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Book</span><span class="pun">(</span><span class="pln">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
    </span><span class="str">"""Model representing a book (but not a specific copy of a book)."""</span><span class="pln">
    title </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">200</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># اُستخدِم المفتاح الخارجي لأنه لا يمكن أن يكون للكتاب سوى مؤلف واحد، ولكن يمكن أن يكون للمؤلفين عدة كتب</span><span class="pln">
    </span><span class="com"># المؤلف هو سلسلة نصية وليس كائنًا بسبب عدم النصريح عنه بعد في الملف</span><span class="pln">
    author </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">ForeignKey</span><span class="pun">(</span><span class="str">'Author'</span><span class="pun">,</span><span class="pln"> on_delete</span><span class="pun">=</span><span class="pln">models</span><span class="pun">.</span><span class="pln">SET_NULL</span><span class="pun">,</span><span class="pln"> null</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">

    summary </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">TextField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">1000</span><span class="pun">,</span><span class="pln"> help_text</span><span class="pun">=</span><span class="str">'Enter a brief description of the book'</span><span class="pun">)</span><span class="pln">
    isbn </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="str">'ISBN'</span><span class="pun">,</span><span class="pln"> max_length</span><span class="pun">=</span><span class="lit">13</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">
                             help_text</span><span class="pun">=</span><span class="str">'13 Character &lt;a href="https://www.isbn-international.org/content/what-isbn"&gt;ISBN number&lt;/a&gt;'</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># استُخدم حقل‫ ManyToManyField لأن النوع genre يمكن أن يحتوي على العديد من الكتب، ويمكن أن تغطي الكتب العديد من الأنواع.‫</span><span class="pln">
    </span><span class="com"># ‫عُرِّف الصنف Genre، وبالتالي يمكننا تحديد الكائن السابق.</span><span class="pln">
    genre </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">ManyToManyField</span><span class="pun">(</span><span class="typ">Genre</span><span class="pun">,</span><span class="pln"> help_text</span><span class="pun">=</span><span class="str">'Select a genre for this book'</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> __str__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="str">"""String for representing the Model object."""</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">title

    </span><span class="kwd">def</span><span class="pln"> get_absolute_url</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="str">"""Returns the URL to access a detail record for this book."""</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> reverse</span><span class="pun">(</span><span class="str">'book-detail'</span><span class="pun">,</span><span class="pln"> args</span><span class="pun">=[</span><span class="pln">str</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">id</span><span class="pun">)])</span></pre>

<p>
	يكون الحقل <code>genre</code> من النوع <code>ManyToManyField</code>، بحيث يمكن أن يكون للكتاب أنواع متعددة ويمكن أن يحتوي النوع على العديد من الكتب. صُرِّح عن المؤلف على أنه من النوع <code>ForeignKey</code>، لذلك سيكون لكل كتاب مؤلف واحد فقط، ولكن يمكن أن يكون للمؤلف العديد من الكتب. يمكن أن يكون للكتاب مؤلفِين متعددين عمليًا، ولكن هذا غير ممكن في هذا التطبيق.
</p>

<p>
	يُصرَّح عن صنف النموذج ذي الصلة في كلا هذين النوعين من الحقول بوصفه أول معامل غير مسمًى باستخدام صنف النموذج أو سلسلة نصية تحتوي على اسم النموذج المرتبط بها، إذ يجب استخدام اسم النموذج بوصفه سلسلة نصية إذا لم يُعرَّف الصنف المرتبط به في هذا الملف قبل الإشارة إليه. المعاملات الأخرى ذات الأهمية في حقل المؤلف <code>author</code> هي <code>null=True</code> الذي يسمح لقاعدة البيانات بتخزين قيمة <code>Null</code> عند عدم تحديد أي مؤلف، و <code>on_delete=models.SET_NULL</code> الذي سيحدد قيمة حقل مؤلف الكتاب إلى <code>Null</code> عند حذف سجل المؤلف المرتبط به.
</p>

<p>
	<strong>تحذير</strong>: يكون المعامل <code>on_delete=models.CASCADE</code> افتراضيًا، مما يعني أنه إذا حُذِف المؤلف، فسيُحذَف هذا الكتاب أيضًا. استخدمنا <code>SET_NULL</code> هنا، ولكن يمكننا أيضًا استخدام <code>PROTECT</code> أو <code>RESTRICT</code> لمنع حذف المؤلف أثناء استخدام أيّ كتاب له.
</p>

<p>
	يعرّف النموذج أيضًا التابعَ <code>‎__str__()‎</code> باستخدام حقل عنوان الكتاب <code>title</code> لتمثيل سجل <code>Book</code>، إذ يعيد التابع الأخير <code>get_absolute_url()‎</code> عنوان URL يمكن استخدامه للوصول إلى سجل تفصيلي لهذا النموذج، إذ يجب لتحقيق ذلك تعريف ربط لعنوان URL له الاسم <code>book-detail</code> وتعريف عرض وقالب مرتبط به.
</p>

<h3>
	النموذج BookInstance
</h3>

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

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

<ul>
	<li>
		<p>
			حقلًا من النوع <code>ForeignKey</code> لتحديد النموذج <code>Book</code> المرتبط به (يمكن أن يكون لكل كتاب نسخ متعددة، ولكن يمكن أن يكون للنسخة كتاب <code>Book</code> واحد فقط). يحدِّد المفتاح <code>on_delete=models.RESTRICT</code> لضمان عدم إمكانية حذف النموذج <code>Book</code> عندما يشير إليه النموذج <code>BookInstance</code>.
		</p>
	</li>
	<li>
		<p>
			حقلًا من النوع <code>CharField</code> لتمثيل الطبعة (إصدار محدد) للكتاب.
		</p>
	</li>
</ul>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8284_43" style=""><span class="kwd">import</span><span class="pln"> uuid </span><span class="com"># مطلوب لنسخ الكتاب الفريدة</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">BookInstance</span><span class="pun">(</span><span class="pln">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
    </span><span class="str">"""Model representing a specific copy of a book (i.e. that can be borrowed from the library)."""</span><span class="pln">
    id </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">UUIDField</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"> default</span><span class="pun">=</span><span class="pln">uuid</span><span class="pun">.</span><span class="pln">uuid4</span><span class="pun">,</span><span class="pln"> help_text</span><span class="pun">=</span><span class="str">'Unique ID for this particular book across whole library'</span><span class="pun">)</span><span class="pln">
    book </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">ForeignKey</span><span class="pun">(</span><span class="str">'Book'</span><span class="pun">,</span><span class="pln"> on_delete</span><span class="pun">=</span><span class="pln">models</span><span class="pun">.</span><span class="pln">RESTRICT</span><span class="pun">,</span><span class="pln"> null</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
    imprint </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">200</span><span class="pun">)</span><span class="pln">
    due_back </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">DateField</span><span class="pun">(</span><span class="pln">null</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">,</span><span class="pln"> blank</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">

    LOAN_STATUS </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
        </span><span class="pun">(</span><span class="str">'m'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Maintenance'</span><span class="pun">),</span><span class="pln">
        </span><span class="pun">(</span><span class="str">'o'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'On loan'</span><span class="pun">),</span><span class="pln">
        </span><span class="pun">(</span><span class="str">'a'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Available'</span><span class="pun">),</span><span class="pln">
        </span><span class="pun">(</span><span class="str">'r'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Reserved'</span><span class="pun">),</span><span class="pln">
    </span><span class="pun">)</span><span class="pln">

    status </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">
        max_length</span><span class="pun">=</span><span class="lit">1</span><span class="pun">,</span><span class="pln">
        choices</span><span class="pun">=</span><span class="pln">LOAN_STATUS</span><span class="pun">,</span><span class="pln">
        blank</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">,</span><span class="pln">
        default</span><span class="pun">=</span><span class="str">'m'</span><span class="pun">,</span><span class="pln">
        help_text</span><span class="pun">=</span><span class="str">'Book availability'</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">Meta</span><span class="pun">:</span><span class="pln">
        ordering </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">'due_back'</span><span class="pun">]</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> __str__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="str">"""String for representing the Model object."""</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> f</span><span class="str">'{self.id} ({self.book.title})'</span></pre>

<p>
	نصرّح إضافةً إلى ذلك عن بعض الأنواع الجديدة من الحقول وهي:
</p>

<ul>
	<li>
		<p>
			يُستخدَم النوع <code>UUIDField</code> للحقل <code>id</code> لضبطه بوصفه مفتاحًا رئيسيًا <code>primary_key</code> لهذا النموذج. يخصِّص هذا النوع من الحقول قيمةً فريدةً عامة لكل نسخة (قيمة لكل كتاب تجده في المكتبة).
		</p>
	</li>
	<li>
		<p>
			يُستخدَم النوع <code>DateField</code> للتاريخ <code>due_back</code>، أي التاريخ المتوقع ليصبح فيه الكتاب متاحًا بعد استعارته أو صيانته، إذ يمكن أن تكون القيمة <code>blank</code> أو <code>null</code> (مطلوبة عندما يكون الكتاب متاحًا). تستخدم بيانات النموذج الوصفية (<code>Class Meta</code>) هذا الحقل لترتيب السجلات عند إعادتها ضمن استعلام.
		</p>
	</li>
	<li>
		<p>
			الحقل <code>status</code> من النوع <code>CharField</code> الذي يعرّف قائمة الاختيار أو الخيارات. نعرّف كما ترى صفًا يحتوي على صفوف من أزواج مفتاح-قيمة ونمرّرها إلى وسيط الاختيارات. تُعَد القيمة في زوج مفتاح/قيمة قيمة عرض display value يمكن للمستخدم تحديدها، بينما تكون المفاتيح هي القيم المحفوظة عند تحديد الخيار. ضبطنا قيمة افتراضية هي "m" (للصيانة Maintenance) عند إنشاء الكتب في البداية إذ تكون غير متوفرة قبل تخزينها على الرفوف.
		</p>
	</li>
</ul>

<p>
	يمثل التابع <code>‎__str__()‎</code> كائن <code>BookInstance</code> باستخدام مجموعة من المعرّف الفريد وعنوان الكتاب <code>Book</code> المرتبط به.
</p>

<p>
	<strong>ملاحظة</strong>: إليك بعض المعلومات عن لغة بايثون:
</p>

<ul>
	<li>
		يمكنك بدءًا من الإصدار 3.6 للغة بايثون استخدام صيغة توليد السلاسل النصية (المعروفة أيضًا باسم f-strings) بالشكل التالي:
	</li>
</ul>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8284_45" style=""><span class="pln">f</span><span class="str">'{self.id} ({self.book.title})'</span><span class="pun">‎</span></pre>

<ul>
	<li>
		استخدمنا سابقًا صيغة <a href="https://peps.python.org/pep-3101/" rel="external nofollow">السلاسل النصية المُنسَّقة Formatted String</a> التي تُعَد طريقةً صالحةً لتنسيق السلاسل النصية في لغة بايثون، مثل <code>‎'{0} ({1})'.format(self.id,self.book.title)‎</code>.
	</li>
</ul>

<h3>
	النموذج Author
</h3>

<p>
	انسخ نموذج <code>Author</code> التالي بعد الشيفرة البرمجية الموجودة في الملف models.py:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8284_47" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Author</span><span class="pun">(</span><span class="pln">models</span><span class="pun">.</span><span class="typ">Model</span><span class="pun">):</span><span class="pln">
    </span><span class="str">"""Model representing an author."""</span><span class="pln">
    first_name </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">100</span><span class="pun">)</span><span class="pln">
    last_name </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">CharField</span><span class="pun">(</span><span class="pln">max_length</span><span class="pun">=</span><span class="lit">100</span><span class="pun">)</span><span class="pln">
    date_of_birth </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">DateField</span><span class="pun">(</span><span class="pln">null</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">,</span><span class="pln"> blank</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
    date_of_death </span><span class="pun">=</span><span class="pln"> models</span><span class="pun">.</span><span class="typ">DateField</span><span class="pun">(</span><span class="str">'Died'</span><span class="pun">,</span><span class="pln"> null</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">,</span><span class="pln"> blank</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Meta</span><span class="pun">:</span><span class="pln">
        ordering </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">'last_name'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'first_name'</span><span class="pun">]</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> get_absolute_url</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="str">"""Returns the URL to access a particular author instance."""</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> reverse</span><span class="pun">(</span><span class="str">'author-detail'</span><span class="pun">,</span><span class="pln"> args</span><span class="pun">=[</span><span class="pln">str</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">id</span><span class="pun">)])</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> __str__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="str">"""String for representing the Model object."""</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> f</span><span class="str">'{self.last_name}, {self.first_name}'</span></pre>

<p>
	يجب أن تكون جميع الحقول والتوابع مألوفةً الآن، إذ يعرِّف هذا النموذج مؤلفًا يحمل اسمًا أول واسم عائلة وتاريخ ميلاد ووفاة (كلاهما اختياري)، ويحدّد أنّ التابع <code>‎__str__()‎</code> افتراضيًا يعيد الاسم بالترتيب: اسم العائلة last name ثم الاسم الأول firstname. يعكس التابع <code>get_absolute_url()‎</code> ربط عنوان URL لتفاصيل المؤلف <code>author-detail</code> للحصول على عنوان URL لعرض مؤلف واحد.
</p>

<h2>
	إعادة تشغيل عمليات تهجير قاعدة البيانات
</h2>

<p>
	أنشأتَ حتى الآن جميع نماذجك، لذا أعِد تشغيل عمليات تهجير قاعدة البيانات لإضافتها إلى قاعدة بياناتك كما يلي:
</p>

<pre class="ipsCode" id="ips_uid_8284_51">python3 manage.py makemigrations
python3 manage.py migrate</pre>

<h2>
	النموذج Language- تحدي
</h2>

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

<p>
	إليك بعض الأشياء التي يجب مراعاتها:
</p>

<ul>
	<li>
		هل يجب ربط اللغة بالنموذج <code>Book</code> أو <code>BookInstance</code> أو أيّ كائن آخر؟
	</li>
	<li>
		هل يجب تمثيل اللغات المختلفة باستخدام نموذج، أم حقل نص حر، أم قائمة اختيار ثابتة؟
	</li>
</ul>

<p>
	أضف الحقل بعد أن تقرر ما تريده، ويمكنك أن ترى ما قررناه على <a href="https://github.com/mdn/django-locallibrary-tutorial/blob/main/catalog/models.py" rel="external nofollow">GitHub</a>.
</p>

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

<pre class="ipsCode">python3 manage.py makemigrations
python3 manage.py migrate
</pre>

<h2>
	الخلاصة
</h2>

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

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

<p>
	ترجمة -وبتصرُّف- للمقال <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Models" rel="external nofollow">Django Tutorial Part 3: Using models</a>.
</p>

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

<ul>
	<li>
		المقال التالي: <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D9%85%D9%88%D9%82%D8%B9-%D9%85%D8%AF%D9%8A%D8%B1-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-admin-r2105/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin</a>
	</li>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2090/" rel="">تطبيق عملي لتعلم إطار عمل جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%B4%D9%8A%D9%81%D8%B1%D8%A7%D8%AA-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D8%B5%D9%8A%D8%BA-%D8%B4%D8%A7%D8%A6%D8%B9%D8%A9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B9%D9%84%D9%89-%D9%86%D8%AD%D9%88-%D8%AE%D8%A7%D8%B7%D8%A6-r1971/" rel="">كتابة شيفرات بايثون: صيغ شائعة الاستخدام على نحو خاطئ</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%88%D8%AA%D9%88%D8%B5%D9%8A%D9%84%D9%87-%D8%A8%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-r1626/" rel="">إنشاء تطبيق جانغو وتوصيله بقاعدة بيانات</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D9%84%D9%86%D9%85%D8%A7%D8%B0%D8%AC-models-%D9%88%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D8%B9%D9%84%D8%A7%D9%85-%D8%B9%D9%86-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%81%D9%8A-django-r405/" rel="">النماذج Models والاستعلام عن البيانات في جانغو</a>
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/intro/tutorial02/" rel="external nofollow">كتابة أول تطبيق جانغو - الجزء 2</a> (توثيق جانغو)
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/topics/db/queries/" rel="external nofollow">إجراء الاستعلامات</a> (توثيق جانغو)
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/ref/models/querysets/" rel="external nofollow">مرجع واجهة برمجة تطبيقات QuerySet</a> (توثيق جانغو)
	</li>
</ul>
]]></description><guid isPermaLink="false">2092</guid><pubDate>Sun, 27 Aug 2023 16:04:00 +0000</pubDate></item><item><title>&#x62A;&#x637;&#x628;&#x64A;&#x642; &#x639;&#x645;&#x644;&#x64A; &#x644;&#x62A;&#x639;&#x644;&#x645; &#x625;&#x637;&#x627;&#x631; &#x639;&#x645;&#x644; &#x62C;&#x627;&#x646;&#x63A;&#x648; - &#x627;&#x644;&#x62C;&#x632;&#x621; &#x627;&#x644;&#x623;&#x648;&#x644;: &#x625;&#x646;&#x634;&#x627;&#x621; &#x645;&#x648;&#x642;&#x639; &#x648;&#x64A;&#x628; &#x647;&#x64A;&#x643;&#x644;&#x64A; &#x644;&#x645;&#x643;&#x62A;&#x628;&#x629; &#x645;&#x62D;&#x644;&#x64A;&#x629;</title><link>https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-%D9%87%D9%8A%D9%83%D9%84%D9%8A-%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-r2090/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_08/----------------.png.8baddb5b0f106b664d4b3c5fea1a521d.png" /></p>
<p>
	يشرح هذا المقال ما ستتعلمه لبناء موقع ويب باستخدام إطار عمل جانغو Django، ويوفر نظرةً عامةً على مثال موقع المكتبة المحلية الذي سنعمل عليه ونطوّره في المقالات اللاحقة، وسنوضّح كيفية إنشاء مشروع موقع ويب هيكلي skeleton يمثّل الأساس الذي يمكنك ملؤه لاحقًا بالإعدادات والمسارات والنماذج Models والعروض Views والقوالب Templates الخاصة بالموقع.
</p>

<ul>
	<li>
		<strong>المتطلبات الأساسية</strong>: قراءة <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="">مقال مدخل إلى إطار عمل جانغو</a>، كما يجب <a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%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%AC%D8%A7%D9%86%D8%BA%D9%88-django-r2049/" rel="">إعداد بيئة تطوير جانغو</a>.
	</li>
	<li>
		<strong>الهدف</strong>: مقدمة إلى التطبيق العملي الذي سنبنيه في المقالات اللاحقة، وفهم الموضوعات التي سنتناولها، والقدرة على استخدام أدوات جانغو لبدء مشروعات مواقع الويب الجديدة الخاصة بك.
	</li>
</ul>

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

<ul>
	<li>
		استخدم أدوات جانغو لإنشاء موقع وتطبيق هيكلي.
	</li>
	<li>
		بدء وإيقاف خادم التطوير.
	</li>
	<li>
		إنشاء نماذج لتمثيل بيانات تطبيقك.
	</li>
	<li>
		استخدام موقع مدير جانغو لتعبئة بيانات موقعك.
	</li>
	<li>
		إنشاء عروض لاسترجاع بيانات محددة استجابةً لطلبات مختلفة، وإنشاء قوالب لعرض البيانات بتنسيق HTML في المتصفح.
	</li>
	<li>
		إنشاء روابط Mappers لربط أنماط عناوين URL المختلفة مع عروض محددة.
	</li>
	<li>
		إضافة تصريح المستخدم والجلسات للتحكم في سلوك الموقع والوصول إليه.
	</li>
	<li>
		العمل مع الاستمارات Forms.
	</li>
	<li>
		كتابة شيفرة اختبار تطبيقك البرمجية.
	</li>
	<li>
		استخدام أمان جانغو بفعالية.
	</li>
	<li>
		نشر تطبيقك في بيئة الإنتاج.
	</li>
</ul>

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

<p>
	تتألف هذه السلسلة الفرعية من السلسلة الأشمل <a href="https://academy.hsoub.com/tags/%D8%AA%D8%B9%D9%84%D9%85%20%D8%AA%D8%B7%D9%88%D9%8A%D8%B1%20%D8%A7%D9%84%D9%88%D9%8A%D8%A8/" rel="">تعلم تطوير الويب</a> من المقالات التالية:
</p>

<ul>
	<li>
		<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>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%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%AC%D8%A7%D9%86%D8%BA%D9%88-django-r2049/" rel="">إعداد بيئة تطوير تطبيقات جانغو</a>
	</li>
	<li>
		<span ipsnoautolink="true">تطبيق عملي لتعلم جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية</span>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%86%D9%85%D8%A7%D8%B0%D8%AC-models-r2092/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D9%85%D9%88%D9%82%D8%B9-%D9%85%D8%AF%D9%8A%D8%B1-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-admin-r2105/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B1%D8%A7%D8%A8%D8%B9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B5%D9%81%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%A7%D9%84%D8%B1%D8%A6%D9%8A%D8%B3%D9%8A%D8%A9-r2106/" rel="">تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AE%D8%A7%D9%85%D8%B3-%D8%A7%D9%84%D8%B9%D8%B1%D9%88%D8%B6-views-%D8%A7%D9%84%D8%B9%D8%A7%D9%85%D8%A9-%D9%88%D8%A7%D9%84%D8%AA%D9%81%D8%B5%D9%8A%D9%84%D9%8A%D8%A9-r2107/" rel="">تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B3%D8%A7%D8%AF%D8%B3-%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D8%AC%D9%84%D8%B3%D8%A7%D8%AA-sessions-r2118/" rel="">تطبيق عملي لتعلم جانغو - الجزء السادس: إدارة الجلسات Sessions</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%B3%D8%A7%D8%A8%D8%B9-%D8%A7%D8%B3%D8%AA%D9%8A%D8%AB%D8%A7%D9%82-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D9%8A%D9%86-%D9%88%D8%A3%D8%B0%D9%88%D9%86%D8%A7%D8%AA%D9%87%D9%85-r2119/" rel="">تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%85%D9%86-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-forms-r2120/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AA%D8%A7%D8%B3%D8%B9-%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2121/" rel="">تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-10-%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%81%D9%8A-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%A7%D8%AC-r2122/" rel="">تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A3%D9%85%D8%A7%D9%86-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-r2123/" rel="">تعرف على أمان تطبيقات جانغو</a>
	</li>
</ul>

<h2>
	موقع المكتبة المحلية LocalLibrary
</h2>

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

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

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

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

<p>
	سنوفر في هذه السلسلة من المقالات مقتطفات من الشيفرة البرمجية المناسبة لك لنسخها ولصقها في كل مرحلة، وستكون هناك شيفرة برمجية أخرى نأمل أن توسّعها بنفسك مع بعض الإرشادات. إذا واجهتك مشكلة، فيمكنك العثور على كامل النسخة المُطوَّرة من موقع الويب على <a href="https://github.com/mdn/django-locallibrary-tutorial" rel="external nofollow">غيت هب GitHub</a>.
</p>

<p>
	حان الوقت لبدء إنشاء مشروع هيكلي بعد أن تعرّفت على موقع المكتبة المحلية وما ستتعلمه.
</p>

<h2>
	إنشاء موقع ويب هيكلي
</h2>

<p>
	سنوضح الآن كيفية إنشاء موقع ويب هيكلي يمكنك ملؤه لاحقًا بإعدادات ومسارات ونماذج وعروض وقوالب (سنناقشها في مقالات لاحقة) خاصة بالموقع.
</p>

<p>
	اتبع الخطوات التالية للبدء:
</p>

<ol>
	<li>
		استخدم أداة <code>django-admin</code> لإنشاء مجلد المشروع وقوالب الملفات الأساسية والملف manage.py الذي يعمل بوصفه سكربتًا لإدارة المشروع.
	</li>
	<li>
		استخدم manage.py لإنشاء تطبيق واحد أو أكثر.
	</li>
	<li>
		سجّل التطبيقات الجديدة لتضمينها في المشروع.
	</li>
	<li>
		صِل رابط Mapper عنوان url / المسار path لكل تطبيق.
	</li>
</ol>

<p>
	<strong>ملاحظة</strong>: يمكن أن يتكون موقع الويب من قسم واحد أو أكثر، مثل الموقع الرئيسي والمدونة والويكي wiki ومنطقة التنزيل download area وغيرها. يشجعك إطار عمل جانغو على تطوير هذه المكونات بوصفها تطبيقات منفصلة، بحيث يمكن إعادة استخدامها لاحقًا في مشاريع مختلفة إذا رغبت في ذلك.
</p>

<p>
	نسمّي موقع ويب المكتبة المحلية ومجلدات المشروع بالاسم "locallibrary" ويتضمن تطبيقًا واحدًا يسمى "catalog"، لذا ستكون بنية مجلد المستوى الأعلى كما يلي:
</p>

<pre class="ipsCode">locallibrary/         # مجلد موقع الويب
    manage.py         # ‫سكربت لتشغيل أدوات جانغو لهذا المشروع (أُنشِئ باستخدام django-admin)
    locallibrary/     # ‫مجلد الموقع أو المشروع (أُنشِئ باستخدام django-admin)
    catalog/          # ‫مجلد التطبيق (أُنشِئ باستخدام manage.py)
</pre>

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

<h3>
	إنشاء المشروع
</h3>

<p>
	يمكنك إنشاء المشروع باتباع الخطوات التالية:
</p>

<p>
	أولًا، افتح صدفة الأوامر Command Shell أو نافذة طرفية Terminal، وتأكد من أنك في <a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%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%AC%D8%A7%D9%86%D8%BA%D9%88-django-r2049/" rel="">بيئتك الافتراضية</a>.
</p>

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

<pre class="ipsCode">mkdir django_projects
cd django_projects
</pre>

<p>
	ثالثًا، أنشئ المشروع الجديد باستخدام الأمر <code>django-admin startproject</code>، ثم انتقل إلى مجلد المشروع كما يلي:
</p>

<pre class="ipsCode">django-admin startproject locallibrary
cd locallibrary
</pre>

<p>
	تنشئ الأداة <code>django-admin</code> بنية مجلد أو ملف على النحو التالي:
</p>

<pre class="ipsCode">locallibrary/
    manage.py
    locallibrary/
        __init__.py
        settings.py
        urls.py
        wsgi.py
        asgi.py
</pre>

<p>
	يجب أن يبدو مجلد العمل الحالي كما يلي:
</p>

<pre class="ipsCode">../django_projects/locallibrary/
</pre>

<p>
	يُعَد مجلد المشروع الفرعي locallibrary نقطة الدخول إلى موقع الويب، إذ يحتوي على الملفات التالية:
</p>

<ul>
	<li>
		<p>
			"‎<strong>init</strong>.py"، وهو ملف فارغ يوجّه لغة بايثون للتعامل مع هذا المجلد بوصفه حزمة بايثون.
		</p>
	</li>
	<li>
		<p>
			يحتوي الملف "settings.py" على جميع إعدادات موقع الويب بما في ذلك تسجيل أيّ تطبيقات ننشئها، وموقع ملفاتنا الساكنة، وتفاصيل إعداد قاعدة البيانات وغير ذلك.
		</p>
	</li>
	<li>
		<p>
			يحدد الملف "urls.py" الروابط الخاصة بالموقع لعنوان <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-to-View، ويمكن أن يحتوي هذا الملف على كامل شيفرة ربط عناوين URL، ولكن من الشائع تفويض بعض الروابط إلى تطبيقات معينة كما سترى لاحقًا.
		</p>
	</li>
	<li>
		<p>
			يُستخدَم الملف "wsgi.py" لمساعدة تطبيق جانغو على التواصل مع <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>، إذ يمكنك التعامل مع هذا الملف على أنه نموذج أولي Boilerplate.
		</p>
	</li>
	<li>
		<p>
			يُعَد الملف "asgi.py" معيارًا لتطبيقات وخوادم الويب غير المتزامنة الخاصة بلغة بايثون للتواصل مع بعضها بعضًا. واجهة بوابة الخادم غير المتزامنة Asynchronous Server Gateway Interface -أو اختصارًا ASGI- هي الواجهة التالية غير المتزامنة لواجهة بوابة خادم الويب Web Server Gateway Interface -أو اختصارًا WSGI، وتوفر معيارًا لكل من تطبيقات بايثون غير المتزامنة والمتزامنة، بينما قدمت واجهة WSGI معيارًا للتطبيقات المتزامنة فقط. ASGI متوافقة مع الإصدارات السابقة من واجهة WSGI وتدعم العديد من الخوادم وأطر عمل التطبيقات.
		</p>
	</li>
</ul>

<p>
	يُستخدَم سكربت manage.py لإنشاء التطبيقات والعمل مع قواعد البيانات وبدء خادم تطوير الويب.
</p>

<h3>
	إنشاء تطبيق الدليل catalog
</h3>

<p>
	شغّل الأمر التالي لإنشاء تطبيق دليل المكتبة catalog الذي سيكون ضمن مشروع locallibrary، وتأكّد من تشغيل هذا الأمر من المجلد نفسه مثل manage.py الخاص بمشروعك:
</p>

<pre class="ipsCode"># في نظام لينكس وماك
python3 manage.py startapp catalog

# في نظام ويندوز
py manage.py startapp catalog
</pre>

<p>
	<strong>ملاحظة</strong>: تستخدم هذه السلسلة من المقالات صياغة نظامي لينكس وماك أو إس macOS، فإذا كنت تعمل على نظام ويندوز، فيجب عليك استخدام <code>py</code> (أو <code>py -3</code>) بدلًا من <code>python3</code> عندما ترى أمرًا يبدأ بها.
</p>

<p>
	تنشئ الأداة مجلدًا جديدًا وتعبّئه بملفات لأجزاء مختلفة من التطبيق كما هو موضح في المثال الآتي. تُسمَّى معظم الملفات وفقًا للغرض منها، فمثلًا يجب تخزين العروض في الملف views.py والنماذج في الملف models.py والاختبارات في الملف tests.py وإعداد موقع المدير في الملف admin.py وتسجيل التطبيق في الملف apps.py، وتحتوي الملفات على الحد الأدنى من الشيفرة البرمجية المعيارية للعمل مع الكائنات المرتبطة بها.
</p>

<p>
	يجب أن يبدو مجلد المشروع المُحدَّث كما يلي:
</p>

<pre class="ipsCode">locallibrary/
    manage.py
    locallibrary/
    catalog/
        admin.py
        apps.py
        models.py
        tests.py
        views.py
        __init__.py
        migrations/
</pre>

<p>
	لدينا أيضًا ما يلي:
</p>

<ul>
	<li>
		<p>
			المجلد "migrations" الذي يُستخدَم لتخزين عمليات التهجير Migrations، وهي الملفات التي تتيح لك تحديث قاعدة البيانات تلقائيًا عند تعديل نماذجك.
		</p>
	</li>
	<li>
		<p>
			الملف الفارغ "‎<strong>init</strong>.py" الذي أُنشِئ ليتعرّف جانغو أو بايثون على المجلد بوصفه حزمة بايثون ويسمح باستخدام كائناته في أجزاء أخرى من المشروع.
		</p>
	</li>
</ul>

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

<h3>
	تسجيل التطبيق catalog
</h3>

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

<p>
	افتح ملف إعدادات المشروع (django_projects/locallibrary/locallibrary/settings.py) وابحث عن تعريف القائمة <code>INSTALLED_APPS</code>، ثم أضِف سطرًا جديدًا في نهاية القائمة كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3561_7" style=""><span class="pln">INSTALLED_APPS </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    </span><span class="str">'django.contrib.admin'</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'django.contrib.auth'</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'django.contrib.contenttypes'</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'django.contrib.sessions'</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'django.contrib.messages'</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'django.contrib.staticfiles'</span><span class="pun">,</span><span class="pln">
    </span><span class="com"># أضِف تطبيقنا الجديد</span><span class="pln">
    </span><span class="str">'catalog.apps.CatalogConfig'</span><span class="pun">,</span><span class="pln"> </span><span class="com">#‫أُنشئ هذا الكائن في /catalog/apps.py</span><span class="pln">
</span><span class="pun">]</span></pre>

<p>
	يحدد السطر الجديد كائن إعداد التطبيق (<code>CatalogConfig</code>) الذي أُنشِئ في "‎/locallibrary/catalog/apps.py" عندما أنشأتَ التطبيق.
</p>

<p>
	<strong>ملاحظة</strong>: ستلاحظ أن هناك الكثير من قوائم <code>INSTALLED_APPS</code> الأخرى وقوائم <code>MIDDLEWARE</code> أسفل ملف الإعدادات، مما يتيح دعم موقع مدير جانغو والوظائف التي يستخدمها بما في ذلك الجلسات والاستيثاق وغيرها.
</p>

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

<p>
	يجب الآن تحديد قاعدة البيانات التي ستُستخدَم للمشروع. يُفضَّل استخدام قاعدة البيانات نفسها للتطوير والإنتاج إن أمكن ذلك، لتجنب اختلافات السلوك البسيطة. يمكنك التعرف على الخيارات المختلفة في قواعد البيانات في <a href="https://docs.djangoproject.com/en/4.0/ref/settings/#databases" rel="external nofollow">توثيق جانغو</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> في مثالنا، لأننا لا نتوقع أن نطلب الكثير من الوصول المتزامن إلى قاعدة بيانات توضيحية Demonstration Database التي لا يتطلب إعدادها أيّ عمل إضافي. يمكنك رؤية كيفية إعداد قاعدة البيانات هذه في الملف settings.py:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3561_9" style=""><span class="pln">DATABASES </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">'default'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="str">'ENGINE'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'django.db.backends.sqlite3'</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'NAME'</span><span class="pun">:</span><span class="pln"> BASE_DIR </span><span class="pun">/</span><span class="pln"> </span><span class="str">'db.sqlite3'</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	لا نحتاج إلى إجراء أيّ إعداد إضافي لأننا نستخدم قاعدة بيانات SQLite.
</p>

<h3>
	إعدادات المشروع الأخرى
</h3>

<p>
	يُستخدَم الملف settings.py لضبط عددٍ من الإعدادات الأخرى، ولكن نريد حاليًا فقط تغيير المنطقة الزمنية <a href="https://docs.djangoproject.com/en/4.0/ref/settings/#std:setting-TIME_ZONE" rel="external nofollow">TIME_ZONE</a> التي يجب أن تكون مساوية لسلسلة نصية من القائمة المعيارية للمناطق الزمنية في قاعدة بيانات TZ، إذ يحتوي العمود TZ في الجدول على القيم التي تريدها. عدّل قيمة المنطقة الزمنية <code>TIME_ZONE</code> إلى إحدى هذه السلاسل النصية المناسبة لمنطقتك الزمنية كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3561_11" style=""><span class="pln">TIME_ZONE </span><span class="pun">=</span><span class="pln"> </span><span class="str">'Europe/London'</span></pre>

<p>
	هناك إعدادان آخران لن تغيّرهما الآن، ولكن يجب أن تكون على دراية بهما، وهما:
</p>

<ul>
	<li>
		<p>
			<code>SECRET_KEY</code>: هو مفتاح سري يُستخدَم بوصفه جزءًا من استراتيجية أمان موقع جانغو. إذا لم تحمي هذا الرمز في مرحلة التطوير، فيجب استخدام رمز مختلف (ربما يُقرَأ من متغير بيئة أو ملف) عند وضعه في مرحلة الإنتاج.
		</p>
	</li>
	<li>
		<p>
			<code>DEBUG</code> الذي يتيح عرض سجلات تنقيح الأخطاء Debugging عند حدوث الخطأ بدلًا من استجابات رمز حالة HTTP، ويجب ضبطه على القيمة <code>False</code> في مرحلة الإنتاج عندما تكون معلومات تنقيح الأخطاء مفيدة للمهاجمين، ولكن يمكننا حاليًا الاحتفاظ به مضبوطًا على القيمة <code>True</code>.
		</p>
	</li>
</ul>

<h3>
	وصل رابط عنوان URL
</h3>

<p>
	يُنشَأ موقع الويب باستخدام ملف رابط عنوان URL (هو urls.py) في مجلد المشروع، إذ يمكنك استخدام هذا الملف لإدارة جميع روابط عناوين URL الخاصة بك، ولكن من المعتاد تأجيل هذه الروابط للتطبيق المرتبط به.
</p>

<p>
	افتح الملف locallibrary/locallibrary/urls.py ولاحظ النص الإرشادي الذي يشرح بعض طرق استخدام رابط عنوان URL.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3561_13" style=""><span class="str">"""locallibrary URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/4.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""</span><span class="pln">
</span><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">contrib </span><span class="kwd">import</span><span class="pln"> admin
</span><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">urls </span><span class="kwd">import</span><span class="pln"> path

urlpatterns </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    path</span><span class="pun">(</span><span class="str">'admin/'</span><span class="pun">,</span><span class="pln"> admin</span><span class="pun">.</span><span class="pln">site</span><span class="pun">.</span><span class="pln">urls</span><span class="pun">),</span><span class="pln">
</span><span class="pun">]</span></pre>

<p>
	تُدار روابط عنوان URL من خلال المتغير <code>urlpatterns</code>، وهو قائمة <a href="https://wiki.hsoub.com/Python" rel="external">بايثون</a> لدوال <code>path()‎</code>، بحيث إما تربط هذه الدالة نمط عنوان URL بعرض معين يُعرَض عند مطابقة النمط، أو تربطه بقائمة أخرى من شيفرة اختبار نمط عنوان URL؛ إذ يصبح النمط في الحالة الثانية "عنوان URL الأساسي" للأنماط المُعرَّفة في الوحدة الهدف. تعرِّف قائمة <code>urlpatterns</code> في البداية دالةً واحدة تربط جميع عناوين URL باستخدام النمط "admin/‎" مع الوحدة <code>admin.site.urls</code> التي تحتوي على تعريفات ربط عناوين URL الخاصة بتطبيق المدير.
</p>

<p>
	<strong>ملاحظة</strong>: الوجهة Route في الدالة <code>path()‎</code> هي سلسلة نصية تعرِّف نمط عنوان URL لمطابقته، إذ يمكن أن تحتوي هذه السلسلة النصية على متغير مُسمَّى بين قوسي زاوية، مثل <code>'catalog/&lt;id&gt;/‎'</code>. سيطابق هذا النمط عنوان URL مثل العنوان catalog/any_chars/‎ ويمرِّر <code>any_chars</code> إلى العرض بوصفه سلسلة نصية مع معاملٍ بالاسم <code>id</code>. سنناقش توابع المسار Path Methods وأنماط الوجهة Route Patterns لاحقًا.
</p>

<p>
	يمكن إضافة عنصر قائمة جديد إلى القائمة <code>urlpatterns</code> من خلال إضافة الأسطر الآتية إلى أسفل الملف، إذ يتضمن هذا العنصر الجديد الدالة <code>path()‎</code> التي توجّه الطلبات مع النمط <code>catalog/‎</code> إلى الوحدة <code>catalog.urls</code> (الملف الذي يحتوي على عنوان URL النسبي catalog/urls.py).
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3561_15" style=""><span class="com"># ‎استخدم‫ التابع include()‎ لإضافة مسارات من تطبيق Catalog</span><span class="pln">
</span><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">urls </span><span class="kwd">import</span><span class="pln"> include

urlpatterns </span><span class="pun">+=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    path</span><span class="pun">(</span><span class="str">'catalog/'</span><span class="pun">,</span><span class="pln"> include</span><span class="pun">(</span><span class="str">'catalog.urls'</span><span class="pun">)),</span><span class="pln">
</span><span class="pun">]</span></pre>

<p>
	<strong>ملاحظة</strong>: لاحظ أننا ضمّنا سطر الاستيراد <code>from django.urls import include</code> مع الشيفرة البرمجية التي يستخدمها، لذلك يمكن بسهولة رؤية ما أضفناه، ولكن من الشائع تضمين جميع سطور الاستيراد في أعلى ملف بايثون.
</p>

<p>
	لنعيد الآن توجيه عنوان URL الجذر لموقعنا (أي <code>127.0.0.1:8000</code>) إلى العنوان ‎<code>‏‪‫‎.</code>127.0.0.1:8000‪/catalog/
</p>

<p>
	هذا هو التطبيق الوحيد الذي سنستخدمه في هذا المشروع، ويمكن تحقيق ذلك من خلال استخدام دالة عرض خاصة هي <code>RedirectView</code>، التي تأخذ عنوان URL النسبي الجديد لإعادة التوجيه إلى <code>/catalog/</code> بوصفه وسيطها الأول عند مطابقة نمط عنوان URL المُحدَّد في الدالة <code>path()‎</code> (عنوان URL الجذر في حالتنا).
</p>

<p>
	ضِف الأسطر التالية في نهاية الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3561_17" style=""><span class="com">#ضِف روابط عنوان‫ URL لإعادة توجيه عنوان URL الأساسي إلى تطبيقنا</span><span class="pln">
</span><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">views</span><span class="pun">.</span><span class="pln">generic </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">RedirectView</span><span class="pln">
urlpatterns </span><span class="pun">+=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    path</span><span class="pun">(</span><span class="str">''</span><span class="pun">,</span><span class="pln"> </span><span class="typ">RedirectView</span><span class="pun">.</span><span class="pln">as_view</span><span class="pun">(</span><span class="pln">url</span><span class="pun">=</span><span class="str">'catalog/'</span><span class="pun">,</span><span class="pln"> permanent</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)),</span><span class="pln">
</span><span class="pun">]</span></pre>

<p>
	اترك المعامل الأول لدالة المسار <code>path()‎</code> فارغًا للإشارة إلى '/'. إذا كتبتَ المعامل الأول بالشكل '/'، فسيعطيك جانغو التحذير التالي عند بدء تشغيل خادم التطوير:
</p>

<pre class="ipsCode">System check identified some issues:

WARNINGS:
?: (urls.W002) Your URL pattern '/' has a route beginning with a '/'.
Remove this slash as it is unnecessary.
If this pattern is targeted in an include(), ensure the include() pattern has a trailing '/'.
</pre>

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

<p>
	أضف الكتلة النهائية التالية إلى أسفل الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3561_20" style=""><span class="com"># ‫استخدم الدالة static()‎ لإضافة رابط عنوان URL لتخديم الملفات الساكتة أثناء التطوير (فقط)</span><span class="pln">
</span><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">conf </span><span class="kwd">import</span><span class="pln"> settings
</span><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">conf</span><span class="pun">.</span><span class="pln">urls</span><span class="pun">.</span><span class="pln">static </span><span class="kwd">import</span><span class="pln"> static

urlpatterns </span><span class="pun">+=</span><span class="pln"> static</span><span class="pun">(</span><span class="pln">settings</span><span class="pun">.</span><span class="pln">STATIC_URL</span><span class="pun">,</span><span class="pln"> document_root</span><span class="pun">=</span><span class="pln">settings</span><span class="pun">.</span><span class="pln">STATIC_ROOT</span><span class="pun">)</span></pre>

<p>
	<strong>ملاحظة</strong>: هناك عدد من الطرق لتوسيع قائمة <code>urlpatterns</code>؛ إذ ألحقنا سابقًا عنصر قائمة جديد باستخدام المعامل <code>=+</code> للفصل بوضوح بين الشيفرة البرمجية القديمة والجديدة، لكن كان بإمكاننا بدلًا من ذلك تضمين ربط النمط الجديد في تعريف القائمة الأصلية كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3561_22" style=""><span class="pln">urlpatterns </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    path</span><span class="pun">(</span><span class="str">'admin/'</span><span class="pun">,</span><span class="pln"> admin</span><span class="pun">.</span><span class="pln">site</span><span class="pun">.</span><span class="pln">urls</span><span class="pun">),</span><span class="pln">
    path</span><span class="pun">(</span><span class="str">'catalog/'</span><span class="pun">,</span><span class="pln"> include</span><span class="pun">(</span><span class="str">'catalog.urls'</span><span class="pun">)),</span><span class="pln">
    path</span><span class="pun">(</span><span class="str">''</span><span class="pun">,</span><span class="pln"> </span><span class="typ">RedirectView</span><span class="pun">.</span><span class="pln">as_view</span><span class="pun">(</span><span class="pln">url</span><span class="pun">=</span><span class="str">'catalog/'</span><span class="pun">)),</span><span class="pln">
</span><span class="pun">]</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> static</span><span class="pun">(</span><span class="pln">settings</span><span class="pun">.</span><span class="pln">STATIC_URL</span><span class="pun">,</span><span class="pln"> document_root</span><span class="pun">=</span><span class="pln">settings</span><span class="pun">.</span><span class="pln">STATIC_ROOT</span><span class="pun">)</span></pre>

<p>
	أخيرًا، أنشِئ ملفًا ضمن المجلد catalog يُسمَّى urls.py، وضِف النص التالي لتعريف قائمة <code>urlpatterns</code> المستوردة (الفارغة)، وهذا هو المكان الذي سنضيف فيه أنماطنا عند بناء التطبيق.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3561_24" style=""><span class="kwd">from</span><span class="pln"> django</span><span class="pun">.</span><span class="pln">urls </span><span class="kwd">import</span><span class="pln"> path
</span><span class="kwd">from</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> views

urlpatterns </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">

</span><span class="pun">]</span></pre>

<h3>
	اختبار إطار عمل الموقع
</h3>

<p>
	لدينا الآن مشروع هيكلي كامل. لا يفعل موقع الويب أيّ شيء حتى الآن، ولكن الأمر يستحق تشغيله للتأكد من أن أيّ تغييرات أجريناها لم تعطّل شيئًا ما. يجب علينا أولًا قبل ذلك تشغيل تهجير قاعدة البيانات Database Migration، مما يؤدي إلى تحديث قاعدة بياناتنا (لتضمين أيّ نماذج في تطبيقاتنا المُثبَّتة) ويزيل بعض تحذيرات البناء.
</p>

<h4>
	تشغيل تهجيرات قاعدة البيانات
</h4>

<p>
	يستخدم جانغو رابط كائنات علائقي Object-Relational-Mapper -أو ORM اختصارًا- لربط تعريفات النموذج في شيفرة جانغو البرمجية مع بنية البيانات التي تستخدمها قاعدة البيانات الأساسية. بما أننا نغيّر تعريفات نماذجنا، فسيتتبّع جانغو التغييرات ويمكنه إنشاء سكربتات تهجير قاعدة البيانات (في /locallibrary/catalog/migrations/) لتهجير بنية البيانات الأساسية تلقائيًا في قاعدة البيانات لمطابقة النموذج.
</p>

<p>
	أضاف جانغو تلقائيًا عددًا من النماذج عندما أنشأنا موقع الويب ليستخدمها قسم المدير في الموقع (الذي سنلقي نظرةً عليه لاحقًا).
</p>

<p>
	شغّل الأوامر التالية لتعريف الجداول لتلك النماذج في قاعدة البيانات، وتأكد من أنك في المجلد الذي يحتوي على الملف manage.py:
</p>

<pre class="ipsCode">python3 manage.py makemigrations
python3 manage.py migrate
</pre>

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

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

<p>
	يطبّق الأمر <code>migrate</code> التهجيرات على قاعدة بياناتك، ويتتبّع جانغو التهجيرات المُضافة إلى قاعدة البيانات الحالية.
</p>

<p>
	<strong>ملاحظة</strong>: اطّلع على <a href="https://docs.djangoproject.com/en/4.0/topics/migrations/" rel="external nofollow">عمليات التهجير Migrations</a> في توثيق جانغو للحصول على معلومات إضافية حول أوامر التهجير الأقل استخدامًا.
</p>

<h4>
	تشغيل الموقع
</h4>

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

<p>
	<strong>ملاحظة</strong>: لا يُعَد خادم الويب الخاص بالتطوير قويًا أو ذا أداء كافٍ للاستخدام في الإنتاج، ولكنه طريقة سهلة لتنشيط موقع جانغو وتشغيله أثناء التطوير لمنحه اختبارًا سريعًا مناسبًا، إذ سيخدّم هذا الخادم افتراضيًا الموقعَ لحاسوبك المحلي (<code>http://127.0.0.1:8000/‎</code>)، ولكن يمكنك تحديد حواسيب أخرى على شبكتك لتخديمها. اطلع على <a href="https://docs.djangoproject.com/en/4.0/ref/django-admin/#runserver" rel="external nofollow">django-admin و manage.py: runserver</a> في توثيق جانغو لمزيد من المعلومات.
</p>

<p>
	شغّل خادم الويب الخاص بالتطوير من خلال استدعاء الأمر <code>runserver</code> (في مجلد الملف manage.py نفسه):
</p>

<pre class="ipsCode">$ python3 manage.py runserver

Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
March 01, 2022 - 04:08:45
Django version 4.0.2, using settings 'locallibrary.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
</pre>

<p>
	يمكنك عرض الموقع بمجرد تشغيل الخادم بالانتقال إلى <code>http://127.0.0.1:8000/‎</code> في متصفح الويب المحلي. يُفترَض أن ترى صفحة تظهِر خطأ في الموقع وتبدو كما يلي:
</p>

<p style="text-align: center;">
	<img alt="01_django_404_debug_page.png" class="ipsImage ipsImage_thumbnailed" data-fileid="132826" data-unique="p5oagwfr5" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2023_08/01_django_404_debug_page.png.076be0e14a3b0dc40f569ce213ef9a72.png">
</p>

<p>
	لا تقلق فهذه الصفحة متوقعة لأنه ليس لدينا أي صفحات أو عناوين URLs معرّفة في الوحدة <code>catalog.urls</code> التي أُعيد توجيهنا إليها عندما حصلنا على عنوان URL لجذر الموقع.
</p>

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

<p>
	نعلم في هذه المرحلة أن تطبيق جانغو يعمل.
</p>

<p>
	<strong>ملاحظة</strong>: يجب إعادة تشغيل عمليات التهجير وإعادة اختبار الموقع كلما أجريت تغييرات مهمة، ولا يستغرق الأمر وقتًا طويلًا.
</p>

<h3>
	تحدى نفسك
</h3>

<p>
	يحتوي المجلد "catalog/‎" على ملفات للعروض والنماذج وأجزاء أخرى من التطبيق. افتح هذه الملفات وافحص النموذج المعياري.
</p>

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

<h2>
	الخلاصة
</h2>

<p>
	أنشأت الآن مشروعًا هيكليًا كاملًا لموقع الويب، والذي يمكنك الاستمرار في ملؤه بعناوين URL والنماذج والعروض والقوالب. اكتمل هيكل موقع المكتبة المحلية وجرى تشغيله، وحان الوقت الآن لبدء كتابة الشيفرة البرمجية التي تجعل هذا الموقع يفعل ما يفترض به أن يفعل.
</p>

<p>
	ترجمة -وبتصرُّف- للمقالين <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website" rel="external nofollow">Django Tutorial: The Local Library website</a> و <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/skeleton_website" rel="external nofollow">Django Tutorial Part 2: Creating a skeleton website</a>.
</p>

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

<ul>
	<li>
		المقال التالي: <a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%86%D9%85%D8%A7%D8%B0%D8%AC-models-r2092/" rel="">تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models</a>
	</li>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%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%AC%D8%A7%D9%86%D8%BA%D9%88-django-r2049/" rel="">إعداد بيئة تطوير تطبيقات جانغو Django</a>
	</li>
	<li>
		<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="">مدخل إلى إطار عمل الويب جانغو من طرف الخادم</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%B9%D9%84%D9%89-%D8%A3%D9%88%D8%A8%D9%86%D8%AA%D9%88-r1624/" rel="">تثبيت إطار العمل جانغو على أوبنتو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D9%84%D8%A8%D8%AF%D8%A1-%D9%85%D8%B9-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%84%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-r1625/" rel="">البدء مع إطار العمل جانغو لإنشاء تطبيق ويب</a>
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/intro/tutorial01/" rel="external nofollow">كتابة أول تطبيق جانغو - الجزء 1</a> (توثيق جانغو).
	</li>
	<li>
		<a href="https://docs.djangoproject.com/en/4.0/ref/applications/#configuring-applications" rel="external nofollow">التطبيقات</a> (توثيق جانغو) الذي يحتوي على معلومات حول إعداد التطبيقات.
	</li>
</ul>
]]></description><guid isPermaLink="false">2090</guid><pubDate>Wed, 23 Aug 2023 16:01:00 +0000</pubDate></item></channel></rss>
