-
المساهمات
189 -
تاريخ الانضمام
-
تاريخ آخر زيارة
نوع المحتوى
ريادة الأعمال
البرمجة
التصميم
DevOps
التسويق والمبيعات
العمل الحر
البرامج والتطبيقات
آخر التحديثات
قصص نجاح
أسئلة وأجوبة
كتب
دورات
كل منشورات العضو Ola Abbas
-
سنناقش في هذا المقال كائنات الرسوميات الحاسوبية التي هي الأشعة والنقاط وكيفية تمثيلها في الحاسوب بوصفها مصفوفات عمودية، فالمصفوفة العمودية column matrix هي كائن رياضي له العديد من الاستخدامات إلى جانب استخدامه في الرسوميات الحاسوبية، ولكن سنتعرف في هذا المقال على كيفية استخدامها في الرسوميات الحاسوبية فقط. سنتعرّف في هذا المقال على المواضيع التالية: نمذجة وعرض الرسوميات الحاسوبية. النقاط والأشعة الهندسية. المصفوفات العمودية والسطرية. حساب الإزاحات. مساواة المصفوفات العمودية. أسماء المصفوفات العمودية وعناصرها. تمثيل النقاط باستخدام المصفوفات العمودية. تُعَد النقاط والأشعة الهندسية ضروريةً في الرسوميات الحاسوبية، ويجب تمثيلها بطريقة تسهّل التعامل معها، لذا تُعَد المصفوفة العمودية الخيار المعتاد لذلك. تستخدم بعض كتب الرسوميات مصطلح الشعاع العمودي Column Vector للتعبير عن الكائن الذي نسميه في مقالنا بالمصفوفة العمودية، إذ يُعَد ذلك مجرد اختلافٍ في المصطلحات ولا يؤثر على المفاهيم أو الصيغ التي سنوضّحها لاحقًا، ولكن المشكلة الأسوأ هي أن بعض الكتب تستخدم المصفوفات السطرية Row Matrices، فالمصفوفات العمودية والمصفوفات السطرية متكافئة، ولكن المعادلات المكتوبة باستخدام المصفوفات السطرية ليست هي نفسها المكتوبة باستخدام المصفوفات العمودية. يمكننا ضبط هذه الاختلافات، ولكنها مزعجة قليلًا. مثال مشهد السائح الافتراضي ما هما نوعا الكائنات الهندسية التي يمكن تمثيلها باستخدام المصفوفات العمودية؟ هما النقاط والأشعة. تتكون الرسوميات الحاسوبية من نشاطين: أولهما إنشاء عالم خيالي ثلاثي الأبعاد في الحاسوب، وثانيهما إنتاج صور ثنائية الأبعاد لذلك العالم من نقاط نظر مختلفة. يشبه برنامج الرسوميات السائحَ الذي يتجول في معالم طبيعية رائعة ويلتقط الصور بالكاميرا، ويُجهَّز السائح الافتراضي بكاميرا سينمائية مع الرسوم المتحركة الحاسوبية. يتكون المشهد الخيالي من كائنات في فضاء ثلاثي الأبعاد، ويُحدَّد كل كائن باستخدام النقاط والخطوط التي تقع على سطحه مثل المشهد الشتوي السابق لصاحبه توان فان Tuan Phan. يتكون هذا النموذج من نقاط وقطع مستقيمة تربط بينها، وهناك حاجة إلى مزيدٍ من العمل لملء المنطقة بين القطع المستقيمة ولتطبيق الإضاءة للحصول على صورة واقعية. صورة المشهد الكاملة هل يمكن تمثيل الكرة باستخدام النقاط والخطوط؟ نعم، بالتأكيد. انظر إلى جسد رجل الثلج، إذ يبدو الشكل وكأنه كرة عند ملء المضلعات. هذه هي الصورة الكاملة، مع ملء المضلعات وتطبيق الإضاءة وإلقاء قليلٍ من ندف الثلج. يمكنك طلب نماذج من الكرات والأسطوانات والأقماع ووضعها في مشهدك في المكان الذي تريده باستخدام حزم الرسوميات الحاسوبية ثلاثية الأبعاد مثل OpenGL التي تملأ أيضًا المضلعات تلقائيًا وتطبّق نموذج الإضاءة لإنتاج تظليل واقعي. مشهد افتراضي آخر هل يمكن لسائحنا الافتراضي أن يتجول في مشهد الثلوج الافتراضي ويلتقط صورة مختلفة؟ نعم، يمكنه ذلك. يضع نموذج المشهد الثلجي كائنات مختلفة في فضاء ثلاثي الأبعاد، وبالتالي يمكن لسائحنا الافتراضي أن يتجول على يمين رجل الثلج ويلتقط صورةً أخرى ثنائية الأبعاد باستخدام الكاميرا الافتراضية، إذ أُنتِجت الصورة السابقة باستخدام النموذج والإضاءة نفسها، ولكن مع وضعٍ مختلف للكاميرا. الشعاع Vector ما هي النقطة؟ وضحنا في المقال السابق مفهوم النقطة بأنها موقع في الفضاء، حيث تُعَد النقطة في الهندسة موقعًا في الفضاء فليس لها حجم وخاصيتها الوحيدة هي الموقع، بينما تكون النقطة عادةً في الرسوميات الحاسوبية هي رأس الشكل ثلاثي الأبعاد. أما الشعاع الهندسي فله خاصيتان: الطول والاتجاه، ولكن ليس له موقع ثابت في الفضاء، فإذا كان له موقع ثابت، فسيكون قطعة مستقيمة. قد يكون الحديث عن شيء ليس له موقع أمرًا غريبًا، ولكنه يسهّل تطبيق الرسوميات الحاسوبية ثلاثية الأبعاد. يسمى هذا المزيج من "المسافة والاتجاه" بالإزاحة Displacement أحيانًا، ويمكن تطبيق الإزاحة نفسها (أي إزاحة واحدة فقط) على كل نقطة من النقاط المختلفة. لنفترض أن لدينا المكعب التالي مثلًا، إذ يحتوي الوجه الأمامي على أربعة رؤوس (أربع نقاط). إذا تحركتَ من كل نقطة من هذه النقاط بمقدار المسافة والاتجاه نفسه، فستصل إلى النقاط الأربعة للوجه الخلفي. تمثل العبارة "المسافة والاتجاه نفسه" الشعاعَ الذي يظهر بوصفه خطًا مع رأس سهم، ويوضح الشكل التالي هذا الشعاع أربع مرات، مرة واحدة لكل نقطة من الوجه. المصفوفة العمودية لنفترض أنك قضيت عطلة الربيع على الشاطئ والشمس مشرقة، فهل يسطع ضوء الشمس في الاتجاه نفسه لكل الموجودين على الشاطئ؟ نعم، إذ يكون "الاتجاه نحو الشمس" نفسه لكل الموجودين على الشاطئ، وبالتالي فهو شعاع. نهتم أحيانًا بالاتجاه فقط دون الاهتمام بالموقع أو بالطول كما في السؤال السابق، لذا نستخدم الشعاع لهذا الغرض، فطوله ليس مهمًا، لأننا نستخدم طولًا واحدًا في كثير من الأحيان. نهتم في أحيانٍ أخرى بكلٍ من الاتجاه والحجم، وعندها يُستخدَم كلٌّ من اتجاه وطول الشعاع. يمكن تمثيل الشعاع بقائمةٍ من الأعداد تدعى المصفوفة العمودية، والتي هي قائمة مرتبة من الأعداد المكتوبة ضمن عمود. تستخدم بعض الكتب كلمة "شعاع" لتعبّر عن فكرة الشعاع وتمثيله بوصفه ترتيبًا لثلاثة أعداد، ولكن يمكن أن يكون ذلك مربكًا أحيانًا. إليك فيما يلي مثالًا لمصفوفة عمودية: يسمى كل عددٍ في المصفوفة العمودية عنصرًا Element، وتكون هذه الأعداد حقيقية real، ويسمى عدد العناصر في الشعاع بالبُعد Dimension. المصفوفة السطرية Row Matrix هي قائمة مرتبة من الأعداد المكتوبة في صف واحد مثل المصفوفة السطرية: (12.5, -9.34). سنمثّل الأشعة دائمًا باستخدام مصفوفات عمودية بهدف التناسق، وتمثل بعض الكتب الأشعة باستخدام مصفوفات سطرية، إذ لا يحدث ذلك فرقًا جوهريًا، ولكنه يغيّر بعض الصيغ الرياضية قليلًا. ما هو عدد العناصر الموجودة في كل مصفوفة عمودية؟ الجواب هو: استخدام المتغيرات بوصفها عناصر مصفوفة يمكن أن تكون عناصر المصفوفة العمودية متغيرات variables كما يلي: يُعطَى العنصر الأول في المصفوفة العمودية أحيانًا الفهرس "0" أوالفهرس "1" أحيانًا. العرض المناسب للمصفوفة العمودية هل المصفوفة العمودية التالية: هي المصفوفة العمودية التالية نفسها؟ لا يمكن ذلك، فالمصفوفة العمودية هي قائمة مرتبة من الأعداد، وهذا يعني أن كل موضع في المصفوفة العمودية يحتوي على عدد أو متغير معين. يُعَد مظهر المصفوفات العمودية غريبًا في النصوص المطبوعة، لذا من الشائع كتابتها كما يلي: (2.9, -4.6, 0.0) T يرمز الحرف "T" إلى منقول المصفوفة Transpose الذي يعني تحويل الصفوف إلى أعمدة (سنوضح لاحقًا بالتفصيل معنى المنقول). المساواة بين المصفوفات هل المصفوفة (1.2, -3.9, 0.0) تساوي المصفوفة (1.2, -3.9, 0.0)T؟ لا، فالمصفوفة الأولى هي مصفوفة سطرية، والمصفوفة الثانية هي مصفوفة عمودية، أي مكتوبة ضمن صف، ولكن الحرف "T" يعني أنها مصفوفة عمودية، إذ تُعَد المصفوفات السطرية والمصفوفات العمودية أنواعًا مختلفة من الكائنات، ولا يمكن أن تكون متساوية. ربما لم نميّز سابقًا بين المصفوفات السطرية والمصفوفات العمودية، ولم نوضِّح اختلاف الأشعة الهندسية عن المصفوفات العمودية المستخدمة لتمثيل هذه الأشعة، إذ يمكن أن تبدو هذه الاختلافات صعبة حاليًا، ولكن أبقِها حاضرةً في ذهنك لتجنب الالتباس مستقبلًا. يمكن أن تكون المصفوفتان العموديتان متساويتين إذا كانت: كلتا المصفوفتين مصفوفةً عمودية. لكل منهما البعد (عدد العناصر) نفسه. العناصر المقابلة لبعضها البعض في المصفوفتين متساوية. يمكن أن تكون المصفوفتان السطريتان متساويتين إذا كان: كلتا المصفوفتين مصفوفات سطرية. لكل منهما البعد (عدد العناصر) نفسه. العناصر المقابلة لبعضها البعض في المصفوفتين متساوية. الفروق الدقيقة بين المصفوفات هل المصفوفتان السطريتان (8.0, -1.63, 7.0, 0.0) و (8.0, -1.63, 7.0, 1.0) متساويتان؟ لا. يمكن مقارنة المصفوفات من النوع نفسه فقط، إذ يمكنك المقارنة بين مصفوفتين عموديتين ثلاثيتي الأبعاد، أو مصفوفتين سطريتين رباعيتي الأبعاد مثلًا، فليس من المنطقي مقارنة مصفوفة سطرية ثلاثية الأبعاد مع مصفوفة عمودية ثلاثية الأبعاد. ( 6, 8, 12, -3 )T = ( 6, 8, 12, -3)T ( 6, 8, 12, -3 ) = ( 6, 8, 12, -3 ) ( 6, 8, 12, -3 ) ≠ ( -2.3, 8, 12, -3 ) ( 6, 8, 12, -3 )T ≠ ( 6, 8, 12, -3 ) ( 6, 8, 12, -3 )T ≠ ( 6, 8, 12 )T يمثل المحرف ≠ عدم المساواة (قد يكون من الصعب رؤيته باستخدام متصفح الويب). تكون القواعد أحيانًا مريحة وتؤدي إلى عدم الدقة في التمييز بين المصفوفات السطرية والمصفوفات العمودية، ولكن سيؤدي إبقاء التمييز بينها واضحًا إلى تجنب الارتباك مستقبلًا. هل تُعَد المصفوفتان العموديتان (1.53, -0.03, 9.03, 0.0, +8.64)T و (1.53, -0.03, 9.03, 1.0, -8.64)T متساويتين؟ لا، ليستا متساويتين. أسماء المصفوفات من المفيد أن يكون للمصفوفات أسماء، إذ يُستخدَم عادةً حرف صغير بخط عريض لمصفوفة سطرية أو عمودية كما يلي: a = ( 1.2, -3.6 ) x = ( x1, x2, x3, x4 ) r = ( r0, r1 )T من المعتاد استخدام الأسماء من بداية أحرف الأبجدية الإنجليزية للمصفوفات العمودية التي تكون عناصرها معروفة مثل المصفوفة a السابقة، واستخدام الأسماء من نهاية أحرف الأبجدية الإنجليزية للمصفوفات العمودية التي تكون عناصرها متغيرات. تكون أسماء عناصر المصفوفة العمودية غالبًا مؤلفة من اسم المصفوفة العمودية الكاملة مع رمز سفلي مثل المصفوفة العمودية r وعناصرها r0 وr1. من الصعب كتابة الأحرف ذات الخط العريض بالقلم الرصاص أو بالطباشير، لذا بدلًا من ذلك يُوضَع سهم أو شريط فوق اسم المصفوفة العمودية كما يلي: _ -> x x لنفترض أن لدينا ما يلي: x = ( x1, x2 ) y = ( 3.2, -8.6 ) x = y ما الذي يجب أن يكون صحيحًا بشأن x1 و x2؟ x1 = 3.2 و x2 = -8.6 تمثيل الأشعة باستخدام المصفوفات العمودية تُستخدَم المصفوفات العمودية لتمثيل الأشعة وتُستخدَم لتمثيل النقاط أيضًا. يُستخدَم في الفضاء ثنائي الأبعاد نوع البيانات نفسه -وتكون مصفوفات عمودية ثنائية الأبعاد- لتمثيل نوعين مختلفين من الكائنات الهندسية هما النقاط والأشعة، ويُعَد ذلك أمرًا مربكًا، لذا سنصحح هذا الوضع لاحقًا. يوضح الشكل السابق شعاع الإزاحة الذي يمثل الفرق بين نقطتين في المستوي x-y، إذ نستخدم حاليًا أمثلةً في فضاء ثنائي الأبعاد، وسيأتي الفضاء ثلاثي الأبعاد لاحقًا. النقطة A لها الإحداثيات: x=2 و y=1، ويمكن تمثيلها بمصفوفة عمودية: (2, 1)T. النقطة B لها الإحداثيات: x=7 و y=3، ويمكن تمثيلها بمصفوفة عمودية: (7, 3)T. يمكن حساب الإزاحة من النقطة A إلى النقطة B في مسألتين منفصلتين هما: الإزاحة x هي الفرق بين قيم X أي: 7-2 = 5. الإزاحة y هي الفرق بين قيم Y أي: 3-1 = 2. وشعاع الإزاحة المُعبَّر عنه بمصفوفة هو: (5, 2)T = d. تُصوَّر أشعة الإزاحة على شكل سهم يربط بين نقطتين، إذ تكون النقطة A هي ذيل الشعاع، والنقطة B هي رأس الشعاع في الرسم البياني، ولكن تذكّر أن الأشعة ليس لها موضع، لذا يُعَد هذا الرسم مجرد مكان مناسب لرسمها، ولا يعبّر عن موضعها. الإزاحة Displacement تمثل المصفوفة العمودية d شعاع الإزاحة من النقطة A إلى النقطة B، فما هي المصفوفة العمودية التي تمثل الإزاحة من النقطة B إلى النقطة A؟ الإزاحة من النقطة B إلى النقطة A هي: الإزاحة x هي: 2-7 = -5 الإزاحة y هي: 1-3 = -2 وبالتالي فإن المصفوفة العمودية التي تمثل الإزاحة هي: (-5, -2)T = e يؤشر شعاع الإزاحة إلى الاتجاه المعاكس عند زيارة النقاط بالترتيب المعاكس، ويكون كل عنصر ناتجًا عن ضرب القيمة القديمة بالعدد -1 في المصفوفة العمودية. تختلف الإزاحة من النقطة A إلى النقطة B عن الإزاحة من النقطة B إلى النقطة A، إذ يمكن التفكير في الإزاحة بوصفها "إرشادات حول كيفية المشي من نقطة إلى أخرى"؛ لذا إذا كنت واقفًا عند النقطة A وترغب في الوصول إلى النقطة B، فإن الإزاحة (5, 2)T تقول "امشِ 5 وحدات في الاتجاه الموجب X، ثم امشِ وحدتين في الاتجاه الموجب Y". تحتاج بالطبع إلى اتجاهات مختلفة للانتقال من النقطة B إلى النقطة A، حيث تقول الإزاحة (-5, -2)T "امشِ 5 وحدات في الاتجاه السالب X، ثم امشِ وحدتين في الاتجاه السالب Y"، مما يعيدك إلى النقطة A. يمكن التعبير عن الإزاحة من نقطة البداية إلى نقطة النهاية كما يلي: الإزاحة = (x نقطة النهاية - x نقطة البداية, y نقطة النهاية - y نقطة البداية)T لنفترض أن النقطة C هي: x=4, y=2 وأن النقطة D هي: x=3, y= 5، فما هي المصفوفة العمودية التي تمثل الإزاحة من C إلى D؟ X لنقطة النهاية - X لنقطة البداية = 3 - 4 = -1 Y لنقطة النهاية - Y لنقطة البداية = 5 - 2 = 3 إذًا فالمصفوفة العمودية هي: (-1, 3)T قراءة الإزاحات من الرسوم البيانية الورقية يوضح الرسم البياني التالي شعاعًا بين نقطتين، حيث يمكن قراءة عناصر الإزاحة بين النقطتين من الرسم البياني من خلال حساب عدد المربعات من ذيل الشعاع إلى رأسه: (-3, 5)T. افعل الشيء ذاته الآن مع الرسم البياني التالي: ما هي الإزاحة من النقطة E إلى النقطة F؟ الإزاحة هي: (7, 3)T للتحقق من إجابتك، ابدأ من نقطة البداية واتبع التعليمات التالية: امشِ بمقدار 7 وحدات على محور X، ثم امشِ بمقدار 3 وحدات على محور Y. إذا كانت إجابتك صحيحة، فسينتهي بك الأمر عند نقطة النهاية. ليس للأشعة موقع محدد ما هي الإزاحة من النقطة G: (-3, 4)T إلى النقطة H: (5, -2)T؟ يمكن الإجابة على هذا السؤال من خلال طرح قيم نقطة البداية G من القيم المقابلة لنقطة النهاية H ويعطي ذلك: (8, -6)T. احسب الآن الإزاحة من النقطة M إلى النقطة N في الرسم البياني التالي من خلال طرح قيم نقطة البداية N من القيم المقابلة لنقطة النهاية M، ويعطي ذلك: (8, -6)T، وهي القيمة نفسها للرسم البياني السابق. يُعَد شعاع الإزاحة هندسيًا من النقطة G إلى النقطة H هو شعاع الإزاحة نفسه من النقطة M إلى النقطة N، وتكون المصفوفتان العموديتان متساويتين باستخدام قاعدة مساواة المصفوفات العمودية. يُعَد ذلك منطقيًا، لأنك تقطع المسافة والاتجاه نفسه للمشي من النقطة M إلى النقطة N عند المشي من النقطة G إلى النقطة H، وبالتالي توضح هذه الرسوم البيانية الإزاحات مع الطول والاتجاه نفسه ولكن مع نقاط بداية مختلفة، فليس للأشعة موقعٌ محدد. من الشائع رسم الشعاع على شكل سهم في الرسوم البيانية، ذيله عند نقطة ورأسه عند نقطة أخرى، ولكن يمكن أن يمثل أيّ سهم له الطول والاتجاه نفسه هذا الشعاع. طرح النقاط هل تُعَدّ الإزاحة بين نقطتين فريدة؟ نعم، هذا صحيح. يمكن كتابة صيغة حساب شعاع الإزاحة على النحو التالي، إذ تساوي الإزاحة من النقطة S (نقطة البداية) إلى النقطة F (نقطة النهاية) ما يلي: F - S = ( Xf , Yf )T - ( Xs , Ys )T = ( Xf-Xs , Yf-Ys )T تُستخدَم نقطتان في هذه العملية لإنتاج شعاع واحد، فمن الغريب أن ينتج عن طرح كائنين لهما النوع نفسه كائنٌ من نوع آخر، وهذا يقودنا إلى السؤال التالي: إذا طرحنا نقطةً ثلاثية الأبعاد من نقطة أخرى ثلاثية الأبعاد، فهل ستكون النتيجة شعاعًا؟ نعم، إذ تعمل هذه الطريقة في الفضاء ثلاثي الأبعاد وثنائي الأبعاد. تدريب عملي لحساب الإزاحة لنفترض أن النملة ريم ضاعت عند النقطة (8, 4)T، فأوجد الإزاحة التي ستوصِلها إلى صديقتها سلمى عند النقطة (2, 2)T، أو ما هي الإزاحة التي يجب أن تتحرك بها ريم للعثور على صديقتها سلمى؟ لنطرح النقاط (سلمى - ريم): (2, 2)T - (8, 4)T = (-6, -2)T ستحصل على الإجابة نفسها من خلال عدّ مربعات ورقة الرسم البياني. استخدام الأعداد الحقيقية قد نرغب في تجنب الأعداد السالبة وبالتالي سنحسب الإزاحة بصورة غير صحيحة، إذ يُعَد وجود إزاحات سالبة أمرًا طبيعيًا، فالجزء "السلبي" مطلوب لإظهار الاتجاه. شيء آخر يجب أخذه في الحسبان وهو أن عناصر المصفوفة العمودية هي أعداد حقيقية، ولكن استخدمنا في الأمثلة أعدادًا صحيحة لتسهيل الأمور فقط، فلا يوجد أيّ خطأ في المصفوفة العمودية (-1.2304, 9.3382)T. لنفترض أن لدينا النقطتان: النقطة B وهي: ( 4.75, 6.23 ) والنقطة A وهي: ( 1.25, 4.03 )، فما هي الإزاحة من النقطة A إلى النقطة B؟ الإزاحة من النقطة A إلى النقطة B هي: ( 4.75, 6.23 ) - ( 1.25, 4.03 ) = ( 3.50, 2.20 )T وصلنا إلى نهاية هذا المقال، وسنناقش في المقال التالي العمليات على الأشعة والعمليات المكافئة باستخدام المصفوفات العمودية. ترجمة -وبتصرُّف- للفصل Vectors, Points, and Column Matrices من كتاب Vector Math for 3D Computer Graphics لصاحبه Bradley Kjell. اقرأ المزيد المقال السابق التعرف على النقاط والخطوط في الرسوميات الحاسوبية ثلاثية الأبعاد. الرسم باستخدام الأدوات المساعدة في كريتا دليلك الشامل إلى أنواع البيانات.
-
أنشأتَ واختبرتَ موقع المكتبة المحلية LocalLibrary، ويجب الآن تثبيته على خادم ويب عام ليصل إليه موظفو المكتبة وأعضاؤها عبر الإنترنت. يقدّم هذا المقال نظرة عامة حول كيفية البحث عن مضيف لنشر موقعك، وما عليك فعله لتجهيز موقعك لمرحلة الإنتاج. المتطلبات الأساسية: إكمال المقالات السابقة من هذه السلسلة بما في ذلك مقال عرض بيانات المكتبة والعمل مع الاستمارات. الهدف: معرفة مكان وكيفية نشر تطبيق Express في بيئة الإنتاج. يجب استضافة موقعك بعد الانتهاء منه (أو الانتهاء منه "بما يكفي" لبدء الاختبار العام) في مكان أعم ويمكن الوصول إليه من أجهزة أخرى غير حاسوبك الشخصي الخاص بالتطوير. عملتَ حتى الآن في بيئة تطوير باستخدام خادم ويب تطوير Express/Node لمشاركة موقعك على المتصفح أو الشبكة المحلية، وشغّلتَ موقعك باستخدام إعدادات تطوير (غير آمنة) بحيث يمكن الوصول إلى معلومات تنقيح الأخطاء والمعلومات الخاصة الأخرى. يجب عليك أولًا تنفيذ الخطوات التالية قبل أن تتمكّن من استضافة موقع ويب خارجيًا: اختيار بيئة لاستضافة تطبيق Express. إجراء بعض التغييرات على إعدادات مشروعك. إعداد بنية تحتية على مستوى الإنتاج لتخديم موقع الويب. يقدّم هذا المقال بعض الإرشادات حول الخيارات المتاحة لاختيار موقع استضافة، ونظرة ًعامة مختصرة على ما يجب تطبيقه لتجهيز تطبيق Express لبيئة الإنتاج، ويقدم مثالًا عمليًا لكيفية تثبيت موقع المكتبة المحلية LocalLibrary على خدمة الاستضافة السحابية Railway. ما هي بيئة الإنتاج؟ بيئة الإنتاج هي البيئة التي يوفّرها حاسوب الخادم إذ ستشغِّل موقعك للاستخدام الخارجي، وتشمل هذه البيئة ما يلي: عتاد الحاسوب الذي يعمل عليه الموقع. نظام التشغيل، مثل لينكس أو ويندوز. وقت التشغيل الخاص بلغة البرمجة ومكتبات أطر العمل التي كُتِب عليها موقعك. خادم الويب المُستخدَم لتخديم الصفحات والمحتويات الأخرى، مثل خادم إنجن إكس Nginx وأباتشي Apache. البنية التحتية لخادم الويب وتتضمن خادم الويب والوكيل العكسي Reverse Proxy وموازن الحِمل Load Balancer وغير ذلك. قواعد البيانات التي يعتمد عليها موقعك. يمكن أن يكون حاسوب الخادم موجودًا في مقر عملك ومتصلًا بالإنترنت من خلال رابط سريع، ولكن يمكن استخدام حاسوب مستضاف على السحابة، وهذا يعني تنفيذ شيفرتك البرمجية على بعض الحواسيب البعيدة (أو على حاسوب افتراضي) في مركز أو مراكز بيانات شركتك المضيفة. يقدّم الخادم البعيد عادةً مستوًى مضمونًا من موارد المعالجة، مثل وحدة المعالجة المركزية والذاكرة RAM وذاكرة التخزين وغير ذلك والاتصال بالإنترنت بسعر معين. يشار إلى هذا النوع من عتاد المعالجة والتشبيك الذي يمكن الوصول إليه عن بُعد باسم البنية التحتية كخدمة Infrastructure as a Service -أو IaaS اختصارًا. يوفّر العديد من بائعي خدمة IaaS خيارات التثبيت المُسبَق لنظام تشغيل معين، والذي يجب تثبيت مكونات بيئتك الإنتاجية الأخرى عليه، ويسمح لك البائعون الآخرون باختيار بيئات كاملة الميزات، ويمكن أن يتضمن ذلك إعداد بيئة Node الكاملة. ملاحظة: يمكن للبيئات المبنية مسبقًا أن تسهّل إعداد موقعك لأنها تقلل من عملية الضبط Configuration، ولكن يمكن أن تقيّدك الخيارات المتاحة بخادم غير مألوف لك (أو بمكونات أخرى) ويمكن أن تستند إلى نسخة أقدم من نظام التشغيل. يُفضَّل غالبًا تثبيت المكونات بنفسك حتى تحصل على المكونات التي تريدها، وبالتالي ستعرف من أين تبدأ عندما تحتاج إلى ترقية أجزاء من النظام. يدعم مزوّدو الاستضافة الآخرون إطار عمل Express بوصفه جزءًا من استضافة المنصة كخدمة Platform as a Service -أو PaaS اختصارًا، فلا داعي للقلق في هذا النوع من الاستضافة بشأن معظم أجزاء بيئتك الإنتاجية (الخوادم وموازنو الحِمل load balancers وغيرها) لأن المنصة المضيفة تهتم بهذه الأشياء نيابةً عنك، مما يجعل النشر سهلًا جدًا، لأنك تحتاج فقط إلى التركيز على تطبيق الويب وليس على بنية الخادم التحتية الأخرى. سيختار بعض المطورين المرونة المتزايدة التي توفرها استضافة IaaS على حساب استضافة PaaS، بينما سيقدّر المطورون الآخرون تقليل تكاليف الصيانة وسهولة توسيع نطاق استضافة PaaS. يكون إعداد موقعك على نظام PaaS أسهل بكثير في البداية، وهذا ما سنفعله في هذا المقال. ملاحظة: إذا اخترت مزوّد استضافة متوافق مع Node/Express، فيجب أن يقدّم إرشادات حول كيفية إعداد موقع Express باستخدام عمليات ضبط مختلفة لخادم الويب وخادم التطبيق والوكيل العكسي وغير ذلك. اختيار مزود الاستضافة يوجد العديد من مزوّدات الاستضافة المعروفة التي تدعم بنشاط أو تعمل بصورة جيدة مع بيئة Node و Express، ويوفّر هؤلاء البائعون أنواعًا مختلفة من البيئات (IaaS و PaaS) ومستويات مختلفة من موارد المعالجة والشبكات بأسعار مختلفة. ملاحظة: هناك الكثير من حلول الاستضافة، ويمكن أن تتغير خدماتها وأسعارها بمرور الوقت. سنقدّم بعض الخيارات، ولكن يجدر بك التحقق من هذه الخيارات وغيرها قبل اختيار مزود الاستضافة. إليك بعض الأشياء التي يجب مراعاتها عند اختيار المضيف: مدى انشغال موقعك المُحتمَل وتكلفة البيانات وموارد المعالجة المطلوبة لتلبية هذا المطلب. مستوى الدعم للتوسّع أفقيًا (إضافة المزيد من الأجهزة) وعموديًا (الترقية إلى أجهزة أقوى) وتكاليف ذلك. مكان مراكز بيانات المزوّد، أي المكان الذي يكون الوصول إليه أسرع. أداء وقت التشغيل ووقت التعطل السابقَين للمضيف. الأدوات المتوفرة لإدارة الموقع، لمعرفة إذا كانت سهلة الاستخدام وآمنة، مثل استخدام بروتوكول SFTP أو استخدام بروتوكول FTP. أطر العمل المبنية مسبقًا لمراقبة خادمك. القيود المعروفة، إذ سيوقِف بعض المضيفين عمدًا خدمات معينة مثل البريد الإلكتروني، ويقدّم البعض الآخر فقط عددًا معينًا من ساعات "النشاط" في بعض مستويات الأسعار، أو يقدّم فقط قدرًا صغيرًا من التخزين. الفوائد الإضافية، إذ ستقدّم بعض المزوّدات أسماء نطاقات مجانية ودعمًا لشهادات SSL التي يمكن أن يتعيّن عليك دفع ثمنها. معرفة ما إذا كان المستوى "المجاني" الذي تعتمد عليه تنتهي صلاحيته بمرور الوقت، وما إذا كانت تكلفة التهجير Migrating إلى مستوًى أغلى تعني أنه كان من الأفضل استخدام بعض الخدمات الأخرى من البداية. هناك عددٌ قليل جدًا من المواقع التي توفر بيئات معالجة "مجانية" مخصصة للتقييم والاختبار في البداية، وتكون عادةً بيئات مُقيَّدة أو محدودة الموارد إلى حدٍ ما، ويجب أن تدرك أنه يمكن أن تنتهي صلاحيتها بعد فترة أولية أو يكون لديها قيود أخرى، ولكنها رائعة لاختبار المواقع ذات حركة المرور المنخفضة في بيئة مُستضافة، ويمكن أن توفر تهجيرًا سهلًا للدفع مقابل المزيد من الموارد عندما يصبح موقعك أكثر انشغالًا. تشمل الخيارات الشائعة في هذه الفئة Railway و Python Anywhere و Amazon Web Services و Microsoft Azure وغير ذلك. يقدّم معظم المزوّدين مستوًى أساسيًا مخصصًا لمواقع الإنتاج الصغيرة، والذي يوفّر مستويات أكثر فائدة من قدرة المعالجة وقيودًا أقل. يُعَد Heroku و Digital Ocean و Python Anywhere أمثلة على مزودي الاستضافة المشهورة التي لديها مستوى معالجة أساسي غير مكلف نسبيًا (في نطاق يتراوح بين 5 و 10 دولارات أمريكية شهريًا). ملاحظة: تذكّر أن السعر ليس معيار الاختيار الوحيد، فإذا كان موقعك ناجحًا، فيمكن أن تكون قابلية التوسع هي الأهم. تجهيز موقعك للنشر الأشياء الرئيسية التي يجب التفكير فيها عند نشر موقعك هي أمان الويب والأداء، إذ ستحتاج في الحد الأدنى إلى إزالة تعقّب المكدس المُضمَّن في صفحات الخطأ أثناء التطوير، وترتيب التسجيل، وضبط الترويسات المناسبة لتجنب العديد من هجمات الأمان الشائعة، لذا سنحدّد في الأقسام الفرعية التالية أهم التغييرات الواجب إجراؤها على تطبيقك. ملاحظة: هناك نصائح مفيدة أخرى في توثيق Express، لذا اطلع على أفضل ممارسات عملية الإنتاج: الأداء والموثوقية والأمان، واعتبارات نشر مشاريع Node.js وExpress على الويب. ضبط قاعدة البيانات استخدمنا حتى الآن قاعدة بيانات واحدة ثابتة في الملف app.js، وبسبب عدم احتواء قاعدة بيانات التطوير على معلومات نخشى من كشفها أو إتلافها، فليس هناك خطورة في حال تسريبها، ولكن في حال كنا نتعامل مع بيانات حقيقية، مثل معلومات المستخدم الشخصية، ستكون حماية تلك البيانات أمرًا مهمًا جدًا. نريد عادةً أن نكون قادرين على امتلاك قاعدة بيانات مختلفة لكل من الإنتاج والتطوير وكذلك الاحتفاظ بقاعدة بيانات الإنتاج منفصلة أيضًا عن الشيفرة المصدرية بهدف حمايتها. إذا كان مزود الاستضافة الخاص بك داعمًا لمتغيرات البيئة من خلال واجهة الويب، ستكون إحدى الطرق لتحقيق ما سبق هي جعل الخادم يحصل على عنوان URL لقاعدة البيانات من متغير البيئة، لذلك سنعدّل موقع المكتبة المحلية LocalLibrary للحصول على معرّف URI لقاعدة البيانات من بيئة نظام التشغيل (إذا كان معرّفًا)، وإلّا فسنستخدم قاعدة بيانات التطوير الخاصة بنا. افتح الملف app.js وابحث عن السطر الذي يضبط متغير اتصال قاعدة بيانات MongoDB، والذي سيبدو كما يلي: const mongoDB = "mongodb+srv://your_user_name:your_password@cluster0.lz91hw2.mongodb.net/local_library?retryWrites=true&w=majority"; ضع الشيفرة التالية مكان السطر السابق، إذ تستخدم هذه الشيفرة process.env.MONGODB_URI للحصول على سلسلة الاتصال النصية من متغير البيئة MONGODB_URI إذا جرى ضبطه، واستخدم عنوان URL لقاعدة بياناتك بدلًا من العنصر البديل فيما يلي: // إعداد اتصال mongoose const dev_db_url = const mongoose = require("mongoose"); mongoose.set("strictQuery", false); const dev_db_url = "mongodb+srv://your_user_name:your_password@cluster0.lz91hw2.mongodb.net/local_library?retryWrites=true&w=majority"; const mongoDB = process.env.MONGODB_URI || dev_db_url; main().catch((err) => console.log(err)); async function main() { await mongoose.connect(mongoDB); } ضبط متغير البيئة NODE_ENV على القيمة production يمكننا إزالة تعقّب المكدس في صفحات الخطأ من خلال ضبط متغير البيئة NODE_ENV على قيمة الإنتاج 'production'، إذ يُضبَط على قيمة التطوير 'development' افتراضيًا. يؤدي ضبط المتغير على قيمة الإنتاج إلى إنشاء رسائل خطأ أقل تفصيلًا، بالإضافة إلى التخزين المؤقت لقوالب العرض وملفات CSS المُولَّدة من توسّعات شيفرة CSS، وتشير الاختبارات إلى أن ضبط متغير البيئة NODE_ENV على الإنتاج يمكن أن يحسن أداء التطبيق بمقدار ثلاثة أضعاف. يمكن إجراء هذا التغيير إما باستخدام export، أو ملف بيئة، أو نظام تهيئة لنظام التشغيل. ملاحظة: يُعَد ذلك تغييرًا تجريه في إعداد بيئتك بدلًا من إجرائه في تطبيقك، ولكنه جدير بالذكر حاليًا، إذ سنوضح كيفية ضبطه لمثال الاستضافة الآتي. التسجيل المناسب يمكن أن يكون لتسجيل الاستدعاءات تأثير على موقع الويب الذي يمتلك حركة مرور عالية، إذ يمكن أن تحتاج إلى تسجيل نشاط موقع الويب في بيئة الإنتاج، مثل تعقّب حركة المرور، أو تسجيل استدعاءات واجهة برمجة التطبيقات، ولكن يجب أن تحاول تقليل كمية التسجيل المُضافة لأغراض تنقيح الأخطاء Debugging. تتمثل إحدى طرق تقليل تسجيل "تنقيح الأخطاء" في بيئة الإنتاج في استخدام وحدة مثل الوحدة debug التي تسمح لك بالتحكم في التسجيل الذي يُجرَى من خلال ضبط متغير بيئة، فمثلًا يوضح جزء الشيفرة البرمجية التالي كيف يمكنك إعداد تسجيل المؤلف "author"، إذ يُصرَّح عن المتغير debug بالاسم author، وستُعرَض البادئة "author" تلقائيًا لجميع السجلات من هذا الكائن. const debug = require("debug")("author"); // عرض استمارة تحديث المؤلف باستخدام GET exports.author_update_get = asyncHandler(async (req, res, next) => { const author = await Author.findById(req.params.id).exec(); if (author === null) { // لا توجد نتائج debug(`id not found on update: ${req.params.id}`); const err = new Error("Author not found"); err.status = 404; return next(err); } res.render("author_form", { title: "Update Author", author: author }); }); يمكنك بعد ذلك تفعيل مجموعة معينة من السجلات من خلال تحديدها بوصفها قائمة مفصول بين عناصرها بفواصل في متغير بيئة DEBUG، ويمكنك ضبط المتغيرات لعرض سجلات المؤلف والكتاب كما هو موضح فيما يلي (المحارف البديلة Wildcards مدعومة أيضًا): # في نظام ويندوز set DEBUG=author,book # في نظام لينكس export DEBUG="author,book" ملاحظة: يمكن أن تحل استدعاءات debug محل التسجيل الذي ربما طبّقته مسبقًا باستخدام console.log() أو console.error()، لذا ضع التسجيل باستخدام وحدة debug مكان استدعاءات console.log() في شيفرتك البرمجية، وشغّل التسجيل وأوقفه في بيئة التطوير من خلال ضبط المتغير DEBUG ولاحظ تأثير ذلك على التسجيل. إذا كنت بحاجة إلى تسجيل نشاط موقع الويب، يمكنك استخدام مكتبة تسجيل مثل Winston أو Bunyan. اطلع على أفضل ممارسات عملية الإنتاج: الأداء والموثوقية لمزيد من المعلومات. استخدم ضغط gzip أو deflate للاستجابة تضغط خوادم الويب غالبًا استجابة HTTP المُرسَلة إلى العميل، مما يقلل من الوقت المطلوب للعميل للحصول على الصفحة وتحميلها. سيعتمد تابع الضغط المُستخدَم على توابع فك الضغط التي يدعمها العميل في الطلب، إذ ستُرسَل الاستجابة بحيث تكون غير مضغوطة عندما تكون توابع الضغط غير مدعومة. يمكنك إضافة ذلك إلى موقعك باستخدام برمجية الضغط الوسيطة compression، لذا ثبّتها في جذر مشروعك من خلال تشغيل الأمر التالي: npm install compression افتح الملف "./app.js" واطلب مكتبة الضغط. ضِف مكتبة الضغط إلى سلسلة البرمجيات الوسيطة باستخدام التابع use() الذي يجب أن يظهر قبل أي وجهات Routes تريد ضغطها (جميع الوجهات في حالتنا): const catalogRouter = require("./routes/catalog"); // استيراد الوجهات لمنطقة الدليل "catalog" من موقعك const compression = require("compression"); // إنشاء كائن تطبيق Express const app = express(); // … app.use(compression()); // ضغط جميع الوجهات app.use(express.static(path.join(__dirname, "public"))); app.use("/", indexRouter); app.use("/users", usersRouter); app.use("/catalog", catalogRouter); // إضافة وجهات الدليل catalog إلى سلسلة البرمجيات الوسيطة // … ملاحظة: لن تَستخدمَ هذه البرمجيات الوسيطة لموقع الويب الذي يمتلك حركة مرور عالية في بيئة الإنتاج، لذا يمكنك استخدام وكيل عكسي مثل Nginx بدلًا من ذلك. استخدام حزمة Helmet للحماية من الثغرات المعروفة تُعَد Helmet حزمة برمجيات وسيطة يمكنها ضبط ترويسات HTTP المناسبة التي تساعد في حماية تطبيقك من ثغرات الويب المعروفة. اطّلع على توثيقها للحصول على مزيد من المعلومات حول الترويسات التي تضبطها والثغرات الأمنية التي تحميك منها. ثبّتها في جذر مشروعك من خلال تشغيل الأمر التالي: npm install helmet افتح الملف "./app.js" واطلب مكتبة helmet، ثم ضِف الوحدة إلى سلسلة البرمجيات الوسيطة باستخدام التابع use(): const compression = require("compression"); const helmet = require("helmet"); // إنشاء كائن تطبيق Express const app = express(); // إضافة حزمة helmet إلى سلسلة البرمجيات الوسيطة // ضبط ترويسات CSP للسماح بتخديم Bootstrap و Jquery app.use( helmet.contentSecurityPolicy({ directives: { "script-src": ["'self'", "code.jquery.com", "cdn.jsdelivr.net"], }, }), ); // … يمكن أن نضيف التابع app.use(helmet()); لإضافة مجموعة فرعية من الترويسات المتعلقة بالأمان والتي تكون منطقية لمعظم المواقع، ولكننا نضمّن بعض سكربتات بوتستراب bootstrap و jQuery في قالب موقع المكتبة المحلية LocalLibrary الأساسي، مما يؤدي إلى انتهاك سياسة أمان المحتوى Content Security Policy -أو CSP اختصارًا- الافتراضية الخاصة بحزمة helmet، والتي لا تسمح بتحميل السكربتات العابرة للمواقع. يمكن السماح بتحميل هذه السكربتات من خلال تعديل ضبط helmet بحيث تُضبَط موجّهات CSP للسماح بتحميل السكربت من النطاقات المُشار إليها، ويمكنك بالنسبة لخادمك إضافة أو تعطيل ترويسات محددة حسب الحاجة باتباع الإرشادات الخاصة باستخدام helmet. إضافة معدل محدود إلى وجهات واجهة برمجة التطبيقات API تُعَد Express-rate-limit حزمة برمجية وسيطة يمكن استخدامها للحد من الطلبات المتكررة لواجهات برمجة التطبيقات API والنقاط النهائية، فهناك العديد من الأسباب التي يمكن أن تؤدي إلى زيادة الطلبات على موقعك مثل هجمات حجب الخدمة أو هجمات القوة الغاشمة أو حتى مجرد عميل أو سكربت لا يتصرف كما هو متوقع، ويمكن أن تُحاسَب على حركة المرور الإضافية بغض النظر عن مشاكل الأداء التي يمكن أن تنشأ عن كثرة الطلبات التي تتسبب في إبطاء خادمك. يمكن استخدام حزمة Express-rate-limit للحد من عدد الطلبات التي يمكن إجراؤها على وجهة معينة أو مجموعة من الوِجهات. يمكنك الاطلاع على مقال الهجمات الأمنية Security Attacks في الشبكات الحاسوبية على أكاديمية حسوب لمزيد من المعلومات حول أنواع الهجمات الأمنية. ثبّت هذه الحزمة في جذر مشروعك من خلال تشغيل الأمر التالي: npm install express-rate-limit افتح الملف "./app.js" واطلب مكتبة express-rate-limit كما يلي، ثم ضِف الوحدة إلى سلسلة البرمجيات الوسيطة باستخدام التابع use(): const compression = require("compression"); const helmet = require("helmet"); const app = express(); // إعداد محدِّد المعدل إلى عشرين طلبًا في الدقيقة كحد أعلى const RateLimit = require("express-rate-limit"); const limiter = RateLimit({ windowMs: 1 * 60 * 1000, // 1 دقيقة max: 20, }); // طبّق محدّد المعدّل على جميع الطلبات app.use(limiter); // … ملاحظة: يحدّد الأمر السابق جميع الطلبات لتكون 20 طلبًا في الدقيقة، ولكن يمكنك تغييره حسب الحاجة. يمكن أيضًا استخدام خدمات خارجية مثل Cloudflare إذا كنت بحاجة إلى مزيد من الحماية المتقدمة ضد هجمات حجب الخدمة أو أنواع أخرى من الهجمات. تطبيق عملي: تثبيت موقع المكتبة المحلية LocalLibrary على منصة Railway يقدّم هذا القسم شرحًا عمليًا لكيفية تثبيت موقع المكتبة المحلية LocalLibrary على منصة Railway. سبب استخدام Railway اخترنا استخدام منصة Railway لعدة أسباب هي: تمتلك Railway مستوًى مجانيًا لخطة بداية مع بعض القيود، إذ من المهم أن تكون بأسعار مقبولة لجميع المطورين. تهتم Railway بمعظم البنية التحتية، فلا حاجة للقلق بشأن الخوادم وموازني الحِمل والوكلاء العكسيين وغير ذلك، مما يجعل البدء أسهل بكثير. تركّز Railway على تجربة المطور للتطوير والنشر، مما يؤدي إلى وجود منحنى تعليمي أسرع وأسلس من العديد من البدائل الأخرى. تُعَد المهارات والمفاهيم التي ستتعلمها عند استخدام Railway قابلة للتحويل، إذ تمتلك Railway بعض الميزات الجديدة الممتازة، ولكن تستخدم خدماتُ الاستضافة الشائعة الأخرى العديدَ من الأفكار والأساليب نفسها. لا تؤثر قيود الخدمات والخطط على استخدامنا لمنصة Railway في مثالنا، فمثلًا: تقدّم خطة البداية 500 ساعة فقط من وقت النشر المستمر كل شهر و5 دولارات من الرصيد الذي يُستهلَك بناءً على الاستخدام، ويُعاد ضبط الساعات والرصيد ويجب إعادة نشر المشاريع في نهاية كل شهر. تعني هذه القيود أنه يمكنك تشغيل هذا المثال بصورة مستمرة لمدة 21 يومًا تقريبًا، ويُعَد ذلك أكثر من كافٍ للتطوير والاختبار، ولكن لن تتمكّن من استخدام هذه الخطة لموقع حقيقي للإنتاج. تحتوي بيئة خطة البداية على 512 ميجابايت فقط من الذاكرة RAM و1 جيجابايت من ذاكرة التخزين، وهذا أكثر من كافٍ لمثالنا. لا توجد سوى منطقة واحدة مدعومة وهي الولايات المتحدة الأمريكية حاليًا، إذ يمكن أن تكون الخدمة خارج هذه المنطقة أبطأ أو تحظرها القوانين المحلية. يمكن العثور على قيود أخرى في توثيق خطط Railway للدفع. تبدو الخدمة موثوقة جدًا، وإذا كانت مناسبة لك، فإن الأسعار يمكن التنبؤ بها، ويكون توسيع تطبيقك سهلًا جدًا. تعَد Railway مناسبة لاستضافة مثالنا، ولكن يجب أن تأخذ الوقت الكافي لتحديد ما إذا كانت مناسبة لموقعك. كيفية عمل Railway يُشغَّل كل تطبيق ويب في حاوية افتراضية معزولة ومستقلة خاصة به، لذا يجب أن تكون Railway قادرة على إعداد البيئة والاعتماديات المناسبة، وفهم كيفية إطلاقها لتنفيذ تطبيقك. تجعل Railway هذا الأمر سهلًا، إذ يمكنها التعرف تلقائيًا على العديد من أطر عمل وبيئات تطبيقات الويب المختلفة وتثبيتها بناءً على استخدامها "للمصطلحات الشائعة"، فمثلًا تتعرّف Railway على تطبيقات Node لأن لديها ملف package.json، ويمكنها تحديد مدير الحزم المُستخدَم للبناء من ملف "القفل Lock". إذا احتوى التطبيق على ملف package-lock.json مثلًا، فستعرف Railway أنها ستستخدم مدير حزم npm لتثبيت الحزم، وإذا وجدت yarn.lock فستعرف أنها ستستخدم yarn. ستبحث Railway -بعد تثبيت جميع الاعتماديات- عن سكربتات بالاسم "build" و "start" في ملف الحزمة، وستستخدمها لبناء وتشغيل الشيفرة البرمجية. ملاحظة: تستخدم Railway حزمة Nixpacks للتعرف على العديد من أطر عمل تطبيقات الويب المكتوبة بلغات برمجة مختلفة. لا حاجة إلى معرفة أيّ شيء آخر لهذا المقال، ولكن يمكنك معرفة المزيد حول خيارات نشر تطبيقات Node في توثيق Nixpacks. يمكن للتطبيق بعد تشغيله ضبط نفسه باستخدام المعلومات المقدمة في متغيرات البيئة، فمثلًا يجب أن يحصل التطبيق الذي يستخدم قاعدة بيانات على العنوان باستخدام متغير، ويمكن أن تستضيف Railway خدمة قاعدة البيانات نفسها أو على أي مزوّد آخر. يتفاعل المطورون مع Railway من خلال موقع Railway وباستخدام أداة واجهة سطر أوامر CLI خاصة، إذ تسمح لك واجهة CLI بربط مستودع غيت هب GitHub محلي بمشروع Railway، ورفع المستودع من الفرع المحلي إلى الموقع المباشر، وفحص سجلات العملية الجارية، وإعداد متغيرات الضبط والحصول عليها وغير ذلك. من أهم الميزات أنه يمكنك استخدام واجهة CLI لتشغيل مشروعك المحلي مع متغيرات البيئة نفسها للمشروع المباشر. يجب وضع تطبيق Express الخاص بنا في مستودع غيت git وإجراء بعض التغييرات الطفيفة ليعمل تطبيقنا على Railway، ثم يمكننا إعداد حساب على Railway وتثبيت موقعنا وقاعدة البيانات وتجربة عميل Railway. وهذا هو كل ما تحتاجه من معلومات في البداية. إنشاء مستودع للتطبيق على غيت هب GitHub تتكامل Railway تكاملًا وثيقًا مع غيت هب ونظام التحكم في إصدارات الشيفرة المصدرية git، ويمكنك ضبطها لنشر التحديثات تلقائيًا عند إجراء تغييرات إلى مستودع أو فرع معين على غيت هب، أو يمكنك دفع فرع الشيفرة المحلية الحالي مباشرةً إلى النشر الخاص بمنصة Railway باستخدام واجهة CLI. ملاحظة: يُعَد استخدام نظام إدارة الشيفرة المصدرية مثل غيت هب ممارسة جيدة لتطوير البرمجيات، فإذا كنت تستخدم غيت هب لإدارة شيفرتك المصدرية مسبقًا، فتخطى هذه الخطوة. هناك العديد من الطرق للعمل مع git، ومن أسهلها إعداد حساب على غيت هب أولًا وإنشاء المستودع عليه، ثم المزامنة معه محليًا كما يلي: انتقل إلى موقع GitHub الرسمي وأنشئ حسابًا عليه. انقر على ارتباط + في شريط الأدوات العلوي وحدّد خيار "مستودع جديد New repository" بعد تسجيل الدخول. املأ جميع الحقول في هذه الاستمارة، إذ يمكن أن تكون هذه الحقول غير إلزامية، ولكن يُوصَى بها بشدة. أدخل اسم المستودع الجديد والوصف، فمثلًا يمكنك استخدام الاسم "express-locallibrary-tutorial" والوصف "Local Library website written in Express (Node)" (موقع المكتبة المحلية المكتوب باستخدام Express). اختر الخيار Node في قائمة الاختيار Add .gitignore. اختر الترخيص المفضل لديك في قائمة الاختيار Add license. تحقق من تهيئة المستودع باستخدام README. تحذير: سيجعل الوصول الافتراضي العام "Public" جميع الشيفرة البرمجية المصدرية -بما في ذلك اسم المستخدم وكلمة المرور لقاعدة البيانات- مرئيةً لأي شخص على الإنترنت، لذا تأكد من أن الشيفرة البرمجية المصدرية لا تقرأ اعتماديات إلّا من متغيرات البيئة ولا تحتوي على أيّ اعتماديات ثابتة، وإلّا حدد الخيار خاص "Private" للسماح فقط لأشخاص محدَّدين برؤية الشيفرة المصدرية. اضغط على إنشاء مستودع Create repository. انقر فوق الزر الأخضر نسخ Clone أو تنزيل Download في صفحة مستودعك الجديد. انسخ قيمة URL من حقل النص الموجود في مربع الحوار الذي يظهر، فإذا استخدمت اسم المستودع "express-locallibrary-tutorial"، فيجب أن يكون عنوان URL مثل العنوان: https://github.com/<your_git_user_id>/express-locallibrary-tutorial.git. انتهينا من إنشاء المستودع، ويجب الآن نسخه على حاسوبك المحلي باتباع الخطوات التالية: أولًا، ثبّت git على حاسوبك المحلي، إذ يمكنك العثور على نسخ لأنظمة مختلفة. ثانيًا، افتح موجه الأوامر أو الطرفية وانسخ مستودعك باستخدام عنوان URL الذي نسخته سابقًا كما في الأمر التالي: git clone https://github.com/<your_git_user_id>/express-locallibrary-tutorial.git سيؤدي هذا الأمر إلى إنشاء المستودع في المجلد الحالي. ثالثًا، انتقل إلى مجلد المستودع باستخدام الأمر التالي: cd express-locallibrary-tutorial أخيرًا، انسخ ملفات تطبيقك المصدرية في مجلد المستودع، ثم اجعلها جزءًا من المستودع باستخدام git كما يلي: أولًا، انسخ تطبيق Express إلى هذا المجلد باستثناء المجلد "/node_modules" الذي يحتوي على ملفات الاعتماديات التي يجب جلبها من مدير حزم npm حسب الحاجة. ثانيًا، افتح موجه الأوامر أو الطرفية واستخدم الأمر add لإضافة جميع الملفات إلى git كما يلي: git add -A ثالثًا، استخدم الأمر status للتحقق من صحة جميع الملفات التي تريد تثبيتها commit، إذ نريد تضمين الملفات المصدرية، وليس الملفات الثنائية والمؤقتة وما إلى ذلك، إذ يجب أن تبدو كما يلي: > git status ويكون الخرج الناتج بعد تنفيذ هذا الأمر على النحو التالي: On branch main Your branch is up-to-date with 'origin/main'. Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: … رابعًا، ثبّت باستخدام commit الملفات في مستودعك المحلي عندما تكون راضيًا عن النتيجة، إذ يكافئ ذلك الإقرار بالتغييرات وجعلها جزءًا رسميًا من المستودع المحلي. git commit -m "First version of application moved into GitHub" خامسًا، لم يتغيّر المستودع البعيد حتى الآن، لذا يجب مزامنة (باستخدام الأمر push) مستودعك المحلي مع مستودع GitHub البعيد باستخدام الأمر التالي: git push origin main يجب أن تكون قادرًا عند اكتمال هذه العملية على العودة إلى صفحة غيت حيث أنشأت مستودعك، وتحديث الصفحة، ويجب ملاحظة رفع كامل التطبيق. يمكنك الاستمرار في تحديث مستودعك مع تغير الملفات باستخدام دورة أوامر الإضافة add والتثبيت commit والدفع push. ملاحظة: يمكنك الآن إنشاء نسخة احتياطية من شيفرة مشروعك البرمجية، إذ يمكن أن تكون بعض التغييرات التي سنجريها في الأقسام التالية مفيدة للنشر (أو للتطوير) على أي خدمة استضافة، ويمكن أن تكون بعض التغييرات الأخرى غير مفيدة. أفضل طريقة لذلك هي استخدام git لإدارة الإصدارات، إذ يمكنك باستخدامه الرجوع إلى إصدار سابق معين، ويمكنك الاحتفاظ به في فرع منفصل عن التغييرات الإنتاجية واختيار أيّ تغييرات للتنقل بين فروع الإنتاج والتطوير. يستحق تعلم غيت Git الجهد المبذول، لذا اطلع على مجموعة مقالات Git في أكاديمية حسوب. يُعَد نسخ ملفاتك في موقع آخر الطريقة الأسهل، ولكن يمكنك استخدام أيّ طريقة تتناسب مع معرفتك لنظام git. تحديث التطبيق ليعمل على Railway سنشرح في هذا القسم التغييرات التي يجب إجراؤها على تطبيق المكتبة المحلية LocalLibrary ليعمل على Railway، ولكن لن تمنعك هذه التغييرات من استخدام الاختبار وسير العمل المحلي الذي تعلمناه مسبقًا. ضبط نسخة Node يحتوي الملف package.json على كل ما تحتاجه Railway للعمل على اعتماديات تطبيقك وتحديد الملف الواجب تشغيله لبدء تشغيل موقعك، ولكن المعلومات المهمة الوحيدة المفقودة من الملف package.json الحالي هي نسخة Node، إذ يمكنك العثور على نسخة Node التي نستخدمها للتطوير من خلال إدخال الأمر التالي: >node --version v16.17.1 افتح الملف package.json وأضِف المعلومات التالية في قسم engines > node (مع استخدام رقم النسخة في نظامك): { "name": "express-locallibrary-tutorial", "version": "0.0.0", "engines": { "node": ">=16.17.1" }, "private": true, // … لاحظ أن هناك طرقًا أخرى لتوفير نسخة Node على Railway، ولكننا نستخدم الملف package.json لأنه الأسلوب الذي تدعمه العديد من الخدمات على نطاق واسع. لاحظ أيضًا أن Railway لن تستخدم بالضرورة نسخة Node الدقيقة التي تحددها، إذ ستستخدم نسخةً لها رقم النسخة الرئيسي نفسه. الحصول على الاعتماديات وإعادة الاختبار لنختبر الموقع مرةً أخرى ونتأكد من أنه لم يتأثر بأيٍّ من التغييرات التي أجريناها. أولًا، يجب جلب الاعتماديات (تذكّر أننا لم ننسخ المجلد node_modules في شجرة git)، إذ يمكنك ذلك من خلال تشغيل الأمر التالي في طرفيتك ضمن جذر المشروع: npm install شغّل الموقع (اطّلع على فقرة اختبار الوجهات في مقال الوجهات والمتحكمات لمعرفة الأوامر ذات الصلة) وتحقق من أن الموقع لا يزال يتصرف كما هو متوقع. حفظ التغييرات على غيت هب GitHub احفظ الآن جميع التغييرات التي أجريتها على غيت هب، وأدخِل الأوامر التالية في الطرفية أثناء وجودك في مستودعك: git add -A git commit -m "Added files and changes required for deployment" git push origin main يجب أن نكون جاهزين الآن لبدء نشر موقع المكتبة المحلية LocalLibrary على Railway. الحصول على حساب على Railway يجب أولًا إنشاء حساب لبدء استخدام Railway باتباع الخطوات التالية: اذهب إلى موقع Railway الرسمي وانقر على ارتباط تسجيل الدخول Login في شريط الأدوات العلوي. اختر GitHub في النافذة المنبثقة لتسجيل الدخول باستخدام اعتماديات GitHub الخاصة بك. يمكن أن تحتاج بعد ذلك إلى الانتقال إلى بريدك الإلكتروني والتحقق من حسابك. ستسجل بعد ذلك الدخول إلى لوحة تحكم Railway. النشر على Railway من GitHub يجب الآن إعداد Railway لنشر موقع مكتبتنا من GitHub، لذا اختر أولًا خيار لوحة التحكم Dashboard من القائمة العلوية للموقع، ثم حدّد زر مشروع جديد New Project: ستعرض Railway قائمةً بالخيارات الخاصة بالمشروع الجديد، بما في ذلك خيار نشر مشروع من قالب أنشأته لأول مرة في حسابك على GitHub، وعددًا من قواعد البيانات، لذا حدد خيار النشر Deploy from GitHub repo. ستُعرَض جميع المشاريع في مستودعات GitHub التي شاركتها مع Railway أثناء عملية الإعداد، ولكنك ستختار مستودع GitHub الخاص بموقع المكتبة المحلية: <user-name>/express-locallibrary-tutorial. أكّد النشر من خلال تحديد خيار النشر حالًا Deploy Now. ستحمّل Railway بعد ذلك مشروعك وتنشره، مع عرض التقدم في نافذة عمليات النشر Deployments، ثم سترى شيئًا يشبه ما يلي عند اكتمال النشر بنجاح: اختر الآن نافذة الإعدادات Settings، ثم انتقل إلى الأسفل إلى قسم النطاقات Domains، واضغط على زر توليد نطاق Generate Domain. سيؤدي ذلك إلى نشر الموقع ووضع النطاق في مكان الزر كما هو موضح فيما يلي: حدّد عنوان URL للنطاق لفتح تطبيق المكتبة. لاحظ فتح موقع المكتبة المحلية باستخدام بيانات التطوير الخاصة بك لأننا لم نحدّد قاعدة بيانات الإنتاج. تجهيز وتوصيل قاعدة بيانات MongoDb لننشئ الآن قاعدة بيانات MongoDB الخاصة بالإنتاج لاستخدامها بدلًا من استخدام بيانات التطوير، إذ سننشئ قاعدة البيانات بوصفها جزءًا من مشروع تطبيق Railway، بالرغم من أنه لا يوجد ما يمنعك من إنشائها في مشروعها المنفصل أو استخدام قاعدة بيانات MongoDB Atlas لبيانات الإنتاج، كما فعلت مع قاعدة بيانات التطوير. حدّد الخيار Dashboard من قائمة الموقع العلوية على Railway ثم حدد مشروع تطبيقك، الذي يحتوي حاليًا على خدمة واحدة فقط لتطبيقك، والتي يمكن اختيارها لضبط المتغيرات وتفاصيل الخدمة الأخرى، ثم حدّد الزر جديد New، والذي يستخدم لإضافة خدمات إلى المشروع الحالي. حدد قاعدة البيانات Database عندما يُطلَب منك تحديد نوع الخدمة لإضافتها. حدّد بعد ذلك خيار الإضافة Add MongoDB لبدء إضافة قاعدة البيانات. ستجهز Railway بعد ذلك خدمةً تحتوي على قاعدة بيانات فارغة في المشروع نفسه، وسترى عند الانتهاء كلًا من التطبيق وخدمات قاعدة البيانات في عرض المشروع. حدّد خدمة MongoDB لعرض معلومات حول قاعدة البيانات، ثم افتح نافذة "الاتصال Connect" وانسخ "عنوان URL لاتصال Mongo"، وهو عنوان قاعدة البيانات. يمكن جعل تطبيق المكتبة يصل إلى هذا العنوان من خلال إضافته إلى عملية التطبيق باستخدام متغير بيئة، لذا افتح أولًا خدمة التطبيق، ثم حدّد نافذة المتغيرات Variables واضغط على زر متغير جديد New Variable. أدخِل اسم المتغير MONGODB_URI وعنوان URL للاتصال الذي نسخته لقاعدة البيانات، فالمتغير MONGODB_URI هو اسم متغير البيئة الذي ضبطنا التطبيق منه لقراءة عنوان قاعدة البيانات، وسيظهر لديك ما يلي: حدّد بعد ذلك زر الإضافة Add لإضافة المتغير. تعيد Railway تشغيل تطبيقك عندما تحدّث المتغيرات. إذا فحصتَ الصفحة الرئيسية الآن، فيجب أن تظهِر قيمًا صفرية لأعداد الكائنات، إذ تعني التغييرات السابقة أننا نستخدم الآن قاعدة بيانات جديدة (فارغة). متغيرات الضبط الأخرى تذكّر من القسم السابق أننا بحاجة إلى ضبط NODE_ENV على قيمة الإنتاج 'production' لتحسين الأداء وإنشاء رسائل خطأ أقل تفصيلًا، ويمكننا فعل ذلك في الشاشة نفسها التي ضبطنا منها المتغير MONGODB_URI. افتح خدمة التطبيق، ثم حدد نافذة المتغيرات Variables، إذ سترى أن المتغير MONGODB_URI مُعرَّف مسبقًا، واضغط على زر متغير جديد New Variable. أدخل NODE_ENV بوصفه اسم المتغير الجديد و production بوصفه اسم البيئة، ثم اضغط على زر الإضافة Add. ضُبِط وأُعِدّ تطبيق المكتبة المحلية للاستخدام في بيئة الإنتاج، ويمكنك إضافة البيانات عبر واجهة موقع الويب ويجب أن تعمل بالطريقة نفسها التي كانت تعمل بها أثناء عملية التطوير (مع وصول أقل إلى معلومات تنقيح الأخطاء للصفحات غير الصالحة). ملاحظة: إذا أدرتَ إضافة بعض البيانات للاختبار، فيمكنك استخدام السكربت populatedb مع عنوان URL لقاعدة بيانات MongoDB الخاصة بالإنتاج كما ناقشنا في مقال استخدام قاعدة البيانات باستخدام مكتبة Mongoose. تثبيت العميل نزّل وثبّت عميل Railway لنظام تشغيلك المحلي باتباع التعليمات الواردة في توثيق Railway. ستتمكن من تشغيل الأوامر بعد تثبيت العميل. تتضمن بعض العمليات الأكثر أهمية نشر المجلد الحالي لحاسوبك إلى مشروع Railway المرتبط به دون الحاجة للرفع على GitHub، وتشغيل مشروعك محليًا باستخدام الإعدادات نفسها الموجودة على خادم الإنتاج. يمكنك الحصول على قائمة بجميع الأوامر الممكنة من خلال إدخال الأمر التالي في الطرفية: railway help تنقيح الأخطاء Debugging يوفّر عميل Railway الأمر logs لإظهار السجلات الأخيرة (يتوفر سجل كامل على الموقع لكل مشروع): railway logs الخلاصة وصلنا إلى نهاية مقالنا حول إعداد تطبيقات Express في بيئة الإنتاج، وكذلك إلى نهاية سلسلة مقالات إطار عمل Express، لذا نأمل أنها كانت مفيدة لك، ولا تنسَ أنه يمكنك التحقق من النسخة الكاملة من الشيفرة المصدرية على GitHub. ترجمة -وبتصرُّف- للمقال Express Tutorial Part 7: Deploying to production. اقرأ المزيد المقال السابق تطبيق عملي لتعلم Express - الجزء الرابع: عرض بيانات المكتبة والعمل مع الاستمارات. مرحلة نشر التطبيق في عملية تطوير الويب. تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج.
-
أصبحنا الآن جاهزين لإضافة الصفحات التي تعرض كتب موقع المكتبة المحلية LocalLibrary وبيانات أخرى، إذ ستتضمن هذه الصفحات صفحةً رئيسية توضح عدد السجلات لكل نوع نموذج Model وعددًا من صفحات القائمة والصفحات التفصيلية لجميع النماذج، وبالتالي سنكتسب خبرةً عملية في الحصول على السجلات من قاعدة البيانات واستخدام القوالب. سنشرح أيضًا في هذا المقال كيفية العمل مع استمارات HTML في إطار عمل Express باستخدام لغة القوالب Pug، إذ سنناقش كيفية كتابة استمارات لإنشاء المستندات وتحديثها وحذفها من قاعدة بيانات الموقع. المتطلبات الأساسية: إكمال المقالات السابقة من هذه السلسلة بما في ذلك مقال الوِجهات Routes والمتحكمات Controllers. الهدف: فهم كيفية إجراء عمليات قاعدة البيانات غير المتزامنة باستخدام async/await، واستخدام لغة القوالب Pug، والحصول على البيانات من عنوان URL في دوال المتحكمات، وكتابة الاستمارات للحصول على البيانات من المستخدمين وتحديث قاعدة البيانات باستخدام هذه البيانات. عرض بيانات المكتبة عرّفنا في المقالات السابقة نماذجَ Mongoose التي يمكننا استخدامها للتفاعل مع قاعدة بيانات وأنشأنا بعض سجلات المكتبة الأولية، ثم أنشأنا جميع الوِجهات Routes اللازمة لموقع المكتبة المحلية LocalLibrary، ولكن مع دوال متحكمات وهمية dummy، وهي دوال متحكمات هيكلية تعيد فقط رسالة "not implemented" عند الوصول إلى الصفحة. تتمثل الخطوة التالية في توفير عمليات تقديم مناسبة للصفحات التي تعرض معلومات المكتبة، إذ سنتعرّف على تقديم الصفحات التي تعرض استمارات لإنشاء المعلومات أو تحديثها أو حذفها، ويتضمن ذلك تحديث دوال المتحكمات لجلب السجلات باستخدام النماذج وتعريف القوالب لعرض هذه المعلومات للمستخدمين. سنبدأ بتقديم موضوعات عامة أو أولية توضح كيفية إدارة العمليات غير المتزامنة في دوال المتحكمات وكيفية كتابة القوالب باستخدام مكتبة القوالب Pug، ثم سنوفّر عمليات تقديم لكل صفحة من صفحاتنا الرئيسية المُعَدّة للقراءة فقط مع شرح موجز لأي ميزات خاصة أو جديدة تستخدمها، وبالتالي يجب أن يكون لديك فهم جيد وشامل لكيفية عمل الوجهات والدوال غير المتزامنة والعروض Views والنماذج عمليًا. تمر المواضيع التالية بعملية إضافة الميزات المختلفة المطلوبة لعرض صفحات الموقع المطلوبة، إذ يجب أن تطبّق كل قسم منها عمليًا قبل الانتقال إلى القسم التالي: مقدمة إلى القوالب Template، وإنشاء القالب الأساسي لموقع مكتبة محلية مثالًا. إنشاء الصفحة الرئيسية وصفحات القوائم لموقع المكتبة المحلية التي تتضمن: صفحة قائمة الكتب. صفحة قائمة نسخ الكتب BookInstance. تنسيق التاريخ باستخدام مكتبة Luxon. صفحة قائمة المؤلفين وتحدي صفحة قائمة أنواع الكتب. إنشاء صفحات التفاصيل لموقع المكتبة المحلية التي تتضمن: صفحة تفاصيل نوع الكتاب. صفحة تفاصيل الكتاب. صفحة تفاصيل المؤلف. صفحة تفاصيل نسخ الكتاب والتحدي. أنشأنا جميع الصفحات المُعَدّة للقراءة فقط الخاصة بموقعنا وهي: صفحة رئيسية تعرض عدد نسخ كلّ نموذج من نماذجنا، وصفحات قائمة وتفاصيل الكتب ونسخ الكتب والمؤلفين وأنواع الكتب، واكتسبنا الكثير من المعرفة الأساسية حول المتحكمات وإدارة التحكم في التدفق عند استخدام العمليات غير المتزامنة، وإنشاء عروض باستخدام مكتبة القوالب Pug، والاستعلام في قاعدة بيانات الموقع باستخدام النماذج، وتمرير المعلومات إلى العرض، وإنشاء القوالب وتوسيعها، وستعلّمك التحديات أيضًا عن معالجة التاريخ باستخدام مكتبة Luxon. سننشئ الآن استمارات HTML وشيفرة معالجة الاستمارات لبدء تعديل البيانات التي يخزنها الموقع. العمل مع الاستمارات استمارة HTML هي مجموعة مؤلفة من حقل أو عنصر واجهة مستخدم واحد أو أكثر على صفحة الويب، والتي يمكن استخدامها لجمع المعلومات من المستخدمين لإرسالها إلى الخادم، وتُعَد آليةً مرنة لتجميع مدخلات المستخدم نظرًا لوجود مدخلات مناسبة في الاستمارة التي تكون متاحة لإدخال العديد من أنواع البيانات المختلفة مثل مربعات النص ومربعات الاختيار وأزرار الاختيار ومنتقيات التاريخ وما إلى ذلك. تُعَد الاستمارات أيضًا طريقةً آمنة نسبيًا لمشاركة البيانات مع الخادم، لأنها تسمح بإرسال البيانات في طلبات POST مع الحماية من هجمات طلبات التزوير عبر المواقع. يمكن أن يكون التعامل مع الاستمارات معقدًا، إذ يحتاج المطورون إلى كتابة شيفرة HTML للاستمارة، والتحقق من صحة البيانات المُدخَلة وتطهيرها Sanitize بصورة صحيحة على الخادم وربما في المتصفح أيضًا، وإعادة نشر الاستمارة مع رسائل خطأ لإعلام المستخدمين بأي حقول غير صالحة، والتعامل مع البيانات عند إرسالها بنجاح، وأخيرًا الرد على المستخدم بطريقةٍ ما للإشارة إلى النجاح. سنوضح في هذا المقال كيفية تنفيذ هذه العمليات في إطار عمل Express، إذ سنوسّع موقع المكتبة المحلية LocalLibrary للسماح للمستخدمين بإنشاء عناصر وتعديلها وحذفها من المكتبة. ملاحظة: لم نتطرق إلى كيفية تقييد وجهات Routes معينة للمستخدمين المستوثقين أو المُصرَّح لهم، لذلك سيتمكن أيّ مستخدم من إجراء تغييرات على قاعدة البيانات. استمارات HTML اطّلع أولًا على نظرة عامة موجزة على استمارات HTML. ليكن لدينا استمارة HTML البسيطة التالية مع حقل نص واحد لإدخال اسم "الفريق" والتسمية Label المرتبطة به: تُعرَّف الاستمارات في لغة HTML بوصفها مجموعة من العناصر ضمن وسوم <form>…</form> التي تحتوي على عنصر إدخال input واحد على الأقل من النوع type="submit". <form action="/team_name_url/" method="post"> <label for="team_name">Enter name: </label> <input id="team_name" type="text" name="name_field" value="Default name for team." /> <input type="submit" value="OK" /> </form> ضمّنا في المثال السابق حقلًا نصيًا واحدًا فقط لإدخال اسم الفريق، ولكن يمكن أن تحتوي الاستمارة على أيّ عدد من عناصر الإدخال الأخرى والتسميات Labels المرتبطة بها. تعرّف السمة type الخاصة بالحقل نوع عنصر واجهة المستخدم الذي سيُعرَض، وتُستخدَم سمات الاسم name والمعرّف id الخاصة بالحقل لتعريف الحقل في شيفرة جافا سكريبت Javascript أو CSS أو HTML، بينما تعرّف السمة value القيمة الأولية للحقل عند عرضه لأول مرة. تُحدَّد تسمية الفريق المطابقة باستخدام الوسم label (مثل التسمية "Enter name" السابقة)، مع حقل for الذي يحتوي على قيمة معرّف id لحقل الإدخال input المرتبط به. يُعرَض حقل الإدخال submit بوصفه زرًا افتراضيًا، إذ يمكن للمستخدم الضغط عليه لرفع البيانات التي تحتويها عناصر الإدخال الأخرى إلى الخادم، وهي اسم الفريق team_name فقط في مثالنا. تعرّف سمات الاستمارة تابع HTTP وهو method المستخدَم لإرسال البيانات وهدف البيانات على الخادم (السمة action) كما يلي: action: هو المورد أو عنوان URL، إذ ستُرسَل البيانات للمعالجة عند إرسال الاستمارة. إن لم تُضبَط هذه السمة، أو ضُبِطت بوصفها سلسلة نصية فارغة، فستُرسَل الاستمارة إلى عنوان URL للصفحة الحالية. method: تابع HTTP المُستخدَم لإرسال البيانات: إما POST أو GET. يجب دائمًا استخدام تابع POST إذا أدّت البيانات إلى تغييرٍ في قاعدة بيانات الخادم، لأنه يسمح بمقاومة أكبر لهجمات طلبات التزوير عبر المواقع. يجب استخدام تابع GET فقط للاستمارات التي لا تغير بيانات المستخدم (مثل استمارة البحث)، ويوصَى به عندما تريد أن تكون قادرًا على وضع إشارة مرجعية على عنوان URL أو مشاركته. عملية معالجة الاستمارة تستخدم معالجة الاستمارة الأساليب نفسها التي تعلمناها لعرض معلومات النماذج Models، إذ ترسِل الوِجهة طلبنا إلى دالة متحكم تطبّق أيّ إجراءات قاعدة بيانات مطلوبة بما في ذلك قراءة البيانات من النماذج، ثم تولّد صفحة HTML وتعيدها، ولكن ما يعقّد الأمور هو أن الخادم يحتاج أيضًا إلى أن يكون قادرًا على معالجة البيانات التي يقدّمها المستخدم، وإعادة عرض الاستمارة مع معلومات الخطأ عند وجود مشاكل. يوضّح المخطط التالي عملية معالجة طلبات الاستمارة، بدءًا من طلب الصفحة التي تحتوي على استمارة (كما هو موضّح باللون الأخضر): الأشياء الرئيسية التي يجب أن تطبقها شيفرة معالجة الاستمارة هي: عرض الاستمارة الافتراضية في المرة الأولى التي يطلبها المستخدم. يمكن أن تحتوي الاستمارة على حقول فارغة (إذا أنشأتَ سجلًا جديدًا مثلًا)، أو يمكن ملؤها مسبقًا بالقيم الأولية (إذا غيّرتَ سجلًا أو كان لديك قيم أولية افتراضية مفيدة مثلًا). تلقي البيانات التي يرسلها المستخدم في طلب HTTP من النوع POST مثلًا. التحقق من صحة البيانات وتطهيرها. إذا كان هناك بيانات غير صالحة، فأعِد عرض الاستمارة مع أيّ قيم يملأها المستخدم ورسائل خطأ للحقول التي تحتوي على مشاكل. إذا كانت جميع البيانات صالحة، فطبّق الإجراءات المطلوبة، مثل حفظ البيانات في قاعدة البيانات وإرسال إشعار بالبريد الإلكتروني وإعادة نتيجة البحث وتحميل ملف وإلخ. إعادة توجيه المستخدم إلى صفحة أخرى بعد اكتمال جميع الإجراءات. تُقدَّم شيفرة معالجة الاستمارة غالبًا باستخدام وِجهة GET للعرض الأولي للاستمارة ووجهة POST إلى المسار نفسه للتعامل مع التحقق من صحة بيانات الاستمارة ومعالجتها، إذ سنستخدم هذا الأسلوب في هذا المقال. لا يوفّر إطار عمل Express أيّ دعم لعمليات معالجة الاستمارة، ولكن يمكنه استخدام البرمجيات الوسيطة لمعالجة معاملات POST و GET من الاستمارة وللتحقق من صحة أو تطهير قيمها. التحقق من صحة البيانات وتطهيرها يجب التحقق من صحة البيانات الواردة من النموذج وتطهيرها قبل تخزينها كما يلي: تتحقق عملية التحقق من صحة البيانات Validation من أن القيم المُدخَلة مناسبة لكل حقل، أي أنها ضمن المجال الصحيح والتنسيق الصحيح وإلخ وأن هذه القيم متوفرة لجميع الحقول المطلوبة. تزيل أو تستبدل عملية التطهير Sanitization المحارف الموجودة في البيانات التي يمكن أن تُستخدَم لإرسال محتوًى ضار إلى الخادم. سنستخدم في هذا المقال الوحدة express-validator الشائعة لإجراء كلٍّ من التحقق من صحة بيانات الاستمارة وتطهيرها، إذ يمكننا ثثبيتها من خلال تشغيل الأمر التالي في جذر المشروع: npm install express-validator ملاحظة: يوفر دليل وحدة express-validator على غيت هب GitHub نظرة عامة جيدة على واجهة برمجة التطبيقات، لذا نوصيك بقراءته للحصول على فكرة عن جميع إمكانياتها بما في ذلك استخدام التحقق من صحة المخطط Schema Validation وإنشاء أدوات تحقق مخصصة، إذ سنشرح فيما يلي فقط جزءًا مفيدًا لموقع المكتبة المحلية. يمكن استخدام أداة التحقق من صحة البيانات Validator في المتحكمات من خلال تحديد الدوال التي نريد استيرادها من وحدة express-validator كما يلي: const { body, validationResult } = require("express-validator"); هناك العديد من الدوال المتاحة، مما يسمح بفحص وتطهير البيانات الواردة من معاملات الطلب وجسمه وترويساته وملفات تعريف الارتباط cookies وغير ذلك أو جميعها معًا في وقت واحد، إذ سنستخدم في هذا المقال body و validationResult (كما هو مطلوب سابقًا). تُعرَّف الدوال على النحو التالي: أولًا، body([fields, message]) التي تحدّد مجموعة من الحقول في متن الطلب (معامل POST) للتحقق من صحة البيانات و/أو تطهيرها مع رسالة خطأ اختيارية يمكن عرضها إذا فشلت الاختبارات. تتمثل معايير التحقق من صحة البيانات وتطهيرها بسلسلة متعاقبة في تابع body()، فمثلًا يحدّد السطر التالي أولًا أننا نتحقق من حقل "الاسم name" وأن خطأ التحقق من صحة البيانات سيضبط رسالة الخطأ "اسم فارغ Empty name"، ثم نستدعي تابع التطهير trim() لإزالة المسافة من بداية السلسلة ونهايتها، ثم isLength() للتحقق من أن السلسلة النصية الناتجة ليست فارغة. أخيرًا، نستدعي escape() لإزالة محارف HTML من المتغير الذي يمكن استخدامه في هجمات كتابة سكربتات جافا سكربت العابرة للمواقع. [ // … body("name", "Empty name").trim().isLength({ min: 1 }).escape(), // … ]; يتحقق الاختبار التالي من أن حقل العمر age هو تاريخ صالح ويستخدم optional() لتحديد أن السلاسل النصية الخالية والفارغة لن تفشل في التحقق من صحة البيانات. [ // … body("age", "Invalid age") .optional({ values: "falsy" }) .isISO8601() .toDate(), // … ]; يمكنك أيضًا استخدام سلسلة متعاقبة لأدوات التحقق من صحة البيانات المختلفة وإضافة الرسائل التي تُعرَض إذا كانت أدوات التحقق السابقة صحيحة. [ // … body("name") .trim() .isLength({ min: 1 }) .withMessage("Name empty.") .isAlpha() .withMessage("Name must be alphabet letters."), // … ]; ثانيًا، الدالة validationResult(req) التي تشغّل عملية التحقق من صحة البيانات، وتتيح الأخطاء بصيغة كائن النتيجة validation، وتستدعَى في دالة رد نداء منفصلة كما هو موضح فيما يلي: asyncHandler(async (req, res, next) => { // استخراج رسائل التحقق من صحة البيانات من الطلب const errors = validationResult(req); if (!errors.isEmpty()) { // هناك أخطاء، لذا اعرض الاستمارة مرة أخرى مع القيم المُطهَّرة أو رسائل الأخطاء // يمكن إعادة رسائل الخطأ في مصفوفة باستخدام errors.array() } else { // البيانات الواردة من الاستمارة صالحة } }); نستخدم التابع isEmpty() الخاص بنتيجة التحقق من صحة البيانات للتحقق مما إذا كان هناك أخطاء، والتابع array() للحصول على مجموعة رسائل الخطأ (اطلع على قسم معالجة التحقق من صحة البيانات للحصول على مزيد من المعلومات). تُعَد سلاسل التحقق من صحة البيانات وتطهيرها برمجيات وسيطة يجب تمريرها إلى معالج وجهة Express، إذ نطبّق ذلك بطريقة غير مباشرة باستخدام المتحكم، وتعمل أدوات التحقق من صحة البيانات وتطهيرها بالترتيب المُحدَّد عند تشغيل البرمجية الوسيطة. سنغطي بعض الأمثلة الحقيقية عندما نطبق استمارات موقع المكتبة المحلية LocalLibrary. تصميم الاستمارة ترتبط أو تعتمد العديد من النماذج في المكتبة على بعضها بعضًا، إذ يتطلب الكتاب Book مؤلفًا Author ويمكن أن يكون له نوع Genre واحد أو أكثر، مما يؤدي إلى التساؤل حول كيفية التعامل مع الحالة التي يرغب فيها المستخدم في: إنشاء كائن عندما لا تكون الكائنات المرتبطة به موجودةً بعد، مثل كتاب لم يُعرَّف كائن المؤلف فيه. حذف كائن لا يزال كائن آخر يستخدمه مثل حذف كائن النوع Genre الذي لا يزال كائن الكتاب Book يستخدمه. سنبسّط العمل في هذا المشروع بالقول إن الاستمارة يمكنها فقط: إنشاء كائن يستخدم كائنات موجودة فعليًا، لذلك يجب على المستخدمين إنشاء النسخ المطلوبة من المؤلف Author ونوع الكتاب Genre قبل محاولة إنشاء أي كائنات Book. حذف كائن إن لم تشِر إليه كائنات أخرى، فمثلًا لن تتمكّن من حذف كتاب Book حتى حذف جميع كائنات BookInstance المرتبطة به. ملاحظة: يمكن أن يسمح لك التقديم الأكثر مرونة بإنشاء كائنات معتمدة على بعضها البعض عند إنشاء كائن جديد، وحذف أيّ كائن في أي وقت من خلال حذف الكائنات المعتمدة عليه أو إزالة المراجع التي تشير إلى الكائن المحذوف من قاعدة البيانات مثلًا. الوجهات Routes سنحتاج إلى وجهتين لهما نمط عنوان URL نفسه لتقديم شيفرة معالجة الاستمارة، إذ تُستخدَم الوجهة الأولى GET لعرض استمارة فارغة جديدة لإنشاء الكائن، وتُستخدَم الوجهة الثانية (POST) للتحقق من صحة البيانات التي أدخلها المستخدم، ثم حفظ المعلومات وإعادة التوجيه إلى صفحة التفاصيل (إذا كانت البيانات صالحة) أو إعادة عرض الاستمارة مع وجود أخطاء (إذا كانت البيانات غير صالحة). أنشأنا الوجهات مسبقًا لجميع صفحات إنشاء النموذج في الملف "/routes/catalog.js" مثل وجهات نوع الكتب التالية: // طلب GET لإنشاء نوع كتاب Genre، إذ يجب أن يأتي قبل الوجهة التي تعرض نوع الكتاب (تستخدم المعرّف id) router.get("/genre/create", genre_controller.genre_create_get); // طلب POST لإنشاء نوع كتاب router.post("/genre/create", genre_controller.genre_create_post); توضّح المواضيع التالية عملية إضافة الاستمارات المطلوبة إلى تطبيقنا، إذ يجب العمل على كل منها ثم الانتقال إلى الموضوع التالي: إنشاء استمارة نوع الكتاب Genre: تعريف صفحة لإنشاء كائنات Genre. إنشاء استمارة المؤلف Author واستمارة الكتاب Book واستمارة نسخة الكتاب BookInstance: تعريف صفحة لإنشاء كائنات Author وتعريف صفحة أو استمارة لإنشاء كائنات Book وتعريف صفحة أو استمارة لإنشاء كائنات BookInstance. حذف استمارة المؤلف Author وتحديث استمارة الكتاب Book: تعريف صفحة لحذف كائنات Author وتعريف صفحة لتحديث كائنات Book. تحدى نفسك طبّق صفحات الحذف لنماذج Book و BookInstance و Genre واربطها بصفحات التفاصيل المتعلقة بها باستخدام الطريقة نفسها لصفحة حذف المؤلف، ويجب أن تتبع الصفحات أسلوب التصميم نفسه بحيث: إذا كان هناك مراجع إلى كائن من كائنات أخرى، فيجب عرض هذه الكائنات الأخرى مع ملاحظة أنه لا يمكن حذف هذا السجل حتى حذف الكائنات. إن لم يكن هناك مراجع أخرى إلى الكائن، فيجب أن يطالب العرض بحذفه، وإذا ضغط المستخدم على زر الحذف، فيجب حذف السجل. إليك بعض النصائح لتطبيقها: يشبه حذفُ الكائن Genre حذفَ الكائن Author تمامًا، فكلا الكائنين يعتمد عليهما الكائن Book، لذلك لا يمكنك حذف الكائن إلّا عند حذف الكتب المرتبطة به في كلتا الحالتين. يحدث الشيء نفسه عند حذف الكائن Book أيضًا، إذ يجب التحقق أولًا من عدم وجود كائنات BookInstance مرتبطة به. يُعَد حذف الكائن BookInstance أسهل ما في الأمر لأنه لا توجد كائنات معتمدة عليه، إذ يمكنك في هذه الحالة العثور على السجل وحذفه. طبّق صفحات التحديث لنماذج BookInstance و Author و Genre، واربطها بصفحات التفاصيل المرتبطة بها بالطريقة نفسها لصفحة تحديث الكتاب. واتبع أيضًا النصائح التالية: تُعَد صفحة تحديث الكتاب التي طبّقناها للتو هي الأصعب، ويمكن استخدام الأنماط نفسها لصفحات التحديث للكائنات الأخرى. يُعَد حقل تاريخ وفاة المؤلف Author وتاريخ ميلاده وحقل تاريخ استرجاع نسخة الكتاب BookInstance تنسيقًا خاطئًا لإدخاله في حقل إدخال التاريخ في الاستمارة، إذ يتطلب بياناتٍ في الاستمارة بالتنسيق: "YYYY-MM-DD". أسهل طريقة للتغلب على ذلك هي تعريف خاصية افتراضية جديدة للتواريخ التي تنسّق التواريخ بصورة مناسبة، ثم استخدام هذا الحقل في قوالب العرض المرتبطة به. إذا واجهتك مشكلة، فهناك أمثلة لصفحات التحديث يمكنك الاطلاع عليها. الخلاصة توفّر حزم Express و node والحزم الخارجية لمدير حزم npm كل ما تحتاجه لإضافة استمارات إلى موقع الويب، إذ تعلمت في هذا المقال كيفية إنشاء استمارات باستخدام Pug والتحقق من صحة بيانات الإدخال وتطهيرها باستخدام express-validator وإضافة السجلات وحذفها وتعديلها في قاعدة البيانات، ويجب أن تفهم الآن كيفية إضافة الاستمارات الأساسية وشيفرة معالجة الاستمارات إلى مواقع Node الخاصة بك. ترجمة -وبتصرُّف- للمقالين Express Tutorial Part 5: Displaying library data و Express Tutorial Part 6: Working with forms. اقرأ أيضًا المقال السابق تطبيق عملي لتعلم Express - الجزء الثالث: الوجهات Routes والمتحكمات Controllers. الاستمارات (forms) في متصفح الويب وكيفية التعامل معها في جافاسكربت. اإرسال الاستمارات (form submit) ومعالجتها في جافاسكربت.
-
سنوضح في هذا المقال الأدوات الرياضية المُستخدَمة في الرسوميات الحاسوبية ثلاثية الأبعاد Three Dimensional Computer Graphics -أو 3D Graphics اختصارًا، إذ يبني برنامج الرسوميات ثلاثية الأبعاد مشهدًا مكونًا من كائنات ثلاثية الأبعاد في فضاء ثلاثي الأبعاد، ثم تُنتَج صور ثنائية الأبعاد من المشهد ثلاثي الأبعاد، ويُمثَّل المشهد بوصفه هيكل بيانات في ذاكرة الحاسوب، وتُعَد الصورة ثنائية الأبعاد عادةً هي الصورة الموجودة على واجهة شاشة الحاسوب. تتكون الكائنات ثلاثية الأبعاد من نقاط وخطوط ومضلعات، إذ لا بد أنك تعرف ماهية هذه المفاهيم، ولكننا سنوضح في هذا المقال هذه المفاهيم ونقدم بعض المفردات الخاصة بالرسوميات الحاسوبية حتى نبدأ من أرضية مشتركة، إذ سنتعرّف على المواضيع التالية: النقاط. الخطوط. المثلثات. تمثيل النقاط والخطوط. إطارات الإحداثيات. تحديد الإحداثيات. تأثير تغيير الإطارات على الإحداثيات. الأشعة. تمثيل الأشعة. هناك شيئان أساسيان في الهندسة هما النقاط والخطوط، إذًا لنتعرف على هذين المفهومين، ولكن لنبدأ بالإجابة على سؤال "ما هي النقطة؟". النقطة ما هي النقطة؟ النقطة هي موقع في الفضاء. النقطة الهندسية هي موقع في الفضاء بدون أيّ خصائص أخرى، إذ لا تملك طولًا أو عرضًا أو سماكة، وتعَد موقعًا مجردًا. لن يساعدك هذا التعريف لمفهوم النقطة إن لم يكن لديك فهمٌ مسبق له، إذ تشير عبارة "موقع في الفضاء" إلى مفهوم كلمة "نقطة" نفسه، ولا يمكن للتعريف أن ينقل لك مفهومًا لا تملكه مسبقًا. لا يوجد تعريف لكلمة نقطة في الهندسة، فالنقطة هي إحدى المفاهيم الأولية غير المُعرَّفة التي تُستخدَم لتعريف الكائنات الأخرى، إذ تعطي الكتب أمثلةً عنها وتأمل أن تبني بنفسك مفهومًا منها بطريقةٍ ما بدلًا من إعطائك تعريفًا لها. تُظهر الصورة التالية قبتين على أحد المباني، وتُعَد هذه الصورة ثنائية الأبعاد، لكن فكّر في المبنى الفعلي ثلاثيّ الأبعاد، وركّز على القضيب المعدني (النهائي) الموجود أعلى القبة الأقرب. إذًا، تحدّد النهايةُ الحادة للقضيب المعدني موقعًا دقيقًا بحسب مقياس المبنى، إذ يمكننا تصور هذا الموقع بوصفه نقطة. ما هي النقاط فيزيائيا؟ لنفترض أن هذا القضيب المعدني أمامك على مكتبك، فهل تُعَد نهايته نقطة؟ غالبًا لا، إذ تكون النهاية غير حادة جدًا بحسب هذا المقياس، بحيث لا يمكن عدّها نقطة. تعبّر النقطة عن المثالية، إذ تكون نهاية القضيب المعدني بحسب مقياس المبنى صغيرةً بما يكفي لعَدّها موقعًا محددًا، وبالتالي يمكن عدّها نقطة، ولكن تكون نهايته غير حادة جدًا بحسب مقياس مكتبك بحيث لا يمكنها تحديد موقع واحد فعلًا. يجب أن يتناقص حجم هذا القضيب المعدني إلى حجم الدبوس بحسب مقياس المكتب، وبالتالي يمكنك أن تحدّد نهايته نقطةً ما. لكن إذا وضعت الدبوس تحت المجهر، فستكون نهايته غير حادة جدًا بالنسبة لهذا المقياس مرة أخرى، وبالتالي يجب أن تتقلص أكثر إلى حجم البكتيريا مثلًا قبل أن تتمكن النهاية من تحديد نقطة ما، لكن أصبحت نهاية القضيب المعدني مرةً أخرى سميكةً جدًا بحسب مقياس البكتيريا بحيث لا يمكنها تحديد الموقع بدقة. إذًا تعبّر النقاط في الفضاء ثلاثي الأبعاد عن المثالية، إذ تكون النهاية الحادة للقضيب المعدني دقيقةً بما يكفي لتكون نقطة بالنسبة لنموذج المبنى الحاسوبي، ولا يلزم تحديد الحواف والمستويات والأشكال الأخرى التي يتكون منها المبنى بدقة أكبر. النقاط في الفضاء ما هو موقع نهاية القضيب المعدني الأبعد في الفضاء؟ انظر إلى الصورة مرةً أخرى وتخيل العلاقة بين نقطتي نهايتي القضيبين المعدنيين، إذ لا توجد إجابة فعلية ضرورية حاليًا. تكون نقاط النهاية للقضيبين المعدنيين في مواقع محدّدة من العالم الحقيقي real world، وسنحدّد هذه المواقع باستخدام إطار إحداثي لاحقًا، ولكن لنفكر الآن فقط في النقاط الموجودة في الفضاء. تضع في برامج الرسوميات ثلاثية الأبعاد نقاطًا وخطوطًا وأشياءً أخرى في الفضاء، ثم تسقطها على صورة ثنائية الأبعاد، ويحاكي ذلك ما حدث عند إنتاج الصورة باستخدام الكاميرا، إذ تكون نهايات القضيبين المعدنيين فيزيائيًا نقاطًا في الفضاء ثلاثي الأبعاد. تسقط العدسة كامل المشهد على مستشعر الصور ثنائية الأبعاد، وتحدد نهايات القضبان المعدنية في الصورة ثنائية الأبعاد نقطتين ثنائيتي الأبعاد. الخطوط ما المسافة بين النقطتين عند طرفي القضيبين المعدنيين؟ يمكن أن نخمن أن المسافة حوالي 30 مترًا. يكون الحكم على العمق صعبًا في الصورة ثنائية الأبعاد في أغلب الأحيان، إذ تُفقَد معلومات العمق عند إنتاج صورة ثنائية الأبعاد من مشهد ثلاثي الأبعاد (إما مشهد حقيقي أو مشهد في الحاسوب). فكر الآن في القطعة المستقيمة Line Segment الواقعة بين النقطتين، والتي هي المسار المستقيم بين نقطتين بدون سماكة، ولا توجد سوى قطعة مستقيمة واحدة بين النقطتين. إن لم يكن لديك مفهوم مسبق عن "الخط"، فلن يكون التعريف السابق كافيًا لتعلمه، إذ تشير عبارة "المسار المستقيم" إلى مفهوم الخط الذي عرّفناه. تتعامل غالبًا مع القطع المستقيمة في الرسوميات ثلاثية الأبعاد، بينما تستمر الخطوط بلا نهاية في الهندسة، إذ تكون القطعة المستقيمة جزءًا من الخط بين نقطتين. لا يُعرَّف الخط العالمي في كتب الهندسة عادةً، وهو -مثل النقطة- مفهوم غير مُعرَّف يستخدم لتعريف كائنات أخرى، إذ تقدّم الكتب صورًا وأمثلة عن الخطوط، وتأمل أن تفهم بطريقة أو بأخرى ما هو الخط من خلال النظر إليها. يستخدم بعض الأشخاص مصطلح "خط" بينما يجب عليهم استخدام مصطلح "قطعة مستقيمة" الذي سنستخدمه في هذا المقال، نظرًا لأن القطع المستقيمة شائعة جدًا. القطع المستقيمة هل يمكنك تمديد stretch طول السلك بين النقطتين؟ نعم، من الناحية المفاهيمية على الأقل. يوجد سلك رفيع ممتد بين القضيبين المعدنيين بحسب مقياس المبنى، والذي يعبر عن تقريبٍ لقطعة مستقيمة، وكما تعلم يتعلق هذا الأمر بالمقياس، إذ يُرجَّح أن يكون السلك أسمك من الخط المرسوم بقلم رصاص، فالسلك الموجود على مكتبك سميك جدًا بحيث لا يمكن عدّه قطعة مستقيمة. بينما يكون الخط المرسوم بقلم الرصاص الرفيع والمستقيم رفيعًا بدرجة كافية لعدّه خطًا هندسيًا، وإذا كان سميكًا جدًا، فيمكن أن يكون طول خيط العنكبوت الممتد بين نقطتين مناسبًا. لكن يمكن عَدّ الأسلاك المدروسة على المستوى المعماري بمثابة قطع مستقيمة، ويمكنك بناء نموذج هندسي للمبنى باستخدام القطع المستقيمة لتمثيل الحواف. المثلثات جرّب رسم مثلث يربط بين القضيبين المعدنيين والزاوية العلوية اليسرى من السور الأبيض كما في الصورة التالية: يمكن إنشاء المثلث من خلال ربط ثلاث نقاط في فضاء ثلاثي الأبعاد بقطع مستقيمة، فإذا لم تقع النقاط الثلاث جميعها على الخط نفسه، فستحدّد ثلاث نقاط في الفضاء مثلثًا فريدًا. تُعَد المثلثات الموجودة في الفضاء مهمةً في الرسوميات ثلاثية الأبعاد، إذ تُنشَأ الكائنات ثلاثية الأبعاد التي تشكّل المشهد من المثلثات والمضلعات المسطحة الأخرى. المثلث الموجود في الصورة ثنائية الأبعاد هو إسقاط للمثلث ثلاثي الأبعاد في المشهد، ولكن يشوه الإسقاطُ المثلثَ ثلاثيّ الأبعاد، فالمثلث ثلاثي الأبعاد أكبر بكثير من المثلث ثنائي الأبعاد، وتكون الزوايا مختلفة. لنفترض أن الكائن ثلاثي الأبعاد في المشهد مكونٌ من عدة مثلثات، ولنسقط كل مثلث على صورة ثنائية الأبعاد، والنتيجة هي النسخة المُسقَطة من الكائن ثلاثي الأبعاد. يتكون السور الشبكي الأبيض في المبنى من العديد من المثلثات، وتُظهِر نسخة السور الشبكي الموجودة في الصورة جميع هذه المثلثات التي تسقطها عدسة الكاميرا. التمثيل باستخدام إطار إحداثيات هل تحتاج إلى إطار إحداثي -أي نظام مع تسمية النقاط بالإحداثيات x و y و z- للحديث عن النقاط والخطوط والأشكال المستوية؟ في الحقيقة لا حاجة لذلك. يمكنك التفكير في النقاط والخطوط والأشكال المستوية التي تشكّل المشهد ثلاثي الأبعاد دون استخدام نظام إحداثي، وهذا ما كنا نفعله في الحقيقة، ولكن يجب تمثيل هذه الكائنات بطريقة أو بأخرى والتعامل معها في رسوميات الحاسوب، ولذلك نحتاج إلى طريقة لتمثيلها. اختر نقطةً مناسبة في المشهد ثلاثي الأبعاد وأطلِق عليها اسم نقطة الأصل Origin، وحدد ثلاثة خطوط تسمى X و Y و Z والتي تمر عبر نقطة الأصل، وتكون الخطوط الثلاثة عادةً متعامدة على بعضها بعضًا، ويسمى كل خط محورًا Axis. اختر اتجاهًا إيجابيًا لكل خط، فهناك عدة خيارات لذلك، ولكن لنستخدم الخيار الموضّح في الصورة السابقة. تمثل كل نقطة من المحور X مسافةً فريدةً (موجبة أو سالبة أو صفر) من نقطة الأصل، فمثلًا تقع زاوية المبنى الموجودة على المحور X على مسافة 2.4 متر (8 أقدام) تقريبًا من نقطة الأصل. الإحداثيات تقع نقطة الأصل على ارتفاع صفر، ولكن على أي ارتفاع تقع نقطة نهاية القضيب المعدني الأقرب تقريبًا؟ يمكن القول أن ارتفاعها حوالي 3.6 مترًا (10 أقدام). أو يمكنك القول أن y=3.6 بالنسبة لنقطة نهاية القضيب المعدني، إذ يمكن إسناد تمثيل لأيّ نقطة في مشهدنا ثلاثيّ الأبعاد من خلال قياس المسافة على طول المحاور الثلاثة، إذ سنضع هذه المسافات الثلاث في مصفوفة عمودية على النحو التالي في هذا المقال (وفي العديد من كتب الرسوميات الحاسوبية): قد تكون معتادًا على وضع الإحداثيات الثلاثة في صف ثلاثي مثل (x, y, z)، ولكن سيتبين أنه من الملائم أكثر وضعها في مصفوفة عمودية كما ذكرنا بالنسبة للرسوميات الحاسوبية، فمثلًا إحداثيات زاوية المبنى التي تقع على طول المحور X هي تقريبًا: تحديد الإحداثيات ما هي إحداثيات نقطة زاوية الشبكة البيضاء على طول الحافة العلوية (المسمَّاة P0)؟ يمكن القول أنها تقريبًا: احسب عند تحديد الإحداثيات قياس المسافة على طول الخطوط الموازية لمحاور الإحداثيات، إذ ستشكّل الخطوط التي تستخدمها مستطيلًا أو أوجه صندوق. تشكّل الخطوط السوداء المنقطة المستخدمة للنقطة Q0 مستطيلًا، ويعطي قياس أضلاع المستطيل الإحداثيات التالية: من الصعب معرفة إحداثيات النقطة Q1، فالصورة ثنائية الأبعاد، ولكن المشهد الذي تظهره ثلاثي الأبعاد، وبالتالي فإن المحور Z مشوه، ولكن تشكل الخطوط المنقطة في المشهد ثلاثي الأبعاد صندوقًا، فقياس جميع الزوايا 90 درجة. مزيد من النقاط يمكن تقدير إحداثيات النقطة Q1 بالقيم التالية: لنعُد الآن إلى مشهدنا الواقعي، إذ يبلغ طول الشبكة البيضاء تقريبًا حوالي 2.4 متر (8 أقدام) من كل جانب. النقطة وتمثيلها يمكن تخمين إحداثيات طرف القضيب المعدني الأقرب بالقيم التالية: تُعَد المصفوفة العمودية كائنًا رياضيًا يمثل النقطة باستخدام إطار الإحداثيات الذي اخترناه، ولكن ليست هذه المصفوفة العمودية هي النقطة بحد ذاتها، فالمصفوفة العمودية والنقطة شيئان مختلفان، إذ يُستخدَم أحدهما لتمثيل الآخر مثل استخدام اسمك لتمثيلك. نأمل أن يكون ذلك واضحًا، ولكنه سيكون غامضًا بالنسبة للطلاب الذين يحاولون تخطي مواضيع رياضيات الرسوميات الحاسوبية بسرعة، مما يؤدي إلى ندمهم في نهاية المطاف، إذ يمكن أن يمثل كتاب الرسوميات الحاسوبية النقطة نفسها بعدة طرق مختلفة، لذا من الضروري الفصل بين أفكار النقطة الهندسية والطرق المتعددة التي يمكن تمثيلها باستخدامها. ملاحظة: يتحدث الأشخاص في أغلب الأحيان عرضيًا ويقولون أشياء مثل "النقطة (4, 12, -4)"، كما لو كان هذا الصف الثلاثي من الأرقام متطابقًا مع النقطة، ولكن يُستخدَم ذلك للراحة فقط، إذ ينبغي عليهم أن يقولوا: "النقطة المُمثَّلة في نظام الإحداثيات الذي اخترناه بالتمثيل (4, 12, -4)." تأثير تغيير الإطارات على الإحداثيات هل تقاس المسافات على طول محاور الإحداثيات بوحدة القدم أم بالأمتار؟ لا يهم، طالما أنك تستخدمها بصورة متناسقة. لا تُوضَع إطارات الإحداثيات المستخدمة في كتب الرياضيات في مشهد من العالم الحقيقي عادةً، لذلك لا يُعبَّر عن المسافات بأيّ وحدة معينة، ولكن يتعين عليك تحديد الوحدات التي تستخدمها عندما تنشئ عالمًا ثلاثي الأبعاد في الحاسوب. تُظهر الصورة التالية عالمًا ثلاثيّ الأبعاد مع وضع إطارٍ إحداثي مختلف فيه، إذ تبقى النقاط في العالم نفسها كما كانت سابقًا، ولكن سيكون لديها تمثيلات مختلفة مع إطار إحداثي مختلف: مثلنا النقطة المسمَّاة P1 بما يلي في الإطار السابق: ستكون الآن المسافات على طول المحاور في الإطار الجديد (الأخضر) مختلفة، فالمصفوفة العمودية التي تمثل النقطة P1 في الإطار الجديد هي تقريبًا: لم نمارس الرياضيات بصورة دقيقة هنا، بل نظرنا إلى الصورة وخمّنا المسافات فقط، لذا حاول أن تفعل الشيء نفسه، فالهدف ليس حساب أي شيء، بل التفكير في النقاط في الفضاء. للنقطة نفسها تمثيلات مختلفة في كل إطار، لذا يجب أن تعرف الإطار المُستخدَم عندما تمثل نقطةً بمصفوفة عمودية. ضوء الشمس يمكن تقدير إحداثيات النقطة P0 على الحافة العلوية للشبكة المبينة في الصورة أعلاه بالمصفوفة العمودية التالية: ولكن هذه القيم مجرد تقدير فقط. انظر إلى صورة المبنى مرةً أخرى، إذ تضيء الشمسُ المشهد، ولكنها ليست موجودة في الصورة، إذ يأتي ضوء الشمس في المشهد قطريًا من خارج المشهد إلى يساره. الأشعة Vector ارسم ذهنيًا بعض الخطوط في الصورة التي تظهر ضوء الشمس كما في الشكل التالي: يتدفق الضوء من أعلى اليسار عندما تنظر إلى المبنى، إذ تمثل الصورة ضوء الشمس بأسهم. حاول التفكير في المشهد ثلاثي الأبعاد بالرغم من أن الصورة ثنائية الأبعاد، فالاتجاه الذي يتحرك فيه الضوء موضَّح بالسهم، ويمثل طول السهم مثلًا شدة الضوء. توجد عدة أسهم في الصورة، إلا أنّ أيّ سهمٍ يكفي لتمثيل الضوء وشدته. يمكن تمثيل اتجاه الضوء وشدته بشعاع، والذي هو كائن هندسي له خاصيتان هما: الطول والاتجاه. للضوء الصادر من الشمس خاصيتان، هما: السطوع والاتجاه (مع تجاهل معلومات اللون)، ويتناسب طول الشعاع مع سطوع الضوء. يمكن تمثيل الرياح أيضًا بشعاع، فالرياح لها اتجاه وسرعة، إذ تنتقل ذرة من الغبار مع اتجاه الريح، ويتناسب طول الشعاع مع سرعة ذرة الغبار. ليس للشعاع موضع، إذ تُظهِر الصورة السابقة عدة أسهم تمثل تدفق الضوء القادم من الشمس، ولكن يمكن رسم الأسهم في أيّ مكان طالما أن اتجاهها وطولها هو نفسه. تمثيل الأشعة هل تفترض أن برنامج الرسوميات الحاسوبية يحتاج إلى تمثيل شدة الضوء واتجاهه؟ نعم، في الحقيقة هناك حاجة لذلك. تُستخدَم الأشعة لعدة أغراض في رسوميات الحاسوب ثلاثية الأبعاد، لذلك يجب أن تُمثَّل بطريقة يمكن للبرامج التعامل معها، ويحدث ذلك باستخدام الأرقام. لاحظ أن الصورة التالية تتضمن الآن إطار إحداثياتنا: يُمثَّل الشعاع بمصفوفة عمودية كما هو الحال مع النقاط، ويمكن أن يكون من المربك إلى حدٍ ما أن تُمثَّل النقاط والأشعة باستخدام الشيء نفسه، ولكن ذلك سيثبت أنه هذه الطريقة مناسبة ليتعامل معها الحاسوب. يمكن تمثيل الشعاع بمصفوفة عمودية باتباع الخطوات التالية: اختر إطارًا إحداثيًا. احسب طول X (موجب أو سالب) من ذيل الشعاع إلى رأسه. 3.احسب طول Y (موجب أو سالب) من ذيل الشعاع إلى رأسه. احسب طول Z (موجب أو سالب) من ذيل الشعاع إلى رأسه. ضع هذه الأرقام الثلاثة في عمود. إحدى الطرق الملائمة لذلك هي وضع ذيل الشعاع عند نقطة الأصل، ثم قراءة قيم x و y و z عند رأسه. الأشعة والإطارات كم مترًا في الاتجاه X للمسافة بين رأس الأسهم عن ذيلها في الصورة؟ يمكن أن نخمنها بمقدار حوالي 0.9 متر (3 أقدام). كم مترًا في الاتجاه Y يبعد الرأس عن الذيل؟ يمكن أن نخمنها بمقدار 1.2- متر (-4 أقدام). كم مترًا في الاتجاه Z يبعد الرأس من الذيل؟ يمكن أن نخمنها بمقدار 0.9- متر (-3 أقدام). إذًا، الشعاع الذي يعطي شدة واتجاه ضوء الشمس في الصورة هو: تمثل المصفوفة العمودية السابقة الشعاع باستخدام إطار الإحداثيات الذي اخترناه، ولكنها ليست الشعاع نفسه. إذا اخترنا إطارًا إحداثيًا مختلفًا، فسيكون الشعاع في المشهد الحقيقي هو نفسه، ولكن سيتغير تمثيله باستخدام الإطار الجديد، وسنواصل في المقال التالي هذه المناقشة. مراجعة سريعة: للنقطة خاصية واحدة فقط هي: الموقع. للشعاع خاصيتان، هما: الطول والاتجاه ولكن ليس له موقع. ترجمة -وبتصرُّف- للفصل Points and Lines من كتاب Vector Math for 3D Computer Graphics لصاحبه Bradley Kjell. اقرأ أيضًا تعرف على أشهر برامج وتطبيقات تصميم الصور والرسوميات قواعد تصميم الرسوم البيانية قواعد التعامل مع الصور والرسوميات
-
سنُعِدّ في هذا المقال الوجهات Routes (شيفرة معالجة عناوين URL، ويسميها البعض بالموجهات) باستخدام دوال معالجة وهمية dummy لجميع النقاط النهائية للموارد التي سنحتاجها في موقع المكتبة المحلية LocalLibrary. سيكون لدينا في النهاية بنية معيارية لشيفرة معالجة الوجهات، والتي يمكننا توسيعها باستخدام دوال معالجة حقيقية في المقالات اللاحقة، وسيصبح لدينا أيضًا فهم جيد لكيفية إنشاء وجهات معيارية باستخدام إطار عمل Express. المتطلبات الأساسية: الاطلاع على مقال مدخل إلى إطار عمل الويب Express، وإكمال المقالات السابقة من هذه السلسلة بما في ذلك مقال استخدام قاعدة البيانات. الهدف: فهم كيفية إنشاء وجهات بسيطة وإعداد جميع النقاط النهائية لعناوين URL. تعرّفنا في المقال السابق على نماذج Mongoose للتفاعل مع قاعدة البيانات، واستخدمنا سكربتًا مستقلًا لإنشاء بعض سجلات المكتبة الأولية، ويمكننا الآن كتابة الشيفرة البرمجية لتقديم تلك المعلومات للمستخدمين. يجب أولًا تحديد المعلومات التي نريد عرضها في صفحاتنا، ثم تعريف عناوين URL المناسبة لإعادة تلك الموارد، ثم يجب إنشاء الوجهات (معالجات عناوين URL) والعروض Views (القوالب Templates) لعرض تلك الصفحات. يوضح المخطط البياني الآتي التدفق الرئيسي للبيانات والأشياء التي يجب تقديمها عند التعامل مع طلب أو استجابة HTTP، ويُوضّح العروض والوجهات والمتحكّمات التي تمثل الدوال التي تفصل الشيفرة البرمجية لتوجيه الطلبات من هذه الشيفرة التي تعالج الطلبات فعليًا. أنشأنا النماذج مسبقًا، فالأشياء الرئيسية التي يجب إنشاؤها هي: "الوجهات Routes" لتوجيه الطلبات المدعومة وأيّ معلومات مُشفَّرة في عناوين URL للطلب إلى دوال المتحكم المناسبة. دوال المتحكم للحصول على البيانات المطلوبة من النماذج وإنشاء صفحة HTML تعرض البيانات وإعادتها إلى المستخدم لعرضها في المتصفح. العروض (القوالب) التي تستخدمها المتحكمات لتقديم البيانات. يمكن أن يكون لدينا في النهاية صفحات لإظهار القوائم والمعلومات التفصيلية للكتب وأنواعها والمؤلفين ونسخ الكتب، وصفحات لإنشاء السجلات وتحديثها وحذفها. تُعَد هذه المعلومات كثيرة لعرضها في مقال واحد، لذلك سيركز معظم هذا المقال على إعداد الوجهات والمتحكمات لإعادة محتوًى "وهمي"، وسنوسّع توابع المتحكم في مقالاتنا اللاحقة للعمل مع بيانات النموذج. يوفّر القسم الأول التالي نظرةً عامةً موجزة حول كيفية استخدام برمجية Express الوسيطة Router، ثم سنستخدم ذلك في الأقسام اللاحقة عند إعداد وجهات موقع المكتبة المحلية LocalLibrary. مقدمة إلى الوجهات الوجهة هي قسم من شيفرة Express التي تربط بين فعل HTTP، مثل GET و POST و PUT و DELETE وإلخ مع مسار أو نمط URL ودالة تُستدعى لمعالجة هذا النمط. هناك عدة طرق لإنشاء الوجهات، وسنستخدم في هذا المقال البرمجية الوسيطة express.Router لأنها تسمح بتجميع معالجات الوجهات لجزء معين من الموقع مع بعضها البعض والوصول إليها باستخدام بادئة prefix وجهة مشتركة. سنحتفظ بجميع الوجهات المتعلقة بالمكتبة في وحدة "دليل catalog"، وإذا أضفنا وجهات لمعالجة حسابات المستخدمين أو لدوال أخرى، فيمكننا الاحتفاظ بها مُجمَّعةً بصورة منفصلة. ملاحظة: ناقشنا سابقًا وجهات تطبيق Express باختصار في مقال سابق. يشبه استخدام كائن الموجّه Router إلى حدٍ كبير تعريف الوجهات مباشرةً في كائن تطبيق Express باستثناء تقديم دعم أفضل للتقسيم إلى وحدات Modularization (كما سننافش في القسم التالي). يوفر الجزء المتبقي من هذا القسم نظرةً عامة حول كيفية استخدام الكائن Router لتعريف الوجهات. تعريف واستخدام وحدات وجهة منفصلة تقدم الشيفرة البرمجية التالية مثالًا عن كيفية إنشاء وحدة وجهة ثم استخدامها في تطبيق Express. أولًا، ننشئ وجهات لميزة الويكي wiki في وحدة بالاسم "wiki.js"، إذ تستورد هذه الشيفرة البرمجية كائن تطبيق Express، وتستخدمه للحصول على كائن Router، ثم تضيف بعض الوجهات إليه باستخدام التابع get()، ثم تصدّر الوحدةُ الكائنَ Router. // wiki.js: وحدة وجهة ويكي Wiki const express = require("express"); const router = express.Router(); // وجهة الصفحة الرئيسية router.get("/", function (req, res) { res.send("Wiki home page"); }); // وجهة صفحة About router.get("/about", function (req, res) { res.send("About this wiki"); }); module.exports = router; ملاحظة: عرّفنا في الشيفرة السابقة دوال رد النداء Callbacks الخاصة بمعالج الوجهة مباشرةً في دوال وجهة، وسنعرّف في موقع المكتبة المحلية LocalLibrary دوال رد النداء هذه في متحكم وحدة منفصل. يمكن استخدام وحدة وجهة في ملف تطبيقنا الرئيسي من خلال طلب وحدة الوجهة (wiki.js) باستخدام الدالة require()، ثم استدعاء الدالة use() في تطبيق Express لإضافة كائن Router إلى مسار معالجة البرمجية الوسيطة مع تحديد مسار URL الخاص بالويكي "wiki". const wiki = require("./wiki.js"); // … app.use("/wiki", wiki); ويمكن بعد ذلك الوصول إلى الوجهتين المُعرَّفتين في وحدة وجهة wiki من /wiki/ و /wiki/about/. دوال الوجهة تعرّف الوحدة السابقة بعضًا من دوال الوجهة، إذ تُعرَّف الوجهة "about" (التي سنعيد إنتاجها فيما يلي) باستخدام التابع Router.get()، والذي يستجيب فقط لطلبات HTTP من النوع GET. الوسيط الأول لهذا التابع هو مسار URL والوسيط الثاني هو دالة رد نداء تُستدعَى عند تلقي طلب HTTP من النوع GET مع المسار. router.get("/about", function (req, res) { res.send("About this wiki"); }); تأخذ دالة رد النداء ثلاثة وسائط تُسمَّى عادة req و res و next، والتي ستحتوي على كائن طلب HTTP واستجابة HTTP والدالة التالية next في سلسلة البرمجيات الوسيطة. ملاحظة: تُعَد دوال Router برمجيات Express وسيطة، مما يعني أنها يجب إما إكمال (أو الاستجابة) للطلب أو استدعاء الدالة next في السلسلة، إذ نكمل في حالتنا الطلب باستخدام التابع send()، لذلك لا يُستخدَم الوسيط next ونختار عدم تحديده. تأخذ دالة router السابقة دالة رد نداء واحدة، ولكن يمكنك تحديد عدة وسائط لدوال رد النداء أو مصفوفة من دوال رد النداء. تُعَد كل دالة جزءًا من سلسلة البرمجيات الوسيطة، وستُستدعَى بالترتيب نفسه لإضافتها إلى السلسلة (إلّا إذا أكملت الدالة السابقة الطلب). تستدعي دالة رد النداء في مثالنا التابع send() في الاستجابة لإعادة السلسلة النصية "About this wiki" عندما نتلقى طلب GET مع المسار ("/about"). هناك عدد من توابع الاستجابة الأخرى لإنهاء دورة الطلب/الاستجابة، فمثلًا يمكنك استدعاء التابع res.json() لإرسال استجابة JSON أو التابع res.sendFile() لإرسال ملف. يُعَد التابع render() تابع الاستجابة الذي سنستخدمه غالبًا أثناء بناء المكتبة، والذي ينشئ ويعيد ملفات HTML باستخدام القوالب والبيانات (سنتحدث عن ذلك في مقالٍ لاحق). أفعال HTTP تستخدم أمثلة الوجهات السابقة التابع Router.get() للاستجابة على طلبات HTTP من النوع GET مع مسار معين. يوفّر الكائن Router أيضًا توابع وجهة لجميع أفعال HTTP الأخرى، والتي تُستخدَم غالبًا بنفس الطريقة تمامًا ومن هذه التوابع: post() و put() و delete() و options() و trace() و copy() و lock() و mkcol() و move() و purge() و propfind() و proppatch() و unlock() و report() و mkactivity() و checkout() و merge() و m-search() و notify() و subscribe() و unsubscribe() و patch() و search() و connect(). تتصرف الشيفرة البرمجية التالية مثلًا مثل وجهة /about السابقة، ولكنها تستجيب فقط لطلبات HTTP من النوع POST: router.post("/about", (req, res) => { res.send("About this wiki"); }); مسارات الوجهة تعرِّف مساراتُ الوجهة النقاطَ النهائية التي يمكن إجراء الطلبات عندها، فالأمثلة التي رأيناها حتى الآن كانت مجرد سلاسل نصية، واُستخدِمت كما هو مكتوب تمامًا مثل: '/' و '/about' و '/book' و '/any-random.path'. يمكن أن تكون مسارات الوجهة أيضًا أنماطًا من السلاسل النصية التي تستخدم شكلًا من صيغ التعابير النمطية لتعريف أنماط النقاط النهائية التي ستجري مطابقتها. نوضح فيما يلي هذه الصيغة (لاحظ تفسير الشَرطة الواصلة (-) والنقطة (.) حرفيًا باستخدام المسارات المستندة إلى السلاسل النصية): ?: يجب أن تحتوي النقطة النهائية endpoint على 0 أو 1 من المحرف السابق (أو المجموعة)، فمثلًا سيتطابق مسار الوجهة '/ab?cd' مع النقاط النهائية acd أو abcd. +: يجب أن تحتوي النقطة النهائية على واحد أو أكثر من المحرف السابق (أو المجموعة)، فمثلًا سيتطابق مسار الوجهة '/ab+cd' مع النقاط النهائية abcd و abbcd و abbbcd وإلخ. *: يمكن أن تحتوي النقطة النهائية على سلسلة نصية عشوائية في مكان المحرف *، فمثلًا سيتطابق مسار الوجهة '/ab*cd' مع النقاط النهائية abcd و abXcd و abSOMErandomTEXTcd وإلخ. (): تجميع تطابق مجموعة من المحارف لإجراء عملية أخرى عليها، فمثلًا سينفّذ '/ab(cd)?e' تطابق ? على المجموعة (cd)، إذ سيتطابق مع abe و abcde. يمكن أن تكون مسارات الوجهة أيضًا تعابير نمطية Regular Expressions في لغة جافا سكريبت JavaScript، فمثلًا سيتطابق مسار الوجهة التالي مع catfish و dogfish، ولكن لن يتطابق مع catflap و catfishhead وما إلى ذلك. لاحظ أن مسار التعبير النمطي يستخدم صياغة التعابير النمطية، وهي ليست سلسلة نصية بين علامات اقتباس كما في الحالات السابقة. app.get(/.*fish$/, function (req, res) { // … }); ملاحظة: ستستخدم معظم الوجهات في موقع المكتبة المحلية LocalLibrary سلاسلًا نصية ولن تستخدم تعابيرًا نمطية، وسنستخدم أيضًا معاملات الوجهات كما سنناقش في القسم التالي. معاملات الوجهة تُسمَّى معاملات الوجهة بأجزاء Segments عنوان URL المُستخدَمة لالتقاط القيم في مواضع محددة من عنوان URL، إذ تُسبَق هذه المقاطع بنقطتين ثم الاسم (مثل /:your_parameter_name/). تُخزَّن القيم المُلتقَطة في كائن req.params من خلال استخدام أسماء المعاملات بوصفها مفاتيحًا، مثل req.params.your_parameter_name. ليكن لدينا مثلًا عنوان URL مُشفَّر يحتوي على معلومات حول المستخدمين والكتب: http://localhost:3000/users/34/books/8989، إذ يمكننا استخراج هذه المعلومات كما هو موضح فيما يلي باستخدام معاملات مسار userId و bookId: app.get("/users/:userId/books/:bookId", (req, res) => { // الوصول إلى userId باستخدام req.params.userId // الوصول إلى bookId باستخدام req.params.bookId res.send(req.params); }); يجب أن تتكون أسماء معاملات الوجهة من "محارف كلمات"، أي A-Z و a-z و0-9 و _. ملاحظة: ستجري مطابقة عنوان "/book/create" مع وجهة مثل الوجهة /book/:bookId، والتي ستستخرج قيمة "bookId" الخاصة بالإنشاء 'create'. ستُستخدَم الوجهة الأولى التي تطابق عنوان URL الوارد، لذلك إذا أردتَ معالجة عناوين /book/create بصورة منفصلة، فيجب تعريف معالج الوجهة قبل الوجهة /book/:bookId. هذا كل ما تحتاجه لبدء استخدام الوجهات، ولكن يمكنك العثور على مزيد من المعلومات في مقال كيفية تحديد الوجهات وأنواع طلبات HTTP وفي توثيق Express: التوجيه الأساسي ودليل التوجيه. توضح الأقسام التالية كيفية إعداد الوجهات والمتحكمات لموقع المكتبة المحلية. معالجة الأخطاء في دوال الوجهة تحتوي جميع دوال الوجهة الموضحة سابقًا على وسائط req و res التي تمثل الطلب والاستجابة على التوالي. تُستدعَى دوال الوجهة أيضًا مع وسيط ثالث هو next، والذي يمكن استخدامه لتمرير الأخطاء إلى سلسلة برمجيات Express الوسيطة. توضح الشيفرة التالية كيفية إنجاز ذلك باستخدام مثال استعلام قاعدة البيانات الذي يأخذ دالة رد نداء ويعيد إما خطأ err أو بعض النتائج. تُستدعَى next مع خطأ err بوصفه قيمة معاملها الأول عند إعادة خطأ err، وسينتقل هذا الخطأ في النهاية إلى شيفرة معالجة الأخطاء العامة، وتُعاد البيانات المطلوبة ثم تُستخدَم في الاستجابة في حالة النجاح. router.get("/about", (req, res, next) => { About.find({}).exec((err, queryResults) => { if (err) { return next(err); } // اعرض شيئًا ما في حالة النجاح res.render("about_view", { title: "About", list: queryResults }); }); }); معالجة الاستثناءات في دوال الوجهة يوضح القسم السابق كيف يتوقع إطار عمل Express أن تعيد دوال الوجهة أخطاءً، إذ صُمِّم إطار العمل للاستخدام مع الدوال غير المتزامنة التي تأخذ دالة رد نداء (مع خطأ وووسيط النتيجة)، والتي تُستدعَى عند اكتمال العملية. يُعَد ذلك مشكلة لأننا سنجري لاحقًا استعلامات قاعدة بيانات Mongoose التي تستخدم واجهات برمجة تطبيقات مستندة إلى الوعود promise، والتي يمكن أن تؤدي إلى رمي استثناءات في دوال الوجهة بدلًا من إعادة الأخطاء في دالة رد النداء. يمكن أن يعالج إطار العمل الاستثناءات بصورة صحيحة من خلال اكتشافها ثم توجيهها بوصفها أخطاءً كما هو موضح في القسم السابق. ملاحظة: من المتوقع أن يعالج Express 5 التجريبي استثناءات جافا سكريبت بطريقة أصيلة. ليكن لدينا المثال البسيط من القسم السابق مع وجود About.find().exec() بوصفه استعلام قاعدة بيانات يعيد وعدًا، إذ يمكن كتابة دالة الوجهة ضمن كتلة try...catch كما يلي: exports.get("/about", async function (req, res, next) { try { const successfulResult = await About.find({}).exec(); res.render("about_view", { title: "About", list: successfulResult }); } catch (error) { return next(error); } }); يُعَد ذلك كمًا كبيرًا جدًا من الشيفرة البرمجية المتداولة لإضافتها إلى كل دالة، لذا سنستخدم بدلًا من ذلك وحدة express-async-handler التي تعرّف دالة تغليف تخفي كتلة try...catch والشيفرة البرمجية لتوجيه الخطأ، وبالتالي أصبح المثال نفسه الآن بسيطًا جدًا، لأننا نحتاج فقط إلى كتابة شيفرة برمجية للحالة التي نفترض فيها النجاح: // استيراد الوحدة const asyncHandler = require("express-async-handler"); exports.get( "/about", asyncHandler(async (req, res, next) => { const successfulResult = await About.find({}).exec(); res.render("about_view", { title: "About", list: successfulResult }); }), ); الوجهات اللازمة لموقع المكتبة المحلية سنعرض فيما يلي عناوين URL التي سنحتاجها لصفحاتنا، إذ نضع اسم كل نموذج من نماذجنا (الكتاب ونسخة الكتاب ونوع الكتاب والمؤلف) مكان الكائن object، والكائنات objects هي جمع كائن، والمعرّف id هو نسخة فريدة من الحقل (_id) تُعطَى لكل نسخة من نموذج Mongoose افتراضيًا. catalog/: الصفحة الرئيسية أو صفحة الفهرس. catalog/<objects>/: قائمة بجميع الكتب أو نسخها أو أنواعها أو المؤلفين، مثل /catalog/books/ و /catalog/genres/ وغير ذلك. catalog/<object>/<id>: صفحة التفاصيل لكتاب أو نسخة أو نوع أو مؤلف معين مع قيمة الحقل _id المُحدَّدة، مثل /catalog/book/584493c1f4887f06c0e67d37. catalog/<object>/create: استمارة إنشاء كتاب أو نسخة أو نوع أو مؤلف جديد، مثل /catalog/book/create. catalog/<object>/<id>/update: استمارة لتحديث كتاب أو نسخة أو نوع أو مؤلف معين مع قيمة الحقل _id المُحدَّدة، مثل /catalog/book/584493c1f4887f06c0e67d37/update. catalog/<object>/<id>/delete: استمارة لحذف كتاب أو نسخة أو نوع أو مؤلف معين مع قيمة الحقل _id المُحدَّدة، مثل /catalog/book/584493c1f4887f06c0e67d37/delete. لا تشفِّر الصفحة الرئيسية الأولى وصفحات القائمة أيّ معلومات إضافية، فبينما تعتمد النتائج المُعادة على نوع النموذج والمحتوى في قاعدة البيانات، ستبقى الاستعلامات المُنفَّذة للحصول على المعلومات نفسها دائمًا، وبالمثل، ستكون الشيفرة البرمجية المُنفَّذة لإنشاء الكائن نفسها دائمًا. تُستخدَم عناوين URL الأخرى للعمل على نسخةٍ من مستند أو نموذج معين، وتشفّر هوية العنصر في عنوان URL (كما هو موضّح في العنصر <id> سابقًا). سنستخدم معاملات المسار لاستخراج المعلومات المشفرة وتمريرها إلى معالج الوجهة، وسنستخدمها في مقال لاحق لتحديد المعلومات التي نحصل عليها من قاعدة البيانات ديناميكيًا. نحتاج فقط إلى وجهة واحدة لكل مورد من نوع معين من خلال تشفير المعلومات في عنوان URL، مثل استخدام وجهة واحدة للتعامل مع عرض عنصر كتاب واحد. ملاحظة: يسمح إطار عمل Express بإنشاء عناوين URL الخاصة بك بالطريقة التي تريدها، إذ يمكنك تشفير المعلومات في متن عنوان URL أو استخدام معاملات URL من النوع GET (مثل /book/?id=6). يجب أن تظل عناوين URL نظيفة ومنطقية وقابلة للقراءة بغض النظر عن الطريقة التي تستخدمها. سننشئ فيما يلي دوال رد نداء معالجة الوجهة وشيفرة الوجهة البرمجية لجميع عناوين URL السابقة. إنشاء دوال رد النداء لمعالجة الوجهة سننشئ أولًا جميع دوال رد النداء الوهمية أو الهيكلية التي ستُستدعَى لاحقًا قبل أن نعرّف الوجهات، وستُخزَّن دوال رد النداء في وحدات متحكمات منفصلة لكل من Book و BookInstance و Genre و Author، إذ يمكنك استخدام أي بنية ملفات أو وحدات، ولكن تبدو الطريقة التي سنستخدمها مناسبة ودقيقة لهذا المشروع. ابدأ بإنشاء مجلدٍ للمتحكمات في جذر المشروع /controllers، ثم أنشئ ملفات أو وحدات منفصلة للمتحكمات للتعامل مع كل نموذج كما يلي: /express-locallibrary-tutorial //the project root /controllers authorController.js bookController.js bookinstanceController.js genreController.js ستستخدم المتحكمات الوحدة express-async-handler، لذا ثبّتها في المكتبة باستخدام npm قبل المتابعة كما يلي: npm install --save express-async-handler متحكم المؤلف Author افتح الملف "/controllers/authorController.js" واكتب فيه الشيفرة البرمجية التالية: const Author = require("../models/author"); const asyncHandler = require("express-async-handler"); // عرض قائمة بجميع المؤلفين exports.author_list = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Author list"); }); // عرض صفحة التفاصيل لمؤلف معين exports.author_detail = asyncHandler(async (req, res, next) => { res.send(`NOT IMPLEMENTED: Author detail: ${req.params.id}`); }); // عرض استمارة إنشاء مؤلف باستخدام GET exports.author_create_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Author create GET"); }); // معالجة إنشاء مؤلف باستخدام POST exports.author_create_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Author create POST"); }); // عرض استمارة حذف المؤلف باستخدام GET exports.author_delete_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Author delete GET"); }); // معالجة حذف المؤلف باستخدام POST exports.author_delete_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Author delete POST"); }); // عرض استمارة تحديث المؤلف باستخدام GET exports.author_update_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Author update GET"); }); // معالجة تحديث المؤلف باستخدام POST exports.author_update_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Author update POST"); }); تطلب الوحدة أولًا نموذج المؤلف Author الذي سنستخدمه لاحقًا للوصول إلى بياناتنا وتحديثها، والمغلِّف asyncHandler الذي سنستخدمه لالتقاط الاستثناءات من دوال معالج الوجهة، ثم تصدِّر دوالًا لكل عنوان من عناوين URL التي نرغب في معالجتها. لاحظ أن عمليات الإنشاء والتحديث والحذف تستخدم الاستمارات Forms، وبالتالي تملك توابعًا إضافية لمعالجة طلبات الاستمارة من النوع Post، وسنناقش هذه التوابع في مقال لاحق. تستخدم جميع الدوال الدالة المُغلِّفة Wrapper السابقة في معالجة الاستثناءات في دوال الوجهة مع وسائط للطلب والاستجابة والدالة التالية next، وتستجيب الدوال بسلسلة نصية تشير إلى أن الصفحة المرتبطة لم تُنشَأ بعد. إذا كان من المتوقع أن تتلقى دالة المتحكم معاملات المسار، فستكون هذه المعاملات هي الخرج الموجود في سلسلة الرسالة النصية (لاحظ req.params.id). لاحظ أنه يمكن ألّا تحتوي بعض دوال الوجهة بعد تقديمها على أي شيفرة برمجية يمكنها أن ترمي استثناءات، ولكن يمكننا تغيير تلك الدوال إلى دوال معالجة وجهة عادية عند الوصول إليها. متحكم نسخة الكتاب BookInstance افتح الملف /controllers/bookinstanceController.js وانسخ الشيفرة البرمجية التالية التي تتبع نمطًا مطابقًا لوحدة المتحكم Author: const BookInstance = require("../models/bookinstance"); const asyncHandler = require("express-async-handler"); // عرض قائمة بجميع نسخ الكتب BookInstance exports.bookinstance_list = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: BookInstance list"); }); // عرض صفحة تفاصيل نسخة كتاب BookInstance معينة exports.bookinstance_detail = asyncHandler(async (req, res, next) => { res.send(`NOT IMPLEMENTED: BookInstance detail: ${req.params.id}`); }); // عرض استمارة إنشاء نسخة الكتاب BookInstance باستخدام GET exports.bookinstance_create_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: BookInstance create GET"); }); // معالجة إنشاء نسخة كتاب BookInstance باستخدام POST exports.bookinstance_create_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: BookInstance create POST"); }); // عرض استمارة حذف نسخة كتاب BookInstance باستخدام GET exports.bookinstance_delete_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: BookInstance delete GET"); }); // معالجة حذف نسخة كتاب BookInstance باستخدام POST exports.bookinstance_delete_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: BookInstance delete POST"); }); // عرض استمارة تحديث نسخة كتاب BookInstance باستخدام GET exports.bookinstance_update_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: BookInstance update GET"); }); // معالجة تحديث نسخة الكتاب باستخدام POST exports.bookinstance_update_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: BookInstance update POST"); }); متحكم نوع الكتاب Genre افتح الملف /controllers/genreController.js والصق فيه النص التالي الذي يتبع نمطًا مطابقًا لملفات Author و BookInstance: const Genre = require("../models/genre"); const asyncHandler = require("express-async-handler"); // عرض قائمة بجميع أنواع الكتب Genre exports.genre_list = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Genre list"); }); // عرض صفحة تفاصيل نوع كتاب Genre معين exports.genre_detail = asyncHandler(async (req, res, next) => { res.send(`NOT IMPLEMENTED: Genre detail: ${req.params.id}`); }); // عرض استمارة إنشاء نوع الكتاب Genre باستخدام GET exports.genre_create_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Genre create GET"); }); // معالجة إنشاء نوع كتاب Genre باستخدام POST exports.genre_create_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Genre create POST"); }); // عرض استمارة حذف نوع كتاب Genre باستخدام GET exports.genre_delete_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Genre delete GET"); }); // معالجة حذف نوع كتاب Genre باستخدام POST exports.genre_delete_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Genre delete POST"); }); // عرض استمارة تحديث نوع كتاب Genre باستخدام GET exports.genre_update_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Genre update GET"); }); // معالجة تحديث نوع كتاب Genre باستخدام POST exports.genre_update_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Genre update POST"); }); متحكم الكتاب Book افتح الملف /controllers/bookController.js وانسخ الشيفرة البرمجية التالية، إذ يتبع هذا الملف النمط المتبع نفسه في وحدات المتحكمات الأخرى، ولكنه يحتوي أيضًا على الدالة index() لعرض صفحة الترحيب بالموقع: const Book = require("../models/book"); const asyncHandler = require("express-async-handler"); exports.index = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Site Home Page"); }); // عرض قائمة بجميع الكتب exports.book_list = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Book list"); }); // عرض صفحة تفاصيل كتاب معين exports.book_detail = asyncHandler(async (req, res, next) => { res.send(`NOT IMPLEMENTED: Book detail: ${req.params.id}`); }); // عرض استمارة إنشاء كتاب باستخدام GET exports.book_create_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Book create GET"); }); // معالجة إنشاء كتاب باستخدام POST exports.book_create_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Book create POST"); }); // عرض استمارة حذف كتاب باستخدام GET exports.book_delete_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Book delete GET"); }); // معالجة حذف كتاب باستخدام POST exports.book_delete_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Book delete POST"); }); // عرض استمارة تحديث كتاب باستخدام GET exports.book_update_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Book update GET"); }); // معالجة تحديث كتاب باستخدام POST exports.book_update_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Book update POST"); }); إنشاء دليل وحدات الوجهة سننشئ الآن وجهات لجميع عناوين URL التي يحتاجها موقع المكتبة المحلية LocalLibrary، والتي ستستدعي دوال المتحكمات التي عرّفناها في الأقسام السابقة. تحتوي البنية الهيكلية للموقع مسبقًا على المجلد "./routes" الذي يحتوي على وجهات لصفحة الفهرس index والمستخدمين users. أنشئ ملف وجهة آخر هو catalog.js ضمن هذا المجلد كما يلي: /express-locallibrary-tutorial //the project root /routes index.js users.js catalog.js افتح الملف /routes/catalog.js وانسخ الشيفرة البرمجية التالية: const express = require("express"); const router = express.Router(); // طلب وحدات المتحكمات const book_controller = require("../controllers/bookController"); const author_controller = require("../controllers/authorController"); const genre_controller = require("../controllers/genreController"); const book_instance_controller = require("../controllers/bookinstanceController"); /// وجهات الكتاب /// // الحصول على صفحة الدليل الرئيسية router.get("/", book_controller.index); // طلب GET لإنشاء كتاب. يجب أن يأتي هذا الطلب قبل الوجهات Routes التي تعرض الكتاب (يستخدم المعرّف id) router.get("/book/create", book_controller.book_create_get); // طلب POST لإنشاء كتاب router.post("/book/create", book_controller.book_create_post); // طلب GET لحذف كتاب router.get("/book/:id/delete", book_controller.book_delete_get); // طلب POST لحذف كتاب router.post("/book/:id/delete", book_controller.book_delete_post); // طلب GET لتحديث كتاب router.get("/book/:id/update", book_controller.book_update_get); // طلب POST لتحديث كتاب router.post("/book/:id/update", book_controller.book_update_post); // طلب GET لكتاب واحد router.get("/book/:id", book_controller.book_detail); // طلب GET لقائمة جميع عناصر الكتب router.get("/books", book_controller.book_list); /// وجهات المؤلف /// // طلب GET لإنشاء مؤلف Author. يجب أن يأتي هذا الطلب قبل وجهة المعرّف (مثل عرض مؤلف) router.get("/author/create", author_controller.author_create_get); // طلب POST لإنشاء مؤلف router.post("/author/create", author_controller.author_create_post); // طلب GET لحذف مؤلف router.get("/author/:id/delete", author_controller.author_delete_get); // طلب POST لحذف مؤلف router.post("/author/:id/delete", author_controller.author_delete_post); // طلب GET لتحديث مؤلف router.get("/author/:id/update", author_controller.author_update_get); // طلب POST لتحديث مؤلف router.post("/author/:id/update", author_controller.author_update_post); // طلب GET لمؤلف واحد router.get("/author/:id", author_controller.author_detail); // طلب GET لقائمة جميع المؤلفين router.get("/authors", author_controller.author_list); /// وجهات نوع الكتاب GENRE /// // طلب GET لإنشاء نوع كتاب. يجب أن يأتي هذا الطلب قبل الوجهة التي تعرض نوع الكتاب (يستخدم المعرّف) router.get("/genre/create", genre_controller.genre_create_get); // طلب POST لإنشاء نوع كتاب router.post("/genre/create", genre_controller.genre_create_post); // طلب GET لحذف نوع كتاب router.get("/genre/:id/delete", genre_controller.genre_delete_get); // طلب POST لحذف نوع كتاب router.post("/genre/:id/delete", genre_controller.genre_delete_post); // طلب GET لتحديث نوع كتاب router.get("/genre/:id/update", genre_controller.genre_update_get); // طلب POST لتحديث نوع كتاب router.post("/genre/:id/update", genre_controller.genre_update_post); // طلب GET لنوع كتاب واحد router.get("/genre/:id", genre_controller.genre_detail); // طلب GET لقائمة جميع أنواع الكتب router.get("/genres", genre_controller.genre_list); /// طرق نسخ الكتب BOOKINSTANCE /// // طلب GET لإنشاء نسخة كتاب BookInstance. يجب أن يأتي هذا الطلب قبل الوجهة التي تعرض نسخة الكتاب (يستخدم المعرّف) router.get( "/bookinstance/create", book_instance_controller.bookinstance_create_get, ); // طلب POST لإنشاء نسخة كتاب router.post( "/bookinstance/create", book_instance_controller.bookinstance_create_post, ); // طلب GET لحذف نسخة كتاب router.get( "/bookinstance/:id/delete", book_instance_controller.bookinstance_delete_get, ); // طلب POST لحذف نسخة كتاب router.post( "/bookinstance/:id/delete", book_instance_controller.bookinstance_delete_post, ); // طلب GET لتحديث نسخة الكتاب router.get( "/bookinstance/:id/update", book_instance_controller.bookinstance_update_get, ); // طلب POST لتحديث نسخة كتاب router.post( "/bookinstance/:id/update", book_instance_controller.bookinstance_update_post, ); // طلب GET لنسخة كتاب واحدة router.get("/bookinstance/:id", book_instance_controller.bookinstance_detail); // طلب GET لقائمة جميع نسخ الكتاب router.get("/bookinstances", book_instance_controller.bookinstance_list); module.exports = router; تطلب الوحدة كائن Express ثم تستخدمه لإنشاء كائن Router، إذ تُضبَط جميع الوجهات في هذا الكائن الذي يُصدَّر لاحقًا. تُعرّف الوجهات إما باستخدام تابع .get() أو تابع .post() للكائن Router، وتُعرّف جميع المسارات باستخدام سلاسل نصية، إذ لا نستخدم أنماط سلاسل نصية أو تعابيرًا نمطية. تستخدم الوجهات التي تعمل على بعض الموارد المحددة (مثل الكتاب) معاملاتِ المسار للحصول على معرّف الكائن من عنوان URL، وتُستورَد جميع دوال المعالجة من وحدات المتحكمات التي أنشأناها في القسم السابق. تحديث وحدة وجهة صفحة الفهرس ضبطنا جميع وجهاتنا الجديدة، ولكن لا يزال لدينا الوجهة إلى الصفحة الأصلية، ولكن لنعيد التوجيه إلى صفحة الفهرس الجديدة التي أنشأناها في المسار '/catalog' بدلًا من ذلك. افتح الملف /routes/index.js وضع الدالة التالية بدلًا من الوجهة الحالية: // الحصول على الصفحة الرئيسية router.get("/", function (req, res) { res.redirect("/catalog"); }); ملاحظة: استخدمنا تابع الاستجابة redirect() الذي يؤدي إلى إعادة التوجيه إلى الصفحة المحددة، مع إرسال رمز حالة HTTP الذي هو "302 Found" افتراضيًا، ويمكنك تغيير رمز الحالة المُعاد إن لزم الأمر وتوفير مسارات مطلقة أو نسبية. يمكنك الاطلاع على مقال رموز الإجابة في HTTP والمقال كيفية استكشاف وإصلاح رموز أخطاء HTTP الشائعةعلى أكاديمية حسوب لمزيدٍ من المعلومات حول رموز حالة HTTP. تحديث الملف app.js تتمثل الخطوة الأخيرة في إضافة الوجهات إلى سلسلة البرمجيات الوسيطة في الملف app.js، لذا افتح هذا الملف، واطلب وجهة الدليل بعد الوجهات الأخرى من خلال إضافة السطر الثالث التالي بعد السطرين الآخرين الموجودين مسبقًا في الملف: var indexRouter = require("./routes/index"); var usersRouter = require("./routes/users"); const catalogRouter = require("./routes/catalog"); // استيراد الوجهات لمنطقة "الدليل" في الموقع ضِف بعد ذلك وجهة الدليل catalog إلى مكدس البرمجيات الوسيطة بعد الوجهات الأخرى من خلال إضافة السطر الثالث التالي بعد السطرين الآخرين الموجودين مسبقًا في الملف: app.use("/", indexRouter); app.use("/users", usersRouter); app.use("/catalog", catalogRouter); // إضافة وجهات الدليل إلى سلسلة البرمجيات الوسيطة ملاحظة: أضفنا وحدة دليلنا في المسار '/catalog' الذي سيكون البادئة لجميع المسارات المحدَّدة في وحدة الدليل، فمثلًا سيكون عنوان URL هو /catalog/books/ للوصول إلى قائمة الكتب. يجب أن يكون لدينا الآن مسارات ودوال هيكلية مُفعَّلة لجميع عناوين URL التي سندعمها في موقع المكتبة المحلية LocalLibrary. اختبار الوجهات أولًا، ابدأ تشغيل موقع الويب بالطريقة المعتادة، فالطريقة الافتراضية في أنظمة التشغيل هي: // نظام ويندوز SET DEBUG=express-locallibrary-tutorial:* & npm start // ماك أو لينكس DEBUG=express-locallibrary-tutorial:* npm start ولكن إذا سبق لك إعداد nodemon، فيمكنك بدلًا من ذلك استخدام ما يلي: npm run serverstart انتقل بعد ذلك إلى عدد من عناوين URL الخاصة بموقع المكتبة المحلية LocalLibrary وتحقق من عدم ظهور صفحة خطأ (HTTP 404). إليك مجموعة صغيرة من عناوين URL: http://localhost:3000/ http://localhost:3000/catalog http://localhost:3000/catalog/books http://localhost:3000/catalog/bookinstances/ http://localhost:3000/catalog/authors/ http://localhost:3000/catalog/genres/ http://localhost:3000/catalog/book/5846437593935e2f8c2aa226 http://localhost:3000/catalog/book/create الخلاصة أنشأنا جميع الوجهات الخاصة بموقعنا، مع دوال المتحكمات الوهمية التي يمكننا ملؤها بتقديم كامل في مقالات لاحقة، وتعلمنا الكثير من المعلومات الأساسية حول وجهات Express والتعامل مع الاستثناءات وبعض الأساليب لبناء الوجهات والمتحكمات. سننشئ في المقال التالي صفحة ترحيب مناسبة للموقع باستخدام العروض (القوالب) والمعلومات المخزنة في نماذجنا، وسننشئ استمارات HTML وشيفرة معالجة الاستمارات لبدء تعديل البيانات التي يخزنها الموقع. ترجمة -وبتصرُّف- للمقال Express Tutorial Part 4: Routes and controllers. اقرأ أيضًا المقال السابق تطبيق عملي لتعلم Express - الجزء الثاني: استخدام قاعدة البيانات باستخدام مكتبة Mongoose. إنشاء خادم ويب في Node.js باستخدام الوحدة HTTP. المتحكّمات Controllers والعروض Views في إطار العمل Rails.
-
يقدّم هذا المقال مقدمة موجزة عن قواعد البيانات وكيفية استخدامها مع تطبيقات Node/Express، ثم يوضّح كيفية استخدام مكتبة Mongoose لتوفير الوصول إلى قاعدة بيانات موقع المكتبة المحلية LocalLibrary، ويشرح كيفية التصريح عن مخطط الكائنات object schema والنماذج Models، وأنواع الحقول الرئيسية والتحقق الأساسي من صحة البيانات. يعرض أيضًا بإيجاز بعض الطرق الرئيسية التي يمكنك من خلالها الوصول إلى بيانات النموذج. المتطلبات الأساسية: الاطلاع على مقال إنشاء موقع ويب هيكلي لمكتبة محلية. الهدف: أن تكون قادرًا على تصميم وإنشاء نماذجك باستخدام مكتبة Mongoose. يستخدم موظفو المكتبة موقع المكتبة المحلية لتخزين المعلومات حول الكتب والمستعيرين، بينما يستخدمه أعضاء المكتبة لتصفح الكتب والبحث عنها ولمعرفة ما إذا كان هناك أيّ نسخ متاحة ثم حجزها أو استعارتها. لذا سنخزّن المعلومات في قاعدة بيانات لتخزينها واسترجاعها بكفاءة. يمكن لتطبيقات Express استخدام العديد من قواعد البيانات المختلفة، وهناك العديد من الأساليب التي يمكنك استخدامها لإجراء عمليات الإنشاء والقراءة والتحديث والحذف -أو CRUD اختصارًا. يقدم هذا المقال نظرةً عامةً موجزة عن بعض الخيارات المتاحة ثم ينتقل ليشرح الآليات المختارة بالتفصيل. قواعد البيانات الممكن استخدامها يمكن لتطبيقات Express استخدام أيّ قاعدة بيانات تدعمها بيئة Node، إذ لا يحدد إطارعمل Express أيّ سلوك أو متطلبات إضافية محددة لإدارة قاعدة البيانات، وهناك العديد من الخيارات الشائعة بما في ذلك قواعد بيانات PostgreSQL و MySQL و Redis و SQLite و MongoDB وغير ذلك. يجب أن تأخذ في حساباتك عند اختيار قاعدة بيانات أشياءً مثل منحنى الوقت لتعلمها أو للحصول على نتائج والأداء وسهولة التكرار أو النسخ الاحتياطي والتكلفة ودعم المجتمع وغير ذلك. لا توجد قاعدة بيانات أفضل من الأخرى، ولكن تُعَد أيّ قاعدة بيانات شائعة تقريبًا مقبولة بصورة جيدة لموقع صغير إلى متوسط الحجم مثل موقع مكتبتنا المحلية. اطلع على تكامل قاعدة البيانات في توثيق Express لمزيد من المعلومات حول هذه الخيارات. أفضل طريقة للتفاعل مع قاعدة البيانات هناك طريقتان شائعتان للتفاعل مع قاعدة البيانات، هما: استخدام لغة الاستعلام الأصيلة لقواعد البيانات مثل لغة SQL. استخدام نموذج بيانات الكائن Object Data Model -أو اختصارًا ODM- أو نموذج الكائنات العلاقي Object Relational Model -أو ORM اختصارًا. يمثل نموذج ODM/ORM بيانات موقع الويب بوصفها كائنات جافا سكريبت، والتي تُربَط بعد ذلك بقاعدة البيانات الأساسية، إذ ترتبط بعض نماذج ORM بقاعدة بيانات معينة، بينما يوفر بعضها الآخر واجهة خلفية لا تعتمد على قاعدة البيانات. يمكن الحصول على أفضل أداء باستخدام لغة SQL أو أيّ لغة استعلام تدعمها قاعدة البيانات، إذ يُعَد نموذج ODM أبطأ لأنه يستخدم شيفرة ترجمة للربط بين الكائنات وتنسيق قاعدة البيانات، إذ يمكن ألّا يستخدم هذا النموذج استعلامات قاعدة البيانات الأكثر كفاءة، وهذا صحيح بصورة خاصة إذا كان نموذج ODM يدعم واجهات خلفية لقواعد بيانات مختلفة، وبالتالي يجب تقديم تنازلات أكبر فيما يتعلق بميزات قاعدة البيانات المدعومة. تتمثل فائدة استخدام نموذج ORM في أنه يمكن للمبرمجين الاستمرار في التفكير وفق مصطلحات كائنات جافاسكربت بدلًا من دلالات قواعد البيانات، وهذا صحيح بصورة خاصة إذا كنت بحاجة إلى العمل مع قواعد بيانات مختلفة على موقع الويب نفسه أو مواقع ويب مختلفة، ويوفر هذا النموذج أيضًا مكانًا واضحًا لإجراء التحقق من صحة البيانات. ملاحظة: يؤدي استخدام نماذج ODM/ORM إلى انخفاض تكاليف التطوير والصيانة، إذ يجب أن تفكر كثيرًا في استخدام نموذج ODM إلّا إذا كنت على دراية بلغة الاستعلام الأصيلة أو إذا كان الأداء أمرًا بالغ الأهمية. نموذج ORM/ODM الذي يجب أن استخدامه هناك العديد من حلول ODM/ORM المتاحة على موقع مدير الحزم npm (اطلع على odm و orm للتعرف على بعض من هذه الحلول). إليك بعضًا من هذه الحلول الشائعة: Mongoose: هي أداة نمذجة كائنات قاعدة بيانات MongoDB المُصمَّمة للعمل في بيئة غير متزامنة. Waterline: هو نموذج ORM المُستخرَج من إطار عمل الويب Sails القائم على إطار عمل Express. يوفر واجهة برمجة تطبيقات مُوحَّدة للوصول إلى العديد من قواعد البيانات المختلفة، بما في ذلك Redis و MySQL و LDAP و MongoDB و Postgres. Bookshelf: يتميز بواجهات رد النداء Callback التقليدية القائمة على الوعود Promise، مما يوفر دعمًا لمعامَلات قاعدة البيانات وتحميل العلاقات النشط أو النشط المتداخل والارتباطات متعددة الأشكال ودعم علاقات واحد إلى واحد one-to-one وواحد إلى متعدد one-to-many ومتعدد إلى متعدد many-to-many، ويعمل مع قواعد بيانات PostgreSQL و MySQL و SQLite3. يمكنك الاطلاع على مقال العلاقات بين الجداول في SQL على أكاديمية حسوب لمزيدٍ من المعلومات حول العلاقات بين الجداول. Objection: يسهّل قدر الإمكان استخدام قوة لغة SQL الكاملة ومحرك قاعدة البيانات الأساسي، ويدعم SQLite3 و Postgres و MySQL. Sequelize: هو نموذج ORM مبني على الوعود لكلٍّ من Node.js و io.js، ويدعم الأنواع المختلفة من لغات PostgreSQL و MySQL و MariaDB و SQLite و MSSQL ويتميز بدعمٍ قوي للمعامَلات transaction والعلاقات وتكرار عمليات القراءة وغير ذلك. Node ORM2: هو مدير علاقات الكائنات الخاص ببيئة NodeJS، ويدعم MySQL و SQLite و Progress، مما يساعد على العمل مع قاعدة البيانات باستخدام أسلوب موجَّه بالكائنات. GraphQL: لغة استعلام أساسية لواجهات برمجة التطبيقات restful API، وتحظى لغة GraphQL بشعبية كبيرة ولديها ميزات متاحة لقراءة البيانات من قواعد البيانات. يجب مراعاة كل من الميزات المتوفرة ونشاط المجتمع (التنزيلات والمساهمات وتقارير الأخطاء وجودة التوثيق وغير ذلك) عند اختيار الحل المناسب، وتُعَد مكتبة Mongoose أكثر نماذج ODM شيوعًا، وهو خيار جيد عند استخدام MongoDB لقاعدة بياناتك. استخدام مكتبة Mongoose وقاعدة بيانات MongoDB لموقع المكتبة المحلية سنستخدم في مثالنا مكتبة Mongoose بوصفها نموذج ODM للوصول إلى بيانات مكتبتنا، إذ تتصرف هذه المكتبة بمثابة واجهة أمامية لقاعدة بيانات MongoDB، وهي قاعدة بيانات NoSQL مفتوحة المصدر تستخدم نموذج بيانات موجَّهًا بالمستندات، إذ تشبه مجموعة المستندات في قاعدة بيانات MongoDB جدولًا من الصفوف في قاعدة بيانات علاقية. تحظى هذه المجموعة من نموذج ODM وقاعدة البيانات بشعبية كبيرة في مجتمع Node، ويرجع ذلك جزئيًا إلى أن تخزين المستندات ونظام الاستعلام يشبه إلى حد كبير JSON، وبالتالي فهو مألوف لمطوري جافا سكريبت. ملاحظة: لست بحاجة إلى معرفة قاعدة بيانات MongoDB لاستخدام مكتبة Mongoose، بالرغم من أن أجزاءً من توثيق Mongoose أسهل في الاستخدام والفهم إذا كنت على دراية بقاعدة بيانات MongoDB. سنوضح فيما يلي كيفية تعريف والوصول إلى مخطط ونماذج Mongoose لمثال موقع ويب المكتبة المحلية LocalLibrary. تصميم نماذج موقع المكتبة المحلية يفضَّل أخذ بضع دقائق للتفكير في البيانات التي يجب تخزينها والعلاقات بين الكائنات قبل البدء في كتابة شيفرة النماذج، إذ نعلم أننا بحاجةٍ إلى تخزين معلومات حول الكتب (العنوان والملخص والمؤلف ونوع الكتاب ورقم ISBN)، وقد يكون لدينا نسخٌ متعددة متاحة (مع معرّفات فريدة عامة وحالات توفرها وغير ذلك)، ويمكن أن نحتاج إلى تخزين مزيدٍ من المعلومات حول المؤلف أكثر من مجرد اسمه، ويمكن أن يكون هناك عدة مؤلفين لهم الاسم نفسه أو أسماء متشابهة. نريد أن نكون قادرين على فرز المعلومات بناءً على عنوان الكتاب والمؤلف ونوع الكتاب Genre وفئته Category. من المنطقي أن يكون لديك نماذج منفصلة لكل كائن (مجموعة من المعلومات المتعلقة ببعضها) عند تصميم نماذجك، وبالتالي فإن بعض المرشّحين الواضحين لهذه النماذج هم الكتب ونسخ الكتب والمؤلفون. يمكن أن ترغب في استخدام النماذج لتمثيل خيارات قائمة الاختيار (مثل قائمة اختيارات منسدلة) بدلًا من كتابة شيفرة ثابتة للخيارات في موقع الويب نفسه، إذ يوصَى بذلك عندما لا تكون جميع الخيارات معروفة مسبقًا أو أنها ستتغير، ومن الأمثلة الجيدة لذلك هو نوع الكتاب، مثل النوع الخيالي والخيال العلمي وغير ذلك. يجب الآن التفكير في العلاقات بين النماذج والحقول بعد تحديدها، إذ يوضح مخطط ارتباط باستخدام لغة UML الآتي النماذج التي سنعرّفها في حالتنا (على شكل صناديق)، إذ أنشأنا نماذجًا للكتاب (تفاصيل الكتاب العامة)، ونسخة الكتاب (حالة نسخ الكتاب الحقيقية المحدَّدة المتاحة في النظام)، والمؤلف، وقررنا أيضًا أن يكون لدينا نموذج لنوع الكتاب بحيث يمكن إنشاء القيم ديناميكيًا. لم ننشئ نموذجًا لحالة نسخة الكتاب BookInstance:status، إذ سنجعل القيم المقبولة ثابتة لأننا لا نتوقع تغييرها. يمكنك رؤية اسم النموذج وأسماء الحقول وأنواعها والتوابع وأنواع الإعادة الخاصة بها في كل صندوق. يوضح المخطط البياني الآتي أيضًا العلاقات بين النماذج، بما في ذلك درجة تعدّدها Multiplicities، وهي الأعداد الموجودة على المخطط والتي توضح عدد أو الحد الأقصى والحد الأدنى لكل نموذج الذي يمكن أن يكون موجودًا في العلاقة، فمثلًا يوضّح الخط المتصل بين الصناديق أن الكتاب Book والنوع Genre مرتبطان، وتوضح الأعداد القريبة من نموذج الكتاب Book أنه يجب يكون للنوع Genre صفر أو أكثر من الكتب Book (بقدر ما تريد)، بينما توضح الأعداد الموجودة على الطرف الآخر من الخط بجوار نموذج النوع Genre أن الكتاب يمكن أن يكون له صفر أو أكثر من الأنواع Genre المتعلقة به. ملاحظة: يُفضَّل غالبًا أن يكون لديك الحقل الذي يعرّف العلاقة بين المستندات/النماذج في نموذج واحد فقط كما سنوضّح لاحقًا، ولا يزال بإمكانك العثور على العلاقة العكسية من خلال البحث عن _id المرتبط بها في النموذج الآخر. اخترنا فيما يلي تعريف العلاقة بينBook/Genre و Book/Author في مخطط Schema الكتاب Book، والعلاقة بين Book/BookInstance في مخطط نسخة الكتاب BookInstance، إذ كان هذا الاختيار عشوائيًا إلى حدٍ ما، وكان من الممكن أيضًا أن يكون أحد الحقول موجودًا في المخطط الآخر. ملاحظة: يوفر القسم التالي شرحًا بسيطًا عن كيفية تعريف النماذج واستخدامها، لذلك ضع في بالك أثناء القراءة كيف سنبني كل نموذج من النماذج الموجودة في المخطط البياني السابق. واجهات برمجة تطبيقات قاعدة البيانات غير المتزامنة تُعَد توابع قاعدة البيانات لإنشاء السجلات أو العثور عليها أو تحديثها أو حذفها غير متزامنة، وهذا يعني أن التوابع تعيد القيم مباشرةً ويكون تشغيل الشيفرة البرمجية الخاصة بمعالجة نجاح أو فشل التابع في وقت لاحق عند اكتمال العملية. يمكن تنفيذ شيفرة برمجية أخرى أثناء انتظار الخادم لاكتمال عملية قاعدة البيانات، لذلك يمكن أن يظل الخادم مستجيبًا للطلبات الأخرى. تحتوي لغة جافا سكريبت Javascript على عدد من الآليات لدعم السلوك غير المتزامن، إذ اعتمدت كثيرًا سابقًا على تمرير دوال رد النداء إلى توابع غير متزامنة لمعالجة حالات النجاح والخطأ، وحلّت الوعود Promises محل دوال رد النداء إلى حد كبير في لغة جافا سكربت الحديثة. الوعود هي كائنات يعيدها (مباشرةً) تابع غير متزامن يمثل حالتها المستقبلية، ويستقر كائن الوعد عند اكتمال العملية، ويحقق كائنًا يمثل نتيجة العملية أو الخطأ. هناك طريقتان رئيسيتان يمكنك من خلالهما استخدام الوعود لتشغيل الشيفرة البرمجية عند استقرار الوعد، إذ نوصي بقراءة كيفية استخدام الوعود للحصول على نظرة عامة عالية المستوى على كلا الأسلوبين. سنستخدم في هذا المقال await لانتظار اكتمال الوعد في async function، لأن هذا الأسلوب يؤدي إلى الحصول على شيفرة برمجية غير متزامنة مفهومة وأكثر قابلية للقراءة. تتمثل الطريقة التي يعمل بها هذا الأسلوب في أنك تستخدم الكلمة الأساسية async function لتمييز الدالة بوصفها غير متزامنة، ثم تطبّق ضمن هذه الدالة تابع await على أيّ تابع يعيد وعدًا، وتتوقف عملية الدالة غير المتزامنة عند تنفيذها مؤقتًا عند أول تابع await حتى استقرار الوعد، ثم تعيد الدالة غير المتزامنة ويمكنك تشغيل الشيفرة البرمجية الموجودة بعد تلك الدالة من منظور الشيفرة البرمجية المحيطة. يعيد التابع await ضمن الدالة غير المتزامنة النتيجة لاحقًا عند استقرار الوعد، أو يعطي خطأً عند رفض الوعد، ثم تُنفَّذ الشيفرة البرمجية الموجودة في الدالة غير المتزامنة حتى يظهر تابع await آخر، إذ ستتوقف مؤقتًا مرةً أخرى، أو حتى تشغيل الشيفرة البرمجية بأكملها الموجودة في الدالة. يمكنك أن ترى كيفية عمل هذه الطريقة في المثال الآتي، إذ تُعَد myFunction() دالة غير متزامنة تُستدعَى ضمن كتلة try...catch. يُوقََف تنفيذ الشيفرة البرمجية مؤقتًا في التابع methodThatReturnsPromise() عند تشغيل الدالة myFunction() حتى تحقيق الوعد، وعندها يستمر تنفيذ الشيفرة البرمجية حتى الوصول إلى التابع aFunctionThatReturnsPromise() وينتظر مرةً أخرى. تُشغَّل الشيفرة البرمجية الموجودة في كتلة catch عند رمي خطأ في الدالة غير المتزامنة، إذ سيحدث ذلك عند رفض الوعد الذي يعيده أيّ من هذين التابعين. async function myFunction { // ... await someObject.methodThatReturnsPromise(); // ... await aFunctionThatReturnsPromise(); // ... } try { // ... myFunction(); // ... } catch (e) { // شيفرة معالجة الخطأ } تُشغَّل التوابع غير المتزامنة السابقة تسلسليًا، وإذا لم تعتمد التوابع على بعضها بعضًا، فيمكنك تشغيلها على التوازي وإنهاء العملية بأكملها بسرعة أكبر، ويمكن تحقيق ذلك باستخدام التابع Promise.all() الذي يأخذ تكرارًا من الوعود بوصفها دخلًا ويعيد وعدًا Promise واحدًا. يمكن الوفاء بهذا الوعد المُعاد عند الوفاء بجميع وعود الدخل مع مجموعة من قيم الوفاء، ويرفَض عند رفض أيٍّ من وعود الدخل مع سبب الرفض الأول. توضح الشيفرة البرمجية التالية كيفية عمل ذلك، إذ لدينا أولًا دالتان تعيدان وعودًا، إذ ننتظرهما await حتى يكتملا باستخدام الوعد الذي يعيده التابع Promise.all(). يعيد await بمجرد أن تكتمل كلتا الدالتين وتُملَأ مصفوفة النتائج، ثم يستمر تنفيذ الدالة حتى الوصول إلى تابع await التالي، وتنتظر حتى استقرار الوعد الذي تعيده الدالة anotherFunctionThatReturnsPromise(). يمكنك استدعاء الدالة myFunction() في كتلة try...catch لالتقاط الأخطاء. async function myFunction { // ... const [resultFunction1, resultFunction2] = await Promise.all([ functionThatReturnsPromise1(), functionThatReturnsPromise2() ]); // ... await anotherFunctionThatReturnsPromise(resultFunction1); } تسمح الوعود مع await/async بالتحكم المرن والمنطقي بالتنفيذ غير المتزامن. مقدمة إلى مكتبة Mongoose يقدم هذا القسم نظرةً عامة حول كيفية توصيل مكتبة Mongoose بقاعدة بيانات MongoDB، وكيفية تعريف المخططات والنماذج، وكيفية تطبيق الاستعلامات الأساسية. تثبيت Mongoose و MongoDB تُثبَّت مكتبة Mongoose في مشروعك (في الملف package.json) مثل أي اعتمادية أخرى باستخدام مدير حزم npm، إذ يمكن تثبيتها باستخدم الأمر التالي في مجلد مشروعك: npm install mongoose يضيف تثبيت مكتبة Mongoose جميع اعتمادياتها بما في ذلك مشغِّل قاعدة بيانات MongoDB، لكنه لا يؤدي إلى تثبيت MongoDB. إذا أدرتَ تثبيت خادم MongoDB، فيمكنك تنزيل المثبِّتات لأنظمة تشغيل مختلفة وتثبيتها محليًا، ويمكنك استخدام نسخ من MongoDB المستندة إلى السحابة. ملاحظة: سنستخدم في هذا المقال قاعدة البيانات المستندة إلى السحابة MongoDB Atlas بوصفها طبقة خدمة مجانية لتوفير قاعدة البيانات، وهي مناسبة لبيئة التطوير والتعلم لأنها تجعل نظام تشغيل التثبيت مستقلًا، وتُعَد قاعدة البيانات التي تمثل خدمة Database-as-a-service أيضًا إحدى الطرق التي يمكن أن تستخدمها لقاعدة بيانات بيئة الإنتاج. الاتصال بقاعدة بيانات MongoDB تتطلب مكتبة Mongoose اتصالًا بقاعدة بيانات MongoDB، وذلك باستخدام الدالة require() والاتصال بقاعدة بيانات مستضافة محليًا باستخدام mongoose.connect() كما يلي، ولكن سنتّصل بدلًا من ذلك في هذا المقال بقاعدة بيانات مستضافة عبر الإنترنت: // استيراد وحدة mongoose const mongoose = require("mongoose"); // اضبط `strictQuery: false` للاشتراك العام في الترشيح وفق الخاصيات غير المُدرَجة في المخطط // لأن هذا الخيار يزيل تحذيرات Mongoose 7 الأولية. // اطّلع على https://mongoosejs.com/docs/migrating_to_6.html#strictquery-is-removed-and-replaced-by-strict mongoose.set("strictQuery", false); // حدّد عنوان URL لقاعدة البيانات للاتصال به const mongoDB = "mongodb://127.0.0.1/my_database"; // انتظر حتى الاتصال بقاعدة البيانات، مع تسجيل خطأ إذا كانت هناك مشكلة main().catch((err) => console.log(err)); async function main() { await mongoose.connect(mongoDB); } ملاحظة: ننتظر await الوعد الذي يعيده التابع connect() ضمن دالة مُصرَّح عنها باستخدام async function كما ناقشنا سابقًا في قسم واجهات برمجة التطبيقات لقاعدة البيانات غير المتزامنة من هذا المقال. نستخدم المعالج catch() الخاص بالوعد لمعالجة الأخطاء عند محاولة الاتصال، ولكن يمكن أيضًا استدعاء main() ضمن كتلة try...catch. يمكنك الحصول على كائن Connection الافتراضي باستخدام mongoose.connection، وإذا كنت بحاجة إلى إنشاء اتصالات إضافية، فيمكنك استخدام التابع mongoose.createConnection() الذي يأخذ صيغة معرّف URI نفسه الخاص بقاعدة البيانات (مع المضيف وقاعدة البيانات والمنفذ والخيارات وإلخ) الذي يستخدمه التابع connect() ويعيد كائن Connection. لاحظ أن createConnection() يعيد مباشرةً، وبالتالي إذا كنت بحاجة إلى الانتظار حتى إنشاء الاتصال، فيمكنك استدعاؤه مع asPromise() لإعادة وعد (mongoose.createConnection(mongoDB).asPromise()). تعريف وإنشاء النماذج تُعرَّف النماذج باستخدام الواجهة Schema التي تتيح تعريف الحقول المُخزَّنة في المستندات مع متطلبات التحقق من صحة البيانات والقيم الافتراضية. يمكنك أيضًا تعريف التوابع المساعدة الثابتة ونُسَخ منها لتسهيل العمل مع أنواع بياناتك، والخاصيات الافتراضية التي يمكنك استخدامها مثل الحقول الأخرى، ولكنها غير مخزنة في قاعدة البيانات فعليًا (سنناقش ذلك لاحقًا). تُصرَّف بعد ذلك واجهات Schema إلى نماذج باستخدام التابع mongoose.model()، ثم يمكنك استخدام النموذج للعثور على كائنات من نوعٍ محدَّد وإنشائها وتحديثها وحذفها. ملاحظة: يُربَط كل نموذج بمجموعة من المستندات في قاعدة بيانات MongoDB، إذ ستحتوي المستندات على أنواع الحقول/المخططات Schema المحددة في نموذج Schema. تعريف المخططات يوضّح جزء الشيفرة البرمجية التالي كيفية تعريف مخطط بسيط، إذ يجب أولًا طلب مكتبة mongoose باستخدام الدالة require()، ثم استخدم باني Schema لإنشاء نسخة من المخطط الجديد، مع تعريف الحقول المختلفة ضمنه في معامل باني الكائن. // طلب مكتبة Mongoose const mongoose = require("mongoose"); // تعريف مخطط const Schema = mongoose.Schema; const SomeModelSchema = new Schema({ a_string: String, a_date: Date, }); لدينا في المثال السابق حقلين فقط نوعاهما: سلسلة نصية String وتاريخ Date، وسنعرض في الأقسام التالية بعض أنواع الحقول الأخرى والتحقق من صحتها والتوابع الأخرى. إنشاء نموذج تُنشَأ النماذج من المخططات باستخدام التابع mongoose.model() كما يلي: // تعريف مخطط const Schema = mongoose.Schema; const SomeModelSchema = new Schema({ a_string: String, a_date: Date, }); // تصريف النموذج من المخطط const SomeModel = mongoose.model("SomeModel", SomeModelSchema); الوسيط الأول هو الاسم المفرد للمجموعة التي ستُنشَأ لنموذجك، إذ ستنشِئ مكتبة Mongoose مجموعة قاعدة البيانات للنموذج SomeModel السابق، والوسيط الثاني هو المخطط الذي تريد استخدامه في إنشاء النموذج. ملاحظة: يمكنك استخدام أصناف Classes نموذجك بعد تعريفها لإنشاء سجلات أو تحديثها أو حذفها، ويمكنك تشغيل الاستعلامات للحصول على جميع السجلات أو مجموعات فرعية معينة من السجلات. سنوضح كيفية تحقيق ذلك لاحقًا في قسم استخدام النماذج وعندما ننشئ العروض Views. أنواع المخططات والحقول يمكن أن يحتوي المخطط على عدد عشوائي من الحقول، إذ يمثل كلٌّ منها حقلًا في المستندات المخزنة في قاعدة بيانات MongoDB. يوضّح المثال التالي مخططًا يحتوي على العديد من أنواع الحقول وكيفية التصريح عنها: const schema = new Schema({ name: String, binary: Buffer, living: Boolean, updated: { type: Date, default: Date.now() }, age: { type: Number, min: 18, max: 65, required: true }, mixed: Schema.Types.Mixed, _someId: Schema.Types.ObjectId, array: [], ofString: [String], // يمكنك أيضًا الحصول على مصفوفة لكل نوع من الأنواع الأخرى nested: { stuff: { type: String, lowercase: true, trim: true } }, }); لا تحتاج معظم أنواع المخططات SchemaTypes (الواصفات الموجودة بعد "type:" أو بعد أسماء الحقول) شرحًا، ولكن هناك بعض الاستثناءات وهي: ObjectId: يمثل نسخًا محدّدة لنموذجٍ في قاعدة البيانات، فمثلًا يمكن أن يستخدم الكتاب هذا النوع لتمثيل كائن مؤلفه، وسيحتوي هذا النوع على معرّف فريد (_id) للكائن، ويمكننا استخدام التابع populate() لسحب المعلومات عند الحاجة. Mixed: نوع مخطط عشوائي. []: مصفوفة من العناصر، إذ يمكنك إجراء عمليات مصفوفات جافاسكربت على هذه النماذج (الدفع والسحب إلغاء الإزاحة وإلخ). توضح الأمثلة السابقة مصفوفةً من الكائنات بدون نوع محدد ومصفوفة من كائنات String، ولكن يمكن أن يكون لديك مصفوفة من أيّ نوع من الكائنات. توضّح الشيفرة البرمجية أيضًا طريقتين للتصريح عن الحقل هما: اسم الحقل ونوعه مثل زوج قيمة-مفتاح (كما هو الحال مع اسم الحقول name و binary و living مثلًا). اسم الحقل متبوعًا بكائن يحدد النوع type وأيّ خيارات أخرى للحقل، إذ تتضمن هذه الخيارات ما يلي: قيم افتراضية. أدوات التحقق المبنية مسبقًا، مثل القيم العليا أو الدنيا، ودوال التحقق من صحة البيانات المُخصَّصة. ما إذا كان الحقل مطلوبًا. ما إذا كان يجب ضبط حقول String تلقائيًا بأحرف صغيرة أو كبيرة أو حذف المسافات في بداية ونهاية السلسلة النصية، مثل: { type: String, lowercase: true, trim: true } اطّلع على أنواع المخططات في توثيق Mongoose لمزيد من المعلومات حول الخيارات. التحقق من صحة البيانات توفر مكتبة Mongoose أدوات تحقق من صحة البيانات مبنية مسبقًا ومخصصة، وأدوات تحقق متزامنة وغير متزامنة، وتسمح بتحديد كلٍّ من مجال القيم المقبول ورسالة الخطأ التي تمثل فشل التحقق من صحة البيانات في جميع الحالات. تتضمن أدوات التحقق من صحة البيانات المبنية مسبقًا ما يلي: تحتوي جميع أنواع المخططات على أداة التحقق required المبنية مسبقًا التي تُستخدم لتحديد ما إذا كان يجب توفير الحقل لحفظ مستندٍ ما. تحتوي الأعداد Numbers على أدوات تحقق من صحة الحد الأدنى min والحد الأعلى max. تحتوي السلاسل النصية Strings على أدوات التحقق التالية: enum: تحدد مجموعة القيم المسموح بها للحقل. match: تحدد التعبير النمطي Regular Expression الذي يجب أن تتطابق معه السلسلة النصية. الطول الأقصى maxLength والطول الأدنى minLength للسلسلة النصية. يوضح المثال التالي -المأخوذ من توثيق Mongoose- كيفية تحديد بعض أنواع أدوات التحقق من صحة البيانات ورسائل الخطأ: const breakfastSchema = new Schema({ eggs: { type: Number, min: [6, "Too few eggs"], max: 12, required: [true, "Why no eggs?"], }, drink: { type: String, enum: ["Coffee", "Tea", "Water"], }, }); اطّلع على التحقق من صحة البيانات في توثيق Mongoose للحصول على معلومات كاملة حول التحقق من صحة الحقول. الخاصيات الافتراضية الخاصيات الافتراضية هي خاصيات المستند التي يمكنك جلبها وضبطها دون استمرار وجودها في قاعدة بيانات MongoDB، إذ تُعَد الجوالب Getters مفيدة لتنسيق الحقول أو دمجها، وتكون الضوابط Setters مفيدة في تفكيك قيمة واحدة إلى قيم متعددة لتخزينها. يبني المثال الموجود في توثيق Mongoose (ويهدم) خاصية افتراضية للاسم الكامل من حقل الاسم الأول والأخير، ويُعَد ذلك أسهل وأنظف من بناء اسم كامل في كل مرة يُستخدَم أحدها في قالب. ملاحظة: سنستخدم خاصية افتراضية في موقع المكتبة المحلية لتعريف عنوان URL فريد لكل سجل نموذج باستخدام مسار وقيمة _id الخاصة بالسجل. اطلع على الخاصيات الافتراضية في توثيق Mongoose لمزيد من المعلومات. التوابع والاستعلامات المساعدة يمكن أن يحتوي المخطط أيضًا على نسخ من التوابع Instance methods وتوابع ثابتة static methods واستعلامات مساعدة query helpers، إذ تتشابه نسخ التوابع والتوابع الثابتة، ولكن مع وجود اختلاف واضح في أن نسخ التوابع مرتبطة بسجل معين ويمكنها الوصول إلى الكائن الحالي. تسمح الاستعلامات المساعدة بتوسيع واجهة برمجة تطبيقات باني الاستعلامات القابلة للتسلسل الخاصة بمكتبة mongoose، مثل السماح بإضافة استعلام وفق الاسم "byName"، إضافةً إلى توابع find() و findOne() و findById(). استخدام النماذج يمكنك بعد إنشاء مخطط استخدامه لإنشاء النماذج، إذ يمثل النموذج مجموعة من المستندات الموجودة في قاعدة البيانات التي يمكنك البحث عنها، بينما تمثل نسخ النموذج المستندات الفردية التي يمكنك حفظها واسترجاعها. سنقدم فيما يلي نظرة عامة موجزة، لذا يمكنك الاطلاع على النماذج في توثيق Mongoose لمزيد من المعلومات. ملاحظة: يُعَد إنشاء السجلات وتحديثها وحذفها والاستعلام عنها عمليات غير متزامنة تعيد وعدًا. سنوضح في الأمثلة التالية استخدام التوابع المتعلقة بهذا الموضوع والتابع await، أي سنوضح الشيفرة البرمجية الأساسي لاستخدام التوابع، إذ سنحذف دالة async function المحيطة وكتلة try...catch لالتقاط الأخطاء للتوضيح. إنشاء وتعديل المستندات يمكنك إنشاء سجل من خلال تعريف نسخة من النموذج ثم استدعاء save(). تفترض الأمثلة التالية أن SomeModel هو نموذج (له حقل واحد هو name) أنشأناه من المخطط. // أنشئ نسخة من النموذج SomeModel const awesome_instance = new SomeModel({ name: "awesome" }); // احفظ نسخة النموذج الجديدة بطريقة غير متزامنة await awesome_instance.save(); يمكنك أيضًا استخدام create() لتعريف نسخة من النموذج في الوقت الذي تحفظها فيه. سننشئ فيما يلي نسخة واحدة فقط، ولكن يمكنك إنشاء نسخ متعددة من خلال تمرير مصفوفة من الكائنات. await SomeModel.create({ name: "also_awesome" }); لكل نموذج اتصاله المرتبط به، والذي سيكون الاتصال الافتراضي عند استخدام mongoose.model()، ويمكنك إنشاء اتصال جديد واستدعاء .model() لإنشاء المستندات في قاعدة بيانات مختلفة. يمكنك الوصول إلى الحقول في هذا السجل الجديد باستخدام الصيغة النقطية وتغيير القيم، ويجب استدعاء save() أو update() لتخزين القيم المُعدَّلة في قاعدة البيانات. // الوصول إلى قيم حقول النموذج باستخدام الصيغة النقطية console.log(awesome_instance.name); // يجب تسجيل 'also_awesome' أيضًا // تغيير السجل من خلال تعديل الحقول ثم استدعاء save() awesome_instance.name = "New cool name"; await awesome_instance.save(); البحث عن السجلات يمكنك البحث عن السجلات باستخدام توابع الاستعلام من خلال تحديد شروط الاستعلام بوصفها مستند JSON. يوضح جزء الشيفرة التالي كيفية العثور على جميع الرياضيين Athlete الذين يلعبون كرة المضرب في قاعدة بيانات، ويعيد فقط حقول اسم name وعُمر age الرياضي، إذ نحدد فقط حقلًا واحدًا مطابقًا (الرياضة sport)، ولكن يمكنك إضافة مزيدٍ من المعايير أو تحديد معايير التعبير النمطي أو إزالة جميع الشروط لإعادة جميع الرياضيين. const Athlete = mongoose.model("Athlete", yourSchema); // العثور على جميع الرياضيين الذين يلعبون كرة المضرب مع تحديد حقول 'name' و 'age' const tennisPlayers = await Athlete.find( { sport: "Tennis" }, "name age" ).exec(); ملاحظة: من المهم أن تتذكر أن عدم العثور على أي نتائج ليس خطأً في البحث، ولكنه يمكن أن يكون حالة فشل في سياق تطبيقك. إذا توقّع تطبيقك بحثًا ما للعثور على قيمة، فيمكنك التحقق من عدد المدخلات المُعَادة في النتيجة. تعيد واجهات برمجة تطبيقات الاستعلام مثل find() متغيرًا من النوع Query، ويمكنك استخدام كائن استعلام لبناء استعلام ضمن أجزاء قبل تنفيذه باستخدام التابع exec() الذي ينفّذ الاستعلام ويعيد وعدًا يمكنك انتظاره باستخدام await للحصول على النتيجة. // العثور على جميع الرياضيين الذين يلعبون كرة المضرب const query = Athlete.find({ sport: "Tennis" }); // اختيار حقول 'name' و 'age' query.select("name age"); // قصر نتائجنا على 5 عناصر query.limit(5); // الفرز وفق العمر query.sort({ age: -1 }); // تنفيذ الاستعلام في وقت لاحق query.exec(); عرّفنا شروط الاستعلام في التابع find()، ويمكننا تطبيق ذلك أيضًا باستخدام الدالة where()، ويمكننا سَلسَلة جميع أجزاء الاستعلام مع بعضها باستخدام المعامل النقطي (.) بدلًا من إضافتها بصورة منفصلة. جزء الشيفرة البرمجية التالي هو الاستعلام السابق نفسه مع شرط إضافي للعُمر: Athlete.find() .where("sport") .equals("Tennis") .where("age") .gt(17) .lt(50) // استعلام where إضافي .limit(5) .sort({ age: -1 }) .select("name age") .exec(); يحصل التابع find() على جميع السجلات المطابقة، ولكنك تريد الحصول على تطابق واحد فقط في أغلب الأحيان، لذا يمكنك استخدام توابع الاستعلام التالية لسجل واحد: findById(): يبحث عن المستند باستخدام المعرّف id، فلكل مستندٍ معرّفٌ فريد. findOne(): يبحث عن مستند واحد يطابق معاييرًا محدَّدة. findByIdAndRemove() و findByIdAndUpdate() و findOneAndRemove() و findOneAndUpdate(): تبحث عن مستند واحد باستخدام المعرّف id أو المعايير، فإما أن تحدّثه أو تزيله، إذ تُعَد هذه الدوال ملائمة ومفيدة لتحديث السجلات وإزالتها. ملاحظة: هناك أيضًا التابع countDocuments() الذي يمكنك استخدامه للحصول على عدد العناصر التي تطابق الشروط، ويُعَد مفيدًا إذا أردتَ إجراء تعداد دون جلب السجلات فعليًا. هناك الكثير من الأمور التي يمكنك تطبيقها على الاستعلامات، لذا اطّلع على الاستعلامات في توثيق Mongoose لمزيد من المعلومات. العمل مع المستندات- الملء Population يمكنك إنشاء مراجعٍ من نسخة مستند أو نموذج إلى آخر باستخدام حقل المخطط ObjectId، أو من مستند إلى عدة مستندات باستخدام مصفوفة من ObjectId. يخزّن هذا الحقل معرّف النموذج المرتبط به، وإذا كنت بحاجة إلى محتوى المستند الفعلي، فيمكنك استخدام التابع populate() في استعلام لاستبدال المعرّف بالبيانات الفعلية. يعرّف المخطط التالي مثلًا المؤلفين والقصص، إذ يمكن أن يكون لكل مؤلف قصص متعددة، والتي نمثلها بوصفها مصفوفة من ObjectId، ويمكن أن يكون لكل قصة مؤلف واحد. تخبر الخاصية ref المخطط بالنموذج الذي يمكن إسناده إلى هذا الحقل. const mongoose = require("mongoose"); const Schema = mongoose.Schema; const authorSchema = Schema({ name: String, stories: [{ type: Schema.Types.ObjectId, ref: "Story" }], }); const storySchema = Schema({ author: { type: Schema.Types.ObjectId, ref: "Author" }, title: String, }); const Story = mongoose.model("Story", storySchema); const Author = mongoose.model("Author", authorSchema); يمكننا حفظ المراجع التي تشير إلى المستند من خلال إسناد قيمة _id إليها، إذ سننشئ فيما يلي مؤلفًا ثم ننشئ قصة ونسند معرّف المؤلف إلى حقل مؤلف القصة: const bob = new Author({ name: "Bob Smith" }); await bob.save(); // Bob موجود الآن، لذا لننشئ قصة const story = new Story({ title: "Bob goes sledding", author: bob._id, // إسناد _id للمؤلف Bob، إذ يُنشَأ هذا المعرّف افتراضيًا }); await story.save(); ملاحظة: إحدى الفوائد الرائعة لهذا النمط من البرمجة هي أنه لا يتعين علينا تعقيد المسار الرئيسي لشيفرتنا البرمجية من خلال التحقق من الأخطاء، فإذا فشلت أيّ عملية حفظ save()، سيُرفَض الوعد وسيُرمَى خطأ. تتعامل شيفرة معالجة الأخطاء مع ذلك بصورة منفصلة (في كتلة catch() عادةً)، لذا يُعَد الهدف من شيفرتنا البرمجية واضحًا جدًا. يحتوي مستند القصة الآن على مؤلف يُشار إليه باستخدام معرّف مستند المؤلف، ونستخدم التابع populate() كما هو موضح فيما يلي للحصول على معلومات المؤلف في نتائج القصة: Story.findOne({ title: "Bob goes sledding" }) .populate("author") // استبدال معرّف المؤلف بمعلومات المؤلف الفعلية في النتائج .exec(); ملاحظة: سيلاحظ القراء المتمرسون أننا أضفنا مؤلفًا إلى القصة، لكننا لم نفعل أي شيء لإضافة القصة إلى مصفوفة stories الخاصة بالمؤلف. تتمثل إحدى الطرق للحصول على جميع القصص لمؤلف معين في إضافة القصة إلى مصفوفة القصص، ولكن ذلك يمكن أن يؤدي إلى وجود مكانين للاحتفاظ بالمعلومات المتعلقة بالمؤلفين والقصص. توجد طريقة أفضل، وهي الحصول على معرّف _id المؤلف، ثم استخدام find() للبحث عنه في حقل المؤلف عبر جميع القصص. Story.find({ author: bob._id }).exec(); اطلّع على Population في توثيق Mongoose لمزيد من المعلومات التفصيلية. مخطط أو نموذج واحد لكل ملف يمكنك إنشاء مخططات ونماذج باستخدام أي بنية ملفات تريدها، ولكننا نوصي بتعريف كل مخطط نموذج في وحدته أو ملفه الخاص، ثم تصدير التابع لإنشاء النموذج كما يلي: // الملف: ./models/somemodel.js // طلب Mongoose const mongoose = require("mongoose"); // تعريف مخطط const Schema = mongoose.Schema; const SomeModelSchema = new Schema({ a_string: String, a_date: Date, }); // تصدير دالة لإنشاء صنف النموذج "SomeModel" module.exports = mongoose.model("SomeModel", SomeModelSchema); يمكنك بعد ذلك طلب النموذج واستخدامه مباشرةً في ملفات أخرى، وسنوضح فيما يلي كيفية استخدامه للحصول على جميع نسخ النموذج: // إنشاء نموذج SomeModel من خلال طلب الوحدة const SomeModel = require("../models/somemodel"); // استخدام كائن (نموذج) SomeModel للعثور على كافة سجلات SomeModel const modelInstances = await SomeModel.find().exec(); إعداد قاعدة بيانات MongoDB تعرّفنا على ما يمكن أن تفعله مكتبة Mongoose وكيفية تصميم نماذجنا، وحان الوقت الآن لبدء العمل على موقع المكتبة المحلية LocalLibrary، وأول شيء يجب فعله هو إعداد قاعدة بيانات MongoDB التي يمكننا استخدامها لتخزين بيانات مكتبتنا. سنستخدم في هذا المقال قاعدة البيانات التجريبية المُستضافَة على السحابة MongoDB Atlas، إذ لا تُعَد طبقة قاعدة البيانات هذه مناسبة لمواقع الويب في بيئة الإنتاج لأنها لا تحتوي على تكرار Redundancy، ولكنها رائعة لعملية التطوير والنماذج الأولية، وسنستخدمها لأنها مجانية وسهلة الإعداد، ولأنها بائع شائع لقاعدة البيانات التي تمثل خدمة، والتي يمكن أن تختارها لقاعدة بيانات الإنتاج الخاصة بك، وتشمل الخيارات الشائعة الأخرى Compose و ScaleGrid و ObjectRocket. ملاحظة: يمكنك أيضًا إعداد قاعدة بيانات MongoDb محليًا من خلال تنزيل وتثبيت الملفات الثنائية المناسبة لنظامك، إذ ستكون بقية الإرشادات الواردة في هذا المقال متشابهة باستثناء عنوان URL لقاعدة البيانات الذي يمكن أن تحدده عند الاتصال. نستضيف لاحقًا في مقال نشر تطبيق Express في بيئة الإنتاج كلًا من التطبيق وقاعدة البيانات على منصة Railway، ولكن يمكن أيضًا استخدام قاعدة بيانات على MongoDB Atlas. يجب أولًا إنشاء حساب على MongoDB Atlas، وهو مجاني ويتطلب فقط إدخال تفاصيل الاتصال الأساسية والإقرار بشروط الخدمة. ستنتقل بعد تسجيل الدخول إلى الشاشة الرئيسية، لذا اتبع الخطوات التالية: أولًا، انقر على زر "إنشاء Create" في قسم نظرة عامة Overview. ثانيًا، سيؤدي ذلك إلى فتح شاشة نشر قاعدة بيانات سحابية Deploy a cloud database. انقر على زر MO FREE. ثالثًا، سيؤدي ذلك إلى سرد خيارات مختلفة للاختيار بينها: حدد أي مزوّد من قسم المزوّد والمنطقة Provider & Region، إذ تقدّم المناطق المختلفة مزوّدين مختلفين. يمكنك تغيير اسم العنقود ضمن قسم اسم العنقود Cluster Name، إذ سنسميه Cluster0. انقر بعد ذلك على زر "إنشاء عنقود Create Cluster"، وسيستغرق إنشاء العنقود بضع دقائق. رابعًا، سيؤدي ذلك إلى فتح قسم بداية سريعة للأمان Security Quickstart. أدخِل اسم المستخدم وكلمة المرور، وتذكّر نسخ الاعتماديات وتخزينها بأمان إذ سنحتاج إليها لاحقًا. انقر على زر "إنشاء مستخدم Create User". ملاحظة: تجنب استخدام محارف خاصة في كلمة مرور مستخدم MongoDB لأن مكتبة Mongoose يمكن ألّا يحلّل سلسلة الاتصال بصورة صحيحة. أدخل 0.0.0.0/0 في حقل عنوان IP الذي يخبر قاعدة بيانات MongoDB أننا نريد السماح بالوصول من أيّ مكان، ثم انقر على زر "إضافة إدخال Add Entry". ملاحظة: يُعَد قصر عناوين IP التي يمكنها الاتصال على قاعدة بياناتك ومواردك الأخرى من أفضل الممارسات، ولكن سنسمح في مثالنا بالاتصال من أيّ مكان لأننا لا نعرف من أين سيأتي الطلب بعد النشر. انقر بعد ذلك على زر "إنهاء وإغلاق Finish and Close". خامسًا، سيؤدي ذلك إلى فتح الشاشة التالية، لذا انقر على زر "الانتقال إلى قواعد البيانات Go to Databases". سادسًا، ستعود بعد ذلك إلى شاشة نظرة عامة Overview. انقر على قسم قاعدة البيانات Database تحت قائمة "Deployment" الموجودة على اليسار وانقر على زر استعراض التجميعات Browse Collections. سابعًا، سيؤدي ذلك إلى فتح قسم التجميعات Collections. انقر على زر "إضافة بياناتي الخاصة Add My Own Data". ثامنًا، ستظهر الآن شاشة إنشاء قاعدة بيانات Create Database. أدخِل الاسم local_library لاسم قاعدة البيانات الجديدة، ثم أدخِل اسم المجموعة Collection0، ثم انقر على زر "إنشاء Create" لإنشاء قاعدة البيانات. تاسعًا، ستعود إلى شاشة المجموعات Collections مع وجود قاعدة بياناتك التي أنشأتها. انقر على نافذة "نظرة عامة Overview" للعودة إلى شاشة نظرة عامة على العنقود. عاشرًا، انقر على زر "اتصال Connect" من شاشة نظرة عامة Overview للعنقود Cluster0. سيؤدي ذلك إلى فتح شاشة الاتصال بالعنقود Connect to Cluster. انقر على خيار Drivers الموجود تحت خيار الاتصال بتطبيقك Connect your application. أخيرًا، ستظهر لك شاشة الاتصال Connect. اتبع بعد ذلك الخطوات التالية: حدد مشغّل driver ونسخة Node كما هو موضح في الشكل السابق. انقر على أيقونة النسخ Copy لنسخ سلسلة الاتصال. الصقها في محرر نصوصك المحلي. حدّث اسم المستخدم وكلمة المرور بكلمة مرور مستخدمك. أدخِل اسم قاعدة البيانات "local_library" في المسار قبل الخيارات (...mongodb.net/local_library?retryWrites...). احفظ الملف الذي يحتوي على هذه السلسلة في مكان آمن. أنشأتَ قاعدة البيانات، ولديك عنوان URL (مع اسم مستخدم وكلمة مرور) الذي يمكن استخدامه للوصول إليها، إذ سيبدو كما يلي: mongodb+srv://your_user_name:your_password@cluster0.lz91hw2.mongodb.net/local_library?retryWrites=true&w=majority تثبيت Mongoose افتح موجّه الأوامر وانتقل إلى المجلد الذي أنشأتَ فيه موقعك الهيكلي الخاص بالمكتبة المحلية، ثم أدخِل الأمر التالي لتثبيت مكتبة Mongoose (واعتمادياتها) وضِفها إلى ملف package.json، إن لم تكن قد فعلتَ ذلك مسبقًا عند قراءة فقرة مقدمة إلى مكتبة Mongoose. npm install mongoose الاتصال بقاعدة بيانات MongoDB افتح الملف "/app.js" في جذر مشروعك وانسخ النص التالي في مكان التصريح عن كائن تطبيق Express (بعد سطر const app = express();). ضع عنوان URL الخاص بالموقع الذي يمثل قاعدة بياناتك (أي باستخدام المعلومات الواردة من mongoDB Atlas) مكان سلسلة عنوان URL لقاعدة البيانات ('insert_your_database_url_here'). // إعداد اتصال mongoose const mongoose = require("mongoose"); mongoose.set("strictQuery", false); const mongoDB = "insert_your_database_url_here"; main().catch((err) => console.log(err)); async function main() { await mongoose.connect(mongoDB); } تنشئ هذه الشيفرة البرمجية الاتصال الافتراضي بقاعدة البيانات ويبلّغ عن وجود أيّ أخطاء في الطرفية. تعريف مخطط المكتبة المحلية سنعرّف وحدة منفصلة لكل نموذج كما وضّحنا سابقًا. ابدأ بإنشاء مجلد للنماذج في جذر المشروع (/models) ثم أنشئ ملفات منفصلة لكل نموذج كما يلي: /express-locallibrary-tutorial // the project root /models author.js book.js bookinstance.js genre.js نموذج المؤلف Author انسخ شيفرة مخطط المؤلف Author التالية والصقها في ملف "./models/author.js"، إذ يعرّف هذا المخطط مؤلفًا يحتوي على حقول من نوع المخطط String للاسم الأول واسم العائلة (مطلوبة بحد أقصى 100 محرف) وحقول من النوع Date لتواريخ الميلاد والوفاة. const mongoose = require("mongoose"); const Schema = mongoose.Schema; const AuthorSchema = new Schema({ first_name: { type: String, required: true, maxLength: 100 }, family_name: { type: String, required: true, maxLength: 100 }, date_of_birth: { type: Date }, date_of_death: { type: Date }, }); // الخاصية الافتراضية لاسم المؤلف الكامل AuthorSchema.virtual("name").get(function () { // يمكن تجنب الأخطاء في الحالات التي لا يحمل فيها المؤلف اسم عائلة أو اسمًا أولًا // من خلال التأكد من معالجة الاستثناء عبر إعادة سلسلة فارغة لهذه الحالة let fullname = ""; if (this.first_name && this.family_name) { fullname = `${this.family_name}, ${this.first_name}`; } return fullname; }); // الخاصية الافتراضية لعنوان URL الخاص بالمؤلف AuthorSchema.virtual("url").get(function () { // لا نستخدم دالة سهمية لأننا نحتاج إلى هذا الكائن return `/catalog/author/${this._id}`; }); // تصدير النموذج module.exports = mongoose.model("Author", AuthorSchema); صرّحنا عن الخاصية الافتراضية لمخطط المؤلف AuthorSchema بالاسم "url"، والتي تعرض عنوان URL المطلق المطلوب للحصول على نسخة معينة من النموذج، إذ سنستخدم هذه الخاصية في قوالبنا كلما احتجنا إلى الحصول على ارتباط إلى مؤلف معين. ملاحظة: يُعَد التصريح عن عناوين URL بوصفها خاصيات افتراضية في المخطط فكرةً جيدة لأن عنوان URL لعنصرٍ ما يجب تغييره في مكانٍ واحد فقط. لن يعمل الرابط الذي يستخدم عنوان URL هذا حاليًا، لأنه ليس لدينا أيّ شيفرة لمعالجة الوجهات Routes لنسخ النماذج الفردية، إذ سنضبطها في مقالٍ لاحق. نصدّر بعد ذلك النموذج في نهاية الوحدة. نموذج الكتاب Book انسخ شيفرة مخطط الكتاب Book التالية والصقها في الملف "./models/book.js"، والتي تشبه في معظمها نموذج المؤلف، إذ صرّحنا عن مخطط يحتوي على عدد من الحقول من النوع String وخاصية افتراضية للحصول على عنوان URL لسجلات كتاب معينة، ثم صدّرنا النموذج. const mongoose = require("mongoose"); const Schema = mongoose.Schema; const BookSchema = new Schema({ title: { type: String, required: true }, author: { type: Schema.Types.ObjectId, ref: "Author", required: true }, summary: { type: String, required: true }, isbn: { type: String, required: true }, genre: [{ type: Schema.Types.ObjectId, ref: "Genre" }], }); // الخاصية الافتراضية لعنوان URL الخاص بالكتاب BookSchema.virtual("url").get(function () { // لا نستخدم دالة سهمية لأننا نحتاج إلى هذا الكائن return `/catalog/book/${this._id}`; }); // تصدير النموذج module.exports = mongoose.model("Book", BookSchema); الاختلاف الرئيسي هنا هو أننا أنشأنا مرجعين إلى نماذج أخرى هما: المؤلف author هو مرجع إلى كائن نموذج Author واحد، وهو مطلوب. النوع genre هو مرجع إلى مصفوفة من كائنات نموذج Genre، ولكننا لم نصرّح عن هذا الكائن بعد. نموذج نسخة الكتاب BookInstance انسخ شيفرة مخطط BookInstance التالية والصقها في الملف "./models/bookinstance.js"، إذ يمثل BookInstance نسخةً محددةً من الكتاب الذي يمكن أن يستعيره شخص ما ويتضمن معلومات حول ما إذا كانت النسخة متوفرة، والتاريخ المتوقع لاسترجاعها، وتفاصيل "الطبعة" أو النسخة. const mongoose = require("mongoose"); const Schema = mongoose.Schema; const BookInstanceSchema = new Schema({ book: { type: Schema.Types.ObjectId, ref: "Book", required: true }, // reference to the associated book imprint: { type: String, required: true }, status: { type: String, required: true, enum: ["Available", "Maintenance", "Loaned", "Reserved"], default: "Maintenance", }, due_back: { type: Date, default: Date.now }, }); // الخاصية الافتراضية لعنوان URL الخاص بالكتاب BookInstanceSchema.virtual("url").get(function () { // لا نستخدم دالة سهمية لأننا نحتاج إلى هذا الكائن return `/catalog/bookinstance/${this._id}`; }); // تصدير النموذج module.exports = mongoose.model("BookInstance", BookInstanceSchema); الأشياء الجديدة هي خيارات الحقول التالية: enum: يسمح بضبط القيم المسموح بها من نوع السلسلة النصية String، إذ نستخدمه في هذه الحالة لتحديد حالة توفر الكتب. يعني استخدام enum أنه يمكننا منع الأخطاء الإملائية والقيم العشوائية للحالة. default: نستخدمه لضبط الحالة الافتراضية لنسخ الكتاب التي أنشأناها على القيمة "في الصيانة Maintenance" وتاريخ due_back الافتراضي على القيمة now. لاحظ كيفية استدعاء دالة التاريخ Date عند ضبط التاريخ. يجب أن يكون كل شيء آخر مألوفًا من المخطط السابق. نموذج نوع الكتاب Genre- التحدي افتح الملف "./models/genre.js" وأنشئ مخططًا لتخزين أنواع الكتب (فئة الكتاب مثل ما إذا كان كتابًا خياليًا أو غير خيالي أو عاطفيًا أو تاريخيًا عسكريًا وغير ذلك). سيكون التعريف مشابهًا جدًا للنماذج الأخرى: يجب أن يحتوي النموذج على نوع المخطط String بالاسم name لوصف نوع الكتاب. يجب أن يكون هذا الاسم مطلوبًا ويتكون من 3 إلى 100 محرف. التصريح عن الخاصية الافتراضية لعنوان URL الخاص بنوع الكتاب بالاسم url. تصدير النموذج. إنشاء بعض العناصر للاختبار لدينا الآن جميع النماذج المُعدَّة للموقع، ويمكننا اختبار النماذج وإنشاء بعض أمثلة الكتب والعناصر الأخرى التي يمكننا استخدامها في المقالات اللاحقة من خلال تشغيل سكربت مستقل لإنشاء عناصر لكل نوع. أولًا، نزّل أو أنشئ الملف populatedb.js ضمن المجلد express-locallibrary-tutorial (في مستوى الملف package.json نفسه). ملاحظة: لست بحاجة إلى معرفة كيفية عمل الملف "populatedb.js"، فهو يضيف فقط عينة بيانات إلى قاعدة البيانات. ثانيًا، شغّل السكريبت باستخدام أمر node في موجه أوامرك مع تمرير عنوان URL لقاعدة بيانات MongoDB (عنوان URL نفسه الذي وضعته مكان العنصر البديل insert_your_database_url_here في الملف app.js سابقًا): node populatedb <your mongodb url> ملاحظة: يجب تغليف عنوان URL لقاعدة البيانات ضمن علامات اقتباس (") مزدوجة في نظام ويندوز، ويمكن أن تحتاج إلى علامات اقتباس مفردة (') في أنظمة التشغيل الأخرى. ثالثًا، يجب تشغيل السكريبت حتى اكتماله، إذ يعرض العناصر أثناء إنشائها في الطرفية. ملاحظة: انتقل إلى قاعدة بياناتك على MongoDB Atlas في نافذة التجميعات Collections، إذ يجب أن تكون الآن قادرًا على التنقل في مجموعات الكتب والمؤلفين والأنواع ونسخ الكتب والتحقق من المستندات الفردية. الخلاصة تعلمنا في هذا المقال بعض الأشياء عن قواعد البيانات ونماذج ORM على Node/Express، وتعرّفنا على كيفية تعريف مخطط ونماذج Mongoose، ثم استخدمنا هذه المعلومات لتصميم وتقديم نماذج Book و BookInstance و Author و Genre لموقع المكتبة المحلية LocalLibrary، واختبرنا نماذجنا من خلال إنشاء عدد من نسخ باستخدام سكريبت مستقل. سنتعرّف في المقال التالي على إنشاء بعض الصفحات لعرض هذه الكائنات. ترجمة -وبتصرُّف- للمقال Express Tutorial Part 3: Using a Database with Mongoose. اقرأ أيضًا المقال السابق تطبيق عملي لتعلم Express - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية مقارنة بين MySQL و MongoDB تجهيز قاعدة البيانات PostgreSQL والتّعريف بمفهومي ORM وإضافات Flask دمج قاعدة البيانات MongoDB في تطبيقك Node
-
يشرح هذا المقال ما ستتعلمه لبناء موقع ويب باستخدام إطار عمل Express، ويوفر نظرةً عامة على مثال موقع المكتبة المحلية الذي سنعمل عليه ونطوّره في المقالات اللاحقة، وسنوضّح كيفية إنشاء مشروع موقع ويب هيكلي يمكنك ملؤه لاحقًا بالوجهات Routes والعروض Views أوالقوالب Templates واستدعاءات قاعدة البيانات الخاصة بالموقع. المتطلبات الأساسية: قراءة مدخل إلى إطار عمل Express،وإعداد بيئة تطوير Node مع Express. الهدف: مقدمة إلى التطبيق العملي الذي سنبنيه في المقالات اللاحقة، وفهم الموضوعات التي سنتناولها، والقدرة على بدء مشاريعك لمواقع الويب الجديدة باستخدام مولّد تطبيقات Express. سنطور موقع ويب يمكن استخدامه لإدارة دليلٍ لمكتبة محلية، وسنتعلم في هذه السلسلة من المقالات ما يلي: استخدام أداة مولّد تطبيقات Express لإنشاء موقع ويب وتطبيق هيكلي. بدء وإيقاف خادم ويب Node. استخدم قاعدة بيانات لتخزين بيانات تطبيقك. إنشاء الوجهات لطلب معلومات مختلفة، وقوالب أو عروض لعرض البيانات بتنسيق HTML لعرضها في المتصفح. العمل مع الاستمارات Forms. نشر تطبيقك في بيئة الإنتاج. تعلّمت مسبقًا عن بعض هذه الموضوعات، وتعرّفت إلى بعضها الآخر بإيجاز، ولكن يجب أن تعرف ما يكفي لتطوير تطبيقات Express بنفسك في نهاية هذه السلسلة من المقالات المتفرعة من سلسلة تعلم تطوير الويب. موقع المكتبة المحلية LocalLibrary LocalLibrary هو اسم موقع الويب الذي سننشئه ونطوّره في المقالات اللاحقة، والغرض الأساسي من هذا الموقع هو توفير دليل Catalog عبر الإنترنت لمكتبة محلية صغيرة، حيث يمكن للمستخدمين تصفح الكتب المتاحة وإدارة حساباتهم. اختير هذا المثال بعناية لأنه يمكن تغيير حجمه لإظهار الكثير أو القليل من التفاصيل التي نحتاجها، ويمكن استخدامه لإظهار أي ميزة من ميزات Express تقريبًا، ويسمح بتوفير مسار إرشادي عبر الوظائف التي ستحتاجها في أيّ موقع ويب كما يلي: سنعرّف في المقالات الأولى مكتبةً بسيطةً للتصفح فقط، إذ يمكن لأعضاء المكتبة استخدامها لمعرفة الكتب المتاحة، مما يتيح استكشاف العمليات المشتركة لكل مواقع الويب تقريبًا، وهي قراءة المحتوى وعرضه من قاعدة بيانات. يتوسّع مثال المكتبة في المقالات اللاحقة لإظهار ميزات موقع الويب الأكثر تقدمًا، فمثلًا يمكننا توسيع المكتبة للسماح بإنشاء كتب جديدة، واستخدام ذلك لتوضيح كيفية استخدام الاستمارات ودعم استيثاق Authentication المستخدمين. سُمِّي هذا المثال بمكتبة محلية لسببٍ ما بالرغم من أنه قابل جدًا للتوسّع، فهدفنا هو إظهار الحد الأدنى من المعلومات التي ستساعدك على بدء استخدام إطار عمل Express وتشغيله بسرعة، لذا سنخزّن معلومات عن الكتب ونسخها والمؤلفين والمعلومات الأساسية الأخرى، ولكن لن نخزّن معلومات حول العناصر الأخرى التي يمكن أن تعيرها المكتبة ولن نوفر البنية الأساسية اللازمة لدعم مواقع مكتبات متعددة أو ميزات مكتبة كبيرة أخرى. سنوفر في هذه السلسلة من المقالات مقتطفات من الشيفرة البرمجية المناسبة لك لنسخها ولصقها في كل مرحلة، مع وجود شيفرة برمجية أخرى نأمل أن توسّعها بنفسك مع بعض الإرشادات. حاول كتابة جميع مقتطفات الشيفرة البرمجية بدلًا من نسخها ولصقها، إذ سيفيدك ذلك على المدى الطويل إذ ستكون أكثر دراية في المرة القادمة التي تكتب فيها شيئًا مشابهًا. إذا واجهتك مشكلة، فيمكنك العثور على النسخة المُطوَّرة بالكامل من موقع الويب على GitHub. ملاحظة: توجد قائمة بالنسخ المحددة من Node و Express والوحدات الأخرى التي جرى اختبار هذه السلسلة من المقالات على أساسها في الملف package.json الخاص بالمشروع. حان الوقت لبدء إنشاء مشروع هيكلي بعد أن تعرّفت على موقع المكتبة المحلية LocalLibrary وما ستتعلمه في هذه السلسلة من المقالات. إنشاء موقع ويب هيكلي سنوضح الآن كيفية إنشاء موقع ويب هيكلي باستخدام أداة مولّد تطبيقات Express، والتي يمكنك بعد ذلك ملؤها بالوجهات والعروض أو القوالب واستدعاءات قاعدة البيانات الخاصة بالموقع، إذ سنستخدم هذه الأداة لإنشاء إطار عمل لموقع المكتبة المحلية الذي سنضيف إليه لاحقًا جميع الشيفرات البرمجية الأخرى التي يحتاجها الموقع. تُعَد هذه العملية بسيطة جدًا، وتتطلب فقط استدعاء المولّد في سطر الأوامر باسم مشروع جديد، وتحديد محرّك قوالب الموقع ومولد شيفرة CSS اختياريًا. توضّح الأقسام التالية كيفية استدعاء مولّد التطبيق، وتوفّر شرحًا بسيطًا حول خيارات العرض أو شيفرة CSS المختلفة، وسنشرح كيفية بناء موقع الويب الهيكلي، وسنوضح كيفية تشغيل موقع الويب للتحقق من أنه يعمل بنجاح. ملاحظة: لا يُعَد مولّد تطبيقات Express المولّد الوحيد لتطبيقات Express، ولا يُعَد المشروع المُولَّد الطريقة الوحيدة القابلة للتطبيق لبناء هيكيلية ملفاتك ومجلداتك، ولكن يحتوي الموقع المُولَّد على بنية معيارية يسهل توسيعها وفهمها. اطلع على مثال Hello world في توثيق Express لمزيد من المعلومات حول الحد الأدنى من تطبيقات Express. يصرّح مولّد تطبيقات Express عن معظم المتغيرات باستخدام var، ولكننا غيّرنا معظمها إلى const، وعددًا قليلًا منها إلى let، لأننا نريد عرض ممارسات جافا سكريبت Javascript الحديثة. يستخدم هذا المقال نسخة Express والاعتماديات الأخرى المُعرَّفة في الملف package.json التي أنشأها مولّد تطبيقات Express، إذ ليست بالضرورة أن تكون النسخة الأحدث، وقد ترغب في تحديثها عند نشر تطبيق حقيقي في بيئة الإنتاج. استخدام مولد التطبيق لا بد أنك ثبّتَ المولّد أثناء إعداد بيئة تطوير Node مع Express، ولكن يمكنك تثبيت أداة المولّد على مستوى الموقع باستخدام مدير حزم npm كما يلي: npm install express-generator -g يحتوي المولّد على عدد من الخيارات التي يمكنك عرضها في سطر الأوامر باستخدام الأمر --help (أو -h? > express --help Usage: express [options] [dir] Options: --version output the version number -e, --ejs add ejs engine support --pug add pug engine support --hbs add handlebars engine support -H, --hogan add hogan.js engine support -v, --view <engine> add view <engine> support (dust|ejs|hbs|hjs|jade|pug|twig|vash) (defaults to jade) --no-view use static html instead of view engine -c, --css <engine> add stylesheet <engine> support (less|stylus|compass|sass) (defaults to plain CSS) --git add .gitignore -f, --force force on non-empty directory -h, --help output usage information يمكنك استخدام الأمر express لإنشاء مشروع ضمن المجلد الحالي باستخدام محرّك العروض Jade وشيفرة CSS، وإذا حدّدتَ اسم مجلد، فسيُنشَأ المشروع في مجلد فرعي بهذا الاسم. express يمكنك أيضًا اختيار محرك عروض أو قوالب باستخدام --view و/أو محرّك توليد شيفرة CSS باستخدام --css. ملاحظة: أهملنا الخيارات الأخرى لاختيار محركات القوالب مثل --hogan و --ejs و --hbs وغيرها، لذا استخدم الخيار --view أو -v محرك العروض الواجب استخدامه يتيح مولّد تطبيقات Express ضبطَ عددٍ من محرّكات العروض أو القوالب الشائعة، بما في ذلك EJS و Hbs و Pug (أو Jade) و Twig و Vash، ويختار Jade افتراضيًا إن لم تحدد خيار العرض، ويمكن أن يدعم Express أيضًا عددًا كبيرًا من لغات القوالب الأخرى. ملاحظة: إذا أردتَ استخدام محرك قوالب لا يدعمه المولّد، فاطلع على استخدام محركات القوالب مع Express في توثيق Express وتوثيق محرك العروض الذي تريد استخدامه. يجب تحديد محرّك القوالب الذي يوفّر جميع الوظائف التي تحتاجها ويسمح لك بالحصول على نتائج في أقرب وقت أو -بعبارة أخرى- باستخدام الطريقة نفسها التي تختار بها أي مكون آخر. إليك بعض الأشياء التي يجب مراعاتها عند الموازنة بين محركات القوالب: الوقت المستغرق للحصول على نتائج: إذا كان فريقك خبيرًا في استخدام لغة القوالب، فيُحتمَل أن يكونوا منتجين بصورة أسرع باستخدام تلك اللغة. إن لم يكن الأمر كذلك، فيجب التفكير في منحنى التعلّم النسبي لمحركات القوالب المُرشَّحة للاستخدام. الشعبية والنشاط: راجع شعبية المحرّك وما إذا كان لديه مجتمع نشط، فمن المهم أن تكون قادرًا على الحصول على الدعم عند ظهور مشاكل طوال مدة عمل الموقع. التنسيق: تستخدم بعض محركات القوالب شيفرة HTML التوصيفية للإشارة إلى المحتوى المُدخَل إلى شيفرة HTML عادية، بينما تبني محركات القوالب الأخرى شيفرة HTML باستخدام صيغة مختلفة (مثل استخدام المسافة البادئة وأسماء الكتل). الأداء أو الوقت اللازم للتقديم rendering time. الميزات: يجب أن تفكر فيما إذا كانت هذه المحركات توفّر الميزات التالية: توريث التخطيط Layout: يسمح بتعريف قالب أساسي ثم يمكنك وراثة أجزاء منه فقط وهي الأجزاء التي تريد أن تكون مختلفة لصفحة معينة، ويُعَد ذلك أسلوبًا أفضل من بناء القوالب من خلال تضمين عدد من المكونات المطلوبة أو بناء قالب من نقطة الصفر في كل مرة. دعم التضمين: يسمح ببناء قوالب من خلال تضمين قوالب أخرى. صيغة مُختصَرة للتحكم في المتغيرات والحلقات. القدرة على ترشيح قيم المتغيرات على مستوى القالب مثل جعل المتغيرات بأحرف كبيرة أو تنسيق قيمة التاريخ. القدرة على توليد تنسيقات الخرج بتنسيقات مختلفة عن تنسيق HTML مثل تنسيق JSON أو XML. دعم العمليات والتدفق غير المتزامن. ميزات من طرف العميل: إذا أمكن استخدام محرّك قوالب من طرف العميل، فسيسمح ذلك بإمكانية إجراء جميع أو معظم التقديم من طرف العميل. سنستخدم في مشروعنا محرك القوالب Pug (وهو محرك Jade الذي أُعيدت تسميته مؤخرًا) الذي يُعَد من أشهر لغات قوالب Express/جافا سكريبت ويدعمه المولّد. محرك تنسيق أو شيفرة CSS الذي يجب استخدامه يتيح مولّد تطبيقات Express إنشاء مشروع مضبوط لاستخدام أكثر محركات تنسيق أو شيفرة CSS شيوعًا مثل LESS و SASS و Compass و Stylus. ملاحظة: تحتوي لغة CSS على بعض القيود التي تصعّب إنجاز بعض المهام، ولكن تتيح محركات تنسيق أو شيفرة CSS استخدام صيغة أقوى لتعريف شيفرة CSS ثم تصريف التعريف في شيفرة CSS قديمة وبسيطة لتستخدمها المتصفحات. يجب أن تستخدم محرك تنسيقات يسمح لفريقك أن يكون أكثر إنتاجية، إذ سنستخدم في مشروعنا شيفرة CSS الصرفة (الافتراضية) لأن متطلبات شيفرة CSS ليست معقدة بما يكفي لاستخدام شيء آخر. قاعدة البيانات الواجب استخدامها لا تستخدم أو تضمّن الشيفرة البرمجية المُولَّدة أيّ قاعدة بيانات، ويمكن لتطبيقات Express استخدام أيّ آلية قاعدة بيانات تدعمها بيئة Node، إذ لا يحدد Express أي سلوك أو متطلبات إضافية محددة لإدارة قاعدة البيانات (سنناقش كيفية التكامل مع قاعدة بيانات في مقال لاحق). إنشاء المشروع سننشئ مشروعًا بالاسم "express-locallibrary-tutorial" باستخدام مكتبة قوالب Pug بدون محرّك شيفرة CSS. أولًا، انتقل إلى المكان الذي تريد إنشاء المشروع فيه ثم شغّل مولّد تطبيقات Express في موجّه الأوامر كما يلي: express express-locallibrary-tutorial --view=pug سينشئ المولّد ملفات المشروع ويسردها كما يلي: create : express-locallibrary-tutorial\ create : express-locallibrary-tutorial\public\ create : express-locallibrary-tutorial\public\javascripts\ create : express-locallibrary-tutorial\public\images\ create : express-locallibrary-tutorial\public\stylesheets\ create : express-locallibrary-tutorial\public\stylesheets\style.css create : express-locallibrary-tutorial\routes\ create : express-locallibrary-tutorial\routes\index.js create : express-locallibrary-tutorial\routes\users.js create : express-locallibrary-tutorial\views\ create : express-locallibrary-tutorial\views\error.pug create : express-locallibrary-tutorial\views\index.pug create : express-locallibrary-tutorial\views\layout.pug create : express-locallibrary-tutorial\app.js create : express-locallibrary-tutorial\package.json create : express-locallibrary-tutorial\bin\ create : express-locallibrary-tutorial\bin\www تغيير المجلد: > cd express-locallibrary-tutorial تثبيت الاعتماديات: > npm install تشغيل التطبيق (باستخدام Bash على نظام لينكس أو ماك) > DEBUG=express-locallibrary-tutorial:* npm start تشغيل التطبيق (باستخدام PowerShell على نظام ويندوز) > $ENV:DEBUG = "express-locallibrary-tutorial:*"; npm start تشغيل التطبيق (باستخدام موجّه الأوامر على نظام ويندوز) > SET DEBUG=express-locallibrary-tutorial:* & npm start يوفر المولّد في نهاية الخرج إرشادات حول كيفية تثبيت الاعتماديات (كما هو مذكور في الملف package.json) وكيفية تشغيل التطبيق على أنظمة تشغيل مختلفة. ملاحظة: تعرّف الملفات التي أنشأها المولّد جميع المتغيرات بوصفها var، لذا افتح جميع الملفات المُولَّدة وغيّر تصريحات var إلى const قبل المتابعة، إذ سنفترض لاحقًا أنك أجريت هذا التعديل. تشغيل الموقع الهيكلي لدينا حاليًا مشروع هيكلي كامل، ولا يجري موقع الويب الكثير من الأمور حتى الآن، لكن الأمر يستحق تشغيله لإثبات أنه يعمل بنجاح. أولًا، ثبّت الاعتماديات باستخدام الأمر install الذي يجلب جميع حزم الاعتماديات المُدرَجة في الملف package.json الخاص بالمشروع: cd express-locallibrary-tutorial npm install ثانيًا، شغّل التطبيق من خلال استخدام الأمر التالي في موجّه أوامر ويندوز CMD: SET DEBUG=express-locallibrary-tutorial:* & npm start واستخدم الأمر التالي في Powershell ضمن نظام ويندوز: ENV:DEBUG = "express-locallibrary-tutorial:*"; npm start ملاحظة: لم نشرح أوامر Powershell بصورة أكبر في هذا المقال، إذ تفترض أوامر ويندوز التي سنستخدمها أنك تستخدم موجّه أوامر ويندوز CMD. استخدم الأمر التالي في نظامي لينكس أو ماك أو إس: DEBUG=express-locallibrary-tutorial:* npm start ثالثًا، حمّل بعد ذلك العنوان http://localhost:3000/ في متصفحك للوصول إلى التطبيق. يجب أن تشاهد صفحة متصفح تشبه ما يلي: أصبح لديك الآن تطبيق Express يعمل بنجاح ويمكن الوصول إليه عبر المنفذ 3000. ملاحظة: يمكنك بدء تشغيل التطبيق فقط باستخدام الأمر npm start. يفعّل تحديد المتغير DEBUG كما هو موضح فيما يلي تسجيل الطرفية أو تنقيح الأخطاء، فمثلًا سترى خرج تنقيح الأخطاء التالي عندما تزور الصفحة السابقة: SET DEBUG=express-locallibrary-tutorial:* & npm start > express-locallibrary-tutorial@0.0.0 start D:\github\mdn\test\exprgen\express-locallibrary-tutorial > node ./bin/www express-locallibrary-tutorial:server Listening on port 3000 +0ms GET / 304 490.296 ms - - GET /stylesheets/style.css 200 4.886 ms - 111 تفعيل إعادة بدء الخادم عند إجراء تغييرات على الملف لا تظهر التغييرات التي تجريها على موقع Express الخاص بك حاليًا حتى تعيد تشغيل الخادم، ويصبح الأمر مزعجًا عندما تضطر إلى إيقاف وإعادة تشغيل الخادم في كل مرة تجري فيها تغييرًا، لذا فإن الأمر يستحق قضاء بعض الوقت لأتمتة إعادة بدء الخادم عند الحاجة. يمكنك استخدام أداة مناسبة لهذا الغرض وهي nodemon، وتُثبَّت عادةً على المستوى العام بوصفها أداةً، ولكننا سنثبّتها ونستخدمها في مثالنا محليًا بوصفها اعتماديةً للمطور، بحيث يحصل عليها أيّ مطور يعمل على المشروع تلقائيًا عند تثبيت التطبيق. استخدم الأمر التالي في المجلد الجذر للمشروع للهيكلي: npm install --save-dev nodemon إذا أردت تثبيت الأداة nodemon على المستوى العام في جهازك، وليس فقط على الملف package.json الخاص بمشروعك، فاستخدم الأمر التالي: npm install -g nodemon إذا فتحتَ الملف package.json الخاص بمشروعك، فسترى الآن قسمًا جديدًا مع الاعتمادية التالية: "devDependencies": { "nodemon": "^2.0.4" } بما أننا لم نثبّت الأداة على المستوى العام، فلا يمكننا تشغيلها من سطر الأوامر (إلا في حالة إضافتها إلى المسار)، ولكن يمكننا استدعاؤها من سكريبت npm، لأن مدير الحزم npm على علم بكل شيء عن الحزم المُثبَّتة. ابحث عن القسم scripts في الملف package.json الذي سيحتوي في البداية على سطر واحد يبدأ بكلمة "start"، لذا حدّثه بوضع فاصلة في نهاية هذا السطر، وضِف سطري "devstart" و "serverstart". سيبدو قسم السكربتات scripts كما يلي في نظامي لينكس وماك أو إس: "scripts": { "start": "node ./bin/www", "devstart": "nodemon ./bin/www", "serverstart": "DEBUG=express-locallibrary-tutorial:* npm run devstart" }, واستخدم الأمر التالي في نظام ويندوز: "serverstart": "SET DEBUG=express-locallibrary-tutorial:* & npm run devstart" يمكننا الآن بدء الخادم بالطريقة السابقة نفسها تمامًا ولكن باستخدام الأمر devstart. ملاحظة: إذا عدّلتَ الآن أيّ ملف في المشروع، فسيُعاد بدء الخادم، أو يمكنك إعادة بدء الخادم بكتابة rs في موجّه الأوامر في أيّ وقت، وستظل بحاجة إلى إعادة تحميل المتصفح لتحديث الصفحة. يجب الآن استدعاء الأمر "npm run <scriptname>" بدلًا من مجرد استخدام npm start، لأن "start" هو أمر npm يُربَط بالسكريبت. كان بإمكاننا استبدال الأمر في السكربت start ولكننا نريد فقط استخدام الأداة nodemon أثناء عملية التطوير، لذلك من المنطقي إنشاء أمر سكربت جديد. يُعَد الأمر serverstart المُضاف إلى قسم السكريبتات scripts في الملف package.json مثالًا جيدًا، إذ يعني استخدام هذا الأسلوب أنك لم تعد مضطرًا إلى كتابة أمر طويل لبدء الخادم. لاحظ أن الأمر المحدد المضاف إلى السكريبت يعمل على نظام ماك أو لينكس فقط. المشروع الناتج لنطّلع الآن على المشروع الذي أنشأناه. بنية المجلد يمتلك المشروع المُولَّد الآن بنية الملفات التالية (الملفات هي العناصر التي ليس أولها "/") بعد تثبيت الاعتماديات. يعرّف الملف package.json اعتماديات التطبيق ومعلومات أخرى، ويعرّف سكريبتًا لبدء التشغيل يستدعي نقطة الدخول إلى التطبيق التي هي ملف جافا سكربت "/bin/www"، ممّا يؤدي إلى إعداد معالجة أخطاء التطبيق ثم تحميل الملف app.js لينفّذ بقية العمل. تُخزَّن وجهات Routes التطبيق في وحدات منفصلة ضمن المجلد "/routes"، وتُخزَّن القوالب في المجلد "/views". express-locallibrary-tutorial app.js /bin www package.json package-lock.json /node_modules [about 6700 subdirectories and files] /public /images /javascripts /stylesheets style.css /routes index.js users.js /views error.pug index.pug layout.pug سنشرح في الأقسام التالية هذه الملفات بمزيد من التفصيل. الملف package.json يعرّف الملف package.json اعتماديات التطبيق ومعلومات أخرى كما يلي: { "name": "express-locallibrary-tutorial", "version": "0.0.0", "private": true, "scripts": { "start": "node ./bin/www" }, "dependencies": { "cookie-parser": "~1.4.4", "debug": "~2.6.9", "express": "~4.16.1", "http-errors": "~1.6.3", "morgan": "~1.9.1", "pug": "2.0.0-beta11" }, "devDependencies": { "nodemon": "^2.0.4" } } تتضمن الاعتماديات الحزمة express وحزمة محرّك العروض المختار pug، وتوجد الحزم التالية المفيدة في العديد من تطبيقات الويب: cookie-parser: تُستخدَم لتحليل ترويسة ملف تعريف الارتباط Cookie وملء req.cookies الذي يوفّر تابعًا ملائمًا للوصول إلى معلومات ملفات تعريف الارتباط. debug: أداة مساعدة صغيرة لتنقيح أخطاء Node المُصمَّمة على غرار تقنية تنقيح أخطاء Node الأساسية. morgan: برمجية وسيطة لتسجيل طلبات HTTP خاصة ببيئة Node. http-errors: تنشئ أخطاء HTTP عند الحاجة (لمعالجة أخطاء Express). يعرّف قسم السكريبتات scripts أولًا السكريبت "start"، وهو السكريبت الذي نستدعيه عند استدعاء الأمر npm start لبدء الخادم، إذ أضاف مولّد تطبيقات Express هذا السكربت. يمكنك أن ترى من تعريف السكريبت أنه يمثّل بداية ملف جافا سكريبت "./bin/www" باستخدام node. "scripts": { "start": "node ./bin/www", "devstart": "nodemon ./bin/www", "serverstart": "DEBUG=express-locallibrary-tutorial:* npm run devstart" }, يمكن استخدام السكريبتات devstart و serverstart لبدء الملف "./bin/www" نفسه باستخدام nodemon بدلًا من node. الملف www يُعَد الملف "/bin/www" نقطة الدخول إلى التطبيق، وأول شيء يفعله هو استدعاء الدالة require() لطلب نقطة الدخول الحقيقية إلى التطبيق (أي الملف app.js في جذر المشروع)، والذي يضبط ويعيد كائن تطبيق express(). #!/usr/bin/env node /** * اعتماديات الوحدة */ const app = require("../app"); ملاحظة: تُعَد الدالة require() دالة Node عامة تُستخدَم لاستيراد الوحدات إلى الملف الحالي. نحدد في مثالنا وحدة app.js باستخدام مسار نسبي ونحذف امتداد الملف الاختياري (.js). تُعِد بقية الشيفرة البرمجية في هذا الملف خادم HTTP الخاص ببيئة Node مع إعداد app على منفذ معين (مُحدَّد في متغير بيئة أو بالقيمة 3000 عند عدم تحديد هذا المتغير)، وتبدأ بالاستماع والإبلاغ عن أخطاء واتصالات الخادم. لا تحتاج حاليًا إلى معرفة أي شيء آخر عن الشيفرة البرمجية، فكل شيء في هذا الملف هو شيفرة متداولة، ولكن لا تتردد في الاطلاع عليها إن أردتَ ذلك. الملف app.js ينشئ هذا الملف كائن تطبيق express (يسمى app اصطلاحيًا)، ويُعِد التطبيق باستخدام إعدادات وبرمجيات وسيطة متنوعة مختلفة، ثم يصدّر التطبيق من الوحدة. توضّح الشيفرة البرمجية التالية أجزاء الملف التي تنشئ وتصدر كائن التطبيق: const express = require("express"); const app = express(); // … module.exports = app; وهو الكائن module.exports في ملف نقطة الدخول www كما ذكرنا سابقًا، حيث يُزوَّد المستدعِي بهذا الكائن عند استيراد هذا الملف. لنتعرّف على الملف app.js بالتفصيل. أولًا، نستورد بعض مكتبات Node المفيدة إلى الملف باستخدام الدالة require() بما في ذلك http-errors و express و morgan و cookie-parser التي نزّلناها مسبقًا لتطبيقنا باستخدام مدير حزم npm، ومكتبة path وهي مكتبة Node أساسية لتحليل مسارات الملفات والمجلدات. const createError = require("http-errors"); const express = require("express"); const path = require("path"); const cookieParser = require("cookie-parser"); const logger = require("morgan"); نطلب بعد ذلك باستخدام الدالة require() وحدات من مجلد الوجهات "routes"، إذ تحتوي هذه الوحدات أو الملفات على شيفرة برمجية للتعامل مع مجموعات معينة من "الوجهات" (مسارات URL). إذا أردنا توسيع التطبيق الهيكلي لسرد جميع الكتب في المكتبة مثلًا، فسنضيف ملفًا جديدًا للتعامل مع الوجهات المتعلقة بالكتب. const indexRouter = require("./routes/index"); const usersRouter = require("./routes/users"); ملاحظة: استوردنا الوحدة، ولم نستخدم وجهاتها حتى الآن، ولكن سيحدث ذلك لاحقًا. ننشئ بعد ذلك الكائن app باستخدام وحدة express المستوردة، ثم نستخدمها لإعداد محرك العروض (أو القوالب)، إذ أن هناك قسمان لإعداد المحرك هما: ضبط قيمة "views" لتحديد المجلد الذي ستُخزَّن القوالب فيه، وهو في حالتنا المجلد الفرعي "/views"، ثم ضبط قيمة "view engine" لتحديد مكتبة القوالب، وهي في حالتنا "pug". const app = express(); // إعداد محرّك العروض app.set("views", path.join(__dirname, "views")); app.set("view engine", "pug"); تستدعي المجموعة التالية من الدوال app.use() لإضافة مكتبات البرمجيات الوسيطة التي استوردناها سابقًا إلى سلسلة معالجة الطلبات، فمثلًا هناك حاجة إلى استخدام express.json() و express.urlencoded() لملء متن الطلب req.body بحقول الاستمارة. نستخدم أيضًا بعد هذه المكتبات البرمجيةَ الوسيطة express.static التي تجعل إطار عمل Express يخدّم جميع الملفات الثابتة في المجلد "/public" ضمن جذر المشروع. app.use(logger("dev")); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(cookieParser()); app.use(express.static(path.join(__dirname, "public"))); ضبطنا جميع البرمجيات الوسيطة الأخرى، وسنضيف الآن شيفرة معالجة الوجهات (التي استوردناها سابقًا) إلى سلسلة معالجة الطلبات، إذ ستعرّف الشيفرة المستوردة وجهات معينة لأجزاء مختلفة من الموقع: app.use("/", indexRouter); app.use("/users", usersRouter); ملاحظة: تُعامَل المسارات المحددة السابقة ('/' و '/users') بوصفها بادئةً للوِجهات المُعرَّفة في الملفات المستوردة، فمثلًا إذا عرّفت وحدةُ المستخدمين users المُستورَدة وجهة المسار /profile، فيمكنك الوصول إلى هذا الوجهة من خلال /users/profile (سنتحدث أكثر عن الوِجهات في مقال لاحق). تضيف البرمجيات الوسيطة الأخيرة في الملف توابع معالجة للأخطاء واستجابات HTTP 404: // التقاط 404 وتوجيه معالج الخطأ app.use((req, res, next) => { next(createError(404)); }); // معالج الخطأ app.use((err, req, res, next) => { // ضبط locals، وتوفير خطأ في عملية التطوير فقط res.locals.message = err.message; res.locals.error = req.app.get("env") === "development" ? err : {}; // تقديم صفحة الخطأ res.status(err.status || 500); res.render("error"); }); ضُبِط كائن تطبيق Express (أو app) كاملًا الآن، والخطوة الأخيرة هي إضافته إلى module.exports، مما يسمح بأن يستورده الملف /bin/www. module.exports = app; الوجهات اطّلع على ملف الوجهات "/routes/users.js" التالي، حيث تشترك ملفات الوجهات ببنية مماثلة، لذلك لا حاجة لعرض الملف index.js أيضًا. يحمّل هذا الملف أولًا الوحدة express ويستخدمها للحصول على الكائن express.Router، ثم يحدد وجهةً لهذا الكائن ويصدِّر الموجّه من الوحدة، مما يسمح باستيراد الملف في app.js. const express = require("express"); const router = express.Router(); /* الحصول على قائمة المستخدمين */ router.get("/", (req, res, next) => { res.send("respond with a resource"); }); module.exports = router; تعرّف الوجهة دالة رد نداء callback ستُستدعَى عند اكتشاف طلب HTTP من النوع GET بالنمط الصحيح، إذ يُعَد نمط المطابقة هو الوجهة المُحدَّدة عند استيراد الوحدة ('/users')، إضافةً إلى كل ما جرى تعريفه في هذا الملف ('/')، وبالتالي ستُستخدَم هذه الوجهة عند تلقي عنوان URL للمستخدمين /users/. ملاحظة: جرب ذلك من خلال تشغيل الخادم باستخدام node وزيارة عنوان URL في متصفحك http://localhost:3000/users/، ويجب أن ترى الرسالة "respond with a resource". تمتلك دالة رد النداء الوسيط الثالث next، وبالتالي هي دالة وسيطة بدلًا من كونها دالة رد نداء بسيطة للوجهة. لا تستخدم الشيفرة البرمجية الوسيط next حاليًا، ولكنه مفيدٌ مستقبلًا إذا أردتَ إضافة معالجات وجهات متعددة إلى مسار الوجهة '/'. العروض أو القوالب تُخزَّن العروض (أو القوالب) في المجلد "/views" (كما هو مُحدَّد في الملف app.js) وتُمنَح امتداد الملف .pug، ويُستخدَم التابع Response.render() لتصيير قالب محدد مع قيم المتغيرات المُسمَّاة الممرَّرة في كائن، ثم إرسال النتيجة بوصفها استجابة. يمكنك أن ترى في الشيفرة التالية من الملف "/routes/index.js" كيف تصيّر هذه الوجهة استجابةً باستخدام القالب "index" الذي يمرّر متغير القالب "title". /* الحصول على الصفحة الرئيسية */ router.get("/", (req, res, next) => { res.render("index", { title: "Express" }); }); القالب المقابل للوجهة السابقة مذكور فيما يلي (index.pug). سنتحدث أكثر عن الصيغة لاحقًا، ولكن كل ما تحتاج إلى معرفته الآن هو أن المتغير title (مع القيمة "Express") يُدرَج في المكان المُحدَّد في القالب. extends layout block content h1= title p Welcome to #{title} تحدى نفسك أنشئ وجهةً جديدة في الملف "/routes/users.js" تعرض النص "You're so cool" على العنوان /users/cool/، واختبرها من خلال تشغيل الخادم وزيارة العنوان http://localhost:3000/users/cool/ في متصفحك. الخلاصة أنشأتَ مشروعًا هيكليًا لموقع المكتبة المحلية وتحققتَ من أنه يعمل باستخدام node، وفهمتَ كيفية تنظيم بنية المشروع، لذلك أصبح لديك فكرةً جيدة عن مكان إجراء تغييرات لإضافة الوجهات والعروض للمكتبة المحلية. سنبدأ في المقال التالي بتعديل الموقع الهيكلي ليعمل بوصفه موقع ويب للمكتبة. ترجمة -وبتصرُّف- للمقالين Express Tutorial: The Local Library website و Express Tutorial Part 2: Creating a skeleton website. اقرأ أيضًا المقال السابق إعداد بيئة تطوير Node مع Express دليل استخدام Node.js وإطار العمل Express للمبتدئين تطبيق عملي لتعلم إطار عمل جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية
-
عرفتَ من خلال المقال السابق الغرض من إطار عمل Express، وسنوضّح كيفية إعداد واختبار بيئة تطوير Node/Express على نظام ويندوز أو لينكس (أوبنتو) أو ماك أو إس macOS، إذ يوفر هذا المقال ما تحتاجه لبدء تطوير تطبيقات Express بالنسبة لأيّ نظام تشغيل من هذه الأنظمة. المتطلبات الأساسية: معرفة باستخدام سطر الأوامر أو الطرفية Terminal وكيفية تثبيت الحزم البرمجية على نظام تشغيل حاسوب التطوير خاصتك. الهدف: إعداد بيئة تطوير لإطار عمل Express على حاسوبك. نظرة عامة على بيئة تطوير Express تسهّل كلٌّ من بيئة Node وإطار عمل Express إعداد حاسوبك للبدء في تطوير تطبيقات الويب، إذ سنقدم في هذا القسم نظرةً عامة على الأدوات المطلوبة، وسنشرح بعض أبسط الطرق لتثبيت Node و Express على أوبنتو وماك أو اس macOS وويندوز، وسنوضح كيفية اختبار التثبيت. ما هي بيئة تطوير Express؟ تتضمن بيئة تطوير Express تثبيت بيئة Node.js ومدير الحزم npm ومولّد تطبيقات Express (اختياريًا) على حاسوبك المحلي. تُثبَّت بيئة Node ومدير الحزم npm مع بعضها من الحزم الثنائية المُعَدّة أو المثبِّتات Installers أو مديري حزم نظام التشغيل أو من المصدر (كما هو موضح في الأقسام التالية). يثبِّت مدير حزم npm إطار عمل Express بوصفه اعتمادية Dependency على تطبيقات ويب Express مع المكتبات الأخرى، مثل محركات القوالب ومشغّلات قواعد البيانات وبرمجيات الاستيثاق الوسيطة والبرمجيات الوسيطة لتخديم الملفات الثابتة وغير ذلك. يمكن أيضًا استخدام مدير حزم npm لتثبيت موّلد تطبيقات Express (على المستوى العام)، وهو أداة مفيدة لإنشاء تطبيقات Express الهيكلية التي تتبع نمط MVC. يُعَد مولّد التطبيقات اختياريًا لأنك لست بحاجة إلى استخدامه لإنشاء تطبيقات تستخدم إطار عمل Express أو بناء تطبيقات Express التي لها التخطيط المعماري أو الاعتماديات نفسها، ولكننا سنستخدمه لأنه يجعل البدء أسهل ويطوّر بنية التطبيق المعيارية. ملاحظة: لا تتضمن بيئة التطوير خادم ويب منفصل للتطوير على عكس بعض أطر عمل الويب الأخرى، إذ ينشئ ويشغّل تطبيق الويب خادمَ الويب الخاص به في Node/Express. هناك أدوات طرفية أخرى تشكل جزءًا من بيئة تطوير نموذجية بما في ذلك محررات النصوص أو بيئات التطوير المتكاملة IDE لتعديل الشيفرة البرمجية وأدوات إدارة التحكم بالشيفرة المصدرية مثل Git لإدارة النسخ المختلفة من شيفرتك البرمجية بأمان. نفترض أنك ثبَّتَ هذه الأنواع من الأدوات وبالأخص محرر النصوص. أنظمة التشغيل المدعومة يمكن تشغيل بيئة Node على أنظمة تشغيل ويندوز وماك أو اس macOS والعديد من إصدارات لينكس ودوكر وغير ذلك، وتوجد قائمة كاملة في صفحة تنزيلات Node.js. يجب أن يتمتع أي حاسوب شخصي تقريبًا بالأداء اللازم لتشغيل بيئة Node أثناء التطوير. يُشغَّل إطار عمل Express في بيئة Node، وبالتالي يمكن تشغيله على أي منصة تشغّل بيئة Node. سنقدم في هذا المقال تعليمات الإعداد لأنظمة ويندوز وماك macOS ولينكس (أوبنتو). نسخة Node/Express التي يجب استخدامها هناك العديد من إصدارات Node، إذ تحتوي الإصدارات الأحدث على إصلاحات للأخطاء ودعم للنسخ الأحدث من معايير ECMAScript (جافا سكريبت) وتحسينات على واجهة برمجة تطبيقات Node. يجب أن تستخدم الإصدار الأحدث من الإصدار المدعوم على المدى الطويل Long-term Supported -أو LTS اختصارًا، إذ سيكون هذا الإصدار أكثر استقرارًا من الإصدار الحالي مع وجود ميزات حديثة نسبيًا (ولا تزال صيانته موجودةً بنشاط)، ولكن يجب أن تستخدم الإصدار الحالي إذا كنت بحاجة إلى ميزة غير موجودة في نسخة LTS. يجب عليك دائمًا استخدام أحدث نسخة بالنسبة إلى إطار عمل Express. قواعد البيانات والاعتماديات الأخرى تُعَد الاعتماديات الأخرى -مثل مشغّلات قاعدة البيانات ومحرّكات القوالب ومحركات الاستيثاق وغير ذلك- جزءًا من التطبيق، وتُستورد إلى بيئة التطبيق باستخدام مدير الحزم npm (سنناقشها لاحقًا في مقالات خاصة بالتطبيق). تثبيت بيئة Node يجب أن تثبّتَ Nodejs ومدير حزم Node -أو npm اختصارًا- على نظام تشغيلك لاستخدام إطار عمل Express، ولتسهيل ذلك، سنثبّت أولًا مدير نسخ Node، ثم سنستخدمه لتثبيت أحدث النسخ المدعومة على المدى الطويل LTS من Node و npm. ملاحظة: يمكنك أيضًا تثبيت nodejs و npm باستخدام المثبِّتات المتوفرة على موقع Nodejs (اضغط على الزر لتنزيل إصدار LTS المُوصَى به لمعظم المستخدمين)، أو يمكنك التثبيت باستخدام مدير حزم نظام تشغيلك. نوصي جدًا باستخدام مدير نسخ Node لأنه يسهّل التثبيت والترقية والتبديل بين نسخ Node و npm. نظام ويندوز يوجد عدد من مديري نسخ Node لنظام تشغيل ويندوز، وسنستخدم في مثالنا nvm-windows الذي يحظى باحترام كبير بين مطوري Node. ثبّت أحدث نسخة باستخدام المثبِّت الذي تختاره من صفحة nvm-windows/releases، ثم افتح بعد تثبيت nvm-windows موجه الأوامر (أو PowerShell) وأدخِل الأمر التالي لتنزيل أحدث نسخة LTS من nodejs و npm: nvm install lts لنفترض أن نسخة LTS من nodejs هي 18.15.0، وبالتالي يمكنك ضبطها بوصفها النسخة الحالية للاستخدام باستخدام الأمر التالي: nvm use 18.15.0 ملاحظة: إذا تلقيت تحذيرات رفض الوصول "Access Denied"، فيجب تشغيل هذا الأمر في موجه الأوامر باستخدام أذونات المدير. استخدم الأمر nvm --help لمعرفة خيارات سطر الأوامر الأخرى مثل سرد جميع نسخ Node المتاحة وجميع نسخ NVM التي جرى تنزيلها. نظام تشغيل أوبنتو وماك أو إس macOS يوجد عدد من مديري نسخ Node لنظامي أوبنتو وماك أو إس، إذ يُعَد مدير نسخ nvm واحدًا من أكثرها شيوعًا، وهو النسخة الأصلية التي يعتمد عليها nvm-windows (اطلع على إرشادات الطرفية لتثبيت أحدث نسخة من nvm). ثبّت nvm، ثم افتح الطرفية وأدخِل الأمر التالي لتنزيل أحدث نسخة LTS من nodejs و npm: nvm install --lts لنفترض أن نسخة LTS من nodejs هي 18.15.0، إذ يعرض الأمر nvm list مجموعة النسخ الذي جرى تنزيلها والنسخة الحالية. يمكنك ضبط نسخة معينة بوصفها النسخة الحالية باستخدام الأمر التالي (أمر nvm-windows نفسه): nvm use 18.15.0 استخدم الأمر nvm --help لمعرفة خيارات سطر الأوامر الأخرى التي تكون مشابهةً أو مماثلةً لتلك التي يوفّرها nvm-windows. اختبار تثبيت Nodejs و npm يمكنك اختبار التثبيت بعد ضبط nvm لاستخدام نسخة Node معينة، إذ توجد طريقة جيدة لذلك وهي استخدام أمر النسخة "version" في موجه الأوامر او الطرفية والتحقق من إعادة سلسلة النسخة المتوقعة كما يلي: > node -v v18.15.0 يجب أيضًا تثبيت مدير حزم npm، ويمكن اختباره بالطريقة نفسها: > npm -v 9.3.1 لننشئ خادم Node نقي وبسيط يطبع الجملة "Hello World" في المتصفح عندما تزور عنوان URL الصحيح في متصفحك. أولًا، انسخ النص التالي إلى ملف بالاسم hellonode.js، إذ تستخدم الشيفرة التالية ميزات Node النقية بدون ميزات Express: // حمّل وحدة HTTP const http = require("http"); const hostname = "127.0.0.1"; const port = 3000; // أنشئ خادم HTTP واستمع إلى المنفذ 3000 للطلبات const server = http.createServer((req, res) => { // اضبط استجابة ترويسة HTTP بحالة HTTP ونوع المحتوى res.statusCode = 200; res.setHeader("Content-Type", "text/plain"); res.end("Hello World\n"); }); // الاستماع إلى الطلبات على المنفذ 3000، وتسجيل المنفذ الذي استمعنا إليه بوصفه دالة رد نداء server.listen(port, hostname, () => { console.log(`Server running at http://${hostname}:${port}/`); }); تستورد الشيفرة البرمجية وحدة http وتستخدمها لإنشاء خادم createServer() يستمع إلى طلبات HTTP على المنفذ 3000، ثم يطبع السكريبت رسالةً إلى الطرفية حول عنوان URL للمتصفح الذي يمكنك استخدامه لاختبار الخادم. تأخذ الدالة createServer() وسيطًا هو دالة رد النداء التي ستُستدعَى عند تلقي طلب HTTP، وتعيد هذه الدالة استجابة مع رمز حالة HTTP هو 200 ("OK") والنص "Hello World". ملاحظة: لا تقلق إن لم تفهم بالضبط ما تفعله هذه الشيفرة البرمجية حتى الآن، إذ سنشرحها بمزيد من التفصيل بمجرد أن نبدأ باستخدام Express. ثانيًا، ابدأ الخادم من خلال الانتقال إلى مجلد الملف hellonode.js نفسه في موجّه الأوامر، ثم استدعاء node مع اسم السكريبت كما يلي: >node hellonode.js Server running at http://127.0.0.1:3000/ ثالثًا، انتقل إلى العنوان http://127.0.0.1:3000. إذا عمل كل شيء بنجاح، فيجب أن يعرض المتصفح السلسلة النصية "Hello World". استخدام مدير الحزم npm يُعَد مدير حزم npm -إضافةً إلى بيئة Node- الأداة الأكثر أهمية للعمل مع تطبيقات Node، ويُستخدَم لجلب أيّ حزم (مكتبات جافا سكريبت) يحتاجها التطبيق للتطوير و/أو الاختبار و/أو الإنتاج، ويمكن أيضًا استخدامه لتشغيل الاختبارات والأدوات المستخدمة في عملية التطوير. ملاحظة: يُعَد إطار Express من منظور بيئة Node مجرد حزمة أخرى يجب تثبيتها باستخدام مدير الحزم npm ثم تطلبها في شيفرتك البرمجية. يمكنك استخدام مدير حزم npm يدويًا لجلب الحزم المطلوبة بصورة منفصلة، ويمكن إدارة الاعتماديات باستخدام ملف تعريف نصي بالاسم package.json، إذ يسرد هذا الملف جميع الاعتماديات الخاصة بحزمة جافا سكريبت معينة، بما في ذلك اسم الحزمة ونسختها ووصفها والملف الأولي المطلوب تنفيذه واعتماديات الإنتاج واعتماديات التطوير ونسخ Node التي يمكن أن العمل معها وغير ذلك. يجب أن يحتوي ملف package.json على كل شيء يحتاجه مدير حزم npm لجلب التطبيق وتشغيله، فإذا كنت بصدد كتابة مكتبة قابلة لإعادة الاستخدام، فيمكنك استخدام هذا التعريف لرفع حزمتك إلى مستودع npm وإتاحتها لمستخدمين آخرين. إضافة الاعتماديات توضح الخطوات التالية كيفية استخدام مدير حزم npm لتنزيل حزمة وحفظها في اعتماديات المشروع ثم طلبها في تطبيق Node. ملاحظة: سنعرض الإرشادات الخاصة بجلب حزمة Express وتثبيتها، وسنبيّن لاحقًا كيفية تحديد هذه الحزمة وغيرها باستخدام مولّد تطبيقات Express. يوفّر هذا القسم فهم كيفية عمل مدير حزم npm وما ينشئه مولّد التطبيق. أولًا، أنشئ مجلدًا لتطبيقك الجديد وانتقل إليه كما يلي: mkdir myapp cd myapp ثانيًا، استخدم أمر npm الذي هو init لإنشاء ملف package.json لتطبيقك. يطالبك هذا الأمر بعدد من الأشياء، بما في ذلك اسم ونسخة تطبيقك واسم ملف نقطة الدخول الأولي (وهو index.js افتراضيًا)، وما عليك حاليًا سوى قبول الإعدادات الافتراضية: npm init إذا عرضتَ ملف package.json (باستخدام الأمر cat package.json)، فسترى الإعدادات الافتراضية التي قبلتها وينتهي الملف بالترخيص License كما يلي: { "name": "myapp", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC" } ثالثًا، ثبّت Express في المجلد myapp واحفظه في قائمة الاعتماديات لملف package.json: npm install express سيظهر الآن قسم الاعتماديات "dependencies" الخاصة بملف package.json في نهايته وسيتضمن Express كما يلي: { "name": "myapp", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "dependencies": { "express": "^4.17.1" } } رابعًا، يمكن استخدام مكتبة Express من خلال استدعاء الدالة require() في الملف index.js لتضمينها في تطبيقك. أنشئ هذا الملف الآن في جذر مجلد التطبيق "myapp"، وضع فيه المحتويات التالية: const express = require("express"); const app = express(); const port = 3000; app.get("/", (req, res) => { res.send("Hello World!"); }); app.listen(port, () => { console.log(`Example app listening on port ${port}!`); }); تُظهِر هذه الشيفرة البرمجية أصغر تطبيق Express وهو "مرحبًا للعالم"، إذ تستورد هذه الشيفرة الوحدةَ "express" باستخدام الدالة require() وتستخدمها لإنشاء خادم (app) يستمع إلى طلبات HTTP على المنفذ 3000 ويطبع رسالة إلى الطرفية تشرح عنوان URL للمتصفح الذي يمكنك استخدامه لاختبار الخادم. تستجيب الدالة app.get() فقط لطلبات HTTP من النوع GET بمسار URL المحدد ('/') من خلال استدعاء دالة لإرسال رسالة Hello World!"" في مثالنا. ملاحظة: تسمح علامات الاقتباس المائلة في !{Example app listening on port ${port بإدخال قيمة $port في السلسلة النصية. خامسًا، يمكنك بدء تشغيل الخادم من خلال استدعاء Node مع السكربت في موجه الأوامر كما يلي: >node index.js سترى الخرج التالي على الطرفية: Example app listening on port 3000 سادسًا، انتقل إلى عنوان http://localhost:3000/، فإذا عمل كل شيء على ما يرام، فيجب أن يعرض المتصفح السلسلة النصية "Hello World!". تطوير الاعتماديات إذا اُستخدِمت الاعتمادية أثناء مرحلة التطوير فقط، فيجب حفظها بوصفها "اعتمادية تطوير" حتى لا يضطر مستخدمو الحزمة إلى تثبيتها في مرحلة الإنتاج، فمثلًا يمكنك استدعاء أمر npm التالي لاستخدام أداة تنقيح صياغة شيفرة جافا سكريبت الشهيرة ESLint: npm install eslint --save-dev ويمكنك الاطلاع على مقال تقييم صلاحية بيانات التطبيق واستخدام المدقق ESLint على أكاديمية حسوب لمزيد من المعلومات عن المدقق ESLint. سيُضاف بعد ذلك الإدخال التالي إلى ملف package.json الخاص بتطبيقك: "devDependencies": { "eslint": "^7.10.0" } ملاحظة: تُعَد منقحات الصياغة Linters أدوات تجري تحليلًا ثابتًا على البرمجيات للتعرف على الالتزام أو عدم الالتزام بمجموعة معينة من أفضل ممارسات كتابة الشيفرة البرمجية والإبلاغ عنها. تشغيل المهام يمكنك -إضافةً إلى تعريف وجلب الاعتماديات- تعريف السكريبتات المُسمَّاة في ملفات package.json واستدعاء مدير حزم npm لتنفيذها باستخدام الأمر run-script، إذ يُستخدَم هذا الأسلوب لأتمتة تشغيل الاختبارات وأجزاء من عملية التطوير أو إنشاء سلسلة أدوات، مثل أدوات التشغيل لتقليل شيفرة جافا سكريبت وتقليص الصور وتنقيح صياغة أو تحليل شيفرتك البرمجية وغير ذلك. ملاحظة: يمكن أيضًا استخدام مشغِّلات المهام مثل Gulp و Grunt لتشغيل الاختبارات والأدوات الخارجية الأخرى. يمكن أن نضيف مثلًا كتلة السكريبت التالية إلى ملف package.json (بافتراض أن شيفرة تطبيقنا المصدرية موجودة في المجلد /src/js) لتعريف سكريبت لتشغيل اعتمادية تطوير eslint التي حددناها في القسم السابق: "scripts": { // … "lint": "eslint src/js" // … } يُعَد eslint src/js أمرًا يمكننا إدخاله في سطر الأوامر أو في الطرفية لتشغيل eslint على ملفات جافا سكريبت الموجودة في المجلد src/js ضمن مجلد تطبيقنا. يوفّر تضمين ما ورد سابقًا ضمن الملف package.json الخاص بتطبيقنا اختصارًا لهذا الأمر وهو lint. يمكننا بعد ذلك تشغيل eslint باستخدام npm من خلال استدعاء ما يلي: npm run-script lint # أو (باستخدام الاسم البديل) npm run lint يمكن ألا يبدو المثال السابق أقصر من الأمر الأصلي، ولكن يمكنك تضمين أوامر أكبر بكثير ضمن سكريبتات npm بما في ذلك سلاسل أوامر متعددة، ويمكنك تحديد سكربت npm واحد يشغّل جميع اختباراتك مرة واحدة. تثبيت مولد تطبيقات Express تولّد أداة مولّد تطبيقات Express Application Generator تطبيق Express هيكلي، إذ يمكنك تثبيت هذا المولّد باستخدام npm كما يلي: npm install express-generator -g ملاحظة: يمكن أن تحتاج إلى أن تبدأ هذا السطر بالكلمة sudo على نظامي أوبنتو أو ماك أو إس، وتثبّت الراية -g الأداة بصورة عامة بحيث يمكنك استدعاؤها من أيّ مكان. يمكن إنشاء تطبيق Express بالاسم "helloworld" مع الإعدادات الافتراضية من خلال الانتقال إلى المكان الذي تريد إنشاءه فيه وتشغيل التطبيق كما يلي: express helloworld ملاحظة: يمكنك بدلًا مما سبق تخطي التثبيت وتشغيل express-generator باستخدام npx إن لم تستخدم نسخةً قديمة من nodejs أقل من 8.2.0، فالأمر التالي له التأثير نفسه للتثبيت ثم تشغيل express-generator ولكنه لا يثبّت الحزمة على نظامك: npx express-generator helloworld يمكنك أيضًا تحديد مكتبة القوالب المُراد استخدامها وعدد من الإعدادات الأخرى، لذا استخدم الأمر help لرؤية جميع هذه الخيارات. express --help سيُنشئ المولّد تطبيق Express الجديد في مجلد فرعي لموقعك الحالي، ويعرض تقدّم البناء على الطرفية، وستعرض الأداة عند الانتهاء الأوامر التي تحتاج إلى إدخالها لتثبيت اعتماديات Node وبدء التطبيق. سيحتوي التطبيق الجديد على ملف package.json في مجلده الجذر، ويمكنك فتحه لمعرفة الاعتماديات التي جرى تثبيتها، بما في ذلك Express ومكتبة القوالب Jade: { "name": "helloworld", "version": "0.0.0", "private": true, "scripts": { "start": "node ./bin/www" }, "dependencies": { "cookie-parser": "~1.4.4", "debug": "~2.6.9", "express": "~4.16.1", "http-errors": "~1.6.3", "jade": "~1.11.0", "morgan": "~1.9.1" } } ثبّت جميع الاعتماديات الخاصة بتطبيق helloworld باستخدام npm كما يلي: cd helloworld npm install شغّل التطبيق (تكون الأوامر مختلفة قليلًا لأنظمة تشغيل ويندوز ولينكس/ماك macOS) كما يلي: # شغّل تطبيق helloworld على ويندوز باستخدام موجّه الأوامر SET DEBUG=helloworld:* & npm start # شغّل تطبيق helloworld على ويندوز باستخدام PowerShell SET DEBUG=helloworld:* | npm start # شغّل تطبيق helloworld على لينكس/ماك macOS DEBUG=helloworld:* npm start ينشئ الأمر DEBUG تسجيلًا مفيدًا ينتج عنه خرج يشبه الخرج التالي: >SET DEBUG=helloworld:* & npm start > helloworld@0.0.0 start D:\GitHub\expresstests\helloworld > node ./bin/www helloworld:server Listening on port 3000 +0ms افتح متصفحك وانتقل إلى العنوان http://localhost:3000/ لمشاهدة صفحة ترحيب Express الافتراضية. سنتحدث أكثر عن التطبيق المُولَّد عندما ننتقل إلى المقال التالي. الخلاصة لديك الآن بيئة تطوير Node تعمل على حاسوبك والتي يمكن استخدامها لإنشاء تطبيقات ويب Express، وتعرّفت على كيفية استخدام npm لاستيراد Express في تطبيق وكيفية إنشاء تطبيقات باستخدام أداة مولّد تطبيقات Express ثم تشغيلها. نبدأ في المقال التالي العمل باستخدام تطبيق عملي لإنشاء تطبيق ويب كامل باستخدام هذه البيئة والأدوات المرتبطة بها. ترجمة -وبتصرُّف- للمقال Setting up a Node development environment. اقرأ أيضًا المقال السابق: مدخل إلى إطار عمل الويب Express وبية Node كتابة أول برنامج في بيئة Node.js وتنفيذه توثيق Node.js
-
يُعَد Express إطار عمل ويب شائع الاستخدام وغير مشتبث برأيه Unopinionated، أي لديه آراء حول الطريقة الصحيحة للتعامل مع أيّ مهمة معينة، ويدعم التطور السريع أو حل المشاكل في مجال معين، ومكتوب بلغة جافا سكريبت Javascript ومُستضاف في بيئة تشغيل Node.js. سنوضح في هذه السلسلة من المقالات المتفرعة عن السلسة الرئيسية تعلم تطوير الويب بعض الفوائد الرئيسية لإطار عمل Express، وكيفية إعداد بيئة التطوير وتطبيق مهام تطوير ونشر الويب الشائعة. المتطلبات الأساسية يجب قبل البدء التعرف على مفهوم برمجة الويب من طرف الخادم وأطر الويب من خلال الاطلاع على مقال مدخل إلى برمجة مواقع الويب من طرف الخادم. يوصَى بشدة بمعرفة مفاهيم البرمجة ولغة جافا سكريبت، ولكنها ليست ضرورية لفهم المفاهيم الأساسية. ملاحظة: اطلع على مقال أساسيات لغة جافا سكريبت في سياق التطوير من طرف العميل، إذ تعَد لغة ومفاهيم جافا سكريبت الأساسية هي نفسها بالنسبة للتطوير من طرف الخادم في بيئة Node.js. تقدم Node.js واجهات برمجة تطبيقات إضافية لدعم الوظائف المفيدة في البيئات التي لا تستخدم المتصفحات (لإنشاء خوادم HTTP والوصول إلى نظام الملفات مثلًا)، ولكنها لا تدعم واجهات برمجة تطبيقات جافا سكريبت للعمل مع المتصفحات ونموذج DOM. ستوفر هذه السلسلة من المقالات بعض المعلومات حول العمل مع بيئة Node.js وإطار عمل Express. تتألف هذه السلسلة من المقالات التالية: مدخل إلى إطار عمل الويب Express: نجيب في هذا المقال على أسئلة "ما هي بيئة Node؟" و"ما هو إطار عمل Express؟"، وسنأخذ فكرةً عامة على ما يجعل إطار عمل Express مميزًا، وسنحدد الميزات الرئيسية وسنعرض بعض البنى الأساسية لتطبيق Express، بالرغم من أنه لن يكون لديك بعد بيئة تطوير لاختبارها في هذه المرحلة. إعداد بيئة تطوير Node (في إطار عمل Express) (هذا المقال): سنوضح في هذا المقال كيفية إعداد واختبار بيئة تطوير Node/Express على أنظمة تشغيل ويندوز ولينكس (أوبنتو) وماك أو اس macOS. يجب أن يوفر لك هذا المقال ما تحتاجه لتتمكن من بدء تطوير تطبيقات Express مهما كان نظام التشغيل الذي تستخدمه. تطبيق عملي لتعلم Express - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية: يشرح المقال الأول من التطبيق العملي لتعلم إطار عمل Express ما ستتعلمه، ويقدم نظرة عامة على مثال موقع "المكتبة المحلية" الذي سنعمل عليه ونطوره في المقالات اللاحقة. يوضح هذا المقال كيفية إنشاء مشروع موقع ويب هيكلي، والذي يمكنك ملؤه لاحقًا بالوجهات Routes والقوالب أو العروض وقواعد البيانات الخاصة بالموقع. تطبيق عملي لتعلم Express - الجزء الثاني: استخدام قاعدة البيانات (باستخدام مكتبة Mongoose): يوضح هذا المقال بإيجاز قواعد بيانات Node/Express، ثم يوضح كيفية استخدام مكتبة Mongoose لتوفير الوصول إلى قاعدة البيانات لموقع المكتبة المحلية LocalLibrary، ويشرح كيفية التصريح عن مخطط Schema الكائنات والنماذج وأنواع الحقول الرئيسية والتحقق من صحة البيانات الأساسي، ويعرض بإيجاز بعض الوجهات الرئيسية التي يمكنك من خلالها الوصول إلى بيانات النموذج. تطبيق عملي لتعلم Express - الجزء الثالث: الوجهات Routes والمتحكمات Controllers: سنُعِد في هذا المقال الوجهات (شيفرة معالجة عناوين URL) باستخدام دوال معالجة "وهمية dummy" لجميع النقاط النهائية للموارد التي سنحتاجها في موقع LocalLibrary. سيكون لدينا في النهاية بنية معيارية لشيفرة معالجة الوجهات، والتي يمكننا توسيعها باستخدام دوال معالجة حقيقية في المقالات اللاحقة. سنوضح أيضًا كيفية إنشاء وجهات معيارية باستخدام إطار عمل Express. تطبيق عملي لتعلم Express - الجزء الرابع: عرض بيانات المكتبة والعمل مع الاستمارات: سنضيف في هذا المقال الصفحات التي تعرض كتب موقع LocalLibrary وبيانات أخرى، حيث ستتضمن الصفحات صفحة رئيسية توضح عدد السجلات لكل نوع نموذج وصفحات القائمة والصفحات التفصيلية لجميع نماذجنا، وسنكتسب في هذا المقال خبرة عملية في الحصول على السجلات من قاعدة البيانات واستخدام القوالب. سنشرح أيضًا كيفية العمل مع استمارات HTML في إطار عمل Express باستخدام مكتبة Pug، وخاصة كيفية كتابة الاستمارات لإنشاء المستندات وتحديثها وحذفها من قاعدة البيانات. تطبيق عملي لتعلم Express - الجزء الخامس: النشر في بيئة الإنتاج: أنشأت موقع المكتبة المحلية LocalLibrary، ولكنك تريد تثبيته على خادم ويب عام حتى يصل إليه موظفو وأعضاء المكتبة عبر الإنترنت، لذا يقدّم هذا المقال نظرة عامة حول كيفية البحث عن مضيف لنشر موقعك، وما عليك فعله لتجهيز موقعك لمرحلة الإنتاج. هذه هي جميع المواضيع التي يجب تعلّمها، ولكن إذا أردتَ معرفة المزيد وتوسيع هذه السلسلة من المقالات، فاطلع على بعض الموضوعات الأخرى التي يجب تغطيتها وهي: استخدام الجلسات. استيثاق المستخدمين. الترخيص للمستخدمين وأذوناتهم. اختبار تطبيق Express. أمان الويب لتطبيقات Express. كما سيكون وجود تقييم نهائي إضافةً رائعة. سنجيب في هذا المقال على الأسئلة: "ما هي بيئة Node؟" و"ما هو إطار عمل Express؟"، ويقدّم نظرة عامة على ما يجعل إطار عمل Express مميزًا. سنحدد الميزات الرئيسية، وسنعرض بعض البنى الأساسية لتطبيق Express، بالرغم من أنه لن يكون لديك بعد بيئة تطوير لاختبارها في هذه المرحلة. المتطلبات الأساسية: المهارات الحاسوبية الأساسية، وفهم عام لبرمجة مواقع الويب من طرف الخادم وخاصةً تفاعلات الخادم مع العميل في مواقع الويب. الهدف: التعرف على إطار عمل Express وكيفية ملاءمته مع بيئة Node، والوظائف التي يوفرها، والمكونات الأساسية لتطبيق Express. مدخل إلى بيئة Node تُعَد Node -أو Node.js رسميًا- بيئة تشغيل مفتوحة المصدر ومتوافقة مع منصات مختلفة وتتيح للمطورين إنشاء جميع أنواع الأدوات والتطبيقات من طرف الخادم في شيفرة جافا سكريبت. يُعَد وقت التشغيل مخصَّصًا للاستخدام خارج سياق المتصفح، أي التشغيل مباشرةً على حاسوب أو نظام تشغيل خادم، وبالتالي تحذف البيئة واجهات برمجة تطبيقات جافا سكريبت الخاصة بالمتصفح وتضيف دعمًا لواجهات برمجة تطبيقات نظام التشغيل التقليدية بما في ذلك مكتبات HTTP ومكتبات نظام الملفات. تتمتع بيئة Node بعدد من الفوائد من منظور تطوير خادم الويب، وهي: أداء رائع: صُمِّمت بيئة Node لتحسين الإنتاجية وقابلية التوسع في تطبيقات الويب، وتُعَد حلًا جيدًا للعديد من مشاكل تطوير الويب الشائعة، مثل تطبيقات الويب في الوقت الفعلي. تُكتَب الشيفرة البرمجية باستخدام لغة جافا سكريبت قديمة وبسيطة، مما يقلل الوقت في التعامل مع "تغيير السياق" بين اللغات عند كتابة الشيفرة البرمجية من طرف العميل والخادم. تُعَد لغة جافا سكريبت لغة برمجة جديدة نسبيًا وتستفيد من التحسينات في تصميم اللغة عند مقارنتها مع لغات خادم الويب التقليدية الأخرى، مثل بايثون و PHP وغير ذلك. تُصرَّف أو تُحوَّل العديد من اللغات الجديدة والشائعة إلى لغة جافا سكريبت حتى يمكنك استخدام لغات TypeScript و CoffeeScript و ClojureScript و Scala و LiveScript وغيرها. يوفر مدير حزم Node -أو اختصارًا npm- الوصول إلى مئات الألوف من الحزم القابلة لإعادة الاستخدام، ولديه التحليل Resolution الأفضل للاعتماديات Dependency Resolution، ويمكن استخدامه لأتمتة معظم سلسلة أدوات البناء. تُعَد بيئة Node.js قابلة للنقل، فهي متوفرة على أنظمة تشغيل مايكروسوفت ويندوز وماك أو اس macOS ولينكس و Solaris و FreeBSD و OpenBSD و WebOS و NonStop OS، ويدعمها العديد من مزودي خدمات استضافة الويب الذين يوفرون في أغلب الأحيان بنية تحتية وتوثيقًا محددًا لاستضافة مواقع Node. تمتلك نظام بيئي ومجتمع مطورين خارجي نشط جدًا بوجود الكثير من الأشخاص المستعدين للمساعدة. يمكنك استخدام بيئة ب Node.js لإنشاء خادم ويب بسيط باستخدام حزمة Node HTTP. مرحبا Node.js ينشئ المثال التالي خادم ويب يستمع إلى أيّ نوع من طلبات HTTP على عنوان URL هو http://127.0.0.1:8000/، إذ سيستجيب السكريبت بالسلسلة النصية "Hello World" عند تلقي طلبٍ ما. إذا كانت بيئة Node مثبَّتةً لديك مسبقًا، فيمكنك اتباع الخطوات التالية لتجربة مثالنا: أولًا، افتح الطرفية، أو افتح الأداة المساعدة لسطر الأوامر على نظام ويندوز. ثانيًا، أنشئ المجلد الذي تريد حفظ البرنامج فيه مثل المجلد "test-node"، ثم انتقل إليه من خلال إدخال الأمر التالي في الطرفية: cd test-node ثالثًا، أنشئ ملفًا بالاسم "hello.js" وضع فيه الشيفرة البرمجية التالية باستخدام محرر النصوص المفضل لديك: // حمّل وحدة HTTP const http = require("http"); const hostname = "127.0.0.1"; const port = 8000; // أنشئ خادم HTTP const server = http.createServer(function (req, res) { // اضبط ترويسة استجابة HTTP بحالة HTTP ونوع المحتوى res.writeHead(200, { "Content-Type": "text/plain" }); // أرسل متن الاستجابة "Hello World" res.end("Hello World\n"); }); // اطبع سجلًا بمجرد أن يبدأ الخادم بالاستماع server.listen(port, hostname, function () { console.log(`Server running at http://${hostname}:${port}/`); }); رابعًا، احفظ الملف في المجلد الذي أنشأته سابقًا. خامسًا، ارجع إلى الطرفية واكتب الأمر التالي: node hello.js أخيرًا، انتقل إلى العنوان http://localhost:8000 في متصفح الويب، إذ يجب أن ترى النص "Hello World" في الجزء العلوي الأيسر من صفحة ويب أخرى فارغة. أطر عمل الويب لا تدعم بيئة Node مهام تطوير الويب الشائعة الأخرى مباشرةً بنفسها، فإذا أدرتَ إضافة معالجة محددة لأفعال HTTP المختلفة، مثل GET و POST و DELETE وغير ذلك، فعالج الطلبات بصورة منفصلة في مسارات URL المختلفة ("الوجهات Routes")، أو خدّم الملفات الثابتة، أو استخدم القوالب Templates لإنشاء الاستجابة ديناميكيًا، إذ لن تكون بيئة Node ذات فائدة كبيرة من تلقاء نفسها. يجب عليك إما كتابة الشيفرة البرمجية بنفسك، أو يمكنك تجنب إعادة اختراع العجلة واستخدام إطار عمل ويب. مقدمة إلى إطار عمل Express يُعَد Express إطار عمل الويب الخاص ببيئة Node الأكثر شيوعًا، وهو المكتبة الأساسية لعدد من أطر عمل الويب الخاصة ببيئة Node الشائعة الأخرى، ويوفر آليات بهدف: كتابة معالجات للطلبات ذات أفعال HTTP مختلفة في وجهات URL المختلفة. التكامل مع محرّكات تقديم "العرض View" لتوليد استجابات من خلال إدخال بيانات في القوالب. ضبط إعدادات تطبيقات الويب الشائعة مثل المنفذ المُستخدَم للاتصال وموقع القوالب المستخدمة لتقديم الاستجابة. إضافة برمجيات وسيطة middleware لمعالجة الطلبات الإضافية في أيّ وقت ضمن خط معالجة الطلبات. يُعَد إطار عمل Express بسيط إلى حدٍ ما، ولكن أنشأ المطورون حزمًا وسيطة متوافقة لمعالجة أيّ مشكلة في تطوير الويب تقريبًا، وتوجد مكتبات للعمل مع ملفات تعريف الارتباط والجلسات وتسجيل دخول المستخدمين ومعاملات عنوان URL وبيانات POST وترويسات الأمان وغير ذلك الكثير. اطلع على قائمة الحزم الوسيطة التي يهتم بها فريق Express مع قائمة ببعض الحزم الخارجية الشائعة. ملاحظة: تُعَد هذه المرونة سيفًا ذا حدين، إذ توجد حزم وسيطة لمعالجة أيّ مشكلة أو متطلب تقريبًا، ولكن يمكن أن يكون العمل على استخدام الحزم المناسبة تحديًا في بعض الأحيان. كما لا توجد طريقة صحيحة لبناء تطبيق، إذا لا تُعَد العديد من الأمثلة التي تجدها على الإنترنت مثالية، أو يمكن أن تعرض فقط جزءًا صغيرًا مما تحتاج إلى فعله لتطوير تطبيق ويب. تاريخ بيئة Node وإطار عمل Express أُصدِرت بيئة Node في البداية لنظام لينكس فقط في عام 2009، وأُصدِر مدير الحزم npm في عام 2010، وأُضيف دعم ويندوز الأصلي في عام 2012. أُصدِر إطار عمل Express في البداية في الشهر 11 من عام 2010 وهو حاليًا في الإصدار الرئيسي الرابع من واجهة برمجة التطبيقات API، ويمكنك التحقق من سجل التغييرات للحصول على معلومات حول التغييرات في الإصدار الحالي و GitHub للحصول على ملاحظات الإصدارات التاريخية الأكثر تفصيلًا. ما مدى شعبية Node و Express؟ تُعد شعبية إطار عمل الويب أمرًا مهمًا لأنه مؤشر على استمرار صيانته والموارد التي يُحتمَل أن تكون متاحة من حيث التوثيق والمكتبات الإضافية والدعم الفني. لا يوجد أيّ مقياس نهائي ومتوفر حاليًا لشعبية أطر العمل من طرف الخادم، بالرغم من أنه يمكنك تقدير الشعبية باستخدام آليات مثل حساب عدد مشاريع غيت هَب GitHub وأسئلة موقع StackOverflow لكل منصة. لذا يُفضَّل أن تسأل ما إذا كان Node و Express يتمتعان بشعبية كافية لتجنب مشاكل المنصات التي لا تحظى بشعبية، وما إذا كانا مستمرين في التطور ويمكنك الحصول على المساعدة إن احتجت إليها، وإذا كان هناك فرصة لك للحصول على عمل مدفوع الأجر إذا تعلمت Express. يمكن القول أن Express إطار عمل شائع الاستخدام استنادًا إلى عدد الشركات البارزة التي تستخدمه وعدد الأشخاص المساهمين في الشيفرة البرمجية الأساسية وعدد الأشخاص الذين يقدمون الدعم المجاني والمدفوع. هل Express إطار عمل قائم على رأيه Opinionated؟ تشير أطر عمل الويب إلى نفسها في أغلب الأحيان على أنها "قائمة على رأيها Opinionated" أو أنها "غير قائمة على رأيها Unopinionated"، فأطر العمل القائمة على رأيها هي أطر العمل التي لديها آراء حول الطريقة الصحيحة للتعامل مع أيّ مهمة معينة، وتدعم التطور السريع في نطاق محدد أو حل مشاكل من نوع معين لأن الطريقة الصحيحة لفعل أي شيء تكون عادةً مفهومة ومُوثَّقة جيدًا. لكن يمكن أن تكون أقل مرونة في حل المشاكل خارج مجالها الرئيسي، وتميل إلى تقديم خيارات أقل للمكونات والأساليب التي يمكن أن تستخدمها. يكون لأطر العمل غير القائمة على رأيها قيود أقل بكثير على أفضل طريقة لربط المكونات مع بعضها بعضًا لتحقيق هدفٍ ما أو حتى لتحديد المكونات التي يجب استخدامها، وتسهّل على المطورين استخدام أنسب الأدوات لإكمال مهمة معينة وإن كان ذلك على حساب الجهد التي تحتاجه للعثور على تلك المكونات بنفسك. يُعَد إطار عمل Express غير قائم على رأيه، إذ يمكنك إدخال أيّ برمجية وسيطة متوافقة تريدها تقريبًا إلى سلسلة معالجة الطلبات وبأيّ ترتيب تريده تقريبًا، ويمكنك بناء التطبيق في ملف واحد أو في ملفات متعددة وباستخدام أي بنية مجلدات، إذ ستشعر أحيانًا أن لديك الكثير من الخيارات. كيف تبدو شيفرة Express البرمجية؟ ينتظر تطبيق الويب طلبات HTTP من متصفح الويب (أو عميل آخر) في موقع ويب تقليدي مُوجَّه بالبيانات. يعمل التطبيق عند تلقي طلب على تحديد الإجراء المطلوب بناءً على نمط عنوان URL وربما على المعلومات المرتبطة به الواردة في بيانات POST أو بيانات GET، ثم يمكنه بعد ذلك -اعتمادًا على ما هو مطلوب- قراءة المعلومات أو كتابتها في قاعدة بيانات أو أداء مهام أخرى مطلوبة لتلبية الطلب، ثم سيعيد التطبيق استجابةً إلى متصفح الويب، وينشئ غالبًا صفحة HTML ديناميكيًا ليعرضها المتصفح من خلال إدخال البيانات المُسترجَعة ضمن عناصر بديلة في قالب HTML. يوفر Express توابعًا لتحديد الدالة المستدعاة لفعل HTTP معين، مثل GET و POST و SET وغير ذلك ونمط عنوان URL ("الوِجهة Route")، وتوابعًا لتحديد محرّك القالب ("العرض View") المستخدَم، ومكان وجود ملفات القالب، والقالب الذي يجب استخدامه لتقديم الاستجابة. يمكنك استخدام برمجيات Express الوسيطة لإضافة الدعم لملفات تعريف الارتباط والجلسات والمستخدمين والحصول على معاملات POST / GET وغير ذلك، ويمكنك استخدام أيّ آلية قاعدة بيانات تدعمها بيئة Node، إذ لا يحدد إطار عمل Express أيّ سلوك متعلق بقاعدة البيانات. سنشرح في الأقسام التالية بعض الأشياء التي ستراها عند العمل باستخدام شيفرة Express و Node. مثال لطباعة مرحبا بالعالم Helloworld باستخدام إطار عمل Express أولًا، ضع في بالك مثال مرحبًا بالعالم المعياري، إذ سنناقش كل جزء منه فيما يلي وفي الأقسام التالية. ملاحظة: إذا كان لديك بيئة Node وإطار عمل Express مُثبَّتين مسبقًا (أو إذا ثبّتهما كما هو موضح في المقال التالي)، فيمكنك حفظ الشيفرة البرمجية التالية في ملف نصي بالاسم "app.js" وتشغيله في موجه أوامر باش Bash من خلال استدعاء الأمر: node ./app.js. const express = require("express"); const app = express(); const port = 3000; app.get("/", function (req, res) { res.send("Hello World!"); }); app.listen(port, function () { console.log(`Example app listening on port ${port}!`); }); يطلب السطران الأوليان باستخدام require() الوحدةَ express وينشئان تطبيق Express. يحتوي هذا الكائن- الذي يُطلَق عليه تقليديًا الاسم app- على توابع لتوجيه طلبات HTTP وضبط البرمجيات الوسيطة وتصيير عروض HTML وتسجيل محرك القوالب وتعديل إعدادات التطبيق التي تتحكم في سلوك التطبيق، مثل وضع البيئة وما إذا كانت تعريفات الوجهة حساسة لحالة الأحرف وغير ذلك. يعرض الجزء الأوسط من الشيفرة (الأسطر الثلاثة التي تبدأ بالتابع app.get) تعريف الوجهة، إذ يحدّد التابع app.get() دالة رد النداء callback المُستدعاة عندما يكون هناك طلب HTTP من النوع GET له المسار (/) المتعلق بجذر الموقع. تأخذ دالة رد النداء طلبًا وكائن استجابة بوصفهما وسيطين، وتستدعي التابع send() للاستجابة لإعادة السلسلة النصية "Hello World!". تشغّل الكتلة النهائية الخادم على منفذ محدد ("3000") وتطبع تعليق سجل في الطرفية، ويمكنك مع تشغيل الخادم الانتقال إلى localhost:3000 في متصفحك لترى مثال الاستجابة المُعادة. استيراد وإنشاء الوحدات الوحدة Module هي مكتبة أو ملف جافا سكريبت يمكنك استيراده في شيفرة برمجية أخرى باستخدام دالة require() الخاصة ببيئة Node، إذ يُعَد إطار عمل Express بحد ذاته وحدةً مثل مكتبات البرمجيات الوسيطة وقواعد البيانات التي نستخدمها في تطبيقات Express. توضح الشيفرة البرمجية التالية كيفية استيراد وحدة باسمها باستخدام إطار عمل Express، إذ نستدعي أولًا الدالة require() من خلال تحديد اسم الوحدة بوصفها سلسلة نصية ('express')، ثم نستدعي الكائن المُعاد لإنشاء تطبيق Express، ويمكننا بعد ذلك الوصول إلى خاصيات ودوال كائن التطبيق. const express = require("express"); const app = express(); يمكنك أيضًا إنشاء وحداتك الخاصة التي يمكن استيرادها بالطريقة نفسها. ملاحظة: من فوائد إنشاء وحداتك الخاصة أنها تسمح لك بتنظيم شيفرتك البرمجية إلى أجزاء يمكن إدارتها، إذ يصعب فهم وصيانة تطبيق مؤلف من ملف واحد، ويساعدك استخدام الوحدات في إدارة فضاء أسمائك، إذ تُستورَد المتغيرات التي تصدّرها بصورة صريحة فقط عند استخدام إحدى الوحدات. يمكن جعل الكائنات متاحةً خارج الوحدة من خلال جعلها خاصيات إضافية في الكائن exports، فمثلًا تُعَد الوحدة square.js التالية ملفًا يصدّر التوابع area() و perimeter(): exports.area = function (width) { return width * width; }; exports.perimeter = function (width) { return 4 * width; }; يمكننا استيراد هذه الوحدة باستخدام الدالة require()، ثم استدعاء التابع (أو التوابع) المُصدَّرة كما يلي: const square = require("./square"); // نطلب هنا باستخدام الدالة require() اسم الملف بدون لاحقة الملف .js (الاختيارية) console.log(`The area of a square with a width of 4 is ${square.area(4)}`); ملاحظة: يمكنك أيضًا تحديد مسار مطلق، (أو اسم ما كما فعلنا في البداية) للوحدة. إذا أردتَ تصدير كائن كامل في مهمة واحدة بدلًا من بناء خاصيةً واحدة في كل مرة، فأسنده إلى module.exports كما يلي، ويمكنك إجراء ذلك لجعل جذر كائن exports بانيًا Constructor أو دالة أخرى: module.exports = { area(width) { return width * width; }, perimeter(width) { return 4 * width; }, }; ملاحظة: يمكنك عَدّ exports بوصفه اختصارًا للمصطلح module.exports ضمن وحدة معينة، ولكن يُعَد exports مجرد متغير مهيَّأ بقيمة module.exports قبل تقييم الوحدة، وهذه القيمة هي مرجع إلى كائن (كائن فارغ في هذه الحالة)، وهذا يعني أن exports يحتوي على مرجع للكائن نفسه الذي يشير إليه module.exports، مما يعني أيضًا أن exports لم يعد مرتبطًا بالكائن module.exports من خلال إسناد قيمة أخرى إلى exports. اطلع على مقال تعرف على وحدات Node.js الأساسية وإنشاء وحدات برمجية Modules في Node.js والوحدات Modules في توثيق Node لمزيد من المعلومات. استخدام واجهات برمجة التطبيقات غير المتزامنة تستخدم شيفرة جافا سكريبت في أغلب الأحيان واجهات برمجة تطبيقات غير متزامنة بدلًا من واجهات برمجة تطبيقات متزامنة للعمليات التي يمكن أن تستغرق بعض الوقت لتكتمل، حيث تُعَد واجهة برمجة التطبيقات المتزامنة واجهة يجب أن تكتمل فيها العملية قبل أن تبدأ العملية التي تليها مثل دوال log المتزامنة التالية التي ستطبع النص إلى الطرفية بالترتيب (First, Second): console.log("First"); console.log("Second"); بينما تبدأ واجهة برمجة التطبيقات غير المتزامنة عمليةً وتُعاد مباشرةً قبل اكتمال العملية، وستستخدم واجهة برمجة التطبيقات API بعض الآليات لإجراء عمليات إضافية بمجرد انتهاء العملية، فمثلًا ستطبع الشيفرة التالية "Second, First" لأنه العملية لا تكتمل إلا بعد عدة ثوانٍ بالرغم من استدعاء التابع setTimeout() أولًا والإعادة مباشرةً. setTimeout(function () { console.log("First"); }, 3000); console.log("Second"); يُعَد استخدام واجهات برمجة التطبيقات غير المتزامنة وغير المُعطِّلة في Node أكثر أهمية من استخدامها في المتصفح لأن بيئة Node هي بيئة تنفيذ ذات خيط برمجي Thread واحد وتقاد بالأحداث، فاعتمادها على خيط واحد يعني تشغيل جميع الطلبات الواردة إلى الخادم على خيط واحد (بدلًا من توليدها في عمليات منفصلة). يُعَد هذا النموذج فعالًا جدًا من حيث السرعة وموارد الخادم، ولكنه يعني أنه إذا استدعَت أيٌّ من دوالك توابعًا متزامنة تستغرق وقتًا طويلًا حتى تكتمل، فلن تعطِّل الطلب الحالي فحسب، بل ستعطِّل كل طلب آخر يعالجه تطبيقك. هناك عدد من الطرق لتُعلِم واجهةُ برمجة التطبيقات غير المتزامنة تطبيقَك بأنه اكتمل، ولكن الطريقة الأكثر شيوعًا هي تسجيل دالة رد النداء عند استدعاء واجهة برمجة التطبيقات غير المتزامنة، والتي ستُستدعَى مرة أخرى عند اكتمال العملية، وهذا هو الأسلوب الذي استخدمناه مسبقًا. ملاحظة: يمكن أن يكون استخدام دوال رد النداء "فوضويًا" إذا كان لديك سلسلة من العمليات غير المتزامنة الاعتمادية التي يجب إجراؤها بالترتيب لأن ذلك ينتج عنه مستويات متعددة من دوال رد النداء المتداخلة. تُعرف هذه المشكلة عمومًا باسم "جحيم دوال رد النداء Callback Hell"، ويمكن تقليل هذه المشكلة من خلال ممارسات كتابة الشيفرة البرمجية الجيدة باستخدام وحدةٍ مثل الوحدة async، أو إعادة إنتاج الشيفرة البرمجية إلى ميزات جافا سكريبت أصيلة مثل الوعود Promises واللاتزامن والانتظار async/await، حيث توفّر بيئة Node الدالة utils.promisify لتحويل دالة رد النداء إلى وعدٍ بطريقة مريحة. ملاحظة: من الأمور الشائعة لكلٍّ من Node و Express استخدام دوال رد النداء ذات قيمة الخطأ أولًا، حيث تكون القيمة الأولى في دوال رد النداء قيمة خطأ، بينما تحتوي الوسائط اللاحقة على بيانات نجاح. إنشاء معالجات الوجهة Route Handlers عرّفنا في مثال مرحبًا بالعالم باستخدام إطار عمل Express السابق دالة معالجة الوجهة (دالة رد لنداء) لطلبات HTTP من النوع GET إلى جذر الموقع ('/'). app.get("/", function (req, res) { res.send("Hello World!"); }); تأخذ دالة رد النداء كائني طلب واستجابة بوصفهما وسطاء، وتستدعي التابع send() في هذه الحالة للاستجابة لإعادة السلسلة النصية "Hello World!". هناك عدد من توابع الاستجابة الأخرى لإنهاء دورة الطلب/الاستجابة، فمثلًا يمكنك استدعاء التابع res.json() لإرسال استجابة بتنسيق JSON أو التابع res.sendFile() لإرسال ملف. ملاحظة: يمكنك استخدام أيّ أسماء وسطاء تريدها في دوال رد النداء، إذ سيكون المتغير الأول هو الطلب وسيظل الوسيط الثاني هو الاستجابة دائمًا عند استدعاء دالة رد النداء، ولكن من المنطقي تسميتها بحيث يمكنك تحديد الكائن الذي تعمل به في متن دالة رد النداء. يوفّر كائن تطبيق Express توابعًا لتعريف معالجات الوجهة لجميع أفعال HTTP الأخرى، والتي تُستخدَم غالبًا بالطريقة نفسها تمامًا وهي: checkout(), copy(), delete(), get(), head(), lock(), merge(), mkactivity(), mkcol(), move(), m-search(), notify(), options(), patch(), post(), purge(), put(), report(), search(), subscribe(), trace(), unlock(), unsubscribe() يوجد تابع توجيه خاص هو app.all() يُستدعَى في استجابة أيّ تابع HTTP، ويُستخدَم هذا التابع لتحميل دوال وسيطة في مسار معين لجميع توابع الطلب. يوضّح المثال التالي (من توثيق Express) معالجًا يُنفَّذ لطلبات القسم /secret بغض النظر عن فعل HTTP المُستخدَم (بشرط أن تدعمه وحدة http). app.all("/secret", function (req, res, next) { console.log("Accessing the secret section…"); next(); // تمرير التحكم إلى المعالج التالي }); تسمح الوجهات Routes بمطابقة أنماط معينة من المحارف في عنوان URL واستخراج بعض القيم من عنوان URL وتمريرها بوصفها معاملات إلى معالج الوجهة، مثل سمات كائن الطلب المُمرَّر بوصفه معاملًا. من المفيد تجميع معالجات الوجهة لجزء معين من الموقع مع بعضها بعضًا والوصول إليها باستخدام بادئة وجهة مشتركة، فمثلًا يحتوي موقعٌ له خاصية ويكي Wiki على جميع الوجهات المتعلقة بها في ملف واحد ويمكن الوصول إليها باستخدام بادئة وجهة هي "/wiki/"، ويمكن تحقيق ذلك في إطار عمل Express باستخدام كائن express.Router. يمكننا مثلًا إنشاء وجهة wiki في وحدة بالاسم wiki.js ثم تصدير كائن Router كما يلي: // wiki.js - وحدة وجهة Wiki const express = require("express"); const router = express.Router(); // وجهة الصفحة الرئيسية router.get("/", function (req, res) { res.send("Wiki home page"); }); // وجهة الصفحة About router.get("/about", function (req, res) { res.send("About this wiki"); }); module.exports = router; ملاحظة: تشبه إضافة الوجهات إلى الكائن Router إضافةَ الوجهات إلى الكائن app كما هو موضح سابقًا. سنطلب باستخدام الدالة require() وحدة الوجهة (wiki.js) لاستخدام الموجّه في ملف تطبيقنا الرئيسي، ثم نستدعي التابع use() في تطبيق Express لإضافة الكائن Router إلى مسار المعالجة الوسيطة، ويمكن بعد ذلك الوصول إلى الوجهتين من /wiki/ و /wiki/about/. const wiki = require("./wiki.js"); // … app.use("/wiki", wiki); اطلع على مقال الموجّهات والمتحكمات لمزيد من التفاصيل حول العمل مع الوجهات وخاصة استخدام الكائن Router. استخدام البرمجيات الوسيطة تُستخدَم البرمجيات الوسيطة على نطاق واسع في تطبيقات Express -للمهام بدءًا تخديم الملفات الثابتة إلى معالجة الأخطاء- لضغط استجابات HTTP. تنهي دوال الوجهات دورة طلب واستجابة HTTP من خلال إعادة الاستجابة لعميل HTTP، ولكن تجري الدوال الوسيطة بعض العمليات على الطلب أو الاستجابة ثم تستدعي الدالة التالية في المكدس stack، والتي يمكن أن تكون برمجية وسيطة أو معالج وجهة، ويعود ترتيب استدعاء البرمجيات الوسيطة إلى مطور التطبيق. ملاحظة: يمكن للبرمجيات الوسيطة إجراء أيّ عملية وتنفيذ أيّ شيفرة برمجية وإجراء تغييرات على كائن الطلب والاستجابة، ويمكنها إنهاء دورة الطلب والاستجابة. إذا لم تنتهِ الدورة، فيجب استدعاء التابع next() لتمرير التحكم إلى الدالة الوسيطة التالية، أو سيُترَك الطلب مُعلَّقًا. تستخدم معظم التطبيقات برمجيات وسيطة خارجية لتبسيط مهام تطوير الويب الشائعة، مثل العمل مع ملفات تعريف الارتباط cookies والجلسات واستيثاق المستخدمين والوصول إلى طلب POST وبيانات جسون JSON والتسجيل وغير ذلك. يمكنك العثور على قائمة بحزم البرمجيات الوسيطة التي يهتم بها فريق Express، والتي تتضمن أيضًا الحزم الخارجية الشائعة الأخرى، وتتوفر حزم Express الأخرى في مدير الحزم npm. يجب تثبيت برمجية وسيطة خارجية في تطبيقك باستخدام مدير الحزم npm لاستخدامها، فمثلًا يمكن تثبيت البرمجية الوسيطة morgan لتسجيل طلبات HTTP باستخدام الأمر التالي: npm install morgan يمكنك بعد ذلك استدعاء التابع use() في كائن تطبيق Express لإضافة البرمجيات الوسيطة إلى المكدس: const express = require("express"); const logger = require("morgan"); const app = express(); app.use(logger("dev")); // … ملاحظة: تُستدعَى دوال التوجيه والبرمجيات الوسيطة بالترتيب نفسه للتصريح عنها، إذ يكون الترتيب مهمًا بالنسبة لبعض البرمجيات الوسيطة، فمثلًا إذا اعتمدت البرمجيات الوسيطة للجلسة على البرمجيات الوسيطة لملفات تعريف الارتباط، فيجب إضافة معالج ملفات تعريف الارتباط أولًا. تُستدعَى البرمجيات الوسيطة قبل إعداد الوجهات غالبًا، أو لن تتمكن معالجات الوجهات من الوصول إلى الوظائف التي تضيفها برمجيتك الوسيطة. يمكنك كتابة دوالك الوسيطة، إذ يُحتمَل أن تضطر إلى ذلك إذا أردتَ إنشاء شيفرة معالجة الأخطاء فقط. يتمثل الاختلاف الوحيد بين دالة البرمجية الوسيطة ودالة رد استدعاء معالج الوجهة في أن دوال البرمجيات الوسيطة لها وسيط ثالث هو next الذي يُتوقَّع أن تستدعيه دوال البرمجيات الوسيطة إن لم تكن الدالة التي تكمل دورة الطلب، إذ تحتوي الدالة الوسيطة على الدالة next التي يجب استدعاؤها عند استدعاء هذه الدالة الوسيطة. يمكنك إضافة دالة وسيطة إلى سلسلة المعالجة لجميع الاستجابات باستخدام التابع app.use()، أو لفعل HTTP محدَّد باستخدام التابع المرتبط به مثل: app.get() و app.post() وغير ذلك، وتُحدَّد الوجهة بالطريقة نفسها لكلتا الحالتين، بالرغم من أن الوجهة اختيارية عند استدعاء التابع app.use(). يوضح المثال التالي كيفية إضافة الدالة الوسيطة باستخدام كلا الأسلوبين مع أو بدون وجهة: const express = require("express"); const app = express(); // مثال على دالة وسيطة const a_middleware_function = function (req, res, next) { // إجراء بعض العمليات next(); // استدعاء next() ليستدعي Express الدالة الوسيطة التالية في السلسلة }; // الدالة المُضافة باستخدام التابع use() لجميع الوجهات والأفعال app.use(a_middleware_function); // الدالة المُضافة باستخدام التابع use() لوجهة محددة app.use("/someroute", a_middleware_function); // الدالة الوسيطة المُضافة لفعل HTTP ووجهة محددة app.get("/", a_middleware_function); app.listen(3000); ملاحظة: صرّحنا عن دالة وسيطة بصورة منفصلة ثم ضبطناها بوصفها دالة رد نداء، وصرّحنا عن دالة رد النداء عند استخدامها في دالة معالج الوجهة السابقة، ويُعَد هذان الأسلوبان صالحين في جافا سكريبت. تخديم الملفات الثابتة يمكنك استخدام البرمجية الوسيطة express.static لتخديم الملفات الثابتة بما في ذلك الصور وملفات CSS وجافا سكريبت، وتُعَد static() الدالة الوسيطة الوحيدة التي هي جزء من إطار عمل Express. يمكنك مثلًا استخدام السطر التالي لتخديم الصور وملفات CSS وجافا سكريبت من مجلد بالاسم public في المستوى نفسه الذي تستدعيه فيه Node: app.use(express.static("public")); تُخدَّم الملفات في المجلد public من خلال إضافة اسم هذه الملفات (بالنسبة إلى المجلد "public" الأساسي) إلى عنوان URL الأساسي كما في الأمثلة التالية: http://localhost:3000/images/dog.jpg http://localhost:3000/css/style.css http://localhost:3000/js/app.js http://localhost:3000/about.html يمكنك استدعاء الدالة static() عدة مرات لتخديم مجلدات متعددة، بحيث إذا لم تعثر دالة وسيطة على ملفٍ ما، فسيُمرَّر إلى البرمجية الوسيطة التالية، إذ يعتمد ترتيب استدعاء البرمجيات الوسيطة على ترتيب تصريحها. app.use(express.static("public")); app.use(express.static("media")); يمكنك أيضًا إنشاء بادئة افتراضية لعناوين URL الثابتة الخاصة بك بدلًا من إضافة الملفات إلى عنوان URL الأساسي، فمثلًا نحدد فيما يلي مسار ربط Mount Path بحيث تُحمَّل الملفات باستخدام البادئة "/media": app.use("/media", express.static("public")); يمكنك الآن تحميل الملفات الموجودة في المجلد public من بادئة المسار /media: http://localhost:3000/media/images/dog.jpg http://localhost:3000/media/video/cat.mp4 http://localhost:3000/media/cry.mp3 معالجة الأخطاء تُعالَج الأخطاء باستخدام دالة وسيطة خاصة واحدة أو أكثر لها أربعة وسائط بدلًا من الوسائط الثلاثة المعتادة: (err, req, res, next) كما يلي: app.use(function (err, req, res, next) { console.error(err.stack); res.status(500).send("Something broke!"); }); تعيد الشيفرة السابقة أيّ محتوًى مطلوب، ولكن يجب استدعاؤها بعد جميع توابع app.use() واستدعاءات الوجهات الأخرى، بحيث تكون آخر برمجية وسيطة في عملية معالجة الطلب. يحتوي إطار عمل Express على معالج أخطاء مبني مسبقًا، والذي يهتم بأيّ أخطاء متبقية يمكن أن تواجهها في التطبيق، وتُضاف دالة معالجة الأخطاء الافتراضية هذه في نهاية مكدس الدوال الوسيطة. إذا مرّرتَ خطأً إلى الدالة next() ولم تعالجه في معالج الأخطاء، فسيعالجه معالج الأخطاء المبني مسبقًا، إذ سيُكتَب الخطأ إلى العميل باستخدام متعقّب المكدس stack trace. لا يُضمَّن متعقّب المكدس في بيئة الإنتاج، لذا يجب ضبط متغير البيئة NODE_ENV على القيمة 'production' لتشغيله في وضع الإنتاج. ملاحظة: لا يجري التعامل مع HTTP404 ورموز حالة الخطأ الأخرى بوصفها أخطاء، فإذا أردتَ معالجتها، فيمكنك إضافة دالة وسيطة لذلك. استخدام قواعد البيانات يمكن لتطبيقات Express استخدام أيّ آلية قاعدة بيانات تدعمها بيئة Node، ولا يعرّف Express أيّ سلوك أو متطلبات إضافية محددة لإدارة قاعدة البيانات، إذ هناك العديد من الخيارات لاستخدامها مثل PostgreSQL و MySQL و Redis و SQLite و MongoDB وغير ذلك. يجب أولًا تثبيت مشغّل قاعدة البيانات باستخدام مدير الحزم npm، فمثلًا يمكنك استخدام الأمر التالي لتثبيت مشغّل قاعدة بيانات NoSQL MongoDB: npm install mongodb يمكنك تثبيت قاعدة البيانات محليًا أو على خادم سحابي، إذ ستطلب المشغّل في شيفرة Express، ثم تتصل بقاعدة البيانات، ثم تجري عمليات الإنشاء والقراءة والتحديث والحذف -أو اختصارًا CRUD. يوضح المثال التالي (من توثيق Express) كيفية العثور على سجلات "mammal" باستخدام قاعدة بيانات MongoDB، وتعمل الشيفرة التالية مع الإصدارات الأقدم من إصدار mongodb الذي هو 2.2.33: const MongoClient = require("mongodb").MongoClient; MongoClient.connect("mongodb://localhost:27017/animals", (err, db) => { if (err) throw err; db.collection("mammals") .find() .toArray((err, result) => { if (err) throw err; console.log(result); }); }); يمكنك استخدام الشيفرة التالية بالنسبة للإصدار رقم 3.0 من mongodb والإصدارات الأحدث: const MongoClient = require("mongodb").MongoClient; MongoClient.connect("mongodb://localhost:27017/animals", (err, client) => { if (err) throw err; const db = client.db("animals"); db.collection("mammals") .find() .toArray((err, result) => { if (err) throw err; console.log(result); client.close(); }); }); هناك طريقة شائعة أخرى وهي الوصول إلى قاعدة بياناتك بطريقة غير مباشرة باستخدام رابط الكائنات العلائقي Object Relational Mapper -أو ORM اختصارًا، إذ تعرّف في هذه الطريقة بياناتك بوصفها كائنات أو نماذج ويربط رابط ORM هذه البيانات بتنسيق قاعدة البيانات الأساسية. تتمتع هذه الطريقة بفائدة أنه يمكنك -بصفتك مطورًا- الاستمرار في التفكير وفق مصطلحات كائنات جافا سكريبت بدلًا من التفكير وفق دلالات قاعدة البيانات، وأن هناك مكانًا واضحًا لإجراء التحقق من صحة البيانات الواردة وفحصها (سنتحدث أكثر عن قواعد البيانات في مقال لاحق (استخدام قاعدة البيانات). تصيير البيانات- العروض Views تسمح محركات القوالب (يُشار إليها أيضًا باسم "محركات العروض" في توثيق Express) بتحديد بنية مستندات الخرج في قالبٍ ما باستخدام العناصر البديلة للبيانات التي ستُملَأ عند توليد الصفحة، إذ تُستخدَم القوالب لإنشاء صفحات HTML، ولكن يمكنها أيضًا إنشاء أنواع أخرى من المستندات، ويدعم Express عددًا من محركات القوالب. تضبط في شيفرة إعدادات تطبيقك محركَ القوالب لاستخدامه وتضبط الموقع الذي يجب أن يبحث فيه Express عن القوالب باستخدام إعدادات 'views' و 'view engine' كما يلي، ويجب أيضًا تثبيت الحزمة التي تحتوي على مكتبة قوالبك: const express = require("express"); const path = require("path"); const app = express(); // اضبط المجلد ليتضمن القالب ('views') app.set("views", path.join(__dirname, "views")); // اضبط محرك القوالب للاستخدام، ويكون في هذه الحالة 'some_template_engine_name' app.set("view engine", "some_template_engine_name"); يعتمد مظهر القالب على المحرك الذي تستخدمه. بافتراض أن لديك ملف قالب بالاسم "index." الذي يحتوي على عناصر بديلة لمتغيرات البيانات المُسمَّاة "title" و "message"، فيمكنك استدعاء Response.render() في دالة معالج الوجهة لإنشاء استجابة HTML وإرسالها كما يلي: app.get("/", function (req, res) { res.render("index", { title: "About dogs", message: "Dogs rock!" }); }); بنية الملفات لا يضع إطار عمل Express أيّ افتراضات للبنية أو المكونات التي تستخدمها، إذ يمكن أن توضَع الوجهات والعروض والملفات الثابتة والشيفرة الأخرى الخاصة بالتطبيق في أيّ عدد من الملفات وبأيّ بنية مجلدات. يمكن أن يكون كامل تطبيق Express في ملف واحد، ولكن من المنطقي تقسيم تطبيقك إلى ملفات بناءً على الوظيفة، مثل إدارة الحسابات والمدونات ولوحات المناقشة، ونطاق المشكلات المعمارية، مثل النموذج، أو العرض، أو المتحكم إذا استخدمتَ معمارية MVC وهي اختصار لثلاث كلمات هي: النموذج Model الذي يعني بيانات التطبيق فهو يتفاعل مباشرة مع قاعدة البيانات الخاصة بك ويسترد المعلومات منها. العرض View الذي يعني واجهة التطبيق فهو يعرض الصفحات التي يتفاعل معها المستخدم مباشرة. المتحكم Controller وهو صلة الوصل بين العرض والنموذج فهو يستقبل طلبات المستخدمين ويسترد البيانات المطلوبة من النموذج ويعالجها ويرسلها إلى صفحات العرض. سنستخدم في مقال لاحق مولّد تطبيقات Express أو Express Application Generator الذي يُنشِئ تطبيقًا هيكليًا معياريًا يمكننا توسيعه بسهولة لإنشاء تطبيقات الويب. الخلاصة أكملنا الخطوة الأولى في رحلة تعلم Express/Node، إذ يجب أن تفهم الآن فوائد Express و Node الرئيسية، وما تبدو عليه الأجزاء الرئيسية لتطبيق Express (الوجهات والبرمجيات الوسيطة ومعالجة الأخطاء وشيفرة القوالب)، ويجب أن تفهم أيضًا أن الطريقة التي تجمع بها هذه الأجزاء مع بعضها بعضًا والمكتبات التي تستخدمها أمرٌ متروكٌ لك بسبب كون Express إطار عمل غير قائم على رأيه. يُعَد Express إطار عمل تطبيق ويب خفيف الوزن، إذ تأتي الكثير من فوائده وإمكاناته من مكتبات وميزات خارجية، إذ سنلقي نظرةً عليها بمزيد من التفصيل في المقالات التالية، وسنتعرّف في المقال التالي على إعداد بيئة تطوير Node، بحيث يمكنك البدء في رؤية بعض شيفرات Express. ترجمة -وبتصرُّف- للمقالين Express web framework (Node.js/JavaScript) و Express/Node introduction. اقرأ أيضًا المقال السابق: تعرف على أمان تطبيقات جانغو أطر عمل الويب من طرف الخادم دليل استخدام Node.js وإطار العمل Express للمبتدئين مدخل إلى Node.js وExpress
-
تُعَد حماية بيانات المستخدم جزءًا أساسيًا من تصميم أيّ موقع ويب، لذا يقدّم هذا المقال شرحًا عمليًا لكيفية تعامل أدوات الحماية المبنية مسبقًا في جانغو Django مع الهجمات التي تعرّفنا على بعضٍ منها سابقًا في مقال تعرف على أمان مواقع الويب. المتطلبات الأساسية: قراءة مقال تعرف على أمان مواقع الويب الخاص بالبرمجة من طرف الخادم، والاطلاع على سلسلة مقالات تعلم جانغو حتى مقال العمل مع الاستمارات Forms على الأقل. الهدف: فهم الأشياء الرئيسية التي يجب تطبيقها، أو عدم تطبيقها لتأمين تطبيق ويب جانغو. يوفر أمان موقع الويب نظرةً عامة حول ما يعنيه أمان موقع الويب للتصميم من طرف الخادم وبعض الهجمات الأكثر شيوعًا التي يجب عليك حماية موقعك منها، فإحدى الدروس الرئيسية التي نتعلمها من ذلك المقال أن جميع الهجمات تنجح تقريبًا عندما يثق تطبيق الويب بالبيانات الواردة من المتصفح. ملاحظة: الدرس الوحيد الأكثر أهمية الذي يمكنك تعلّمه حول أمان مواقع الويب هو عدم الوثوق أبدًا في البيانات الواردة من المتصفح، ويتضمن ذلك بيانات طلب GET في معاملات عنوان URL وبيانات POST وترويسات HTTP وملفات تعريف الارتباط والملفات التي يرفعها المستخدم وغير ذلك، لذا تحقق دائمًا من جميع البيانات الواردة وطهّرها، وافترض الأسوأ دائمًا. يتعامل جانغو مع العديد من الهجمات الأكثر شيوعًا، ويمكنك الاطلاع على الأمان في توثيق جانغو لمعرفة ميزات أمان جانغو وكيفية تأمين موقع ويب يدعمه. تتألف هذه السلسلة الفرعية من السلسلة الأشمل تعلم تطوير الويب من المقالات التالية: مدخل إلى إطار عمل الويب جانغو Django إعداد بيئة تطوير تطبيقات جانغو تطبيق عملي لتعلم جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية تطبيق عملي لتعلم جانغو - الجزء السادس: إدارة الجلسات Sessions تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج تعرف على أمان تطبيقات جانغو الهجمات الشائعة وحماية موقعك منها سنشرح في هذا المقال عددًا من ميزات الأمان في سياق تطبيقنا العملي لموقع المكتبة المحلية LocalLibrary لتعلم جانغو. هجمات السكربتات العابرة للمواقع XSS يُستخدَم مصطلح هجمات السكربتات العابرة للمواقع Cross Site Scripting -أو اختصارًا XSS- لوصف فئة من الهجمات التي تسمح للمهاجمين بحقن سكربتات من طرف العميل عبر موقع الويب في متصفحات المستخدمين الآخرين، ويمكن تحقيقه من خلال تخزين سكربتات ضارة في قاعدة البيانات حيث يمكن استرجاعها وإظهارها للمستخدمين الآخرين، أو من خلال جعل المستخدمين ينقرون على رابط يؤدي إلى أن ينفِّذ متصفحُ المستخدم شيفرة جافا سكريبت الخاصة بالمهاجم. يحميك نظام قوالب جانغو من أغلب هجمات XSS من خلال محارف هروب معينة Escaping specific characters، والتي تُعَد خطيرة في لغة HTML. يمكننا إثبات ذلك من خلال محاولة إدخال شيفرة جافا سكريبت في موقع المكتبة المحلية LocalLibrary باستخدام استمارة إنشاء مؤلف Create-author التي أنشأناها في مقال العمل مع الاستمارات Forms باتباع الخطوات التالية: شغّل موقع الويب باستخدام خادم التطوير python3 manage.py runserver. افتح الموقع في متصفحك المحلي وسجّل الدخول إلى حساب مستخدمك المميز. انتقل إلى صفحة إنشاء المؤلف، والتي يجب أن تكون على عنوان URL الذي هو: http://127.0.0.1:8000/catalog/author/create/. أدخِل الأسماء وتفاصيل التاريخ لمستخدم جديد، ثم ألحِق نصًا بحقل اسم العائلة Last Name هو: <script>alert('Test alert');</script>. ملاحظة: يُعَد النص الذي أدخلناه سكربتًا غير ضار، إذ سيعرض مربع تنبيه في متصفحك إذا جرى تنفيذه. إذا عُرِض التنبيه عند إرسال السجل، فسيكون الموقع عُرضة لهجمات XSS. اضغط على زر إرسال Submit لحفظ السجل. سيظهر ما يلي عند حفظ المؤلف، إذ يجب عدم تنفيذ التابع alert() بسبب الحماية من هجمات XSS، لذا يُعرَض السكربت بوصفه نصًا عاديًا. إذا عرضتَ الشيفرة المصدرية لصفحة HTML، فيمكنك أن ترى أن المحارف الخطيرة لوسوم السكربت مُحوَّلة إلى ما يكافؤها من رموز الهروب غير الضارة، إذ تحوّل < إلى > مثلًا. <h1> Author: Boon<script>alert('Test alert');</script>, David (Boonie) </h1> يحميك استخدام قوالب جانغو من أغلب هجمات XSS، ولكن يمكن أن توقِف تشغيل هذه الحماية، مع عدم تطبيقها تلقائيًا على جميع الوسوم التي لا تُملَأ من إدخال المستخدم عادةً، فمثلًا لا يوفّر المستخدم نص التعليمات help_text في حقل الاستمارة، لذلك لا يهرّب جانغو هذه القيم. يمكن أيضًا أن تنشأ هجمات XSS من مصدر بيانات آخر غير موثوق به مثل ملفات تعريف الارتباط Cookies أو خدمات الويب أو الملفات المرفوعة عند عدم تعقيم sanitize البيانات تعقيمًا كافيًا قبل تضمينها في الصفحة، لذا يمكن أن تحتاج إلى إضافة رمز تعقيمك الخاص إذا أردتَ عرض بيانات من هذه المصادر. الحماية من هجمات تزوير الطلبات عبر المواقع CSRF تسمح هجمات Cross-Site Request Forgery -أو اختصارًا CSRF- للمستخدم الضار بتنفيذ الإجراءات باستخدام اعتماديات مستخدم آخر دون معرفة هذا المستخدم أو موافقته. لنأخذ مثلًا الحالة التي يكون لدينا فيها مخترق يريد إنشاء مؤلفين إضافيين لمكتبتنا المحلية. ملاحظة: من الواضح أن المخترق لن يعمل ذلك من أجل المال، ولكن يمكن للمخترق الأكثر طموحًا استخدام الأسلوب نفسه على المواقع الأخرى لإجراء مهام أكثر ضررًا مثل تحويل الأموال إلى حسابه الخاص وغير ذلك. يمكن للمخترق إنشاء ملف HTML مثل الملف الآتي الذي يحتوي على استمارة إنشاء المؤلف، مثل الاستمارة التي استخدمناها في القسم السابق، والتي تُرسَل بمجرد تحميل الملف، ثم يرسل المخترق الملف إلى جميع أمناء المكتبات ويقترح عليهم فتح الملف الذي سيحتوي على بعض المعلومات غير الضارة في مثالنا. إذا فتح أمينُ المكتبة الذي سجّل دخوله الملفَ، فستُرسَل الاستمارة مع اعتمادياتها وسيُنشأ مؤلف جديد. <html lang="en"> <body onload="document.EvilForm.submit()"> <form action="http://127.0.0.1:8000/catalog/author/create/" method="post" name="EvilForm"> <table> <tr> <th><label for="id_first_name">First name:</label></th> <td> <input id="id_first_name" maxlength="100" name="first_name" type="text" value="Mad" required /> </td> </tr> <tr> <th><label for="id_last_name">Last name:</label></th> <td> <input id="id_last_name" maxlength="100" name="last_name" type="text" value="Man" required /> </td> </tr> <tr> <th><label for="id_date_of_birth">Date of birth:</label></th> <td> <input id="id_date_of_birth" name="date_of_birth" type="text" /> </td> </tr> <tr> <th><label for="id_date_of_death">Died:</label></th> <td> <input id="id_date_of_death" name="date_of_death" type="text" value="12/10/2016" /> </td> </tr> </table> <input type="submit" value="Submit" /> </form> </body> </html> شغِّل خادم الويب الخاص بالتطوير، وسجّل الدخول باستخدام حساب مستخدمك المميز، ثم انسخ النص السابق والصقه في ملفٍ ما وافتحه في المتصفح. يجب أن تحصل على خطأ CSRF، لأن جانغو لديه حماية ضد هذا النوع من الهجمات. تُفعَّل الحماية من خلال تضمين وسم القالب {% csrf_token %} في تعريف استمارتك، ثم يُعرَض هذا المفتاح في شيفرة HTML كما يلي مع قيمة خاصة بالمستخدم في المتصفح الحالي: <input type="hidden" name="csrfmiddlewaretoken" value="0QRWHnYVg776y2l66mcvZqp8alrv4lb8S8lZ4ZJUWGZFA5VHrVfL2mpH29YZ39PW" /> يولّد جانغو مفتاحًا خاصًا بالمستخدم أو بالمتصفح ويرفض الاستمارات التي لا تحتوي على الحقل أو التي تحتوي على قيمة حقل غير صحيحة للمستخدم أو المتصفح. يجب على المخترق الآن اكتشاف وتضمين مفتاح CSRF للمستخدم المستهدف لاستخدام هذا النوع من الهجمات، ولا يمكنه استخدام أسلوب "التبعثر Scattergun" لإرسال ملف ضار إلى جميع أمناء المكتبة على أمل أن يفتحه أحدهم، نظرًا لأن مفتاح CSRF خاص بالمتصفح. تُشغَّل حماية جانغو من هجمات CSRF افتراضيًا، ويجب عليك دائمًا استخدام وسم القالب {% csrf_token %} في استماراتك واستخدام طلبات POST التي يمكن أن تغير أو تضيف بيانات إلى قاعدة البيانات. الحماية من الهجمات الأخرى يوفّر جانغو أشكالًا أخرى من الحماية، إليك بعضًا منها. الحماية من هجمات حقن استعلامات SQL تمكّن الثغرات الأمنية الخاصة بحقن استعلامات SQL المستخدمين الضارين من تنفيذ شيفرة SQL عشوائية على قاعدة بيانات، مما يسمح بالوصول إلى البيانات أو تعديلها أو حذفها بغض النظر عن أذونات المستخدم. ستصل في كل حالة تقريبًا إلى قاعدة البيانات باستخدام مجموعات استعلام أو نماذج جانغو، لذلك سيهرِّب مشغّل قاعدة البيانات الأساسية شيفرة SQL الناتجة بصورة صحيحة. إذا كنت بحاجة إلى كتابة استعلامات بسيطة أو استعلامات SQL مُخصَّصة، فيجب عليك التفكير في منع حقن استعلامات SQL. الحماية من هجمات الاختطاف بالنقر Clickjacking يختطف المستخدم الضار في هذه الهجمات النقرات الخاصة بموقع المستوى الأعلى المرئي ويوجّهها إلى صفحة مخفية تحته، فمثلًا يمكن استخدام هذه التقنية لعرض موقع مصرف قانوني مع التقاط اعتماديات تسجيل الدخول ضمن عنصر <iframe> غير مرئي يتحكم فيه المهاجم. يتضمن جانغو حمايةً من هجمات الاختطاف بالنقر في صيغة X-Frame-Options middleware التي يمكنها منع عرض الموقع ضمن إطار في متصفح داعم لذلك. فرض بروتوكول SSL/HTTPS يمكن تفعيل بروتوكول SSL/HTTPS على خادم الويب لتشفير كل حركة المرور بين الموقع والمتصفح، بما في ذلك اعتماديات الاستيثاق التي يمكن إرسالها ضمن نص عادي. يوصَى جدًا بتفعيل HTTPS، إذ سيوفّر جانغو بتفعيله عددًا من وسائل الحماية الأخرى التي يمكنك استخدامها وهي: يمكن استخدام SECURE_PROXY_SSL_HEADER للتحقق مما إذا كان المحتوى آمنًا، حتى وإن كان قادمًا من وكيل غير HTTP. يُستخدَم SECURE_SSL_REDIRECT لإعادة توجيه جميع طلبات HTTP إلى HTTPS. استخدم أمن نقل HTTP الصارم HTTP Strict Transport Security -أو اختصارًا HSTS، وهو ترويسة HTTP التي تخبر المتصفح بأن جميع الاتصالات المستقبلية إلى موقع معين يجب أن تستخدم HTTPS دائمًا. يضمن هذا الإعداد -إلى جانب إعادة توجيه طلبات HTTP إلى HTTPS- استخدام بروتوكول HTTPS دائمًا بعد نجاح الاتصال. يمكن ضبط HSTS مع SECURE_HSTS_SECONDS و SECURE_HSTS_INCLUDE_SUBDOMAINS أو على خادم الويب. استخدم ملفات تعريف الارتباط "الآمنة" من خلال ضبط SESSION_COOKIE_SECURE و CSRF_COOKIE_SECURE على القيمة True، مما يضمن إرسال ملفات تعريف الارتباط عبر بروتوكول HTTPS فقط. التحقق من صحة ترويسة المضيف استخدم ALLOWED_HOSTS لقبول الطلبات من المضيفين الموثوق بهم فقط. هناك العديد من وسائل الحماية والتحذيرات لاستخدام الآليات السابقة، ولكننا نأمل أن نكون قد أعطيناك نظرةً عامةً على ما يقدمه جانغو، إذ يجب عليك أيضًا الاطلاع على توثيق أمان جانغو. الخلاصة يتمتع جانغو بحماية فعالة ضد عدد من الهجمات الشائعة بما في ذلك هجمات XSS و CSRF، وأوضحنا في هذا المقال كيف يتعامل جانغو مع هذه الهجمات في موقع المكتبة المحلية LocalLibrary، وقدمنا أيضًا لمحةً موجزةً عن بعض أشكال الحماية الأخرى. الخطوة التالية والأخيرة في هذه السلسلة من المقالات حول جانغو هي إكمال اختبار التقييم في المقال التالي. ترجمة -وبتصرُّف- للمقال Django web application security. اقرأ أيضًا المقال السابق: تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج احم موقعك من الاختراق. تعرف على أمان مواقع الويب. الأمان في جانغو (في توثيق جانغو)
-
أنشأنا واختبرنا موقع المكتبة المحلية LocalLibrary، ويجب الآن تثبيته على خادم ويب عام ليصل إليه موظفو المكتبة وأعضاؤها عبر الإنترنت، لذا يقدّم هذا المقال نظرةً عامةً حول كيفية البحث عن مضيف لنشر موقعك، وما عليك تطبيقه لتجهيز موقعك لمرحلة الإنتاج. المتطلبات الأساسية: اطّلع على جميع المقالات السابقة من هذه السلسلة، بما في ذلك المقال الخاص باختبار تطبيق جانغو. الهدف: معرفة مكان وكيفية نشر تطبيق جانغو في بيئة الإنتاج. يجب استضافة موقعك بعد الانتهاء منه (أو الانتهاء منه "بما يكفي" لبدء الاختبار العام) في مكان أعم ويمكن الوصول إليه من أجهزة أخرى غير حاسوبك الشخصي الخاص بالتطوير. عملتَ حتى الآن في بيئة تطوير باستخدام خادم ويب تطوير جانغو Django لمشاركة موقعك على المتصفح أو الشبكة المحلية، وشغّلتَ موقعك باستخدام إعدادات تطوير غير آمنة، بحيث يمكن الوصول إلى معلومات تنقيح الأخطاء والمعلومات الخاصة الأخرى. ينبغي عليك أولًا تنفيذ الخطوات التالية قبل أن تتمكّن من استضافة موقع ويب خارجيًا: إجراء بعض التغييرات على إعدادات مشروعك. اختيار بيئة لاستضافة تطبيق جانغو. اختيار بيئة لاستضافة أيّ ملفات ثابتة. إعداد بنية تحتية على مستوى بيئة الإنتاج لتخديم موقع الويب. يقدّم هذا المقال بعض الإرشادات حول الخيارات المتاحة لاختيار موقع استضافة، ونظرة عامة مختصرة على ما يجب تطبيقه لتجهيز تطبيق جانغو لبيئة الإنتاج، ومثالًا عمليًا لكيفية تثبيت موقع المكتبة المحلية LocalLibrary على خدمة الاستضافة السحابية Railway. تتألف هذه السلسلة الفرعية من السلسلة الأشمل تعلم تطوير الويب من المقالات التالية: مدخل إلى إطار عمل الويب جانغو Django إعداد بيئة تطوير تطبيقات جانغو تطبيق عملي لتعلم جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية تطبيق عملي لتعلم جانغو - الجزء السادس: إدارة الجلسات Sessions تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج تعرف على أمان تطبيقات جانغو ما هي بيئة الإنتاج؟ بيئة الإنتاج هي البيئة التي يوفّرها حاسوب الخادم إذ ستشغّل موقعك للاستخدام الخارجي، وتشمل هذه البيئة ما يلي: عتاد الحاسوب الذي يعمل عليه الموقع. نظام التشغيل، مثل لينكس وويندوز. وقت التشغيل الخاص بلغة البرمجة ومكتبات أطر العمل التي كُتِب عليها موقعك. خادم الويب المُستخدَم لتخديم الصفحات والمحتويات الأخرى، مثل خادم Nginx وأباتشي Apache. خادم التطبيق الذي يمرّر الطلبات "الديناميكية" بين موقع جانغو وخادم الويب. قواعد البيانات التي يعتمد عليها موقعك. ملاحظة: يمكن أن يكون لديك أيضًا وكيل عكسي Reverse Proxy وموازن حِمل Load Balancer وغير ذلك بناءً على كيفية ضبط بيئتك الإنتاجية. يمكنك الاطلاع على المقالين التاليين الموجودين على موقع أكاديمية حسوب لمزيد من المعلومات حول الوكيل العكسي، وهي: كيفيّة ضبط Nginx للعمل كخادم ويب وكوسيط عكسي لـ Apache على خادم Ubuntu 18.04 وكيف تستخدم Apache ليعمل وسيطًا عكسيًّا Reverse Proxy على Ubuntu 16.04. يمكن أن يكون حاسوب الخادم موجودًا في مقر عملك ومتصلًا بالإنترنت من خلال رابط سريع، ولكن يمكن استخدام حاسوب مستضاف على السحابة، وهذا يعني تنفيذ شيفرتك البرمجية على بعض الحواسيب البعيدة، أو على حاسوب افتراضي، في مركز أو مراكز بيانات شركتك المضيفة. يقدّم الخادم البعيد عادةً مستوًى مضمونًا من موارد المعالجة (وحدة المعالجة المركزية والذاكرة RAM وذاكرة التخزين وغير ذلك) والاتصال بالإنترنت بسعر معين. يُشار إلى هذا النوع من عتاد المعالجة والتشبيك الذي يمكن الوصول إليه عن بُعد باسم البنية التحتية مثل خدمة Infrastructure as a Service -أو IaaS اختصارًا. يوفّر العديد من بائعي خدمة IaaS خيارات التثبيت المُسبَق لنظام تشغيل معين، والذي يجب تثبيت مكونات بيئتك الإنتاجية الأخرى عليه، ويسمح لك البائعون الآخرون باختيار بيئات كاملة الميزات، ويمكن أن يتضمن ذلك الإعداد الكامل لجانغو وخادم الويب. ملاحظة: يمكن للبيئات المبنية مسبقًا أن تسهّل إعداد موقعك لأنها تقلل من عملية الضبط، ولكن يمكن أن تقيّدك الخيارات المتاحة بخادم غير مألوف لك، أو بمكونات أخرى ويمكن أن تستند إلى نسخة أقدم من نظام التشغيل. يُفضَّل في أغلب الأحيان تثبيت المكونات بنفسك حتى تحصل على المكونات التي تريدها، وبالتالي ستعرف من أين تبدأ عندما تحتاج إلى ترقية أجزاء من النظام. يدعم مزوّدو الاستضافة الآخرون إطار عمل جانغو بوصفه جزءًا من استضافة المنصة مثل خدمة Platform as a Service -أو PaaS اختصارًا، فلا داعٍ للقلق في هذا النوع من الاستضافة بشأن معظم أجزاء بيئتك الإنتاجية، أي خادم الويب وخادم التطبيق وموازن الحِمل لأن المنصة المضيفة تهتم بهذه الأشياء نيابةً عنك، إضافةً إلى أنها تهتم بمعظم ما يجب تطبيقه لتوسيع نطاق تطبيقك، مما يجعل النشر سهلًا جدًا، لأنك تحتاج فقط إلى التركيز على تطبيق الويب وليس على كامل بنية الخادم التحتية الأخرى. سيختار بعض المطورين المرونة المتزايدة التي توفرها استضافة IaaS على حساب استضافة PaaS، بينما سيقدّر المطورون الآخرون تقليل تكاليف الصيانة وسهولة توسيع نطاق استضافة PaaS. يكون إعداد موقعك على نظام PaaS أسهل بكثير في البداية، وهذا ما سنفعله في هذا المقال. ملاحظة: إذا اخترت مزوّد استضافة متوافق مع بايثون/جانغو، فيجب أن يقدّم إرشادات حول كيفية إعداد موقع جانغو باستخدام عمليات ضبط مختلفة لخادم الويب وخادم التطبيق والوكيل العكسي وغير ذلك، ولكن لا يتطابق ذلك مع اختيارك لاستضافة PaaS. اختيار مزود الاستضافة يوجد أكثر من 100 مزوّد استضافة معروفين يدعمون جانغو بنشاط أو يعملون بصورة جيدة معه، ويمكنك العثور على قائمة كبيرة إلى حدٍ ما في مضيفي DjangoFriendly، ويوفّر هؤلاء البائعون أنواعًا مختلفة من البيئات IaaS و PaaS، ومستويات مختلفة من موارد المعالجة والشبكات بأسعار مختلفة. إليك بعض الأشياء التي يجب مراعاتها عند اختيار مضيف: مدى انشغال موقعك وتكلفة البيانات وموارد المعالجة المطلوبة لتلبية هذا المطلب. مستوى الدعم للتوسّع أفقيًا (إضافة المزيد من الأجهزة) وعموديًا (الترقية إلى أجهزة أقوى) وتكاليف ذلك. مكان مراكز بيانات المزوّد، أي المكان الذي يكون الوصول إليه أسرع. أداء وقت التشغيل ووقت التعطل السابقين للمضيف. الأدوات المتوفرة لإدارة الموقع، لمعرفة إذا كانت سهلة الاستخدام وآمنة، مثل استخدام بروتوكول SFTP أو استخدام بروتوكول FTP. أطر العمل المبنية مسبقًا لمراقبة خادمك. القيود المعروفة، إذ سيوقِف بعض المضيفين عمدًا خدمات معينة مثل البريد الإلكتروني، ويقدّم البعض الآخر فقط عددًا معينًا من ساعات "النشاط" في بعض مستويات الأسعار، أو يقدّم فقط قدرًا صغيرًا من التخزين. الفوائد الإضافية، إذ ستقدّم بعض المزوّدات أسماء نطاقات مجانية ودعمًا لشهادات SSL التي يمكن أن يتعيّن عليك دفع ثمنها. معرفة ما إذا كان المستوى "المجاني" الذي تعتمد عليه تنتهي صلاحيته بمرور الوقت، وما إذا كانت تكلفة التهجير Migrating إلى مستوًى أغلى تعني أنه كان من الأفضل استخدام بعض الخدمات الأخرى من البداية. هناك عددٌ قليل جدًا من المواقع التي توفر بيئات معالجة "مجانية" مخصصة للتقييم والاختبار في البداية، وتكون عادةً بيئات مُقيَّدة أو محدودة الموارد إلى حدٍ ما، ويجب أن تدرك أنه يمكن أن تنتهي صلاحيتها بعد فترة أولية أو يكون لديها قيود أخرى، ولكنها رائعة لاختبار المواقع ذات حركة المرور المنخفضة في بيئة مُستضافة، ويمكن أن توفر تهجيرًا سهل الدفع مقابل المزيد من الموارد عندما يصبح موقعك أكثر انشغالًا. تشمل الخيارات الشائعة في هذه الفئة Railway و Python Anywhere و Amazon Web Services و Microsoft Azure وغير ذلك. تقدّم معظم المزوّدات مستوًى أساسيًا مخصصًا لمواقع الإنتاج الصغيرة، والذي يوفّر مستويات أكثر فائدة من قدرة المعالجة وقيودًا أقل. يُعَد Heroku و Digital Ocean و Python Anywhere أمثلةً على مزودات الاستضافة المشهورة التي لديها مستوى معالجة أساسي غير مكلف نسبيًا (في نطاق يتراوح بين 5 و 10 دولارات أمريكية شهريًا). ملاحظة: تذكّر أن السعر ليس معيار الاختيار الوحيد، فإذا كان موقعك ناجحًا، فيمكن أن تكون قابلية التوسع هي الأهم. تجهيز موقعك للنشر ضُبِط موقع جانغو الهيكلي الذي أنشأناه باستخدام أدوات "django-admin" و "manage.py" لتسهيل عملية التطوير، ويجب أن تكون العديد من إعدادات مشروع جانغو المُحدَّدة في الملف "settings.py" مختلفةً لعملية الإنتاج لأسباب تتعلق بالأمان أو الأداء. ملاحظة: من الشائع أن يكون لديك ملف "settings.py" منفصل لعملية الإنتاج و/أو أن تستورد إعدادات حساسة استيرادًا مشروطًا من ملف منفصل أو متغير بيئة، ويجب بعد ذلك حماية هذا الملف، حتى ولو كانت بقية الشيفرة المصدرية متاحةً في مستودع عام. الإعدادات الأساسية التي يجب التحقق منها هي: DEBUG: ينبغي ضبطه على القيمة False في مرحلة الإنتاج، أي DEBUG = False، مما يؤدي إلى إيقاف عرض تعقّب تنقيح الأخطاء ومعلومات المتغيرات الحساسة أو السرية. SECRET_KEY: هو قيمة عشوائية كبيرة تُستخدَم للحماية من هجمات CSRF وغير ذلك. من المهم ألّا يكون المفتاح المستخدم في عملية الإنتاج تحت سيطرة المصدر أو يمكن الوصول إليه خارج خادم الإنتاج، إذ يشير توثيق جانغو إلى أنه من الأفضل تحميله من متغير بيئة أو قراءته من ملف خادم فقط. # اقرأ المفتاح السري SECRET_KEY من متغير البيئة import os SECRET_KEY = os.environ['SECRET_KEY'] # أو # اقرأ المفتاح السري من ملف with open('/etc/secret_key.txt') as f: SECRET_KEY = f.read().strip() لنعدّل تطبيق المكتبة المحلية LocalLibrary حتى نقرأ متغيرات SECRET_KEY و DEBUG من متغيرات البيئة إذا كانت مُعرَّفة، وإلّا فاستخدم القيم الافتراضية في ملف الضبط. افتح الملف "/locallibrary/settings.py" وعطّل ضبط SECRET_KEY الأصلي وضِف الأسطر الجديدة الموضحة فيما يلي. لن تُحدَّد متغيرات بيئة للمفتاح أثناء عملية التطوير، لذلك ستُستخدَم القيمة الافتراضية. لا يهم ما هو المفتاح الذي تستخدمه هنا أو إذا تسرّب المفتاح، لأنك لن تستخدمه في عملية الإنتاج. # تحذير أمني: حافظ على سرية المفتاح السري المُستخدَم في عملية الإنتاج # SECRET_KEY = "cg#p$g+j9tax!#a3cup@1$8obt2_+&k3q+pmu)5%asj6yjpkag" import os SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'cg#p$g+j9tax!#a3cup@1$8obt2_+&k3q+pmu)5%asj6yjpkag') علّق بعد ذلك إعداد DEBUG الحالي وضِف السطر الجديد التالي: # تحذير أمني: لا تنفّذ أثناء تشغيل تنقيح الأخطاء في عملية الإنتاج # DEBUG = True DEBUG = os.environ.get('DJANGO_DEBUG', '') != 'False' سيكون DEBUG له القيمة True افتراضيًا، ولكنه سيكون False فقط عند ضبط قيمة متغير البيئة DJANGO_DEBUG على القيمة False. لاحظ أن متغيرات البيئة هي سلاسل نصية وليست من أنواع بايثون، لذلك يجب موازنة السلاسل النصية، والطريقة الوحيدة لضبط المتغير DEBUG على القيمة False هي ضبطه على السلسلة النصية False. يمكنك ضبط متغير البيئة على القيمة "False" على نظام لينكس باستخدام الأمر التالي: export DJANGO_DEBUG=False توفّر قائمة النشر في توثيق جانغو قائمة كاملة من الإعدادات التي يمكن أن ترغب في تغييرها، ويمكنك سرد عددٍ منها باستخدام أمر الطرفية التالي: python3 manage.py check --deploy تطبيق عملي: تثبيت موقع المكتبة المحلية LocalLibrary على منصة Railway يقدّم هذا القسم شرحًا عمليًا لكيفية تثبيت موقع المكتبة المحلية LocalLibrary على منصة Railway. سبب استخدام Railway اخترنا استخدام منصة Railway لعدة أسباب هي: تمتلك Railway مستوًى مجانيًا لخطة بداية مع بعض القيود، إذ من المهم أن تكون بأسعار مقبولة لجميع المطورين. تهتم Railway بمعظم البنية التحتية، فلا حاجة للقلق بشأن الخوادم وموازني الحِمل والوكلاء العكسيين وغير ذلك، مما يجعل البدء أسهل بكثير. تركّز Railway على تجربة المطور للتطوير والنشر، مما يؤدي إلى وجود منحنى تعليمي أسرع وأسلس من العديد من البدائل الأخرى. تُعَد المهارات والمفاهيم التي ستتعلمها عند استخدام Railway قابلة للتحويل، إذ تمتلك Railway بعض الميزات الجديدة الممتازة، ولكن تستخدم خدماتُ الاستضافة الشائعة الأخرى العديد من الأفكار والأساليب نفسها. لا تؤثر قيود الخدمات والخطط على استخدامنا لمنصة Railway في مثالنا، فمثلًا: تقدّم خطة البداية 500 ساعة فقط من وقت النشر المستمر كل شهر و5 دولارات من الرصيد الذي يُستهلَك بناءً على الاستخدام، ويُعاد ضبط الساعات والرصيد ويجب إعادة نشر المشاريع في نهاية كل شهر. تعني هذه القيود أنه يمكنك تشغيل مثالنا بصورة مستمرة لمدة 21 يومًا تقريبًا، ويُعَد ذلك أكثر من كافٍ للتطوير والاختبار، ولكن لن تتمكّن من استخدام هذه الخطة لموقع حقيقي للإنتاج. تحتوي بيئة خطة البداية على 512 ميجابايت فقط من الذاكرة RAM و1 جيجابايت من ذاكرة التخزين، وهذا أكثر من كافٍ لمثالنا. لا توجد سوى منطقة واحدة مدعومة وهي الولايات المتحدة الأمريكية حاليًا، إذ يمكن أن تكون الخدمة خارج هذه المنطقة أبطأ أو تحظرها القوانين المحلية. يمكن العثور على قيود أخرى في توثيق خطط Railway للدفع. تبدو الخدمة موثوقة جدًا، وإذا كانت مناسبة لك، فإن الأسعار يمكن التنبؤ بها، ويكون توسيع تطبيقك سهلًا جدًا. تعَد Railway مناسبةً لاستضافة مثالنا، ولكن يجب أن تأخذ الوقت الكافي لتحديد ما إذا كانت مناسبة لموقعك. كيفية عمل Railway يُشغَّل كل تطبيق ويب في حاوية افتراضية virtualized container معزولة ومستقلة خاصة به، لذا يجب أن تكون Railway قادرة على إعداد البيئة والاعتماديات المناسبة، وفهم كيفية إطلاقها من أجل تنفيذ تطبيقك. نقدّم هذه المعلومات في عددٍ من الملفات النصية بالنسبة لتطبيقات جانغو، وهي: runtime.txt: يوضح لغة البرمجة والنسخة المُراد استخدامها. requirements.txt: يسرد اعتماديات بايثون اللازمة لموقعك بما في ذلك جانغو. Procfile: قائمة العمليات التي ستُنفَّذ لبدء تطبيق الويب. سيكون هذا الملف عادةً في تطبيقات جانغو خادم تطبيق ويب Gunicorn مع سكربت .wsgi. wsgi.py: ضبط واجهة WSGI لاستدعاء تطبيق جانغو في بيئة Railway. يمكن للتطبيق بعد تشغيله ضبط نفسه باستخدام المعلومات المقدمة في متغيرات البيئة، فمثلًا يمكن للتطبيق الذي يستخدم قاعدة بيانات الحصول على العنوان باستخدام المتغير DATABASE_URL، ويمكن أن تستضيف Railway خدمة قاعدة البيانات نفسها أو على أي مزوّد آخر. يتفاعل المطورون مع Railway من خلال موقع Railway وباستخدام أداة واجهة سطر أوامر CLI خاصة، إذ تسمح لك واجهة CLI بربط مستودع غيت هب GitHub محلي بمشروع Railway، ورفع المستودع من الفرع المحلي إلى الموقع المباشر، وفحص سجلات العملية الجارية، وإعداد متغيرات الضبط والحصول عليها وغير ذلك. من أهم الميزات أنه يمكنك استخدام واجهة CLI لتشغيل مشروعك المحلي مع متغيرات البيئة نفسها للمشروع المباشر. يجب وضع تطبيق جانغو الخاص بنا في مستودع غيت git وإضافة الملفات السابقة والتكامل مع إضافة قاعدة البيانات وإجراء تغييرات للتعامل بصورة صحيحة مع الملفات الثابتة ليعمل تطبيقنا على Railway، ثم يمكننا إعداد حساب على Railway والحصول على عميل Railway وتثبيت موقعنا. وهذا هو كل ما تحتاجه من معلومات في البداية. إنشاء مستودع للتطبيق على غيت هب GitHub تتكامل Railway تكاملًا وثيقًا مع غيت هب ونظام التحكم في إصدارات الشيفرة المصدرية لغيت، ويمكنك ضبطها لنشر التغييرات تلقائيًا إلى مستودع أو فرع معين على غيت هب، أو يمكنك دفع فرع الشيفرة المحلية الحالي مباشرةً إلى النشر الخاص بمنصة Railway باستخدام واجهة CLI. ملاحظة: يُعَد استخدام نظام إدارة الشيفرة المصدرية مثل غيت هب ممارسةً جيدةً لتطوير البرمجيات، فإذا كنت تستخدم غيت هب لإدارة شيفرتك المصدرية مسبقًا، فتخطى هذه الخطوة. هناك العديد من الطرق للعمل مع غيت، ولكن من أسهل هذه الطرق إعداد حساب على غيت هب GitHub أولًا وإنشاء المستودع عليه، ثم المزامنة معه محليًا كما يلي: انتقل إلى موقع GitHub الرسمي وأنشئ حسابًا عليه. انقر على رابط "+" في شريط الأدوات العلوي وحدّد خيار "مستودع جديد New repository" بعد تسجيل الدخول. املأ جميع الحقول في هذه الاستمارة، إذ يمكن أن تكون هذه الحقول غير إلزامية، ولكن يُوصَى بها بشدة. أدخِل اسم المستودع الجديد والوصف، فمثلًا يمكنك استخدام الاسم "djangolocallibrary" والوصف "Local Library website written in Django" -أي موقع المكتبة المحلية المكتوب باستخدام جانغو. اختر الخيار Python في قائمة الاختيار "Add .gitignore". اختر الترخيص المفضل لديك في قائمة الاختيار "Add license". تحقّق من تهيئة المستودع باستخدام README. اضغط على إنشاء مستودع Create repository. انقر فوق الزر الأخضر نسخ Clone أو تنزيل Download في صفحة مستودعك الجديد. انسخ قيمة URL من حقل النص الموجود في مربع الحوار الذي يظهر. إذا استخدمت اسم المستودع "djangolocallibrary"، فيجب أن يكون عنوان URL مثل العنوان: "https://github.com//django_local_library.git". انتهينا من إنشاء المستودع، ويجب الآن نسخه على الحاسوب المحلي باتباع الخطوات التالية: ثبّت غيت git على حاسوبك المحلي، إذ يمكنك العثور على نسخ لأنظمة مختلفة. افتح موجه الأوامر أو الطرفية وانسخ مستودعك باستخدام عنوان URL الذي نسخته سابقًا كما في الأمر التالي: git clone https://github.com/<your_git_user_id>/django_local_library.git سيؤدي هذا الأمر إلى إنشاء المستودع في مجلد جديد في مجلد العمل الحالي. انتقل إلى المستودع الجديد باستخدام الأمر التالي: cd django_local_library أخيرًا، انسخ تطبيقك في مجلد المشروع المحلي ثم ضِف -أو "ادفع push" حسب مصطلحات غيت- المستودع المحلي إلى مستودع غيت هب البعيد باتباع الخطوات التالية: أولًا، انسخ تطبيق جانغو في هذا المجلد. جميع الملفات موجودة في مستوى الملف manager.py نفسه وأدنى منه، وليست بمستوى المجلد locallibrary الذي يحتوي عليها. ثانيًا، افتح ملف ".gitignore" وانسخ الأسطر التالية في نهايته، ثم احفظه، إذ يُستخدم هذا الملف لتحديد الملفات التي لا يجب تحميلها إلى غيت افتراضيًا. # الملفات النصية الاحتياطية *.bak # قاعدة البيانات *.sqlite3 ثالثًا، افتح موجه الأوامر أو الطرفية واستخدم الأمر add لإضافة جميع الملفات إلى غيت، مما يؤدي إلى إضافة الملفات التي لا يتجاهلها ملف ".gitignore" إلى "منطقة مرحلة التحضير Staging Area". git add -A رابعًا، استخدم الأمر status للتحقق من صحة جميع الملفات التي تريد تثبيتها commit، إذ نريد تضمين الملفات المصدرية، وليس الملفات الثنائية والمؤقتة وما إلى ذلك، إذ يجب أن تبدو كما يلي: > git status On branch main Your branch is up-to-date with 'origin/main'. Changes to be committed: (use "git reset HEAD <file>..." to unstage) modified: .gitignore new file: catalog/__init__.py ... new file: catalog/migrations/0001_initial.py ... new file: templates/registration/password_reset_form.html خامسًا، ثبّت باستخدام commit الملفات في مستودعك المحلي عندما تكون راضيًا عن النتيجة، إذ يكافئ ذلك الإقرار بالتغييرات وجعلها جزءًا رسميًا من المستودع المحلي. git commit -m "First version of application moved into GitHub" سادسًا، لم يتغيّر المستودع البعيد حتى الآن، لذا يجب مزامنة مستودعك المحلي مع مستودع غيت هب البعيد باستخدام الأمر push: git push origin main يجب أن تكون قادرًا عند اكتمال هذه العملية على العودة إلى صفحة غيت هب، حيث أنشأتَ مستودعك، وتحديث الصفحة، وملاحظة رفع التطبيق بالكامل. يمكنك الاستمرار في تحديث مستودعك مع تغير الملفات باستخدام دورة أوامر الإضافة add والتثبيت commit والدفع push. ملاحظة: يمكنك الآن إنشاء نسخة احتياطية من شيفرة مشروعك البرمجية، إذ يمكن أن تكون بعض التغييرات التي سنجريها في الأقسام التالية مفيدة للنشر أو التطوير على أيّ نظام، ويمكن أن تكون بعض التغييرات الأخرى غير مفيدة. أفضل طريقة لذلك هي استخدام غيت لإدارة الإصدارات، إذ يمكنك باستخدامه الرجوع إلى إصدار قديم معين، ويمكنك الاحتفاظ به في فرع منفصل عن التغييرات الإنتاجية واختيار أيّ تغييرات للتنقل بين فروع الإنتاج والتطوير. يستحق تعلم غيت الجهد المبذول، لذا اطلع على مجموعة مقالات Git في أكاديمية حسوب لمزيدٍ من المعلومات حول ذلك. يُعَد نسخ ملفاتك في موقع آخر الطريقة الأسهل، ولكن يمكنك استخدام أيّ طريقة تتناسب مع معرفتك لنظام غيت. تحديث التطبيق ليعمل على Railway سنشرح في هذا القسم التغييرات التي يجب إجراؤها على تطبيق المكتبة المحلية LocalLibrary ليعمل على Railway، ولكن لن تمنعك هذه التغييرات من استخدام الاختبار وسير العمل المحلي الذي تعلمناه مسبقًا. الملف Procfile يُعَد الملف Procfile "نقطة الدخول" إلى تطبيق الويب، فهو يسرد الأوامر التي ستنفّذها Railway لبدء موقعك. لذا أنشئ ملف Procfile دون امتداد في جذر مستودع غيت هب وضع فيه النص التالي: web: python manage.py migrate && python manage.py collectstatic --no-input && gunicorn locallibrary.wsgi تخبر البادئة web: منصة Railway أن هذه عملية ويب ويمكن إرسالها عبر حمولة HTTP، ثم نستدعي أمر تهجير جانغو python manage.py migrate لإعداد جداول قاعدة البيانات، ثم نستدعي أمر جانغو python manage.py collectstatic لتجميع الملفات الثابتة في المجلد الذي حدّده إعداد مشروع STATIC_ROOT الذي سنتعرّف عليه لاحقًا. أخيرًا، نبدأ عملية gunicorn الذي هو خادم تطبيقات ويب شائع، ونمرّر له معلومات الضبط في الوحدة locallibrary.wsgi التي أُنشئت باستخدام تطبيقنا الهيكلي: "/locallibrary/wsgi.py". لاحظ أنه يمكنك أيضًا استخدام الملف Procfile لبدء العمليات العاملة أو لتشغيل مهام أخرى غير تفاعلية قبل نشر الإصدار. خادم Gunicorn Gunicorn هو خادم HTTP من بايثون يُستخدَم بصورة شائعة لتخديم تطبيقات جانغو WSGI على Railway كما أشرنا في فقرة ملف Procfile السابقة. لسنا بحاجة خادم Gunicorn لتخديم تطبيق المكتبة المحلية LocalLibrary أثناء عملية التطوير، ولكننا سنثبّته محليًا، بحيث يصبح جزءًا من متطلباتنا التي تعِدّها Railway على الخادم البعيد. تأكّد أولًا من أنك في بيئة بايثون الافتراضية التي أُنشِئت عند إعداد بيئة التطوير. استخدم الأمر workon [name-of-virtual-environment]، ثم ثبّت الخادم Gunicorn محليًا في سطر الأوامر باستخدام الأداة pip كما يلي: pip3 install gunicorn ضبط قاعدة البيانات تُعَد SQLite قاعدة بيانات جانغو الافتراضية التي استخدمناها لعملية التطوير، وهي خيار مقبول للمواقع الصغيرة إلى المتوسطة، ولكن لا يمكن استخدامها في بعض خدمات الاستضافة الشائعة مثل Heroku، لأنها لا توفر تخزينًا دائمًا للبيانات في بيئة التطبيق (أحد متطلبات SQLite). يمكن ألّا يؤثّر ذلك علينا في Railway، ولكننا سنعرض لك طريقةً أخرى تعمل على Railway و Heroku وبعض الخدمات الأخرى. تتمثل هذه الطريقة في استخدام قاعدة بيانات تُشغَّل ضمن عمليتها الخاصة في مكانٍ ما على الإنترنت ويصل إليها تطبيق مكتبة جانغو باستخدام عنوان مُمرَّر بوصفه متغير بيئة، إذ سنستخدم في هذه الحالة قاعدة بيانات Postgres التي نستضيفها أيضًا Railway، ولكن يمكنك استخدام خدمة استضافة قاعدة البيانات التي تريدها. سنوفّر معلومات اتصال قاعدة البيانات لجانغو باستخدام متغير البيئة DATABASE_URL، إذ سنستخدم حزمة dj-database-url بدلًا من تثبيت هذه المعلومات في جانغو لتحليل متغير البيئة DATABASE_URL وتحويله تلقائيًا إلى تنسيق ضبط جانغو المطلوب، ويجب أيضًا تثبيت psycopg2 الذي يحتاجه جانغو للتفاعل مع قواعد بيانات Postgres. حزمة dj-database-url تُستخدَم حزمة dj-database-url لاستخراج ضبط قاعدة بيانات جانغو من متغير البيئة. ثبّت هذه الحزمة محليًا بحيث تصبح جزءًا من متطلباتنا التي تُعِّدها Railway على الخادم البعيد كما يلي: pip3 install dj-database-url الملف settings.py افتح الملف /locallibrary/settings.py وانسخ الضبط التالي وضعه في نهاية الملف: # تحديث ضبط قاعدة البيانات من $DATABASE_URL import dj_database_url db_from_env = dj_database_url.config(conn_max_age=500) DATABASES['default'].update(db_from_env) سيستخدم جانغو الآن ضبط قاعدة البيانات الموجود في DATABASE_URL إذا كان متغير البيئة مضبوطًا، وإلّا فإنه يستخدم قاعدة بيانات SQLite الافتراضية. تجعل القيمة conn_max_age=500 الاتصال مستمرًا، وهو أكثر كفاءة بكثير من إعادة إنشاء الاتصال في كل دورة طلب، ويُعَد ذلك اختياريًا ويمكن إزالته عند الحاجة. psycopg2 يحتاج جانغو إلى psycopg2 للعمل مع قواعد بيانات Postgres، لذا ثبّته محليًا بحيث يصبح جزءًا من متطلباتنا التي تُعِّدها Railway على الخادم البعيد كما يلي: pip3 install psycopg2-binary لاحظ أن جانغو سيستخدم قاعدة بيانات SQLite أثناء عملية التطوير افتراضيًا إذا لم يُضبَط متغير البيئة DATABASE_URL، ولكن يمكنك التبديل إلى قاعدة بيانات Postgres واستخدام قاعدة البيانات المُستضافة نفسها لعمليتي التطوير والإنتاج من خلال ضبط متغير البيئة نفسه في بيئة التطوير، إذ تسهّل Railway استخدام البيئة نفسها للإنتاج والتطوير. يمكنك بدلًا من ذلك تثبيت واستخدام قاعدة بيانات Postgres ذاتية الاستضافة على حاسوبك المحلي. تخديم الملفات الثابتة في عملية الإنتاج نستخدم أثناء عملية التطوير جانغو وخادم ويب تطوير جانغو لتخديم كلٍّ من ملفات HTML الديناميكية والملفات الثابتة، مثل ملفات CSS وجافا سكريبت JavaScript وغيرها، ولكن يُعَد ذلك غير فعال للملفات الثابتة، لأن الطلبات يجب أن تمر عبر جانغو بالرغم من أن جانغو لا يفعل أيّ شيء بها. لا يُعَد ذلك مهمًا أثناء عملية التطوير، إلّا أنه سيكون له تأثير كبير على الأداء إذا استخدمنا الطريقة نفسها في عملية الإنتاج. نفصل عادةً الملفات الثابتة عن تطبيق جانغو في بيئة الإنتاج، مما يسهل تخديمها مباشرةً من خادم الويب أو من شبكة توصيل المحتوى Content Delivery Network -أو CDN اختصارًا. إليك متغيرات الإعداد المهمة التي يجب تضعها في حساباتك: STATIC_URL: موقع عنوان URL الأساسي الذي ستُخدَّم منه الملفات الثابتة مثل شبكة توصيل المحتوى CDN. STATIC_ROOT: المسار المطلق للمجلد الذي ستجمِّع فيه أداة جانغو collectstatic الملفات الثابتة المشار إليها في قوالبنا، ثم يمكن رفعها على أنها مجموعة إلى مكان استضافة الملفات. STATICFILES_DIRS: يسرد المجلدات الإضافية التي يجب أن تبحث فيها أداة جانغو collectstatic عن الملفات الثابتة. تشير قوالب جانغو إلى مواقع الملفات الثابتة المتعلقة بالوسم static (يمكنك رؤيتها في القالب الأساسي المُعَّرف في مقال إنشاء صفحتنا الرئيسية)، والتي بدورها تربطها بإعداد STATIC_URL، وبالتالي يمكن رفع الملفات الثابتة إلى أيّ مضيف ويمكنك تحديث تطبيقك للعثور عليها باستخدام هذا الإعداد. تُستخدَم الأداة collectstatic لتجميع الملفات الثابتة في المجلد الذي يحدّده إعداد المشروع STATIC_ROOT، إذ تُستدعَى باستخدام الأمر التالي: python3 manage.py collectstatic تشغِّل Railway أداة collectstatic في هذا المقال قبل رفع التطبيق، مما يؤدي إلى نسخ جميع الملفات الثابتة في التطبيق إلى الموقع المُحدَّد في STATIC_ROOT، ثم تجد مكتبة Whitenoise الملفات في الموقع الذي يحدّده STATIC_ROOT افتراضيًا وتخدّمها في عنوان URL الأساسي الذي يحدّده STATIC_URL. الملف settings.py افتح الملف "/locallibrary/settings.py" وانسخ الضبط الآتي في نهاية الملف، إذ يجب أن يكون BASE_DIR مُعرَّفًا مسبقًا في ملفك، ويمكن أن يكون STATIC_URL معرَّفًا ضمن الملف عند إنشائه، ويمكنك أيضًا حذف الإشارة إليه السابقة المكررة بالرغم من أنها لن تسبب أيّ ضرر. # الملفات الثابتة (ملفات CSS و JavaScript والصور) # https://docs.djangoproject.com/en/4.0/howto/static-files/ # مسار المجلد المطلق حيث ستجمع أداة collectstatic الملفات الثابتة للنشر. STATIC_ROOT = BASE_DIR / 'staticfiles' # عنوان URL المراد استخدامه عند الإشارة إلى الملفات الثابتة (أي من حيث ستُخدَّم) STATIC_URL = '/static/' سنخدّم الملف باستخدام المكتبة WhiteNoise، والتي سنثبّتها ونضبطها في القسم التالي. مكتبة Whitenoise هناك العديد من الطرق لتخديم الملفات الثابتة في بيئة الإنتاج، فمثلًا رأينا إعدادات جانغو ذات الصلة في الأقسام السابقة. يوفّر مشروع WhiteNoise إحدى أسهل الطرق لتخديم الملفات الثابتة مباشرةً من خادم Gunicorn في بيئة الإنتاج. تستدعي Railway أداة collectstatic لتحضير ملفاتك الثابتة لتستخدمها المكتبة WhiteNoise بعد رفع تطبيقك. اطّلع على توثيق المكتبة WhiteNoise للحصول على شرح لكيفية عملها وسبب عَدّ عملية التقديم Implementation هذه طريقةً فعالة نسبيًا لتخديم هذه الملفات. سنوضح فيما يلي خطوات إعداد المكتبة WhiteNoise لاستخدامها مع المشروع. تثبيت المكتبة whitenoise ثبّت المكتبة whitenoise محليًا باستخدام الأمر التالي: pip3 install whitenoise الملف settings.py افتح الملف "/locallibrary/settings.py"، وابحث عن الإعداد MIDDLEWARE وضف WhiteNoiseMiddleware بالقرب من أعلى القائمة وبعد SecurityMiddleware مباشرةً لتثبيت مكتبة WhiteNoise في تطبيق جانغو كما يلي: MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'whitenoise.middleware.WhiteNoiseMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] يمكنك اختياريًا تقليل حجم الملفات الثابتة عند تخديمها، إذ يُعَد ذلك فعّالًا أكثر، فما عليك سوى إضافة ما يلي في نهاية الملف "/locallibrary/settings.py": # تخديم ملف ثابت مبسط # https://pypi.org/project/whitenoise/ STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' لست بحاجة إلى إجراء أيّ شيء آخر لضبط مكتبة WhiteNoise لأنها تستخدم إعدادات مشروعك لكل من STATIC_ROOT و STATIC_URL افتراضيًا. ملف المتطلبات Requirements يجب تخزين متطلبات بايثون لتطبيقك في الملف "requirements.txt" ضمن جذر مستودعك، ثم ستثبّت Railway بعد ذلك هذه المتطلبات تلقائيًا عندما تعيد بناء بيئتك، إذ يمكنك إنشاء هذا الملف باستخدام الأداة pip في سطر الأوامر. شغّل الأمر التالي في جذر المستودع: pip3 freeze > requirements.txt يجب أن يحتوي الملف "requirements.txt" على العناصر الآتية على الأقل بعد تثبيت جميع الاعتماديات السابقة (يمكن أن تكون أرقام النسخ مختلفة)، لذا احذف أيّ اعتماديات أخرى ليست موجودة فيما يلي، إن لم تضِفها صراحةً لتطبيقك: 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 ملف وقت التشغيل Runtime يخبر الملف runtime.txt -إذا كان مُعرَّفًا مسبقًا- منصةَ Railway بنسخة بايثون التي يجب استخدامها. أنشئ هذا الملف في جذر المستودع وضِف إليه النص التالي: python-3.10.2 ملاحظة: لا يدعم مزوّدو الاستضافة بالضرورة جميع النسخ الثانوية لوقت تشغيل بايثون، إذ سيستخدمون أقرب نسخة مدعومة للقيمة التي تحددها. إعادة الاختبار وحفظ التغييرات في غيت هب اختبر الموقع أولًا محليًا مرةً أخرى قبل المتابعة وتأكّد من عدم تعطله بسبب أيِّ من التغييرات التي ذكرناها سابقًا، لذا شغّل خادم الويب الخاص بالتطوير كما هو معتاد، ثم تحقق من أن الموقع لا يزال يعمل كما هو متوقع على متصفحك كما يلي: python3 manage.py runserver لندفع باستخدام الأمر push بعد ذلك التغييرات إلى غيت هب، لذا أدخِل الأوامر التالية في الطرفية بعد الانتقال إلى مستودعنا المحلي: git add -A git commit -m "Added files and changes required for deployment" git push origin main يجب أن نكون جاهزين الآن لبدء نشر موقع المكتبة المحلية LocalLibrary على Railway. الحصول على حساب على Railway يجب أولًا إنشاء حساب لبدء استخدام Railway باتباع الخطوات التالية: اذهب إلى موقع Railway الرسمي وانقر على رابط تسجيل الدخول Login في شريط الأدوات العلوي. اختر "GitHub" في النافذة المنبثقة لتسجيل الدخول باستخدام اعتماديات غيت هب الخاصة بك. يمكن أن تحتاج بعد ذلك إلى الانتقال إلى بريدك الإلكتروني والتحقق من حسابك. ستسجل بعد ذلك الدخول إلى لوحة تحكم Railway. النشر على Railway من غيت هب يجب الآن إعداد Railway لنشر موقع مكتبتنا من غيت هب، لذا اختر أولًا خيار لوحة التحكم Dashboard من القائمة العلوية للموقع، ثم حدّد زر مشروع جديد New Project: ستعرض Railway قائمةً بالخيارات الخاصة بالمشروع الجديد، بما في ذلك خيار نشر مشروع من قالب أنشأته لأول مرة في حسابك على غيت هب، وعددًا من قواعد البيانات، لذا حدّد خيار النشر "Deploy from GitHub repo". ستُعرَض جميع المشاريع في مستودعات غيت هب التي شاركتها مع Railway أثناء عملية الإعداد، ولكنك ستختار مستودع غيت هب الخاص بموقع المكتبة المحلية: <user-name>/django-locallibrary-tutorial. أكّد النشر من خلال تحديد خيار النشر حالًا "Deploy Now". ستحمّل Railway بعد ذلك مشروعك وتنشره، مع عرض التقدم في تبويب عمليات النشر Deployments، ثم سترى شيئًا يشبه ما يلي عند اكتمال النشر بنجاح: يمكنك النقر على عنوان URL الخاص بالموقع (المُحدَّد في الشكل السابق) لفتح الموقع في المتصفح. لن يعمل الموقع حاليًا، لأن الإعداد لم يكتمل بعد. ضبط ALLOWED_HOSTS و CSRF_TRUSTED_ORIGINS سترى الآن شاشة تنقيح أخطاء كما يلي عند فتح الموقع، وهذا خطأ أمان جانغو الذي ظهر بسبب أن شيفرتك المصدرية لا تعمل على مضيف مسموح به "allowed host": ملاحظة: يُعَد هذا النوع من معلومات تنقيح الأخطاء مفيدًا جدًا في عملية الإعداد، ولكنه يمثل خطرًا أمنيًا عند نشر موقع، لذا سنوضح كيفية تعطيل تنقيح الأخطاء عند رفع وتشغيل الموقع. افتح الملف "/locallibrary/settings.py" في مشروع غيت هب وعدّل الإعداد ALLOWED_HOSTS لتضمين عنوان URL لموقعك على Railway كما يلي: ## مثلًا لعنوان URL لموقع على 'web-production-3640.up.railway.app' ## (ضع عنوان URL الخاص بموقعك بدلًا من السلسلة النصية التالية): ALLOWED_HOSTS = ['web-production-3640.up.railway.app', '127.0.0.1'] # يمكنك بدلًا من ذلك ضبط عنوان URL الأساسي أثناء التطوير # (يمكن أن تقرر تغيير الموقع عدة مرات) # ALLOWED_HOSTS = ['.railway.com','127.0.0.1'] تستخدم التطبيقات الحماية ضد هجمات CSRF، لذا يجب أيضًا ضبط مفتاح CSRF_TRUSTED_ORIGINS. افتح الملف "/locallibrary/settings.py" وضِف سطرًا مثل السطر التالي: ## مثلًا لعنوان URL لموقع على 'web-production-3640.up.railway.app' ## (ضع عنوان URL الخاص بموقعك بدلًا من السلسلة النصية التالية): CSRF_TRUSTED_ORIGINS = ['https://web-production-3640.up.railway.app'] # يمكنك بدلًا من ذلك ضبط عنوان URL الأساسي أثناء التطوير في هذا المقال # CSRF_TRUSTED_ORIGINS = ['https://*.railway.app'] احفظ بعد ذلك إعداداتك وثبّتها في مستودع غيت هب. ستحدّث Railway تطبيقَك وتعيد نشره تلقائيًا. git add -A git commit -m 'Update ALLOWED_HOSTS and CSRF_TRUSTED_ORIGINS with site URL' git push origin main تجهيز وتوصيل قاعدة بيانات Postgres SQL يجب الآن إنشاء قاعدة بيانات Postgres وتوصيلها بتطبيق جانغو الذي نشرناه، فإذا فتحتَ الموقع الآن، فستحصل على خطأ جديد لأنه لا يمكن الوصول إلى قاعدة البيانات. سننشئ قاعدة البيانات بوصفها جزءًا من مشروع التطبيق، ولكن يمكنك إنشاء قاعدة البيانات في مشروع منفصل خاص بها. حدّد خيار لوحة التحكم Dashboard على Railway من القائمة العلوية للموقع، ثم حدّد مشروع تطبيقك، إذ تحتوي لوحة التحكم حاليًا على خدمة واحدة فقط لتطبيقك، والتي يمكن تحديدها لضبط المتغيرات وتفاصيل أخرى للخدمة. يمكن تحديد زر الإعدادات Settings لتغيير الإعدادات على مستوى المشروع. حدّد زر جديد New، والذي يُستخدَم لإضافة خدمات إلى المشروع. اختر قاعدة البيانات Database عندما يُطلَب منك تحديد نوع الخدمة المراد إضافتها كما يلي: اختر بعد ذلك خيار الإضافة Add PostgreSQL لبدء إضافة قاعدة البيانات: ستجهز Railway بعد ذلك خدمةً تحتوي على قاعدة بيانات فارغة في المشروع نفسه، وسترى عند الانتهاء كلًا من خدمات التطبيق وقاعدة البيانات في عرض المشروع كما يلي: اختر خدمة PostgreSQL لعرض معلومات حول قاعدة البيانات، ثم افتح نافذة "الاتصال Connect" وانسخ "عنوان URL لاتصال Postgres"، وهو العنوان الذي ضبطنا موقع المكتبة المحلية locallibrary لقراءته بوصفه متغير بيئة. يمكن جعل تطبيق المكتبة يصل إلى هذا العنوان من خلال إضافته إلى عملية التطبيق باستخدام متغير بيئة، لذا افتح أولًا خدمة التطبيق، ثم حدّد نافذة المتغيرات Variables واضغط على زر متغير جديد New Variable. أدخِل اسم المتغير DATABASE_URL وعنوان URL للاتصال الذي نسخته لقاعدة البيانات، وسيظهر لديك ما يلي: حدّد زر الإضافة Add لإضافة المتغير، ثم سيُعاد نشر المشروع. إذا فتحت المشروع الآن، فسيُعرَض تمامًا كما عُرِض محليًا، ولكن لاحظ أنه لا توجد طريقة لملء المكتبة بالبيانات حتى الآن، لأننا لم ننشئ حساب مستخدم مميز Superuser بعد، إذ سنفعّل ذلك باستخدام أداة CLI على حاسوبنا المحلي. تثبيت العميل نزّل وثبّت عميل Railway لنظام تشغيلك المحلي باتباع التعليمات الواردة في توثيق Railway. ستتمكن من تشغيل الأوامر بعد تثبيت العميل. تتضمن بعض العمليات الأكثر أهمية نشر المجلد الحالي لحاسوبك إلى مشروع Railway المرتبط به دون الحاجة للرفع على غيت هب، وتشغيل مشروع جانغو محليًا باستخدام الإعدادات نفسها الموجودة على خادم الإنتاج، وسنعرض ذلك في الأقسام التالية. يمكنك الحصول على قائمة بجميع الأوامر من خلال إدخال الأمر التالي في الطرفية: railway help ملاحظة: سنستخدم في القسم التالي الأمرين railway login و railway link لربط المشروع الحالي بمجلدٍ ما، وإذا سجّل النظام خروجك، فيجب استدعاء كلا الأمرين مرةً أخرى لإعادة ربط المشروع. ضبط مستخدم مميز يمكن إنشاء مستخدم مميز من خلال استدعاء أمر جانغو createsuperuser لقاعدة بيانات الإنتاج، وهي العملية نفسها التي أجريناها محليًا في مقال مدير موقع جانغو. لا توفّر Railway وصولًا مباشرًا من الطرفية إلى الخادم، ولا يمكننا إضافة هذا الأمر إلى الملف Procfile لأنه تفاعلي، لذا نستدعي هذا الأمر محليًا في مشروع جانغو عندما يكون متصلًا بقاعدة بيانات الإنتاج، إذ يسهّل عميل Railway ذلك من خلال توفير آلية لتشغيل الأوامر محليًا باستخدام متغيرات بيئة خادم الإنتاج نفسها، بما في ذلك سلسلة اتصال قاعدة البيانات. افتح أولًا الطرفية أو موجه الأوامر ضمن نسخ غيت لمشروع مكتبتنا المحلية، ثم سجّل الدخول إلى حساب متصفحك باستخدام الأمر login أو login --browserless. اتبع أيّ طلبات وإرشادات واردة من العميل أو موقع الويب لإكمال تسجيل الدخول: railway login اربط مجلد locallibrary الحالي بمشروع Railway المرتبط به باستخدام الأمر التالي بعد تسجيل الدخول. لاحظ أنه يجب تحديد أو إدخال مشروع معين عندما يُطلَب منك ذلك: railway link ربطنا المجلد المحلي والمشروع، ويمكنك الآن تشغيل مشروع جانغو المحلي مع إعدادات من بيئة الإنتاج، ولكن تأكّد أولًا من أن بيئة تطوير جانغو العادية جاهزة، ثم استدعِ الأمر التالي مع إدخال الاسم والبريد الإلكتروني وكلمة المرور المطلوبة: railway run python manage.py createsuperuser ينبغي أن تكون الآن قادرًا على فتح منطقة مدير موقعك https://[your-url].railway.app/admin/ وملء قاعدة البيانات كما تعلمنا في مقال موقع مدير جانغو. إعداد متغيرات الضبط يجب الآن أن نجعل الموقع آمنًا، إذ يجب تعطيل تسجيل تنقيح الأخطاء وضبط مفتاح CSRF السري، إذ عملنا مسبقًا في هذا المقال على قراءة القيم المطلوبة من متغيرات البيئة DJANGO_DEBUG و DJANGO_SECRET_KEY. افتح شاشة المعلومات الخاصة بالمشروع، وحدّد نافذة المتغيرات Variables التي يجب أن تحتوي على DATABASE_URL كما يلي: هناك العديد من الطرق لتوليد مفتاح سري مُعمَّى، ولكن توجد طريقة بسيطة وهي تشغيل أمر بايثون التالي على حاسوبك الخاص بالتطوير: python -c "import secrets; print(secrets.token_urlsafe())" حدّد زر متغير جديد New Variable وأدخِل المفتاح DJANGO_SECRET_KEY مع قيمتك السرية، ثم اضغط إضافة Add. أدخِل بعد ذلك المفتاح DJANGO_DEBUG مع القيمة False، إذ يجب أن يبدو الضبط النهائي للمتغيرات كما يلي: تنقيح الأخطاء Debugging يوفّر عميل Railway الأمر logs لإظهار السجلات الأخيرة (يتوفر سجل كامل على الموقع لكل مشروع): railway logs اطلع على توثيق جانغو لمعلومات أكثر. الخلاصة وصلنا إلى نهاية مقالنا حول إعداد تطبيقات جانغو في بيئة الإنتاج، وكذلك إلى نهاية سلسلة مقالات إطار عمل جانغو، لذا نأمل أنها كانت مفيدة لك، ولا تنسَ أنه يمكنك التحقق من النسخة الكاملة من الشيفرة المصدرية على غيت هب GitHub. ترجمة -وبتصرُّف- للمقال Django Tutorial Part 11: Deploying Django to production. اقرأ المزيد المقال التالي: تعرف على أمان تطبيقات جانغو المقال السابق: تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو مرحلة نشر التطبيق في عملية تطوير الويب. كيفية نشر الملفات الثابتة (توثيق جانغو) نشر تطبيق Django على Heroku (توثيق Heroku) نشر تطبيق جانغو آمن وقابل للتوسيع باستخدام كوبيرنتس Kubernetes إعداد تطبيق جانغو جاهز للنشر مع قاعدة بيانات Postgres وخادم Nginx و Gunicorn
-
يصبح اختبار مواقع الويب أصعب يدويًا عند نموها، إذ يصبح هناك مزيدٌ من الأمور لاختبارها، وتصبح التفاعلات بين المكونات أكثر تعقيدًا، إذ يمكن أن يؤثر تغيير بسيط في منطقةٍ ما على مناطق أخرى، لذلك ستكون هناك حاجة إلى مزيد من التغييرات لضمان استمرار عمل كل شيء بنجاح وعدم ظهور أخطاء عند إجراء مزيدٍ من التغييرات. تتمثل إحدى طرق التخفيف من هذه المشاكل في كتابة اختبارات آلية، والتي يمكن تشغيلها بسهولة وموثوقية في كل مرة تجري فيها تغييرًا. يوضح هذا المقال كيفية أتمتة اختبار الوحدة Unit Testing لموقعك باستخدام إطار اختبار جانغو Django. المتطلبات الأساسية: اطّلع على جميع المقالات السابقة من هذه السلسلة، بما في ذلك المقال الخاص بالعمل مع الاستمارات. الهدف: فهم كيفية كتابة اختبارات الوحدة لمواقع الويب المستندة إلى جانغو. يحتوي موقع المكتبة المحلية Local Library حاليًا على صفحات لعرض قوائم بجميع الكتب والمؤلفين، والعروض التفصيلية لعناصر Book و Author، وصفحة لتجديد عناصر BookInstance، وصفحات لإنشاء عناصر Author وتحديثها وحذفها، إضافةً إلى سجلات Book أيضًا إذا أكملت قسم التحدي في المقال السابق. يمكن أن يستغرق التنقل يدويًا إلى كل صفحة والتحقق السطحي من أن كل شيء يعمل كما هو متوقع عدة دقائق حتى مع هذا الموقع الصغير نسبيًا، وسيزداد الوقت المطلوب للتحقق يدويًا من أن كل شيء يعمل بصورة صحيحة مع إجراء التغييرات ونمو الموقع. إذا استمرينا على هذا النحو، فسنقضي في النهاية معظم وقتنا في الاختبار ووقتًا قصيرًا جدًا في تحسين شيفرتنا البرمجية. يمكن أن تساعد الاختبارات الآلية في حل هذه المشكلة، إذ يمكن تشغيلها بصورة أسرع بكثير من الاختبارات اليدوية، ويمكن اختبارها بمستوًى أقل من التفاصيل، واختبار الوظيفة نفسها تمامًا في كل مرة (لا يُعَد المختبرون البشر قريبين من هذه الموثوقية). يمكن تنفيذ هذه الاختبارات اليدوية بصورة أكثر انتظامًا لأنها سريعة، وإذا فشل الاختبار، فسيؤشّر بدقة إلى المكان الذي لا تعمل فيه الشيفرة البرمجية كما هو متوقع. يمكن أن تعمل الاختبارات الآلية بوصفها أول مستخدم من العالم الحقيقي لشيفرتك البرمجية، مما يجبرك على أن تكون صارمًا في تحديد وتوثيق كيف ينبغي أن يتصرف موقعك، وتكون في أغلب الأحيان أساسًا لأمثلة شيفرتك البرمجية وتوثيقها. لذا تبدأ بعض عمليات تطوير البرمجيات بتعريف الاختبار وتقديمه، ثم تُكتَب الشيفرة البرمجية لمطابقة السلوك المطلوب، مثل التطوير المُقاد بالاختبار Test-Driven Development والتطوير المُقاد بالسلوك Behavior-Driven Development. يوضح هذا المقال كيفية كتابة الاختبارات الآلية لإطار عمل جانغو من خلال إضافة عدد من الاختبارات إلى موقع المكتبة المحلية LocalLibrary. تتألف هذه السلسلة الفرعية من السلسلة الأشمل تعلم تطوير الويب من المقالات التالية: مدخل إلى إطار عمل الويب جانغو Django إعداد بيئة تطوير تطبيقات جانغو تطبيق عملي لتعلم جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية تطبيق عملي لتعلم جانغو - الجزء السادس: إدارة الجلسات Sessions تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج تعرف على أمان تطبيقات جانغو أنواع الاختبارات هناك العديد من الأنواع والمستويات والتصنيفات للاختبارات وأساليبها، وأهم الاختبارات الآلية هي: اختبارات الوحدة Unit Tests: التي تتحقق من السلوك الوظيفي للمكونات الفردية على مستوى الأصناف والدوال غالبًا. الاختبارات التراجعية Regression Tests: هي الاختبارات التي تعيد إنتاج أخطاء قديمة، إذ يُشغَّل كل اختبار للتحقق من إصلاح الخطأ في البداية، ثم يُعَاد تشغيله للتأكد من عدم ظهوره مرةً أخرى بعد إجراء تغييرات لاحقة على الشيفرة البرمجية. اختبارات التكامل Integration Tests: تتحقق من كيفية عمل مجموعات المكونات عند استخدامها مع بعضها بعضًا، إذ تكون اختبارات التكامل على دراية بالتفاعلات المطلوبة بين المكونات، ولكنها ليست بالضرورة على دراية بالعمليات الداخلية لكل مكون. يمكن أن تغطي اختبارات التكامل مجموعات بسيطة من المكونات في موقع الويب الكامل. ملاحظة: تشمل أنواع الاختبارات الشائعة الأخرى اختبارات الصندوق الأسود والصندوق الأبيض والاختبارات اليدوية والآلية واختبارات الكناري Canary والدخان Smoke والمطابقة والقبول والوظيفية والنظام والأداء والحِمل واختبارات الإجهاد. ماذا يقدم جانغو لعملية الاختبار؟ يُعَد اختبار موقع الويب مهمةً معقدة، لأنه يتكون من عدة طبقات من الشيفرة البرمجية، بدءًا من معالجة الطلبات على مستوى طلبات HTTP، إلى استعلامات النموذج، إلى التحقق من صحة الاستمارة ومعالجتها، وإظهار القالب. يوفّر جانغو إطار عمل للاختبار مع تسلسل هرمي صغير من الأصناف التي تعتمد على مكتبة بايثون المعيارية unittest، إذ يُعَد إطار عمل الاختبار هذا مناسبًا لكل من اختبارات الوحدة والتكامل، فهو يضيف توابع وأدوات واجهة برمجة التطبيقات API للمساعدة في اختبار الويب وسلوك جانغو المُحدَّد، مما يسمح لك بمحاكاة الطلبات وإدخال بيانات الاختبار وفحص خرج تطبيقك. يوفر جانغو أيضًا واجهة برمجة تطبيقات (LiveServerTestCase) وأدوات لاستخدام أطر عمل اختبار مختلفة، فمثلًا يمكنك التكامل مع إطار عمل سيلينيوم Selenium الشهير لمحاكاة مستخدم يتفاعل مع متصفح مباشرةً. يمكنك كتابة اختبار من خلال اشتقاق أيٍّ من أصناف اختبار جانغو الأساسية unittest، مثل SimpleTestCase و TransactionTestCase و TestCase و LiveServerTestCase، ثم كتابة توابع منفصلة للتحقق من أن وظيفة معينة تعمل كما هو متوقع، إذ تستخدم الاختبارات توابع "assert" لاختبار أن ناتج التعابير هو القيمة True أو القيمة False أو تساوي قيمتين أو غير ذلك. ينفّذ إطار العمل توابع الاختبار المختارة في الأصناف المشتقة عند بدء تشغيل الاختبار، إذ تُشغَّل توابع الاختبار بطريقة مستقلة مع سلوك الإعداد و/أو الهدم tear-down الشائع المُعرَّف في الصنف كما هو موضح فيما يلي: class YourTestClass(TestCase): def setUp(self): # تشغيل الإعداد قبل كل تابع اختبار pass def tearDown(self): # تنظيف التشغيل بعد كل تابع اختبار pass def test_something_that_will_pass(self): self.assertFalse(False) def test_something_that_will_fail(self): self.assertTrue(False) أفضل صنف أساسي لمعظم الاختبارات هو الصنف django.test.TestCase، الذي ينشئ قاعدة بيانات نظيفة قبل تشغيل اختباراته، ويشغِّل كل دالة اختبار في تعاملاته الخاصة. يمتلك هذا الصنف أيضًا صنف الاختبار Client الذي يمكنك استخدامه لمحاكاة مستخدم يتفاعل مع الشيفرة البرمجية على مستوى العرض View. سنركز في الأقسام التالية على اختبارات الوحدة المُنشَأة باستخدام صنف TestCase الأساسي. ملاحظة: يُعَد الصنف django.test.TestCase سهل الاستخدام، ولكنه يمكن أن يؤدي إلى أن تكون بعض الاختبارات أبطأ مما يجب، إذ لن يحتاج كل اختبار إلى إعداد قاعدة بياناته أو محاكاة تفاعل العرض. قد ترغب في استبدال بعض اختباراتك بأصناف الاختبار الأبسط المتاحة بعد أن تتعرف على ما يمكنك تطبيقه باستخدام هذا الصنف. ماذا يجب أن تختبر؟ يجب أن تختبر جميع جوانب شيفرتك البرمجية، ولكن لا حاجة إلى اختبار أيّ مكتبات أو وظائف متوفرة مثل جزء من بايثون أو جانغو. ليكن لدينا مثلًا نموذج المؤلف Author الآتي، فلا حاجة لاختبار التخزين الصحيح للحقلين first_name و last_name بوصفهما حقلين من النوع CharField في قاعدة البيانات صراحةً لأن ذلك شيء حدّده جانغو، بالرغم من أنك حتمًا ستختبر هذه الوظيفة أثناء التطوير، ولا حاجة أيضًا لاختبار التحقق من أن الحقل date_of_birth حقل تاريخ، لأنه شيء طبّقه جانغو، لكن ينبغي التحقق من النص المستخدم مع التسميات "First name" و "Last name" و "Date of birth" و "Died" وحجم الحقل المخصص للنص (100 محرف)، لأنها جزء من تصميمك وشيء يمكن أن يُكسَر أو يتغيّر في المستقبل. class Author(models.Model): first_name = models.CharField(max_length=100) last_name = models.CharField(max_length=100) date_of_birth = models.DateField(null=True, blank=True) date_of_death = models.DateField('Died', null=True, blank=True) def get_absolute_url(self): return reverse('author-detail', args=[str(self.id)]) def __str__(self): return '%s, %s' % (self.last_name, self.first_name) ينبغي أيضًا التحقق من أن التابعين المُخصَّصين get_absolute_url() و __str__() يتصرفان بالطريقة المطلوبة لأنهما يمثلان شيفرتك أو منطق عملك. يمكنك في حالة التابع get_absolute_url() الوثوق بتقديم تابع جانغو reverse() بصورة صحيحة، لذا فإنّ ما تختبره هو تعريف العرض المرتبط به. ملاحظة: يمكن أن يلاحظ القراء المتمرسون أننا نرغب في تقييد تاريخ الميلاد والوفاة بقيم مناسبة، والتحقق من أن الوفاة تأتي بعد الميلاد، ويمكن تحقيق هذا التقييد في جانغو من خلال إضافته إلى أصناف استمارتك. يمكنك تعريف أدوات تحقق لحقول النموذج وأدوات تحقق من النماذج نفسها، ولكنها تُستخدَم فقط على مستوى الاستمارة إذا استدعاها التابع clean() الخاص بالنموذج، وهذا يتطلب الصنف ModelForm، أو استدعاء التابع clean() الخاص بالنموذج على وجه التحديد. لنبدأ الآن في التعرّف على كيفية تعريف الاختبارات وتشغيلها. نظرة عامة على بنية الاختبار لنلقِ أولًا نظرة سريعة على مكان وكيفية تعريف الاختبارات قبل أن ندخل في تفاصيل ما الذي يجب اختباره. يستخدم جانغو اكتشاف الاختبار المبني مسبقًا في الوحدة unittest، والذي سيكتشف الاختبارات ضمن مجلد العمل الحالي في أي ملف يسمى بالنمط test*.py، إذ يمكنك استخدام أي بنية تريدها شريطة أن تسمي الملفات بصورة مناسبة. نوصي بإنشاء وحدة لشيفرة اختبارك، وأن يكون لديك ملفات منفصلة للنماذج Modelsوالعروض Views والاستمارات Forms وأيّ أنواع أخرى من الشيفرة البرمجية التي يجب اختبارها كما يلي: catalog/ /tests/ __init__.py test_models.py test_forms.py test_views.py أنشئ بنية الملفات السابقة في مشروع المكتبة المحلية LocalLibrary، وينبغي أن يكون الملف "init.py" فارغًا، مما يخبر بايثون أن المجلد هو حزمة package. يمكنك إنشاء ملفات الاختبار الثلاثة من خلال نسخ وإعادة تسمية ملف الاختبار الهيكلي "/catalog/tests.py". ملاحظة: أُنشِئ ملف اختبار الهيكلي /catalog/tests.py تلقائيًا عند بناء موقع ويب جانغو الهيكلي، إذ يُعَد وضع جميع اختباراتك ضمنه أمرًا "مسموحًا به"، ولكن إذا أجريت الاختبار بصورة صحيحة، فسينتهي بك الأمر سريعًا مع ملف اختبار كبير جدًا ولا يمكن إدارته، لذا احذف الملف الهيكلي لأننا لن نحتاج إليه. افتح الملف "/catalog/tests/test_models.py"، الذي ينبغي أن يستورد الصنف django.test.TestCase كما يلي: from django.test import TestCase # أنشئ اختباراتك هنا ستضيف غالبًا صنفَ اختبار لكل نموذج/عرض/استمارة تريد اختبارها باستخدام توابع فردية لاختبار وظائف معينة، ويمكن أن ترغب في حالات أخرى في الحصول على صنف منفصل لاختبار حالة استخدام محددة باستخدام دوال اختبار فردية تختبر جوانب حالة الاستخدام، مثل صنف اختبار صحة حقل النموذج بصورة صحيحة باستخدام دوال لاختبار كلٍّ من حالات الفشل المحتملة. تُعَد البنية أمرًا متروكًا لك، ولكن يُفضَّل أن تكون متناسقة. أضِف صنف الاختبار التالي إلى نهاية الملف، إذ يوضح هذا الصنف كيفية بناء صنف حالة اختبار من خلال اشتقاق الصنف TestCase: class YourTestClass(TestCase): @classmethod def setUpTestData(cls): print("setUpTestData: Run once to set up non-modified data for all class methods.") pass def setUp(self): print("setUp: Run once for every test method to setup clean data.") pass def test_false_is_false(self): print("Method: test_false_is_false.") self.assertFalse(False) def test_false_is_true(self): print("Method: test_false_is_true.") self.assertTrue(False) def test_one_plus_one_equals_two(self): print("Method: test_one_plus_one_equals_two.") self.assertEqual(1 + 1, 2) يعرّف الصنف الجديد تابعين يمكنك استخدامهما لضبط الاختبار المسبق لإنشاء أيّ نماذج أو كائنات أخرى ستحتاجها للاختبار مثلًا، وهما: setUpTestData(): يُستدعَى مرةً واحدة في بداية تشغيل الاختبار للإعداد على مستوى الصنف، ويمكنك استخدامه لإنشاء كائنات لن تُعدَّل أو تُغيَّر في أيٍّ من توابع الاختبار. setUp(): يُستدعَى قبل دوال الاختبار لإعداد الكائنات التي يمكن أن يعدّلها الاختبار، وستحصل كل دالة اختبار على نسخة "جديدة" من هذه الكائنات. ملاحظة: تحتوي أصناف الاختبار على تابع tearDown() التي لم نستخدمه، ولا يُعَد هذا التابع مفيدًا بخاصة لاختبارات قاعدة البيانات، لأن الصنف الأساسي TestCase يهتم بهدم قاعدة البيانات نيابةً عنك. لدينا فيما يلي عددٌ من توابع الاختبار التي تستخدم دوال Assert لاختبار ما إذا كانت الشروط صحيحة أو خاطئة أو متساوية، وهي: AssertTrue و AssertFalse و AssertEqual. إذا لم يُقيَّم الشرط كما هو متوقع، فسيفشل الاختبار وسيرسَل الخطأ إلى طرفيتك. تُعَد AssertTrue و AssertFalse و AssertEqual تأكيدات معيارية توفّرها المكتبة unittest، وهناك تأكيدات معيارية أخرى في إطار العمل، وتأكيدات خاصة بجانغو لاختبار ما إذا كان العرض يعيد التوجيه assertRedirects، أو يستخدم قالب معين assertTemplateUsed وغير ذلك. ملاحظة: لا ينبغي عادةً تضمين دوال print() في اختباراتك كما هو موضح سابقًا، لكننا نطبّق ذلك في القسم التالي فقط لتتمكّن من رؤية ترتيب استدعاء دوال الإعداد في الطرفية. كيفية تشغيل الاختبارات أسهل طريقة لتشغيل جميع الاختبارات هي استخدام الأمر التالي: python3 manage.py test سيؤدي هذا الأمر إلى اكتشاف جميع الملفات المُسمَّاة باستخدام النمط test*.py في المجلد الحالي وتشغيل جميع الاختبارات التي تعرّفها أصناف أساسية مناسبة، فلدينا هنا عدد من ملفات الاختبار، ولكن يحتوي الملف /catalog/tests/test_models.py فقط على الاختبارات حاليًا. ستقدم الاختبارات افتراضيًا تقريرًا فرديًا فقط عن حالات فشل الاختبار ويتبعه ملخص الاختبار. ملاحظة: إذا حصلتَ على أخطاء مشابهة للخطأ التالي: ValueError: Missing staticfiles manifest entry... يمكن أن يكون هذا الخطأ بسبب عدم تشغيل الاختبار للأمر collectstatic افتراضيًا، وبسبب أن تطبيقك يستخدم صنف تخزين يتطلب ذلك. اطلع على manifest_strict لمزيد من المعلومات. هناك عدد من الطرق التي يمكنك من خلالها التغلب على هذه المشكلة، وأسهلها هو تشغيل الأمر collectstatic قبل تشغيل الاختبارات كما يلي: python3 manage.py collectstatic شغّل الاختبارات في المجلد الجذر لمشروع المكتبة المحلية LocalLibrary، ويجب أن ترى خرجًا مثل الخرج التالي: > 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'... نرى في الخرج السابق أن لدينا فشل اختبار واحد، ويمكننا أن نرى فعلًا الدالة التي فشلت وسبب فشلها، إذ يُعَد هذا الفشل متوقعًا، لأن القيمة False ليست القيمة True. ملاحظة: أهم شيء يجب تعلّمه من خرج الاختبار السابق هو أنه يُفضَّل استخدام أسماء وصفية أو ذات معنًى لكائناتك وتوابعك. يُظهِر خرج دوال print() كيفية استدعاء تابع setUpTestData() مرةً واحدةً للصنف واستدعاء التابع setUp() قبل كل تابع. تذكّر أنك لن تضيف عادةً هذا النوع من دوال print() إلى اختباراتك. توضّح الأقسام التالية كيفية تشغيل اختبارات محددة، وكيفية التحكم في مقدار المعلومات التي تعرضها الاختبارات. عرض مزيد من معلومات الاختبار إذا أردتَ الحصول على مزيد من المعلومات حول تشغيل الاختبار، فيمكنك تغيير قيمة verbosity، فمثلًا يمكنك ضبطها على القيمة "2" لسرد حالات نجاح الاختبار إضافةً إلى حالات الفشل ومجموعة كاملة من المعلومات حول كيفية إعداد قاعدة بيانات الاختبار: python3 manage.py test --verbosity 2 مستويات verbosity المسموح بها هي: 0 و 1 و 2 و 3 والقيمة الافتراضية هي "1". تسريع الاختبارات إذا كانت اختباراتك مستقلة، فيمكنك تسريعها على جهاز متعدد المعالجات من خلال تشغيلها على التوازي، إذ يؤدي استخدام الأمر test --parallel auto الآتي إلى تشغيل عملية اختبار واحدة لكل نواة متاحة، ويُعَد استخدام auto اختياريًا، ويمكنك تحديد عدد معين من الأنوية لاستخدامها. python3 manage.py test --parallel auto اطّلع على DJANGO_TEST_PROCESSES لمزيدٍ من المعلومات، بما في ذلك ما يجب عليك فعله إن لم تكن اختباراتك مستقلة. تشغيل اختبارات محددة إذا أردتَ تشغيل مجموعة فرعية من اختباراتك، فيمكنك ذلك من خلال تحديد المسار النقطي الكامل للحزمة (أو لمجموعة الحزم) أو الوحدة أو الصنف TestCase الفرعي أو التابع كما يلي: # تشغيل الوحدة المُحدَّدة python3 manage.py test catalog.tests # تشغيل الوحدة المُحدَّدة python3 manage.py test catalog.tests.test_models # تشغيل الصنف المُحدَّد python3 manage.py test catalog.tests.test_models.YourTestClass # تشغيل التابع المُحدَّد python3 manage.py test catalog.tests.test_models.YourTestClass.test_one_plus_one_equals_two خيارات مشغل الاختبارات الأخرى يوفر مشغِّل الاختبارات العديد من الخيارات الأخرى، بما في ذلك القدرة على خلط الاختبارات --shuffle، وتشغيلها في وضع تنقيح الأخطاء --debug-mode، واستخدام مسجِّل بايثون Python logger لالتقاط النتائج. اطلع على توثيق مشغّل اختبار جانغو لمزيد من المعلومات، كما يمكنك الاطلاع على المقال التالي كيف تستخدم التسجيل Logging في بايثون 3 على أكاديمية حسوب لمزيدٍ من المعلومات حوال التسجيل. اختبارات موقع المكتبة المحلية LocalLibrary نعرف الآن كيفية تشغيل اختباراتنا ونوع الأمور التي يجب اختبارها، ولنلقِ الآن نظرةً على بعض الأمثلة العملية. ملاحظة: لن نكتب جميع الاختبارات الممكنة، ولكن سيعطيك هذا القسم فكرةً عن كيفية عمل الاختبارات، وما الأمور الأخرى التي يمكنك تطبيقها. النماذج Models يجب أن نختبر أيّ شيء يمثل جزءًا من تصميمنا أو أي شيء تعرّفه الشيفرة البرمجية التي كتبناها بدون المكتبات أو الشيفرة البرمجية التي اختبرها جانغو أو فريق تطوير بايثون مسبقًا. ليكن لدينا نموذج المؤلف Author الآتي، إذ يجب أن نختبر تسميات جميع الحقول، لأن لدينا تصميمًا يوضّح ما يجب أن تكون عليه هذه القيم بالرغم من أننا لم نحدّد معظمها صراحةً، فإن لم نختبر هذه القيم، فلن نعرف أن تسميات الحقول لها قيمها المقصودة الخاصة. وبالمثل، نعرف أن جانغو سيُنشِئ حقلًا بطول محدد، ولكن يُفضَّل تحديد اختبار لهذا الطول للتأكد من تطبيقه كما هو مُخطَّط له. class Author(models.Model): first_name = models.CharField(max_length=100) last_name = models.CharField(max_length=100) date_of_birth = models.DateField(null=True, blank=True) date_of_death = models.DateField('Died', null=True, blank=True) def get_absolute_url(self): return reverse('author-detail', args=[str(self.id)]) def __str__(self): return f'{self.last_name}, {self.first_name}' افتح الملف "/catalog/tests/test_models.py"، وضع شيفرة الاختبار الآتية لنموذج المؤلف Author مكان أي شيفرة برمجية موجودة مسبقًا، إذ سترى أننا نستورد الصنف TestCase أولًا ونشتق منه صنف الاختبار AuthorModelTest باستخدام اسم وصفي لنتمكّن بسهولة من تحديد أيّ اختبارات فاشلة في خرج الاختبار، ثم نستدعي التابع setUpTestData() لإنشاء كائن المؤلف الذي سنستخدمه دون تعديل في أيٍّ من الاختبارات. from django.test import TestCase from catalog.models import Author class AuthorModelTest(TestCase): @classmethod def setUpTestData(cls): # إعداد الكائنات غير المُعدَّلة التي تستخدمها جميع توابع الاختبار Author.objects.create(first_name='Big', last_name='Bob') def test_first_name_label(self): author = Author.objects.get(id=1) field_label = author._meta.get_field('first_name').verbose_name self.assertEqual(field_label, 'first name') def test_date_of_death_label(self): author = Author.objects.get(id=1) field_label = author._meta.get_field('date_of_death').verbose_name self.assertEqual(field_label, 'died') def test_first_name_max_length(self): author = Author.objects.get(id=1) max_length = author._meta.get_field('first_name').max_length self.assertEqual(max_length, 100) def test_object_name_is_last_name_comma_first_name(self): author = Author.objects.get(id=1) expected_object_name = f'{author.last_name}, {author.first_name}' self.assertEqual(str(author), expected_object_name) def test_get_absolute_url(self): author = Author.objects.get(id=1) # سيفشل هذا الاختبار أيضًا إن لم يكن urlconf مُعرَّفًا self.assertEqual(author.get_absolute_url(), '/catalog/author/1') تتحقق اختبارات الحقول من أن قيم تسميات الحقول verbose_name وأن حجم الحقول المحرفية كما هو متوقع. تمتلك جميع هذه التوابع أسماءً وصفية وتتبع النمط نفسه: # الحصول على كائن مؤلف لاختباره author = Author.objects.get(id=1) # الحصول على البيانات الوصفية للحقل المطلوب واستخدامها للاستعلام عن بيانات الحقل المطلوبة field_label = author._meta.get_field('first_name').verbose_name # موازنة القيمة بالنتيجة المتوقعة self.assertEqual(field_label, 'first name') الأمور المهمة التي يجب ملاحظتها هي: لا يمكننا الحصول على verbose_name مباشرةً باستخدام author.first_name.verbose_name، لأن author.first_name هو سلسلة نصية أي ليس مؤشرًا للكائن first_name الذي يمكننا استخدامه للوصول إلى خاصياته، لذا ينبغي بدلًا من ذلك استخدام السمة _meta الخاصة بالمؤلف للحصول على نسخة من الحقل واستخدامها للاستعلام عن المعلومات الإضافية. اخترنا استخدام assertEqual(field_label,'first name') عوضًا عن assertTrue(field_label == 'first name')، والسبب في ذلك هو أن خرج assertEqual يخبرك عن التسمية الفعلية في حالة فشل الاختبار، مما يجعل تنقيح أخطاء المشكلة أسهل قليلًا. ملاحظة: أُهمِلت اختبارات تسميات last_name و date_of_birth واختبار طول الحقل last_name، لذا ضِف نسخك الخاصة الآن باتباع اصطلاحات التسمية والأساليب الموضَّحة سابقًا. يجب أيضًا اختبار توابعنا الخاصة، فمثلًا تتحقق الاختبارات التالية من بناء اسم الكائن كما توقعنا باستخدام تنسيق "Last Name", "First Name"، وأن عنوان URL الذي نحصل عليه لعنصر المؤلف Author كما هو متوقع: def test_object_name_is_last_name_comma_first_name(self): author = Author.objects.get(id=1) expected_object_name = f'{author.last_name}, {author.first_name}' self.assertEqual(str(author), expected_object_name) def test_get_absolute_url(self): author = Author.objects.get(id=1) # سيفشل هذا الاختبار أيضًا إن لم يكن urlconf مُعرَّفًا self.assertEqual(author.get_absolute_url(), '/catalog/author/1') شغّل هذه الاختبارات الآن. إذا أنشأت نموذج المؤلف كما وضّحنا سابقًا، فيُحتمَل أن تحصل على خطأ للتسمية date_of_death كما هو موضح فيما يلي، إذ يفشل الاختبار لأنه مكتوب مع التوقع بأن يتبع تعريف التسمية اصطلاح جانغو بعدم كتابة الحرف الأول من التسمية بأحرف كبيرة، إذ يطبّق جانغو ذلك نيابةً عنك: ====================================================================== FAIL: test_date_of_death_label (catalog.tests.test_models.AuthorModelTest) ---------------------------------------------------------------------- Traceback (most recent call last): File "D:\...\locallibrary\catalog\tests\test_models.py", line 32, in test_date_of_death_label self.assertEqual(field_label,'died') AssertionError: 'Died' != 'died' - Died ? ^ + died ? ^ يُعَد ذلك خطأً بسيطًا جدًا، ولكنه يسلط الضوء على كيفية تحقّق كتابة الاختبارات من أيّ افتراضات قد وضعتها بدقة أكبر. ملاحظة: عدّل تسمية الحقل date_of_death في الملف "/catalog/models.py" إلى "died" وأعِد تشغيل الاختبارات. تتشابه أنماط اختبار النماذج الأخرى، لذا لن نستكمل مناقشة هذه النماذج، ولكن لا تتردد في إنشاء اختباراتك الخاصة للنماذج الأخرى. الاستمارات Forms يُعَد نهج اختبار استماراتك نفس نهج لاختبار نماذجك، إذ ينبغي اختبار أيّ شيء كتبتَ شيفرته البرمجية أو أي شيء يحدده تصميمك، ولكن لا تحتاج إلى اختبار سلوك إطار العمل الأساسي والمكتبات الخارجية الأخرى، لذا يجب اختبار احتواء الاستمارة على الحقول التي تريدها، وأنها معروضةٌ مع التسميات ونص التعليمات المناسب. لا تحتاج إلى التحقق من أن جانغو يتحقق من صحة نوع الحقل بصورة صحيحة، إلّا إذا أنشأت حقلك المُخصَّص مع التحقق من صحته، فلست بحاجة مثلًا لاختبار أن حقل البريد الإلكتروني لا يقبل سوى البريد الإلكتروني، ولكن يمكن أن تحتاج إلى اختبار التحقق من صحة البيانات الإضافي الذي تتوقع إجراءه على الحقول ورسائل الأخطاء التي ستولّدها شيفرتك البرمجية. لتكن لدينا استمارة تجديد الكتب التي تحتوي على حقل واحد فقط لتاريخ التجديد، والذي سيكون له تسمية ونص تعليمات يجب التحقق منه: class RenewBookForm(forms.Form): """Form for a librarian to renew books.""" renewal_date = forms.DateField(help_text="Enter a date between now and 4 weeks (default 3).") def clean_renewal_date(self): data = self.cleaned_data['renewal_date'] # تحقق من أن التاريخ ليس في الماضي if data < datetime.date.today(): raise ValidationError(_('Invalid date - renewal in past')) # تحقق من أن التاريخ موجود ضمن المجال المسموح به (+4 أسابيع من اليوم) if data > datetime.date.today() + datetime.timedelta(weeks=4): raise ValidationError(_('Invalid date - renewal more than 4 weeks ahead')) # تذكّر دائمًا أن تعيد البيانات النظيفة return data افتح الملف "/catalog/tests/test_forms.py" وضع الاختبار التالي لاستمارة RenewBookForm مكان أيّ شيفرة برمجية موجودة مسبقًا. نبدأ باستيراد استمارتنا وبعض مكتبات بايثون وجانغو للمساعدة في اختبار الوظائف المتعلقة بالوقت، ثم نصرّح عن صنف اختبار الاستمارة بالطريقة نفسها التي طبّقناها على النماذج باستخدام اسم وصفي لصنف الاختبار المشتق من الصنف TestCase. import datetime from django.test import TestCase from django.utils import timezone from catalog.forms import RenewBookForm class RenewBookFormTest(TestCase): def test_renew_form_date_field_label(self): form = RenewBookForm() self.assertTrue(form.fields['renewal_date'].label is None or form.fields['renewal_date'].label == 'renewal date') def test_renew_form_date_field_help_text(self): form = RenewBookForm() self.assertEqual(form.fields['renewal_date'].help_text, 'Enter a date between now and 4 weeks (default 3).') def test_renew_form_date_in_past(self): date = datetime.date.today() - datetime.timedelta(days=1) form = RenewBookForm(data={'renewal_date': date}) self.assertFalse(form.is_valid()) def test_renew_form_date_too_far_in_future(self): date = datetime.date.today() + datetime.timedelta(weeks=4) + datetime.timedelta(days=1) form = RenewBookForm(data={'renewal_date': date}) self.assertFalse(form.is_valid()) def test_renew_form_date_today(self): date = datetime.date.today() form = RenewBookForm(data={'renewal_date': date}) self.assertTrue(form.is_valid()) def test_renew_form_date_max(self): date = timezone.localtime() + datetime.timedelta(weeks=4) form = RenewBookForm(data={'renewal_date': date}) self.assertTrue(form.is_valid()) تختبر الدالتان الأوليتان أن تسمية label ونص التعليمات help_text الخاصين بالحقل كما هو متوقع. ينبغي الوصول إلى الحقل باستخدام قاموس الحقول، مثل form.fields['renewal_date']. لاحظ أنه ينبغي اختبار ما إذا كانت قيمة التسمية هي None، لأن جانغو يعيد القيمة None إن لم تُضبَط قيمة التسمية صراحةً بالرغم من أنه سيعرض التسمية الصحيحة. تختبر بقية الدوال أن الاستمارة صالحة لتواريخ التجديد ضمن المجال المقبول وغير صالحة للقيم الموجودة خارج المجال. لاحظ كيفية بناء قيم تاريخ الاختبار حول تاريخنا الحالي datetime.date.today() باستخدام datetime.timedelta() (تحديد عدد الأيام أو الأسابيع في هذه الحالة)، ثم ننشئ الاستمارة فقط، ونمرر بياناتنا، ونختبر فيما إذا كانت صالحة أم لا. ملاحظة: لم نستخدم هنا قاعدة البيانات أو عميل الاختبار فعليًا، لذا ضع في بالك تعديل هذه الاختبارات لاستخدام الصنف SimpleTestCase. يجب أيضًا التحقق من ظهور الأخطاء الصحيحة إذا كانت الاستمارة غير صالحة، ولكن ذلك يحدث عادةً بوصفه جزءًا من معالجة العرض، لذا سنعالج ذلك في القسم التالي. تحذير: إذا استخدمتَ صنف ModelForm الذي هو RenewBookModelForm(forms.ModelForm) بدلًا من الصنف RenewBookForm(forms.Form)، فسيكون اسم حقل الاستمارة "due_back" بدلًا من "renewal_date". هذا كل ما لدينا للتعامل مع الاستمارات وهناك بعض الأمور الأخرى، ولكن تنشِئها عروض التعديل المُعمَّمة المستندة إلى الأصناف Generic Class-based Editing Views تلقائيًا، ويجب اختبارها هناك، لذا يمكنك إجراء الاختبارات والتأكد من أن شيفرتك البرمجية لا تزال تعمل بنجاح. العروض Views يمكن التحقق من صحة سلوك العرض من خلال استخدام الصنف Client لاختبار جانغو، إذ يعمل هذا الصنف بوصفه متصفح ويب وهمي dummy web browser يمكننا استخدامه لمحاكاة طلبات GET و POST على عنوان URL ومراقبة الاستجابة. يمكننا أن نرى كل شيء متعلق بالاستجابة تقريبًا، بدءًا من HTTP منخفض المستوى، أي ترويسات النتائج ورموز الحالة، إلى القالب الذي نستخدمه لعرض صفحة HTML وبيانات السياق التي نمررها إليه، ويمكننا رؤية سلسلة عمليات إعادة التوجيه (إن وجدت) والتحقق من عنوان URL ورمز الحالة في كل خطوة، مما يسمح لنا بالتحقق من أن كل عرض يطبّق المتوقع منه. لنبدأ بواحدٍ من أبسط العروض، والذي يوفّر قائمة بجميع المؤلفين، ويُعرَض على عنوان URL، الذي هو "/catalog/authors/" (عنوان URL الذي اسمه "authors" في ضبط عناوين URL): class AuthorListView(generic.ListView): model = Author paginate_by = 10 ينفِّذ جانغو كل شيء تقريبًا نيابةً عنا لأن هذا العرض هو عرض قائمة مُعمَّمة. يمكن القول أنه إذا كنت واثقًا في جانغو، فالشيء الوحيد الذي يجب اختباره هو أن العرض يمكن الوصول إليه عبر عنوان URL الصحيح ويمكن الوصول إليه باستخدام اسمه، لكن إذا استخدمتَ عملية تطوير مُقادة بالاختبارات، فستكتب اختبارات تؤكد أن العرض يظهر جميع المؤلفين، مع ترقيمهم ضمن مجموعات مؤلفة من 10 عناصر. افتح الملف ""/catalog/tests/test_views.py وضع فيه شيفرة الاختبار التالية للصنف AuthorListView بدلًا من أيّ نص آخر موجود فيه، إذ سنستورد نموذجنا وبعض الأصناف المفيدة، ونضبط في التابع setUpTestData() عددًا من كائنات Author لنتمكّن من اختبار ترقيم الصفحات: from django.test import TestCase from django.urls import reverse from catalog.models import Author class AuthorListViewTest(TestCase): @classmethod def setUpTestData(cls): # إنشاء 13 مؤلفًا لاختبارات ترقيم الصفحات number_of_authors = 13 for author_id in range(number_of_authors): Author.objects.create( first_name=f'Dominique {author_id}', last_name=f'Surname {author_id}', ) def test_view_url_exists_at_desired_location(self): response = self.client.get('/catalog/authors/') self.assertEqual(response.status_code, 200) def test_view_url_accessible_by_name(self): response = self.client.get(reverse('authors')) self.assertEqual(response.status_code, 200) def test_view_uses_correct_template(self): response = self.client.get(reverse('authors')) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'catalog/author_list.html') def test_pagination_is_ten(self): response = self.client.get(reverse('authors')) self.assertEqual(response.status_code, 200) self.assertTrue('is_paginated' in response.context) self.assertTrue(response.context['is_paginated'] == True) self.assertEqual(len(response.context['author_list']), 10) def test_lists_all_authors(self): # الحصول على الصفحة الثانية والتأكد من أنها تحتوي (بالضبط) على 3 عناصر متبقية response = self.client.get(reverse('authors')+'?page=2') self.assertEqual(response.status_code, 200) self.assertTrue('is_paginated' in response.context) self.assertTrue(response.context['is_paginated'] == True) self.assertEqual(len(response.context['author_list']), 3) تستخدم جميع الاختبارات العميل، الذي ينتمي إلى الصنف المشتق من TestCase لمحاكاة طلب GET والحصول على استجابة. تتحقق النسخة الأولى من عنوان URL المحدد (لاحظ وجود المسار المحدد فقط بدون النطاق)، بينما تولّد النسخة الثانية عنوان URL من اسمه في ضبط عناوين URL كما يلي: response = self.client.get('/catalog/authors/') response = self.client.get(reverse('authors')) نستعلم عن الاستجابة بعد الحصول عليها لنحصل على رمز حالتها والقالب المُستخدَم وما إذا كانت الاستجابة مرقمة paginated أم لا وعدد العناصر المُعادة وعدد العناصر الإجمالي. ملاحظة: إذا ضبطتَ المتغير paginate_by في الملف /catalog/views.py إلى عدد آخر غير العدد 10، فتأكد من تحديث الأسطر التي تختبر عرضَ عدد العناصر الصحيح في القوالب ذات الصفحات المرقمة السابقة وفي الأقسام التالية، فمثلًا إذا ضبطتَ متغيرًا لصفحة قائمة المؤلفين على القيمة 5، فعدّل السطر السابق إلى ما يلي: self.assertTrue(len(response.context['author_list']) == 5) المتغير الأهم الذي عرضناه سابقًا هو response.context، وهو متغير السياق الذي يمرّره العرض إلى القالب، ويُعَد هذا المتغير مفيدًا للاختبار، لأنه يسمح لنا بالتأكد من أن قالبنا يحصل على جميع البيانات التي يحتاجها، أي يمكننا التحقق من أننا نستخدم القالب المقصود والبيانات التي يحصل عليها القالب، مما يؤدي إلى التحقق من رجوع أي مشكلات في التصيير rendering إلى القالب فقط. العروض المقيدة على المستخدمين الذين سجلوا الدخول سترغب في بعض الحالات في اختبار العرض المقتصر على المستخدمين الذين سجّلوا الدخول فقط، فالعرض LoanedBooksByUserListView مثلًا مشابه جدًا لعرضنا السابق، ولكنه متاحٌ فقط للمستخدمين الذين سجّلوا الدخول، ويعرض فقط سجلات نسخ الكتاب BookInstance التي استعارها المستخدم الحالي والتي لها الحالة 'on loan' ومرتبة بحسب الأقدم أولًا "oldest first". from django.contrib.auth.mixins import LoginRequiredMixin class LoanedBooksByUserListView(LoginRequiredMixin, generic.ListView): """Generic class-based view listing books on loan to current user.""" model = BookInstance template_name ='catalog/bookinstance_list_borrowed_user.html' paginate_by = 10 def get_queryset(self): return BookInstance.objects.filter(borrower=self.request.user).filter(status__exact='o').order_by('due_back') ضِف شيفرة الاختبار الآتية إلى الملف "/catalog/tests/test_views.py"، إذ نستخدم هنا أولًا التابع SetUp() لإنشاء بعض حسابات تسجيل دخول المستخدم وكائنات BookInstance -مع الكتب المرتبطة بها والسجلات الأخرى- التي سنستخدمها لاحقًا في الاختبارات. يستعير كل مستخدم تجريبي نصف الكتب، ولكننا ضبطنا في البداية حالة جميع الكتب على أنها قيد الصيانة "maintenance"، واستخدمنا التابع SetUp() بدلًا من setUpTestData() لأننا سنعدّل بعض هذه الكائنات لاحقًا. ملاحظة: تنشئ شيفرة setUp() التالية كتابًا بلغة Language محددة، ولكن يمكن ألّا تحتوي شيفرتك البرمجية على النموذج Language الذي تركناه بمثابة تحدٍ لك، لذا يمكنك تعليق أجزاء الشيفرة البرمجية التي تنشئ أو تستورد كائنات Language، ويجب أيضًا تطبيق ذلك في قسم RenewBookInstancesViewTest. import datetime from django.utils import timezone from django.contrib.auth.models import User # مطلوب لضبط المستخدم بوصفه مستعيرًا from catalog.models import BookInstance, Book, Genre, Language class LoanedBookInstancesByUserListViewTest(TestCase): def setUp(self): # أنشئ مستخدمَين test_user1 = User.objects.create_user(username='testuser1', password='1X<ISRUkw+tuK') test_user2 = User.objects.create_user(username='testuser2', password='2HJ1vRV0Z&3iD') test_user1.save() test_user2.save() # أنشئ كتابًا test_author = Author.objects.create(first_name='John', last_name='Smith') test_genre = Genre.objects.create(name='Fantasy') test_language = Language.objects.create(name='English') test_book = Book.objects.create( title='Book Title', summary='My book summary', isbn='ABCDEFG', author=test_author, language=test_language, ) # أنشئ نوع الكتاب genre لخطوة لاحقة genre_objects_for_book = Genre.objects.all() # الإسناد المباشر لأنواع متعدد إلى متعدد غير مسموح به test_book.genre.set(genre_objects_for_book) test_book.save() # أنشئ 30 كائن BookInstance number_of_book_copies = 30 for book_copy in range(number_of_book_copies): return_date = timezone.localtime() + datetime.timedelta(days=book_copy%5) the_borrower = test_user1 if book_copy % 2 else test_user2 status = 'm' BookInstance.objects.create( book=test_book, imprint='Unlikely Imprint, 2016', due_back=return_date, borrower=the_borrower, status=status, ) def test_redirect_if_not_logged_in(self): response = self.client.get(reverse('my-borrowed')) self.assertRedirects(response, '/accounts/login/?next=/catalog/mybooks/') def test_logged_in_uses_correct_template(self): login = self.client.login(username='testuser1', password='1X<ISRUkw+tuK') response = self.client.get(reverse('my-borrowed')) # تحقق من أن المستخدم قد سجل الدخول self.assertEqual(str(response.context['user']), 'testuser1') # تحقق من حصولنا على استجابة تمثل "النجاح" self.assertEqual(response.status_code, 200) # تحقق من استخدامنا للقالب الصحيح self.assertTemplateUsed(response, 'catalog/bookinstance_list_borrowed_user.html') يمكن التحقق من أن العرض سيعيد توجيه المستخدم إلى صفحة تسجيل الدخول إن لم يسجّل دخول من خلال استخدام assertRedirects كما هو موضح في test_redirect_if_not_logged_in(). يمكن التحقق من إظهار الصفحة لمستخدم سجّل الدخول من خلال تسجيل الدخول للمستخدم التجريبي أولًا، ثم الوصول إلى الصفحة مرةً أخرى والتحقق من حصولنا على رمز الحالة status_code التي هي 200 وتمثل النجاح. تتحقق بقية الاختبارات من أن عروضنا تعيد فقط الكتب المُعارة للمستعير الحالي. انسخ الشيفرة البرمجية التالية والصقها في نهاية صنف الاختبار السابق: def test_only_borrowed_books_in_list(self): login = self.client.login(username='testuser1', password='1X<ISRUkw+tuK') response = self.client.get(reverse('my-borrowed')) # تحقق من أن المستخدم قد سجّل الدخول self.assertEqual(str(response.context['user']), 'testuser1') # تحقق من حصولنا على استجابة تمثل "النجاح" self.assertEqual(response.status_code, 200) # تحقق من عدم وجود أيّ كتب في القائمة في البداية (لا توجد كتب مُعارة) self.assertTrue('bookinstance_list' in response.context) self.assertEqual(len(response.context['bookinstance_list']), 0) # غيّر الآن جميع الكتب لتكون مُعارة books = BookInstance.objects.all()[:10] for book in books: book.status = 'o' book.save() # تحقق من أننا قد استعرنا الكتب في القائمة response = self.client.get(reverse('my-borrowed')) # تحقق من أن المستخدم قد سجّل الدخول self.assertEqual(str(response.context['user']), 'testuser1') # تحقق من حصولنا على استجابة تمثل "النجاح" self.assertEqual(response.status_code, 200) self.assertTrue('bookinstance_list' in response.context) # تأكد من أن جميع الكتب تعود إلى المستخدم testuser1 وأنها مُعارة for bookitem in response.context['bookinstance_list']: self.assertEqual(response.context['user'], bookitem.borrower) self.assertEqual(bookitem.status, 'o') def test_pages_ordered_by_due_date(self): # غيّر جميع الكتب لتكون مُعارة for book in BookInstance.objects.all(): book.status='o' book.save() login = self.client.login(username='testuser1', password='1X<ISRUkw+tuK') response = self.client.get(reverse('my-borrowed')) # تحقق من أن المستخدم قد سجّل الدخول self.assertEqual(str(response.context['user']), 'testuser1') # تحقق من حصولنا على استجابة تمثل "النجاح" self.assertEqual(response.status_code, 200) # تأكد من عرض 10 عناصر فقط بسبب ترقيم الصفحات self.assertEqual(len(response.context['bookinstance_list']), 10) last_date = 0 for book in response.context['bookinstance_list']: if last_date == 0: last_date = book.due_back else: self.assertTrue(last_date <= book.due_back) last_date = book.due_back يمكنك أيضًا إضافة اختبارات لترقيم الصفحات Pagination إذا أردتَ ذلك. اختبار العروض مع الاستمارات يُعَد اختبار العرض مع الاستمارات أكثر تعقيدًا من الحالات السابقة، لأنه يجب اختبار مزيدٍ من مسارات الشيفرات البرمجية وهي: الإظهار الأولي، والإظهار بعد فشل التحقق من صحة البيانات، والإظهار بعد نجاح التحقق من صحة البيانات. والخبر السار هو أننا نستخدم العميل للاختبار باستخدام الطريقة نفسها التي استخدمناها لاختبار العروض المتاحة للعرض فقط. لنكتب بعض اختبارات العرض المُستخدَم لتجديد الكتب renew_book_librarian(): from catalog.forms import RenewBookForm @permission_required('catalog.can_mark_returned') def renew_book_librarian(request, pk): """View function for renewing a specific BookInstance by librarian.""" book_instance = get_object_or_404(BookInstance, pk=pk) # إذا كان هذا الطلب من النوع POST، فعالج بيانات الاستمارة if request.method == 'POST': # أنشئ نسخة من الاستمارة واملأها ببيانات من الطلب (الربط Binding): book_renewal_form = RenewBookForm(request.POST) # تحقق من أن الاستمارة صالحة if form.is_valid(): # معالجة البيانات في form.cleaned_data كما هو مطلوب (نكتبها هنا فقط في حقل النموذج due_back) book_instance.due_back = form.cleaned_data['renewal_date'] book_instance.save() # إعادة التوجيه إلى عنوان URL جديد return HttpResponseRedirect(reverse('all-borrowed')) # إذا كان تابع GET (أو أي تابع آخر)، فأنشئ الاستمارة الافتراضية else: proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3) book_renewal_form = RenewBookForm(initial={'renewal_date': proposed_renewal_date}) context = { 'book_renewal_form': book_renewal_form, 'book_instance': book_instance, } return render(request, 'catalog/book_renew_librarian.html', context) ينبغي اختبار أن العرض متاح فقط للمستخدمين الذين لديهم الإذن can_mark_returned، ويُعاد توجيه المستخدمين إلى صفحة خطأ HTTP 404 إذا حاولوا تجديد نسخة كتاب BookInstance ليست موجودة. ينبغي أن نتحقق من ضبط القيمة الأولية للاستمارة بتاريخ لثلاثة أسابيع في المستقبل، وأنه إذا نجحت عملية التحقق من صحة البيانات، فسيُعاد توجيهنا إلى عرض "جميع الكتب المستعارة"، وسنتحقق -كجزء من فحص اختبارات فشل التحقق من صحة البيانات- من أن استمارتنا ترسل رسائل الخطأ المناسبة. أضِف الجزء الأول من صنف الاختبار الموضح فيما يلي إلى نهاية الملف "/catalog/tests/test_views.py"، إذ يؤدي ذلك إلى إنشاء مستخدمَين ونسختين من الكتاب، ولكنه يمنح مستخدمًا واحدًا فقط الإذن المطلوب للوصول إلى العرض: import uuid from django.contrib.auth.models import Permission # مطلوب لمنح الإذن اللازم لضبط كتاب على أنه مُعاد class RenewBookInstancesViewTest(TestCase): def setUp(self): # أنشئ مستخدمًا test_user1 = User.objects.create_user(username='testuser1', password='1X<ISRUkw+tuK') test_user2 = User.objects.create_user(username='testuser2', password='2HJ1vRV0Z&3iD') test_user1.save() test_user2.save() # امنح المستخدم test_user2 الإذن لتجديد الكتب permission = Permission.objects.get(name='Set book as returned') test_user2.user_permissions.add(permission) test_user2.save() # أنشئ كتابًا test_author = Author.objects.create(first_name='John', last_name='Smith') test_genre = Genre.objects.create(name='Fantasy') test_language = Language.objects.create(name='English') test_book = Book.objects.create( title='Book Title', summary='My book summary', isbn='ABCDEFG', author=test_author, language=test_language, ) # أنشئ نوع الكتاب genre لخطوة لاحقة genre_objects_for_book = Genre.objects.all() # الإسناد المباشر لأنواع متعدد إلى متعدد غير مسموح به test_book.genre.set(genre_objects_for_book) test_book.save() # أنشئ كائن BookInstance للمستخدم test_user1 return_date = datetime.date.today() + datetime.timedelta(days=5) self.test_bookinstance1 = BookInstance.objects.create( book=test_book, imprint='Unlikely Imprint, 2016', due_back=return_date, borrower=test_user1, status='o', ) # أنشئ كائن BookInstance للمستخدم test_user2 return_date = datetime.date.today() + datetime.timedelta(days=5) self.test_bookinstance2 = BookInstance.objects.create( book=test_book, imprint='Unlikely Imprint, 2016', due_back=return_date, borrower=test_user2, status='o', ) أضِف الاختبارات التالية إلى نهاية صنف الاختبار، إذ تتحقق هذه الاختبارات من أن المستخدمين الذين لديهم الأذونات الصحيحة فقط (المستخدم testuser2) يمكنهم الوصول إلى العرض. سنتحقق من جميع الحالات وهي: عندما لا يسجّل المستخدم الدخول. عندما يسجّل المستخدم الدخول ولكن ليس لديه الأذونات الصحيحة. عندما يكون لدى المستخدم أذونات ولكنه ليس المستعير (يجب أن تنجح هذه الحالة). كما سنتحقق مما سيحدث عندما يحاولون الوصول إلى نسخة كتاب BookInstance غير موجودة، وكذلك من استخدام القالب الصحيح. def test_redirect_if_not_logged_in(self): response = self.client.get(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance1.pk})) # تحقق يدويًا من إعادة التوجيه # لا يمكن استخدام assertRedirect، لأن عنوان URL لإعادة التوجيه لا يمكن التنبؤ به self.assertEqual(response.status_code, 302) self.assertTrue(response.url.startswith('/accounts/login/')) def test_forbidden_if_logged_in_but_not_correct_permission(self): login = self.client.login(username='testuser1', password='1X<ISRUkw+tuK') response = self.client.get(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance1.pk})) self.assertEqual(response.status_code, 403) def test_logged_in_with_permission_borrowed_book(self): login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD') response = self.client.get(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance2.pk})) # تأكد من أنه يتيح لنا تسجيل الدخول، فهذا كتابنا ولدينا الأذونات الصحيحة self.assertEqual(response.status_code, 200) def test_logged_in_with_permission_another_users_borrowed_book(self): login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD') response = self.client.get(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance1.pk})) # تحقق من أنه يتيح لنا تسجيل الدخول، لأننا أمناء مكتبة، ويمكننا عرض جميع كتب المستخدمين self.assertEqual(response.status_code, 200) def test_HTTP404_for_invalid_book_if_logged_in(self): # معرّف uid لمطابقة نسخة كتابنا- غير مرجح حدوثه test_uid = uuid.uuid4() login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD') response = self.client.get(reverse('renew-book-librarian', kwargs={'pk':test_uid})) self.assertEqual(response.status_code, 404) def test_uses_correct_template(self): login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD') response = self.client.get(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance1.pk})) self.assertEqual(response.status_code, 200) # تحقق من أننا نستخدم القالب الصحيح self.assertTemplateUsed(response, 'catalog/book_renew_librarian.html') أضِف تابع الاختبار التالي الذي يتحقق من أن التاريخ الأولي للاستمارة هو ثلاثة أسابيع في المستقبل، ولاحظ كيف أننا قادرون على الوصول إلى القيمة الأولية لحقل الاستمارة response.context['form'].initial['renewal_date']: def test_form_renewal_date_initially_has_date_three_weeks_in_future(self): login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD') response = self.client.get(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance1.pk})) self.assertEqual(response.status_code, 200) date_3_weeks_in_future = datetime.date.today() + datetime.timedelta(weeks=3) self.assertEqual(response.context['form'].initial['renewal_date'], date_3_weeks_in_future) يتحقق الاختبار التالي (أضفه إلى الصنف أيضًا) من أن العرض يعيد توجيهك إلى قائمة جميع الكتب المستعارة إذا نجح التجديد، والاختلاف هنا هو أننا لأول مرة نعرض كيفية إرسال البيانات POST باستخدام العميل، بحيث تكون هذه البيانات هي الوسيط الثاني للدالة post، وتُحدَّد بوصفها قاموسًا من أزواج مفتاح/قيمة. def test_redirects_to_all_borrowed_book_list_on_success(self): login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD') valid_date_in_future = datetime.date.today() + datetime.timedelta(weeks=2) response = self.client.post(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}), {'renewal_date':valid_date_in_future}) self.assertRedirects(response, reverse('all-borrowed')) تحذير: أُضيف العرض all-borrowed كتحدٍ لك، ويمكن أن تعيد شيفرتك البرمجية التوجيه إلى الصفحة الرئيسية '/' بدلًا من ذلك، فإذا كان الأمر كذلك، فعدّل آخر سطرين من شيفرة الاختبار لتكون مماثلة للشيفرة البرمجية التالية، إذ يضمن الوسيط follow=True في الطلب أن يعيد الطلب عنوان URL للهدف النهائي وعندها يجب تحديد /catalog/ بدلًا من /: response = self.client.post(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}), {'renewal_date':valid_date_in_future}, follow=True) self.assertRedirects(response, '/catalog/') انسخ الدالتين التاليتين في الصنف، واللتين تختبران طلبات POST مرةً ثانية، ولكن مع تواريخ تجديد غير صالحة في هذه الحالة. سنستخدم الدالة assertFormError() للتحقق من أن رسائل الخطأ كما هو متوقع: def test_form_invalid_renewal_date_past(self): login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD') date_in_past = datetime.date.today() - datetime.timedelta(weeks=1) response = self.client.post(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance1.pk}), {'renewal_date': date_in_past}) self.assertEqual(response.status_code, 200) self.assertFormError(response, 'form', 'renewal_date', 'Invalid date - renewal in past') def test_form_invalid_renewal_date_future(self): login = self.client.login(username='testuser2', password='2HJ1vRV0Z&3iD') invalid_date_in_future = datetime.date.today() + datetime.timedelta(weeks=5) response = self.client.post(reverse('renew-book-librarian', kwargs={'pk': self.test_bookinstance1.pk}), {'renewal_date': invalid_date_in_future}) self.assertEqual(response.status_code, 200) self.assertFormError(response, 'form', 'renewal_date', 'Invalid date - renewal more than 4 weeks ahead') يمكن استخدام الأساليب نفسها لاختبار العرض الآخر. القوالب يوفر جانغو واجهات برمجة التطبيقات API خاصة بالاختبار للتحقق من أن عرضك يستدعي القالب الصحيح، وللسماح بالتحقق من إرسال المعلومات الصحيحة، ولكن لا يوجد دعم محدد لواجهة برمجة التطبيقات للاختبار في جانغو بحيث يُظهر خرج صفحة HTML كما هو متوقع. أدوات الاختبار الأخرى الموصى بها يمكن أن يساعدك إطار عمل اختبار جانغو في كتابة اختبارات الوحدة والتكامل الفعالة، إذ تعرّفنا فقط على جزء بسيط مما يمكن أن يفعله إطار عمل unittest الأساسي، بالإضافة إلى إضافات جانغو. اطلّع مثلًا على كيفية استخدام unittest.mock لتعويض نقص المكتبات الخارجية لتتمكّن من اختبار شيفرتك البرمجية بدقة أكبر. هناك العديد من أدوات الاختبار الأخرى التي يمكنك استخدامها، ولكننا سنذكر أداتين هما: Coverage: تعطي هذه الأداة الخاصة ببايثون تقريرًا بمقدار شيفرتك البرمجية التي تنّفذها اختباراتك، وهي مفيدة خاصةً عندما تبدأ وتحاول تحديد ما يجب عليك اختباره بالضبط. إطار عمل سيلينيوم Selenium: وهو إطار عمل لأتمتة الاختبار في متصفح حقيقي، ويسمح بمحاكاة تفاعل مستخدم حقيقي مع الموقع، ويوفر إطار عمل رائع لنظام اختبار موقعك (الخطوة التالية من اختبار التكامل). تحدى نفسك هناك الكثير من النماذج والعروض التي يمكننا اختبارها، لذا حاول إنشاء حالة اختبار للعرض AuthorCreate: class AuthorCreate(PermissionRequiredMixin, CreateView): model = Author fields = '__all__' initial = {'date_of_death':'12/10/2016'} permission_required = 'catalog.can_mark_returned' تذكّر أنه يجب التحقق من أيّ شيء تحدده أو أيّ شيء يمثل جزءًا من التصميم، وسيشمل ذلك مَن يمكنه الوصول والتاريخ الابتدائي والقالب المُستخدم والمكان الذي يعيد العرض التوجيه إليه عند النجاح. الخلاصة لا تُعَد كتابة شيفرة الاختبار ممتعة، وتُترَك غالبًا إلى النهاية، أو لا تُطبَّق إطلاقًا عند إنشاء موقع ويب، لكنها جزءٌ أساسي من التأكد من أن شيفرتك البرمجية آمنة لإصدارها بعد إجراء التغييرات وصيانتها بفعالية من حيث التكلفة. أوضحنا في هذا المقال كيفية كتابة وتشغيل اختبارات النماذج والاستمارات والعروض، وقدّمنا ملخصًا موجزًا لما يجب عليك اختباره، والذي يكون غالبًا أصعب شيء يجب تطبيقه في البداية. أمامك الكثير لتعلّمه، ولكن يجب أن تكون قادرًا على إنشاء اختبارات وحدة فعّالة لمواقعك حتى مع كل ما تعلمته مسبقًا. سنوضح في المقال التالي كيفية نشر موقع جانغو الذي اختبَرناه بالكامل. ترجمة -وبتصرُّف- للمقال Django Tutorial Part 10: Testing a Django web application. اقرأ المزيد المقال التالي: تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج المقال السابق تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms كتابة وفحص الاختبارات (توثيق جانغو) كتابة أول تطبيق جانغو - الجزء الخامس: كتابة اختبار آلي (توثيق جانغو) أدوات الفحص (توثيق جانغو) مدخل إلى اختبارات مشاريع الويب الآلية للتوافق مع المتصفحات الدليل السريع إلى لغة البرمجة بايثون Python
-
سنوضح في هذا المقال كيفية العمل مع استمارات HTML في إطار العمل جانغو Django، خاصةً الطريقة الأسهل لكتابة الاستمارات لإنشاء نسخ من النموذج وتحديثها وحذفها، إذ سنوسّع موقع المكتبة المحلية LocalLibrary بحيث يمكن لأمناء المكتبة تجديد الكتب وإنشاء وتحديث وحذف المؤلفين باستخدام استماراتنا الخاصة عوضًا عن استخدام تطبيق المدير. المتطلبات الأساسية: اطّلع على جميع المقالات السابقة من هذه السلسلة، بما في ذلك المقال الخاص باستيثاق المستخدمين وأذوناتهم. الهدف: فهم كيفية كتابة الاستمارات للحصول على معلومات من المستخدمين وتحديث قاعدة البيانات، وفهم كيف يمكن لعروض التعديل المُعمَّمة المستندة إلى الأصناف تبسيط إنشاء الاستمارات للعمل مع نموذج واحد فقط. تُعَد استمارة HTML مجموعةً مكونةً من حقل واحد أو عنصر واجهة مستخدم widget واحدة أو أكثر على صفحة الويب، والتي يمكن استخدامها لجمع المعلومات من المستخدمين لإرسالها إلى الخادم، فالاستمارات هي آلية مرنة لتجميع دخل المستخدم، نظرًا لوجود عناصر واجهة مستخدم مناسبة لإدخال العديد من أنواع البيانات المختلفة، بما في ذلك مربعات النص ومربعات الاختيار وأزرار الاختيار ومنتقي التواريخ وما إلى ذلك. تُعَد الاستمارات طريقةً آمنة نسبيًا لمشاركة البيانات مع الخادم، لأنها تسمح بإرسال البيانات في طلبات POST مع الحماية من هجمات تزوير الطلبات عبر المواقع. لم ننشِئ أيّ استمارات في هذه السلسلة من المقالات حتى الآن، ولكننا صادفناها في موقع مدير جانغو، فمثلًا تُظهِر لقطة الشاشة التالية استمارةً لتعديل أحد نماذج الكتاب Book، والذي يتكون من عدد من قوائم الاختيار ومحرّري النصوص. يمكن أن يكون العمل مع الاستمارات معقدًا، إذ يجب أن يكتب المطورون شيفرة HTML للاستمارة، ويجب التحقق من صحة البيانات المدخلة وتعقيمها Sanitization (أي التخلص من البيانات الحساسة أو غير الضرورية) على الخادم وربما في المتصفح أيضًا، وإعادة نشر الاستمارة مع رسائل خطأ لإبلاغ المستخدمين بأيّ حقول غير صالحة، والتعامل مع البيانات عند إرسالها بنجاح، وأخيرًا الرد على المستخدم بطريقة ما للإشارة إلى النجاح. تقلّل استمارات جانغو الكثير من العمل المبذول في كل من هذه الخطوات من خلال توفير إطار عمل يتيح لك تعريف الاستمارات وحقولها برمجيًا، ثم استخدام هذه الكائنات لتوليد شيفرة استمارة HTML والتعامل مع الكثير من عمليات التحقق من الصحة وتفاعل المستخدم. سنعرض في هذا المقال بعض الطرق التي يمكنك من خلالها إنشاء الاستمارات والعمل معها، وخاصة كيف يمكن لعروض التعديل المُعمَّمة أن تقلل من حجم العمل الواجب تطبيقه لإنشاء الاستمارات بهدف معالجة النماذج Models، إذ سنوسّع تطبيق المكتبة المحلية LocalLibrary من خلال إضافة استمارة للسماح لأمناء المكتبة بتجديد الكتب، وسننشئ صفحات لإنشاء وتعديل وحذف الكتب والمؤلفين، أو إعادة إنتاج نسخة أساسية من الاستمارة التي وضحناها سابقًا لتعديل الكتب. تتألف هذه السلسلة الفرعية من السلسلة الأشمل تعلم تطوير الويب من المقالات التالية: مدخل إلى إطار عمل الويب جانغو Django إعداد بيئة تطوير تطبيقات جانغو تطبيق عملي لتعلم جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية تطبيق عملي لتعلم جانغو - الجزء السادس: إدارة الجلسات Sessions تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج تعرف على أمان تطبيقات جانغو استمارة HTML أولًا، اطّلع على مفهوم استمارات HTML، ثم أنشئ استمارة HTML بسيطة، مع حقل نصي واحد لإدخال اسم الفريق والتسمية Label المرتبطة به كما يلي: تُعرَّف الاستمارة Form في HTML بوصفها مجموعة من العناصر ضمن وسوم <form>…</form> التي تحتوي على عنصر إدخال input واحد على الأقل من النوع type="submit". <form action="/team_name_url/" method="post"> <label for="team_name">Enter name: </label> <input id="team_name" type="text" name="name_field" value="Default name for team." /> <input type="submit" value="OK" /> </form> لدينا حقل نصي واحد فقط لإدخال اسم الفريق في مثالنا، ولكن يمكن أن تحتوي الاستمارة على أيّ عدد من عناصر الإدخال الأخرى والتسميات المرتبطة بها. تحدّد السمة type الخاصة بالحقل نوع عنصر واجهة المستخدم الذي سيُعرض، ويُستخدَم اسم name ومعرّف id الحقل لتحديد الحقل في شيفرة JavaScript/CSS/HTML، بينما يحدد value القيمة الأولية للحقل عند عرضه لأول مرة. تُحدَّد تسمية الفريق المطابق باستخدام الوسم label (لاحظ التسمية "أدخِل اسمًا Enter name") مع الحقل for الذي يحتوي على قيمة معرّف id حقل الإدخال input المرتبط به. سيُعرَض حقل الإدخال submit بوصفه زرًا افتراضيًا، ويمكن الضغط عليه لتحميل البيانات من جميع عناصر الإدخال الأخرى في الاستمارة إلى الخادم (وهي في حالتنا حقل اسم الفريق team_name فقط). تحدّد سمات الاستمارة نوع طلب HTTP المُستخدَم لإرسال البيانات (عبر method) ووِجهة البيانات على الخادم (action) كما يلي: action: المورد أو عنوان URL لمكان إرسال البيانات لمعالجتها عند إرسال الاستمارة. إذا لم تُضبَط هذه السمة، أو ضُبطت على أنها سلسلة فارغة)، ستُعاد الاستمارة إلى عنوان URL للصفحة الحالية. method: نوع طلب HTTP المُستخدَم لإرسال البيانات وهو إما من النوع POST أو GET. يجب دائمًا استخدام النوع POST إذا كانت البيانات ستؤدي إلى تغيير في قاعدة بيانات الخادم، لأنه يمكن جعلها أكثر مقاومة لهجمات تزوير الطلبات عبر المواقع. يجب استخدام النوع GET فقط للاستمارات التي لا تغير بيانات المستخدم (مثل استمارة البحث)، ويوصَى به عندما تريد أن تكون قادرًا على وضع إشارة مرجعية على عنوان URL أو مشاركته. يتمثل دور الخادم server أولًا في عرض حالة الاستمارة الأولية، فإما أن تحتوي على حقول فارغة، أو تكون مملوءةً مسبقًا بقيم أولية. سيتلقى الخادم بعد أن يضغط المستخدم على زر الإرسال بيانات الاستمارة مع قيم من متصفح الويب ويجب عليه التحقق من صحة المعلومات، فإذا احتوت الاستمارة على بيانات غير صالحة، فيجب أن يعرض الخادم الاستمارة مرةً أخرى، ولكن مع بيانات أدخلها المستخدم ضمن حقول صالحة ورسائل لوصف مشكلة الحقول غير الصالحة. يمكن للخادم تطبيق الإجراء المناسب، مثل حفظ البيانات وإعادة نتيجة البحث وتحميل ملف وغير ذلك بمجرد أن يتلقى الخادم طلبًا بجميع بيانات الاستمارة الصالحة، ومن ثم إعلام المستخدم. يمكن أن يكون إنشاء شيفرة HTML والتحقق من صحة البيانات المُعادة وإعادة عرض البيانات المُدخَلة مع تقارير الأخطاء إن لزم الأمر وتطبيق العملية المطلوبة على البيانات الصالحة أمرًا متعبًا جدًا ليكون صحيحًا، ويجعل جانغو هذا الأمر أسهل بكثير من خلال التخلص من بعض الشيفرة البرمجية الثقيلة والمتكررة. عملية معالجة استمارة جانغو تستخدم معالجة الاستمارات في جانغو الأساليب نفسها التي تعلمناها في مقالات سابقة (لعرض معلومات حول نماذجنا)، إذ يتلقى العرض طلبًا، ويطبّق أي إجراءات مطلوبة بما في ذلك قراءة البيانات من النماذج، ثم يولّد ويعيد صفحة HTML من قالب إلى آخر نمرر إليه سياقًا يحتوي على البيانات المراد عرضها، ولكن ما يعقّد الأمورهو أن الخادم يحتاج أيضًا إلى أن يكون قادرًا على معالجة البيانات التي يقدّمها المستخدم، ويجب أن يعيد عرض الصفحة في حالة وجود أيّ أخطاء. يوضَح المخطط التالي كيفية معالجة جانغو لطلبات الاستمارة، بدءًا من طلب صفحة تحتوي على استمارة والموضح باللون الأخضر: تطبّق عملية معالجة استمارة جانغو الأمور الرئيسية التالية بناءً على المخطط السابق: عرض الاستمارة الافتراضية في المرة الأولى التي يطلبها المستخدم. يمكن أن تحتوي الاستمارة على حقول فارغة إذا أردتَ إنشاء سجل جديد، أو يمكن ملؤها مسبقًا بالقيم الأولية إذا غيّرت سجلًا أو كان لديك قيم أولية افتراضية مفيدة مثلًا. يُشار إلى الاستمارة إلى أنها غير مرتبطة Unbound في هذه المرحلة، لأنها غير مرتبطة بأيّ بيانات أدخلها المستخدم، بالرغم من أنه يمكن أن تحتوي على قيم أولية. تلقي البيانات من طلب الإرسال وربطها بالاستمارة. يعني ربط Binding البيانات بالاستمارة أن البيانات التي أدخلها المستخدم والأخطاء تكون متاحة عندما نحتاج إلى إعادة عرض الاستمارة. تنظيف البيانات والتحقق من صحتها. يؤدي تنظيف البيانات إلى تعقيم حقول الإدخال مثل إزالة المحارف غير الصالحة التي يمكن استخدامها لإرسال محتوًى ضار إلى الخادم، وتحويلها إلى أنواع بايثون متناسقة. تفحص عملية التحقق من صحة البيانات أن القيم مناسبة للحقل، مثل أن تكون موجودة في مجال البيانات الصحيح، أو أنها ليست قصيرة جدًا أو طويلة جدًا وما إلى ذلك. إذا وُجدت بيانات غير صالحة، فأعِد عرض الاستمارة، ولكن هذه المرة مع أيّ قيمٍ يملؤها المستخدم ورسائل خطأ لحقول المشكلة. إذا كانت جميع البيانات صالحة، فطبّق الإجراءات المطلوبة، مثل حفظ البيانات وإرسال بريد إلكتروني وإعادة نتيجة البحث وتحميل ملف وغير ذلك. أعِد توجيه المستخدم إلى صفحة أخرى عند اكتمال جميع الإجراءات. يوفر جانغو عددًا من الأدوات والأساليب لمساعدتك في المهام السابقة، والأكثر أهمية هو الصنف Form الذي يبسّط توليد شيفرة HTML للاستمارة وتنظيف أو التحقق من صحة البيانات. سنشرح في القسم التالي كيفية عمل الاستمارات باستخدام تطبيق عملي لصفحة بهدف السماح لأمناء المكتبة بتجديد الكتب. ملاحظة: سيساعدك فهم كيفية استخدام الصنف Form عندما نناقش أصناف إطار عمل الاستمارة عالية المستوى الخاصة بجانغو. استمارة تجديد الكتب Renew-book باستخدام الصنف Form والعرض المستند إلى الدوال سنضيف صفحةً للسماح لأمناء المكتبة بتجديد الكتب المستعارة من خلال إنشاء استمارة تسمح للمستخدمين بإدخال قيمة تاريخ، إذ سنعطي الحقل قيمة أولية هي 3 أسابيع من التاريخ الحالي (فترة الاستعارة العادية)، وسنضيف تحققًا من صحة البيانات للتأكد من أنّ أمين المكتبة لا يمكنه إدخال تاريخ في الماضي، أو تاريخ بعيد جدًا في المستقبل. إذا أُدخِل تاريخ صالح، فسنكتبه في الحقل BookInstance.due_back الخاص بالسجل الحالي. سيستخدم مثالنا عرضًا مستندًا إلى الدوال والصنف Form، إذ توضح الأقسام التالية كيفية عمل الاستمارات والتغييرات التي يجب إجراؤها على مشروع المكتبة المحلية LocalLibrary. صنف الاستمارة Form يُعَد الصنف Form قلب نظام معالجة الاستمارات في جانغو؛ فهو يحدّد الحقول في الاستمارة وتنسيقها وعناصر واجهة المستخدم للعرض والتسميات والقيم الأولية والقيم الصالحة ورسائل الخطأ (بمجرد التحقق من صحتها) المرتبطة بالحقول غير الصالحة. يوفّر هذا الصنف أيضًا توابعًا لعرض نفسه في القوالب باستخدام تنسيقات مُعرَّفة مسبقًا، مثل الجداول والقوائم وغيرها، أو للحصول على قيمة أيّ عنصر، مثل تفعيل عرض يدوي دقيق. التصريح عن الصنف Form تُعَد صياغة التصريح عن الصنف Form مشابهة جدًا للصياغة المُستخدَمة في التصريح عن الصنف Model، وتتشاركان في أنواع الحقول نفسها وبعض المعاملات المماثلة، وهذا أمرٌ منطقي لأنه في كلتا الحالتين يجب التأكد من أن كل حقل يتعامل مع الأنواع الصحيحة من البيانات، وهو مقيدٌ بالبيانات الصالحة وله وصف description للعرض أو التوثيق. تُخزَّن بيانات الاستمارة في الملف "forms.py" الخاص بالتطبيق ضمن مجلد التطبيق، لذا أنشئ وافتح الملف locallibrary/catalog/forms.py. يمكن إنشاء Form من خلال استيراد المكتبة forms، واشتقاق الصنف Form، والتصريح عن حقول الاستمارة. يوضح المثال البسيط التالي صنف استمارة تجديد كتب المكتبة، لذا أضفه إلى ملفك الجديد: from django import forms class RenewBookForm(forms.Form): renewal_date = forms.DateField(help_text="Enter a date between now and 4 weeks (default 3).") حقول الاستمارة لدينا في مثالنا حقل تاريخ واحد DateField لإدخال تاريخ التجديد الذي سيُعرَض في صفحة HTML بقيمة فارغة، والتسمية الافتراضية ":Renewal date"، وبعض نصوص الاستخدام المفيدة مثل: "أدخِل تاريخًا بين الوقت الحالي و4 أسابيع (القيمة الافتراضية 3 أسابيع).". سيقبل الحقل التواريخ باستخدام تنسيق input_formats مثل: YYYY-MM-DD (2016-11-06) MM/DD/YYYY (02/26/2016) MM/DD/YY (10/25/16) وذلك نظرًا لعدم تحديد أي من الوسطاء الاختيارية الأخرى، وستُعرَض باستخدام عنصر واجهة المستخدم الافتراضية: DateInput. هناك العديد من الأنواع الأخرى لحقول الاستمارة، والتي سنلاحظ تشابهها مع أصناف حقول النموذج المكافئة لها، وهي: BooleanField CharField ChoiceField TypedChoiceField DateField DateTimeField DecimalField DurationField EmailField FileField FilePathField FloatField ImageField IntegerField GenericIPAddressField MultipleChoiceField TypedMultipleChoiceField NullBooleanField RegexField SlugField TimeField URLField UUIDField ComboField MultiValueField SplitDateTimeField ModelMultipleChoiceField ModelChoiceField إليك الوسائط الشائعة في معظم الحقول مع قيمها الافتراضية: required: إذا كانت قيمته True، فلا يجوز ترك الحقل فارغًا أو إعطاءه قيمة None. تكون الحقول مطلوبة افتراضيًا، لذلك يجب أن تضبط required=False للسماح بالقيم الفارغة في الاستمارة. label: التسمية التي ستُستخدَم عند عرض الحقل بتنسيق HTML. إذا لم تُحدَّد تسمية، فسينشئ جانغو تسمية من اسم الحقل من خلال كتابة الحرف الأول بأحرف كبيرة ووضع مسافات مكان الشرطات السفلية، مثل Renewal date. label_suffix: تُعرَض نقطتان بعد التسمية افتراضيًا، مثل Renewal date:، إذ يسمح لك هذا الوسيط بتحديد لاحقة مختلفة تحتوي على محرف أو محارف أخرى. initial: قيمة الحقل الأولية عند عرض الاستمارة. widget: عنصر واجهة المستخدم للعرض المُراد استخدامه. help_text: نص إضافي يمكن عرضه في الاستمارة لشرح كيفية استخدام الحقل. error_messages: قائمة برسائل الخطأ الخاصة بالحقل، ويمكنك تعديلها لتحتوي على رسائلك الخاصة إن لزم الأمر. validators: قائمة بالدوال التي ستُستدعَى في الحقل عند التحقق من صحتها. localize: يفعّل توطين Localization إدخال بيانات الاستمارة. disabled: يُعرَض الحقل ولكن لا يمكن تعديل قيمته إذا كان هذا الوسيط True، والقيمة الافتراضية هي False. التحقق من صحة البيانات يوفر جانغو العديد من الأماكن التي يمكنك من خلالها التحقق من صحة بياناتك، ولكن أسهل طريقة للتحقق من صحة حقل واحد هي تعديل التابع clean_<fieldname>() للحقل الذي تريد التحقق منه، لذلك مثلًا يمكننا التحقق من أن قيم renewal_date المُدخلة تتراوح بين الآن (الوقت الحالي) و 4 أسابيع من خلال تقديم التابع clean_renewal_date()، لذا حدّث الملف "forms.py" ليبدو كما يلي: import datetime from django import forms from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ class RenewBookForm(forms.Form): renewal_date = forms.DateField(help_text="Enter a date between now and 4 weeks (default 3).") def clean_renewal_date(self): data = self.cleaned_data['renewal_date'] # تحقق مما إذا كان التاريخ ليس في الماضي if data < datetime.date.today(): raise ValidationError(_('Invalid date - renewal in past')) # تحقق مما إذا كان التاريخ يقع ضمن النطاق المسموح به (+4 أسابيع من اليوم) if data > datetime.date.today() + datetime.timedelta(weeks=4): raise ValidationError(_('Invalid date - renewal more than 4 weeks ahead')) # تذكّر دائمًا أن تعيد البيانات النظيفة return data هناك شيئان مهمان يجب ملاحظتهما، الأول هو أننا نحصل على بياناتنا باستخدام self.cleaned_data['renewal_date'] وأننا نعيد هذه البيانات سواءً عدّلناها أم لا في نهاية الدالة، إذ تمنحنا هذه الخطوة بيانات "نظيفة" ومُعقمة من المدخلات التي يمكن أن تكون غير آمنة باستخدام أدوات التحقق الافتراضية، ومُحوَّلة إلى النوع المعياري الصحيح للبيانات، وهو في حالتنا كائن بايثون datetime.datetime. النقطة الثانية هي أنه إذا كانت القيمة واقعةً خارج نطاقنا، فسنصدّر خطأ ValidationError مع تحديد نص الخطأ الذي نريد عرضه في الاستمارة إذا أُدخِلت قيمة غير صالحة، إذ يغلّف المثال السابق هذا النص في إحدى دوال الترجمة Translation Functions الخاصة بجانغو هي gettext_lazy() (مستوردة بالشكل ()_)، وهي ممارسة جيدة إذا أردتَ ترجمة موقعك لاحقًا. ملاحظة: هناك العديد من التوابع والأمثلة الأخرى للتحقق من صحة الاستمارات في توثيق جانغو، فمثلًا يمكنك تعديل الدالة Form.clean() وإصدار خطأ ValidationError مرةً أخرى في الحالات التي يكون فيها لديك حقول متعددة تعتمد على بعضها بعضًا. وهذا كل ما نحتاجه للاستمارات في مثالنا. ضبط عناوين URL لنضِف ضبط عنوان URL لصفحة تجديد الكتب قبل إنشاء العرض، لذا انسخ الضبط التالي وضعه في نهاية الملف locallibrary/catalog/urls.py: urlpatterns += [ path('book/<uuid:pk>/renew/', views.renew_book_librarian, name='renew-book-librarian'), ] سيعيد ضبط URL توجيه عناوين URL بالتنسيق "/catalog/book/<bookinstance_id>/renew/" إلى الدالة renew_book_librarian() في الملف views.py، ويرسل معرّف نسخة الكتاب BookInstance بوصفه معاملًا بالاسم pk، إذ يتطابق النمط فقط إذا كان pk مُنسَّقًا بتنسيق uuid بصورة صحيحة. ملاحظة: يمكننا تسمية بيانات URL المأخوذة "pk" بأيّ شيء نريده، لأن لدينا تحكمًا كاملًا بدالة العرض فنحن لا نستخدم صنف عرض تفصيلي مُعمَّم يتوقع معاملات باسم معين، ولكن يُعَد pk اختصارًا للمفتاح الرئيسي "primary key"، وهو اصطلاح مناسب لاستخدامه. العرض يجب أن يخرج render العرض الاستمارة الافتراضية عند استدعائه لأول مرة، ثم يعيد إخراجها مع رسائل خطأ إذا كانت البيانات غير صالحة، أو يعالج البيانات ويعيد التوجيه إلى صفحة جديدة إذا البيانات صالحة، لذا يجب أن يكون العرض قادرًا على معرفة ما إذا كان مُستدعًى لأول مرة لعرض الاستمارة الافتراضية، أو لمرة تالية للتحقق من صحة البيانات. النمط الأكثر شيوعًا بالنسبة للاستمارات التي تستخدم طلب POST لإرسال معلومات إلى الخادم هو اختبار العرض للطلب من النوع POST، أي if request.method == 'POST':، لتحديد طلبات التحقق من صحة الاستمارة، والنوع GET (باستخدام تعليمة else) لتحديد طلب إنشاء الاستمارة الأولية؛ فإذا أدرتَ إرسال بياناتك باستخدام طلب GET، فيمكنك تحديد ما إذا كان هذا الاستدعاء هو استدعاء العرض الأول أو التالي من خلال قراءة بيانات الاستمارة، مثل قراءة قيمة مخفية في الاستمارة. ستُكتَب عملية تجديد الكتاب في قاعدة البيانات، لذلك نستخدم أسلوب طلب POST اصطلاحيًا، إذ يُظهِر جزء الشيفرة البرمجية التالي النمط المعياري لهذا النوع من العرض المستند إلى الدوال: import datetime from django.shortcuts import render, get_object_or_404 from django.http import HttpResponseRedirect from django.urls import reverse from catalog.forms import RenewBookForm def renew_book_librarian(request, pk): book_instance = get_object_or_404(BookInstance, pk=pk) # إذا كان هذا الطلب من النوع POST، فعالج بيانات الاستمارة if request.method == 'POST': # أنشئ نسخة من الاستمارة واملأها ببيانات من الطلب (الربط Binding): form = RenewBookForm(request.POST) # تحقق مما إذا كانت الاستمارة صالحة: if form.is_valid(): # معالجة البيانات في form.cleaned_data كما هو مطلوب (نكتبها هنا فقط في الحقل due_back الخاص بالنموذج) book_instance.due_back = form.cleaned_data['renewal_date'] book_instance.save() # إعادة التوجيه إلى عنوان URL جديد: return HttpResponseRedirect(reverse('all-borrowed')) # إذا كان تابع GET (أو أي تابع آخر)، فأنشئ الاستمارة الافتراضية else: proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3) form = RenewBookForm(initial={'renewal_date': proposed_renewal_date}) context = { 'form': form, 'book_instance': book_instance, } return render(request, 'catalog/book_renew_librarian.html', context) أولًا، نستورد الاستمارة RenewBookForm وعددًا من الكائنات والتوابع المفيدة الأخرى المُستخدَمة في متن دالة العرض وهي: get_object_or_404(): يعيد كائنًا محددًا من نموذج بناءً على قيمة مفتاحه الرئيسي، ويرفع استثناء Http404 (غير موجود) إذا كان السجل غير موجود. HttpResponseRedirect: ينشئ إعادة توجيه إلى عنوان URL محدد (رمز حالة HTTP هو 302). reverse(): يولّد عنوان URL من اسم ضبط URL ومجموعة من الوسائط، وهو مكافئ لغة بايثون للوسم url الذي استخدمناه في قوالبنا. datetime: مكتبة بايثون لمعالجة التواريخ والأوقات. نستخدم أولًا في العرض الوسيط pk ضمن التابع get_object_or_404() للحصول على نسخة الكتاب BookInstance الحالية، وإذا لم تكن موجودة، فسيُنهَى العرض مباشرةً وستعرض الصفحة خطأ "غير موجود". إذا لم يكن هذا الطلب من النوع POST (تعالجه تعليمة else)، فسننشئ الاستمارة الافتراضية التي تمرّر القيمة الأولية initial لحقل renewal_date، وهي 3 أسابيع من التاريخ الحالي. book_instance = get_object_or_404(BookInstance, pk=pk) # إذا كان تابع GET (أو أي تابع آخر)، فأنشئ الاستمارة الافتراضية else: proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3) form = RenewBookForm(initial={'renewal_date': proposed_renewal_date}) context = { 'form': form, 'book_instance': book_instance, } return render(request, 'catalog/book_renew_librarian.html', context) نستدعي الدالة render() بعد إنشاء الاستمارة لإنشاء صفحة HTML، مما يؤدي إلى تحديد القالب والسياق الذي يحتوي على الاستمارة، إذ يحتوي السياق في هذه الحالة على نسخة الكتاب BookInstance التي سنستخدمها في القالب لتقديم معلومات حول الكتاب الذي نجدّده، لكن إذا كان هذا الطلب من النوع POST، فسننشئ كائن form ونملؤه ببيانات من الطلب، وتسمَّى هذه العملية "بالربط Binding" وتسمح لنا بالتحقق من صحة الاستمارة. نتحقق بعد ذلك ما إذا كانت الاستمارة صالحة، مما يؤدي إلى تشغيل شيفرة التحقق من صحة البيانات في جميع الحقول، بما في ذلك الشيفرة المُعمَّمة generic code للتحقق من أن حقل التاريخ هو تاريخ صالح ودالة clean_renewal_date() للتحقق من أن التاريخ ضمن النطاق الصحيح. book_instance = get_object_or_404(BookInstance, pk=pk) # إذا كان هذا الطلب من النوع POST، فعالج بيانات الاستمارة if request.method == 'POST': # أنشئ نسخة من الاستمارة واملأها ببيانات من الطلب (الربط Binding): form = RenewBookForm(request.POST) # تحقق ما إذا كانت الاستمارة صالحة: if form.is_valid(): # معالجة البيانات في form.cleaned_data كما هو مطلوب (نكتبها هنا فقط في الحقل due_back الخاص بالنموذج) book_instance.due_back = form.cleaned_data['renewal_date'] book_instance.save() # إعادة التوجيه إلى عنوان URL جديد: return HttpResponseRedirect(reverse('all-borrowed')) context = { 'form': form, 'book_instance': book_instance, } return render(request, 'catalog/book_renew_librarian.html', context) نستدعي الدالة render() مرةً أخرى إذا لم تكن الاستمارة صالحة، ولكن ستتضمن هذه المرة قيمة الاستمارة المُمرَّرة في السياق رسائل خطأ؛ أما إذا كانت الاستمارة صالحة، فيمكننا البدء في استخدام البيانات والوصول إليها من خلال السمة form.cleaned_data، مثل data = form.cleaned_data['renewal_date']، إذ نحتفظ في مثالنا فقط بالبيانات ضمن قيمة due_back الخاصة بالكائن BookInstance المرتبط بها. تحذير: يمكنك الوصول إلى بيانات الاستمارة مباشرةً من خلال الطلب، مثل request.POST['renewal_date'] أو request.GET['renewal_date'] إذا استخدمتَ طلبًا من النوع GET، ولكن لا ينصح بذلك، إذ تُطهَّر البيانات المُنظَّفة ويُتحقَّق من صحتها وتُحوَّل إلى أنواع متوافقة مع لغة بايثون. تتمثل الخطوة الأخيرة في جزء معالجة الاستمارة ضمن العرض في إعادة التوجيه إلى صفحةٍ أخرى وهي صفحة "النجاح" عادةً، إذ نستخدم في هذه الحالة HttpResponseRedirect و reverse() لإعادة التوجيه إلى العرض الذي اسمه 'all-borrowed' (وهو تحدي المقال السابق)؛ فإذا لم تنشئ هذه الصفحة، ففكّر في إعادة التوجيه إلى الصفحة الرئيسية ذات العنوان '/' هذا كل ما نحتاجه لمعالجة الاستمارة، لكننا ما زلنا بحاجة إلى تقييد الوصول إلى العرض لأمناء المكتبة الذين سجلوا الدخول فقط والذين لديهم إذن لتجديد الكتب، لذا نستخدم المزخرف decorator @login_required لمتطلب تسجيل دخول المستخدم، ومزخرف الدالة @permission_required مع الإذن can_mark_returned الحالي للسماح بالوصول. تُعالَج المزخرفات بالترتيب. لاحظ أنه ربما كان يجب إنشاء إعداد إذن جديد في BookInstance هو "can_renew"، لكننا سنعيد استخدام الإعداد الحالي لتبسيط مثالنا. يكون العرض النهائي كما هو موضح فيما يلي، لذا انسخ الشيفرة التالية في نهاية الملف locallibrary/catalog/views.py: import datetime from django.contrib.auth.decorators import login_required, permission_required from django.shortcuts import get_object_or_404 from django.http import HttpResponseRedirect from django.urls import reverse from catalog.forms import RenewBookForm @login_required @permission_required('catalog.can_mark_returned', raise_exception=True) def renew_book_librarian(request, pk): """View function for renewing a specific BookInstance by librarian.""" book_instance = get_object_or_404(BookInstance, pk=pk) # إذا كان هذا الطلب من النوع POST، فعالج بيانات الاستمارة if request.method == 'POST': # أنشئ نسخة من الاستمارة واملأها ببيانات من الطلب (الربط Binding): form = RenewBookForm(request.POST) # تحقق مما إذا كانت الاستمارة صالحة: if form.is_valid(): # معالجة البيانات في form.cleaned_data كما هو مطلوب (نكتبها هنا فقط في الحقل due_back الخاص بالنموذج) book_instance.due_back = form.cleaned_data['renewal_date'] book_instance.save() # إعادة التوجيه إلى عنوان URL جديد: return HttpResponseRedirect(reverse('all-borrowed')) # إذا كان تابع GET (أو أي تابع آخر)، فأنشئ الاستمارة الافتراضية else: proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3) form = RenewBookForm(initial={'renewal_date': proposed_renewal_date}) context = { 'form': form, 'book_instance': book_instance, } return render(request, 'catalog/book_renew_librarian.html', context) القالب أنشئ القالب المُشار إليه في العرض "/catalog/templates/catalog/book_renew_librarian.html" وانسخ الشيفرة التالية إليه: {% extends "base_generic.html" %} {% block content %} <h1>Renew: {{ book_instance.book.title }}</h1> <p>Borrower: {{ book_instance.borrower }}</p> <p {% if book_instance.is_overdue %} class="text-danger"{% endif %} >Due date: {{ book_instance.due_back }}</p> <form action="" method="post"> {% csrf_token %} <table> {{ form.as_table }} </table> <input type="submit" value="Submit"> </form> {% endblock %} ستكون غالبية هذه الشيفرة البرمجية مألوفةً من المقالات السابقة، إذ نوسّع القالب الأساسي ثم نعيد تعريف كتلة المحتوى content، ويمكننا الإشارة إلى {{ book_instance }} ومتغيراته لأنه مُمرَّر إلى كائن السياق في الدالة render()، ونستخدمه لسرد عنوان الكتاب والمستعير، وتاريخ الاسترجاع الأصلي. شيفرة الاستمارة بسيطة نسبيًا، إذ نصرّح أولًا عن وسوم form، التي تحدّد مكان إرسال الاستمارة action وتابع method لإرسال البيانات (في هذه الحالة هو "POST")، إذ يعني الإجراء action الفارغ إرسال بيانات الاستمارة إلى عنوان URL الحالي للصفحة وهو ما نريده. نعرّف ضمن الوسوم عنصرَ الإدخال submit الذي يمكن للمستخدم الضغط عليه لإرسال البيانات، ويُعَد {% csrf_token %} المُضَاف ضمن وسوم الاستمارة جزءًا من حماية جانغو من هجمات التزوير عبر المواقع، انظر مقال رفع مستوى أمان تطبيقات جانغو في بيئة الإنتاج. ملاحظة: ضِف {% csrf_token %} إلى كل قالب جانغو تنشئه والذي يستخدم طلب POST لإرسال البيانات، لان ذلك سيؤدي إلى تقليل فرصة اختطاف المستخدمين الضارين للاستمارات. بقي متغير القالب {{ form }} الذي مرّرناه إلى القالب في قاموس السياق، وليس مستغربًا أنه يوفر الإخراج الافتراضي لجميع حقول الاستمارة عند استخدامه، بما في ذلك التسميات وعناصر واجهة المستخدم ونص التعليمات، ويكون الإخراج كما هو موضح فيما يلي: <tr> <th><label for="id_renewal_date">Renewal date:</label></th> <td> <input id="id_renewal_date" name="renewal_date" type="text" value="2016-11-08" required /> <br /> <span class="helptext"> Enter date between now and 4 weeks (default 3 weeks). </span> </td> </tr> ملاحظة: يمكن ألّا يكون الأمر واضحًا لأن لدينا حقلًا واحدًا فقط، ولكن يُعرَّف كل حقل في صف الجدول الخاص به افتراضيًا، ويمكن توفير الإخراج نفسه إذا أشرت إلى متغير القالب {{ form.as_table }}. إذا أدخلتَ تاريخًا غير صالح، فستحصل على قائمة بالأخطاء المعروضة على الصفحة، مثل errorlist كما يلي: <tr> <th><label for="id_renewal_date">Renewal date:</label></th> <td> <ul class="errorlist"> <li>Invalid date - renewal in past</li> </ul> <input id="id_renewal_date" name="renewal_date" type="text" value="2015-11-08" required /> <br /> <span class="helptext"> Enter date between now and 4 weeks (default 3 weeks). </span> </td> </tr> طرق أخرى لاستخدام متغير قالب الاستمارة يُخرج كل حقل بوصفه صفًا في جدول باستخدام {{ form.as_table }}، ويمكنك إخراج كل حقل بوصفه عنصر قائمة باستخدام {{ form.as_ul }}، أو فقرة باستخدام {{ form.as_p }}. يمكن التحكم الكامل في إخراج rendering كل جزء من الاستمارة من خلال فهرسة خاصياته باستخدام الصيغة النقطية، لذا يمكننا مثلًا الوصول إلى عدد من العناصر المنفصلة للحقل renewal_date كما يلي: {{ form.renewal_date }}: كامل الحقل. {{ form.renewal_date.errors }}: قائمة الأخطاء. {{ form.renewal_date.id_for_label }}: معرّف التسمية. {{ form.renewal_date.help_text }}: حقل نص التعليمات. اختبار الصفحة إذا قبلت التحدي الموجود في المقال السابق، فستحصل على قائمة بجميع الكتب المُعارة في المكتبة، والتي تكون مرئية فقط لموظفي المكتبة، إذ سيكون العرض مشابهًا لما يلي: {% extends "base_generic.html" %} {% block content %} <h1>All Borrowed Books</h1> {% if bookinstance_list %} <ul> {% for bookinst in bookinstance_list %} <li class="{% if bookinst.is_overdue %}text-danger{% endif %}"> <a href="{% url 'book-detail' bookinst.book.pk %}">{{ bookinst.book.title }}</a> ({{ bookinst.due_back }}) {% if user.is_staff %}- {{ bookinst.borrower }}{% endif %} </li> {% endfor %} </ul> {% else %} <p>There are no books borrowed.</p> {% endif %} {% endblock %} يمكننا إضافة رابط إلى صفحة تجديد الكتاب بجانب كل عنصر من خلال إلحاق شيفرة القالب التالية بنص عنصر القائمة السابق. لاحظ أنه لا يمكن تشغيل شيفرة القالب هذه إلا داخل حلقة {% for %}، لأنه مكان تعريف قيمة bookinst. {% if perms.catalog.can_mark_returned %}- <a href="{% url 'renew-book-librarian' bookinst.id %}">Renew</a>{% endif %} ملاحظة: تذكّر أن تسجيل الدخول التجريبي سيحتاج الحصول على الإذن "catalog.can_mark_returned" لرؤية رابط "التجديد Renew" الجديد والوصول إلى الصفحة المرتبطة بهذا الارتباط، وذلك ربما باستخدام حساب مستخدمك المميز. يمكنك بدلًا من ذلك بناء عنوان URL تجريبي يدويًا مثل "http://127.0.0.1:8000/catalog/book/<bookinstance_id>/renew/"، إذ يمكن الحصول على معرّف bookinstance_id صالح بالانتقال إلى صفحة تفاصيل الكتاب في مكتبتك، ونسخ الحقل id. إذا نجحت في كل ما سبق، فستبدو الاستمارة الافتراضية كما يلي: وستبدو الاستمارة التي تحتوي على قيمة مُدخَلة غير صالحة كما يلي: وستبدو قائمة جميع الكتب التي تحتوي على روابط التجديد كما يلي: الصنف ModelForm يُعَد إنشاء الصنف Form باستخدام الأسلوب الموضح سابقًا مرنًا جدًا، مما يسمح لك بإنشاء أيّ نوع من صفحات الاستمارات التي تريدها وربطها بأيّ نموذج Model أو عدة نماذج، ولكن إذا احتجت استمارة لربط Map حقول نموذج واحد، فسيعرّف نموذجك معظم المعلومات التي تحتاجها في استمارتك، مثل الحقول والتسميات ونص التعليمات وغير ذلك. يُعَد استخدام الصنف المساعد ModelForm لإنشاء الاستمارة من نموذجك أسهل من إعادة إنشاء تعريفات النموذج في استمارتك، ثم يمكن استخدام الصنف ModelForm ضمن العروض Views بطريقة الصنف Form نفسها. إليك فيما يلي صنف ModelForm يحتوي على حقل الصنف RenewBookForm الأصلي نفسه، وكل ما عليك تطبيقه لإنشاء الاستمارة هو إضافة class Meta مع النموذج model المرتبط به (BookInstance) وقائمة بحقول fields النموذج المُراد تضمينها في الاستمارة: from django.forms import ModelForm from catalog.models import BookInstance class RenewBookModelForm(ModelForm): class Meta: model = BookInstance fields = ['due_back'] ملاحظة: يمكنك أيضًا تضمين جميع الحقول في الاستمارة باستخدام fields = '__all__'، أو يمكنك استخدام exclude بدلًا من fields لتحديد الحقول التي لا يجب تضمينها من النموذج. لا يوصَى بأيٍّ من الأسلوبين لأنه ستُضمَّن بعد ذلك الحقول الجديدة المُضافة إلى النموذج في الاستمارة تلقائيًا دون أن يأخذ المطور بالضرورة في حساباته الآثار الأمنية المحتملة. ملاحظة: يمكن أن تعتقد أن هذه الطريقة ليست أبسط بكثير من استخدام الصنف Form، إذ لن تلاحظ ذلك في حالتنا لأن لدينا حقلًا واحدًا فقط، ولكن إذا كان لديك الكثير من الحقول، فيمكن أن يقلل ذلك من مقدار الشيفرة البرمجية بصورة كبيرة. تأتي بقية المعلومات من تعريفات حقول النموذج، مثل التسميات وعناصر واجهة المستخدم ونص التعليمات ورسائل الخطأ، فإن لم تكن هذه المعلومات صحيحة، فيمكننا تعديلها في class Meta، وتحديد قاموس يحتوي على الحقل المطلوب تغييره وقيمته الجديدة. يمكن أن نرغب في هذه الاستمارة مثلًا باستخدام تسميةٍ لحقلنا هي "تاريخ التجديد Renewal date" بدلًا من التسمية الافتراضية التي تعتمد على اسم الحقل "Due Back"، ويمكن أن نرغب أيضًا في أن يكون نص التعليمات محددًا لحالة الاستخدام هذه. يوضح الصنف Meta التالي كيفية تعديل هذه الحقول، ويمكنك ضبط عناصر واجهة المستخدم widgets ورسائل الخطأ error_messages إن لم تكن الإعدادات الافتراضية كافية. class Meta: model = BookInstance fields = ['due_back'] labels = {'due_back': _('New renewal date')} help_texts = {'due_back': _('Enter a date between now and 4 weeks (default 3).')} يمكن إضافة التحقق من صحة البيانات من خلال استخدام الأسلوب المتبع نفسه في الصنف Form العادي، إذ يمكنك تعريف دالة بالاسم clean_<field_name>() ورفع استثناءات ValidationError للقيم غير الصالحة. الاختلاف الوحيد فيما يتعلق بالاستمارة الأصلية هو أن حقل النموذج يسمى due_back وليس "renewal_date"، وهذا التغيير ضروري لأن الحقل المقابل في BookInstance يسمى due_back. from django.forms import ModelForm from catalog.models import BookInstance class RenewBookModelForm(ModelForm): def clean_due_back(self): data = self.cleaned_data['due_back'] # تحقق مما إذا كان التاريخ ليس في الماضي if data < datetime.date.today(): raise ValidationError(_('Invalid date - renewal in past')) # تحقق مما إذا كان التاريخ يقع ضمن النطاق المسموح به (+4 أسابيع من اليوم) if data > datetime.date.today() + datetime.timedelta(weeks=4): raise ValidationError(_('Invalid date - renewal more than 4 weeks ahead')) # تذكّر دائمًا أن تعيد البيانات النظيفة return data class Meta: model = BookInstance fields = ['due_back'] labels = {'due_back': _('Renewal date')} help_texts = {'due_back': _('Enter a date between now and 4 weeks (default 3).')} يُعَد الصنف RenewBookModelForm السابق مكافئًا وظيفيًا للصنف RenewBookForm الأصلي، ويمكنك استيراده واستخدامه في أيّ مكان تستخدم فيه الصنف RenewBookForm حاليًا طالما أنك تحدّث اسم متغير الاستمارة المقابل من renewal_date إلى due_back كما في التصريح عن الاستمارة الثانية: RenewBookModelForm(initial={'due_back': proposed_renewal_date} عروض التعديل المعممة Generic Editing Views تمثل خوارزمية معالجة الاستمارة التي استخدمناها في مثال العرض المستند إلى الدوال السابق نمطًا شائعًا جدًا في عروض تعديل الاستمارة، إذ يجرّد جانغو كثيرًا من هذه "الشيفرة المتداولة Boilerplate" من خلال إنشاء عروض التعديل المُعمَّمة لإنشاء وتعديل وحذف العروض بناءً على النماذج، فهي لا تعالج سلوك "العرض View" فحسب، بل تنشئ تلقائيًا صنف الاستمارة ModelForm من النموذج. ملاحظة: هناك أيضًا -إضافةً إلى عروض التعديل التي وضحناها سابقًا- الصنف FormView الذي يقع في مكانٍ ما بين العرض المستند إلى الدوال والعروض المُعمَّمة الأخرى من حيث المرونة والجهد المبذول لكتابة الشيفرة البرمجية. ما زلت بحاجة إلى إنشاء الصنف Form لاستخدام الصنف FormView، ولكن لا يتوجّب عليك تقديم جميع أنماط معالجة الاستمارة المعيارية، بل يجب فقط توفير تقديم للدالة التي ستُستدعَى بمجرد معرفة أن الإرسال صالح. سنستخدم في هذا القسم عروض التعديل المُعمَّمة لإنشاء صفحات لإضافة وظائف إنشاء سجلات Author وتعديلها وحذفها من المكتبة، مما يوفر إعادة تقديم أساسية لأجزاءٍ من موقع المدير بفعالية. يمكن أن يكون ذلك مفيدًا إذا كنت بحاجة توفير وظائف المدير بطريقة أكثر مرونة مما يمكن توفيره باستخدام موقع المدير. العروض افتح ملف العروض "locallibrary/catalog/views.py" وضع كتلة الشيفرة البرمجية التالية في نهايته: from django.views.generic.edit import CreateView, UpdateView, DeleteView from django.urls import reverse_lazy from catalog.models import Author class AuthorCreate(CreateView): model = Author fields = ['first_name', 'last_name', 'date_of_birth', 'date_of_death'] initial = {'date_of_death': '11/06/2020'} class AuthorUpdate(UpdateView): model = Author fields = '__all__' # غير موصَى به، إذ يمكن أن يسبب مشكلة أمنية محتملة في حالة إضافة مزيد من الحقول class AuthorDelete(DeleteView): model = Author success_url = reverse_lazy('authors') يمكنك إنشاء أو تحديث أو حذف العروض التي تريد اشتقاقها من CreateView و UpdateView و DeleteView على التوالي، ثم تعريف النموذج المرتبط بها. يجب أيضًا -بالنسبة لحالات "الإنشاء create" و"التحديث update"- تحديد الحقول المراد عرضها في الاستمارة باستخدام صياغة ModelForm نفسها، لذا نظهر في هذه الحالة كيفية سرد الحقول بصورة فردية وكيفية صياغة سردها جميعًا. يمكنك تحديد القيم الأولية لكل حقل باستخدام قاموس أزواج اسم الحقل/قيمته field_name/value، وقد ضبطنا هنا تاريخ نهايتها عشوائيًا للتوضيح، إذ يمكن أن ترغب في حذفها. ستعيد هذه العروض توجيهك افتراضيًا عند النجاح إلى صفحة تعرض عنصر النموذج المُنشَأ أو المُعدَّل، والذي سيكون في حالتنا عرض تفاصيل المؤلف الذي أنشأناه في مقال سابق، ويمكنك تحديد موقع إعادة توجيه بديل من خلال التصريح عن المعامل success_url كما حدث للصنف AuthorDelete. لا يحتاج الصنف AuthorDelete إلى عرض أيٍّ من الحقول، فلا حاجة لتحديدها، ولكن يجب تحديد المعامل success_url، لأنه لا توجد قيمة افتراضية واضحة ليستخدمها جانغو، لذا نستخدم في هذه الحالة الدالة reverse_lazy() لإعادة التوجيه إلى قائمة المؤلفين بعد حذف المؤلف، إذ تُعَد الدالة reverse_lazy() نسخةً بطيئة من الدالة reverse()، واستخدمناها هنا لأننا نقدم عنوان URL لسمة عرض مستند إلى الأصناف. القوالب تستخدم عروض "الإنشاء create" و"التحديث update" القالب نفسه افتراضيًا، والذي سيُسمَّى بعد نموذجك وفقًا التنسيق: model_name_form.html. يمكنك تغيير اللاحقة إلى شيء آخر غير ""_form باستخدام الحقل template_name_suffix في عرضك مثل template_name_suffix = '_other_suffix'. أنشئ ملف القالب "locallibrary/catalog/templates/catalog/author_form.html" وانسخ النص التالي: {% extends "base_generic.html" %} {% block content %} <form action="" method="post"> {% csrf_token %} <table> {{ form.as_table }} </table> <input type="submit" value="Submit" /> </form> {% endblock %} يُعَد ذلك مشابهًا للاستمارات السابقة ويعرض الحقول باستخدام جدول. لاحظ أيضًا كيف نصرّح مرةً أخرى عن {% csrf_token %} للتأكد من أن الاستمارات مقاومة لهجمات CSRF. يتوقع عرض "الحذف delete" العثور على قالب مسمًّى بالتنسيق"_" مثل model_name_confirm_delete.html. يمكنك تغيير اللاحقة باستخدام template_name_suffix في عرضك. أنشئ ملف القالب التالي وانسخ النص التالي: الملف "locallibrary/catalog/templates/catalog/author_confirm_delete.html": {% extends "base_generic.html" %} {% block content %} <h1>Delete Author</h1> <p>Are you sure you want to delete the author: {{ author }}?</p> <form action="" method="POST"> {% csrf_token %} <input type="submit" value="Yes, delete." /> </form> {% endblock %} ضبط عناوين URL افتح ملف ضبط عناوين URL الخاص بك "locallibrary/catalog/urls.py" وضِف الضبط التالي إلى نهاية الملف: urlpatterns += [ path('author/create/', views.AuthorCreate.as_view(), name='author-create'), path('author/<int:pk>/update/', views.AuthorUpdate.as_view(), name='author-update'), path('author/<int:pk>/delete/', views.AuthorDelete.as_view(), name='author-delete'), ] لا يوجد شيء جديد هنا، إذ يمكنك أن ترى أن العروض ما هي إلا أصناف، وبالتالي يجب استدعاؤها باستخدام .as_view()، ويجب أن تكون قادرًا على التعرّف على أنماط URL في كل حالة، ويجب أن نستخدم pk بوصفه اسمًا لقيمة المفتاح الرئيسي الملتقطة، لأنه اسم المعامل الذي تتوقعه أصناف العرض. أصبحت صفحات إنشاء وتحديث وحذف المؤلفين جاهزةً الآن للاختبار، ولن نربطها بالشريط الجانبي للموقع في هذه الحالة، بالرغم من أنه يمكنك ذلك إذا أردت. ملاحظة: سيلاحظ المستخدمون شديدو الانتباه أننا لم نفعل أيّ شيء لمنع المستخدمين غير المُصرَّح لهم من الوصول إلى الصفحات، إذ سنترك ذلك بمثابة تمرين لك (تلميح: يمكنك استخدام PermissionRequiredMixin وإنشاء إذن جديد أو إعادة استخدام الإذن can_mark_returned). اختبار الصفحة أولًا، سجّل الدخول إلى الموقع بحساب لديه أيّ أذونات قررت أنها ضرورية للوصول إلى صفحات تعديل المؤلف، ثم انتقل إلى صفحة إنشاء المؤلف "http://127.0.0.1:8000/catalog/author/create/"، والتي يجب أن تبدو كما يلي: أدخِل قيم الحقول ثم اضغط على إرسال Submit لحفظ سجل المؤلف، ويجب أن تُنقَل الآن إلى عرض تفصيلي لمؤلفك الجديد، مع عنوان URL مثل العنوان "http://127.0.0.1:8000/catalog/author/10". يمكنك اختبار تعديل السجلات من خلال إلحاق "/update/" بنهاية عنوان URL للعرض التفصيلي، مثل "http://127.0.0.1:8000/catalog/author/10/update/". لن نعرض لقطة شاشة لأنها تبدو تمامًا مثل صفحة "الإنشاء create". أخيرًا، يمكننا حذف الصفحة من خلال إلحاق "delete" بنهاية عنوان URL لعرض المؤلف التفصيلي، مثل "http://127.0.0.1:8000/catalog/author/10/delete/"، ويجب أن يعرض جانغو صفحة الحذف الآتية. اضغط على "Yes, delete." لإزالة السجل والانتقال إلى قائمة جميع المؤلفين. تحدى نفسك أنشئ بعض الاستمارات لإنشاء وتعديل وحذف سجلات Book، إذ يمكنك استخدام بنية Authors نفسها تمامًا. إذا كان القالب book_form.html مجرد نسخة مُعاد تسميتها من القالب author_form.html، فستظهر صفحة "إنشاء كتاب" الجديدة مثل لقطة الشاشة التالية: الخلاصة يمكن أن يكون إنشاء الاستمارات والتعامل معها عملية معقدة، ولكن يسهّل جانغو هذا الأمر من خلال توفير آليات برمجية للتصريح عن الاستمارات وعرضها والتحقق من صحتها، ويوفر عروضًا معمَّمة لتعديل الاستمارة ويمكنها إنجاز كل العمل تقريبًا لتعريف الصفحات التي يمكنها إنشاء وتعديل وحذف السجلات المرتبطة بنسخة نموذج واحدة. هناك الكثير من الأمور التي يمكن إنجازها باستخدام الاستمارات، ولكن يجب أن تفهم كيفية إضافة الاستمارات الأساسية وشيفرة معالجتها إلى موقعك. ترجمة -وبتصرُّف- للمقال Django Tutorial Part 9: Working with forms. اقرأ المزيد المقال التالي: تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو المقال السابق: تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم التعامل مع الاستمارات كتابة أول تطبيق دانغو - الجزء الرابع: كتابة استمارة بسيطة (توثيق جانغو) واجهة برمجة تطبيقات الاستمارة (توثيق جانغو) حقول الاستمارة (توثيق جانغو) التحقق من صحة الاستمارة والحقل (توثيق جانغو) معالجة الاستمارة باستخدام العروض المستندة إلى الأصناف (توثيق جانغو) إنشاء الاستمارات من النماذج (توثيق جانغو) عروض التعديل المعمَّمة (توثيق جانغو)
-
سنوضح في هذا المقال كيفية السماح للمستخدمين بتسجيل الدخول إلى موقعك باستخدام حساباتهم الخاصة، وكيفية التحكم بما يمكنهم فعله ورؤيته بالاعتماد على ما إذا سجّلوا الدخول أم لا وبالاعتماد على أذوناتهم، إذ سنوسع موقع المكتبة المحلية LocalLibrary، ونضيف صفحات تسجيل الدخول والخروج وصفحات خاصة بالمستخدمين وأخرى خاصة بالموظفين لعرض الكتب المستعارة. المتطلبات الأساسية: اطّلع على جميع المقالات السابقة من هذه السلسلة، بما في ذلك المقال الخاص إدارة الجلسات. الهدف: فهم كيفية إعداد واستخدام استيثاق Authentication المستخدمين وأذوناتهم Permissions. يوفر جانغو Django نظام استيثاق وترخيص Authorization ("إذن permission") مبني على إطار عمل الجلسة (ناقشناه في المقال السابق)، والذي يسمح بالتحقق من اعتماديات Credentials المستخدم وتحديد الإجراءات التي يُسمَح لكل مستخدم بتطبيقها. يحتوي إطار العمل على نماذج مبنية مسبقًا للمستخدمين Users والمجموعات Groups (طريقة عامة لتطبيق الأذونات على أكثر من مستخدم في وقت واحد)، والأذونات أو الرايات التي تحدّد ما إذا كان يمكن للمستخدم تطبيق مهمة، والاستمارات والعروض لتسجيل دخول المستخدمين، وأدوات العرض لتقييد المحتوى. ملاحظة: يهدف نظام الاستيثاق إلى أن يكون عامًا جدًا وفقًا لجانغو، إذ لا يوفر بعض الميزات المتوفرة في أنظمة استيثاق الويب الأخرى، ولكن تتوفر حلول لبعض المشاكل الشائعة بوصفها حزمًا خارجية مثل تقييد محاولات تسجيل الدخول والاستيثاق الخارجية، مثل بروتوكول OAuth. سنوضّح في هذا المقال كيفية تفعيل استيثاق المستخدم في المكتبة المحلية LocalLibrary، وإنشاء صفحات تسجيل الدخول والخروج، وإضافة أذونات إلى نماذجك، والتحكم في الوصول إلى الصفحات. سنستخدم الاستيثاق أو الأذونات لعرض قوائم الكتب المُستعارة لكل من المستخدمين وأمناء المكتبة. يُعَد نظام الاستيثاق مرنًا جدًا، إذ يمكنك إنشاء عناوين URL والاستمارات والعروض والقوالب الخاصة بك من البداية إن أردتَ ذلك، فما عليك سوى استدعاء واجهة برمجة التطبيقات API المتوفرة لتسجيل دخول المستخدم. سنستخدم في هذا المقال عروض واستمارات استيثاق خاصة بجانغو لصفحات تسجيل الدخول والخروج، ولكن يجب إنشاء بعض القوالب، ويُعَد ذلك سهلًا جدًا، وسنوضح أيضًا كيفية إنشاء الأذونات، والتحقق من حالة تسجيل الدخول والأذونات في كلِّ من العروض والقوالب. تتألف هذه السلسلة الفرعية من السلسلة الأشمل تعلم تطوير الويب من المقالات التالية: مدخل إلى إطار عمل الويب جانغو Django إعداد بيئة تطوير تطبيقات جانغو تطبيق عملي لتعلم جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية تطبيق عملي لتعلم جانغو - الجزء السادس: إدارة الجلسات Sessions تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج تعرف على أمان تطبيقات جانغو تفعيل الاستيثاق جرى تفعيل الاستيثاق تلقائيًا عندما أنشأنا موقع الويب الهيكلي سابقًا لذلك لست بحاجة تطبيق أيّ شيء آخر حاليًا. ملاحظة: أُنجِز الضبط Configuration الضروري عندما أنشأنا التطبيق باستخدام الأمر django-admin startproject، وأُنشِئت جداول قاعدة البيانات للمستخدمين وأذونات النموذج عندما استدعينا الأمر python manage.py migrate لأول مرة. يُجرَى إعداد الضبط في الأقسام INSTALLED_APPS و MIDDLEWARE من ملف المشروع "locallibrary/locallibrary/settings.py" كما يلي: INSTALLED_APPS = [ # … 'django.contrib.auth', # إطار عمل الاستيثاق الأساسي ونماذجه الافتراضية 'django.contrib.contenttypes', # نظام نوع محتوى جانغو (يسمح بربط الأذونات بالنماذج) # … MIDDLEWARE = [ # … 'django.contrib.sessions.middleware.SessionMiddleware', # يدير الجلسات عبر الطلبات # … 'django.contrib.auth.middleware.AuthenticationMiddleware', # يربط المستخدمين بالطلبات باستخدام الجلسات # … إنشاء المستخدمين والمجموعات أنشأنا مسبقًا أول مستخدم عندما تعرّفنا على موقع مدير جانغو، وهو مستخدم مميز Superuser أُنشِئ باستخدام الأمر: python manage.py createsuperuser جرى استيثاق مستخدمنا المميز الذي يمتلك جميع الأذونات، لذلك يجب إنشاء مستخدم تجريبي لتمثيل مستخدم موقع عادي، وسنستخدم موقع المدير لإنشاء مجموعات المكتبة المحلية locallibrary وتسجيلات الدخول إلى موقع الويب، وهذه من أسرع الطرق لذلك. ملاحظة: يمكنك إنشاء مستخدمين برمجيًا كما هو موضح فيما يلي، إذ يجب عليك تطبيق ذلك في حالة تطوير واجهة للسماح للمستخدمين "العاديين" بإنشاء عمليات تسجيل الدخول الخاصة بهم مثلًا، ولا ينبغي أن يتمكّن معظم المستخدمين من الوصول إلى موقع المدير: from django.contrib.auth.models import User # إنشاء مستخدم وحفظه في قاعدة البيانات user = User.objects.create_user('myusername', 'myemail@crazymail.com', 'mypassword') # عدّل الحقول ثم احفظها مرةً أخرى user.first_name = 'Tyrone' user.last_name = 'Citizen' user.save() يوصَى بشدة بإعداد نموذج model مستخدم مخصص عند بدء مشروع فعلي، إذ ستتمكّن من تخصيصه بسهولة مستقبلًا إن دعت الحاجة. اطّلع على إنشاء نماذج جانغو Django Models وربطها بقاعدة البيانات على أكاديمية حسوب لمزيد من المعلومات. أولًا، سننشئ فيما يلي مجموعة ثم سننشئ مستخدمًا. لا نمتلك أيّ أذونات لإضافتها إلى أعضاء مكتبتنا حتى الآن، ولكن سيكون من الأسهل إضافتها دفعةً واحدة إلى المجموعة بدلًا من إضافتها بصورة فردية إلى كل عضو إن احتجنا إلى ذلك لاحقًا. شغّل خادم التطوير وانتقل إلى موقع المدير في متصفح الويب المحلي "http://127.0.0.1:8000/admin/"، ثم سجّل الدخول إلى الموقع باستخدام اعتماديات حساب مستخدمك المميز. يعرض القسم العلوي من موقع المدير جميع نماذجك التي يرتبها تطبيق جانغو. يمكنك النقر على ارتباطات المستخدمين Users أو المجموعات Groups من قسم الاستيثاق والترخيص Authentication and Authorization لرؤية سجلاتها الحالية. لننشئ مجموعةً جديدةً لأعضاء مكتبتنا: انقر على زر "الإضافة Add" (بجانب المجموعة Group) لإنشاء مجموعة جديدة، ثم أدخِل الاسم Name "أعضاء المكتبة Library Members" لهذه المجموعة. لا نحتاج أيّ أذونات للمجموعة، لذا اضغط على حفظ SAVE فقط، وستنتقل إلى قائمة المجموعات. لننشئ الآن مستخدمًا، لذا انتقل أولًا إلى الصفحة الرئيسية لموقع المدير. ثانيًا، انقر على زر "الإضافة Add" الموجود بجانب "المستخدمين Users" لفتح نافذة "إضافة مستخدم Add user". ثالثًا، أدخِل اسم مستخدم Username مناسب وكلمة مرور Password وتأكيدها Password confirmation للمستخدم التجريبي. رابعًا، اضغط على حفظ SAVE لإنشاء المستخدم. سينشئ موقع المدير المستخدم الجديد وينقلك مباشرةً إلى شاشة تغيير المستخدم Change user، إذ يمكنك تغيير اسم المستخدم username وإضافة معلومات للحقول الاختيارية لنموذج المستخدم، والتي تتضمن الاسم الأول واسم العائلة وعنوان البريد الإلكتروني وحالة المستخدم وأذوناته (يجب ضبط الراية Active فقط). يمكنك تحديد مجموعات المستخدم وأذوناته، والاطلاع على التواريخ المهمة المتعلقة بالمستخدم، مثل تاريخ انضمامه وتاريخ آخر تسجيل دخول. خامسًا، حدّد المجموعة Library Member من قائمة المجموعات المتاحة Available groups في قسم المجموعات Groups، ثم اضغط على السهم الأيمن بين المربعين لنقل هذه المجموعة إلى مربع المجموعات المختارة Chosen groups. أخيرًا، لا تحتاج تطبيق شيء آخر، لذا اضغط على حفظ SAVE مرةً أخرى للانتقال إلى قائمة المستخدمين. هذا كل شيء. أصبح لديك الآن حساب "عضو مكتبة عادي" يمكنك استخدامه للاختبار (بعد أن نقدّم الصفحات لتمكين هذا العضو من تسجيل الدخول). ملاحظة: يجب أن تحاول إنشاء مستخدم عضو مكتبة آخر، وأن تنشئ مجموعة لأمناء المكتبة، وتضيف مستخدمًا إليها. إعداد عروض الاستيثاق يوفر جانغو كل ما تحتاجه تقريبًا لإنشاء صفحات استيثاق للتعامل مع إدارة عمليات تسجيل الدخول وتسجيل الخروج وكلمات المرور، ويتضمن ذلك رابط عناوين URL والعروض والاستمارات، ولكنه لا يتضمن القوالب، إذ يجب إنشاء قوالبنا الخاصة. سنوضح في هذا القسم كيفية دمج النظام الافتراضي في موقع المكتبة المحلية LocalLibrary وإنشاء القوالب التي سنضعها في عناوين URL الرئيسية للمشروع. ملاحظة: لا يتوجّب عليك استخدام أيٍّ من هذه الشيفرة البرمجية، ولكن يُحتمَل أنك سترغب في ذلك لأنها ستسهّل الأمور كثيرًا. يجب بالتأكيد تغيير شيفرة معالجة الاستمارة إذا غيّرت نموذج مستخدمك، ولكن ستبقى قادرًا على استخدام دوال العرض. ملاحظة: يمكن في حالتنا وضع صفحات الاستيثاق بما في ذلك عناوين URL والقوالب ضمن تطبيق الدليل Catalog، ولكن إذا كان لدينا تطبيقات متعددة، فيُفضَّل فصل سلوك تسجيل الدخول المشترك وإتاحته على كامل الموقع، لذلك هذا ما سنوضّحه في هذا القسم. عناوين URL الخاصة بالمشروع أضف ما يلي إلى نهاية الملف urls.py الخاص بالمشروع "locallibrary/locallibrary/urls.py": # أضف عناوين url لاستيثاق موقع جانغو (لتسجيل الدخول والخروج وإدارة كلمات المرور) urlpatterns += [ path('accounts/', include('django.contrib.auth.urls')), ] انتقل إلى العنوان "http://127.0.0.1:8000/accounts/" (لاحظ الشرطة المائلة للأمام في نهاية العنوان)، وسيُظِهر جانغو خطأً مفاده أنه لم يتمكن من العثور على عنوان URL، ويسرد جميع عناوين URL التي جربها، والتي يمكن منها رؤية عناوين URL التي ستعمل. ملاحظة: يؤدي استخدام الطريقة السابقة إلى إضافة عناوين URL التالية مع أسماء موضوعة بين أقواس معقوفة square bracket، والتي يمكن استخدامها لعكس روابط عناوين URL. لست مضطرًا إلى تطبيق أيّ شيء آخر، إذ تربط عملية ربط عنوان URL السابقة تلقائيًا عناوين URL التالية: 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/<uidb64>/<token>/ [name='password_reset_confirm'] accounts/ reset/done/ [name='password_reset_complete'] حاول الآن الانتقال إلى عنوان URL لتسجيل الدخول "http://127.0.0.1:8000/accounts/login/"، الذي سيفشل مرةً أخرى مع وجود خطأ يخبرك بعدم وجود القالب المطلوب "registration/login.html" في مسار بحث القالب، وسترى الأسطر التالية في القسم الأصفر في الأعلى: Exception Type: TemplateDoesNotExist Exception Value: registration/login.html الخطوة التالية هي إنشاء المجلد registration على مسار البحث ثم إضافة الملف login.html. مجلد القوالب Template تتوقع عناوين URL (والعروض ضمنيًا) التي أضفناها مسبقًا العثورَ على القوالب المرتبطة بها في المجلد "/registration/" في مكان ما في مسار بحث القوالب. سنضع في موقعنا صفحات HTML في المجلد "templates/registration/"، إذ يجب أن يكون هذا المجلد في المجلد الجذر لمشروعك، أي المجلد نفسه للمجلدات catalog و locallibrary. إذًا، لننشئ هذه المجلدات الآن. ملاحظة: يجب أن تبدو بنية مجلدك الآن كما يلي: locallibrary/ # مجلد مشروع جانغو catalog/ locallibrary/ templates/ registration/ يمكن جعل مجلد القوالب "templates" مرئيًا لمحمِّل القوالب template loader من خلال إضافته في مسار بحث القوالب، لذا افتح إعدادات المشروع "/locallibrary/locallibrary/settings.py"، ثم استورد بعد ذلك الوحدة os (أضف السطر التالي بالقرب من بداية الملف): import os # تحتاجه الشيفرة البرمجية الآتية عدّل السطر 'DIRS' الخاص بالقسم TEMPLATES كما يلي: # … TEMPLATES = [ { # … 'DIRS': [os.path.join(BASE_DIR, 'templates')], 'APP_DIRS': True, # … قالب تسجيل الدخول Login تحذير: قوالب الاستيثاق المتوفرة في هذا المقال هي نسخة أساسية أو مُعدَّلة قليلًا من قوالب تسجيل الدخول التوضيحية لجانغو، لذا يمكن أن تخصّصها لاستخدامك الخاص. أنشِئ ملف HTML جديد بالاسم "/locallibrary/templates/registration/login.html" وضع فيه المحتويات التالية: {% extends "base_generic.html" %} {% block content %} {% if form.errors %} <p>Your username and password didn't match. Please try again.</p> {% endif %} {% if next %} {% if user.is_authenticated %} <p>Your account doesn't have access to this page. To proceed, please login with an account that has access.</p> {% else %} <p>Please login to see this page.</p> {% endif %} {% endif %} <form method="post" action="{% url 'login' %}"> {% csrf_token %} <table> <tr> <td>{{ form.username.label_tag }}</td> <td>{{ form.username }}</td> </tr> <tr> <td>{{ form.password.label_tag }}</td> <td>{{ form.password }}</td> </tr> </table> <input type="submit" value="login"> <input type="hidden" name="next" value="{{ next }}"> </form> {# Assumes you setup the password_reset view in your URLconf #} <p><a href="{% url 'password_reset' %}">Lost password?</a></p> {% endblock %} يشترك هذا القالب في بعض أوجه التشابه مع القوالب التي رأيناها سابقًا، فهو يوسّع قالبنا الأساسي ويعدّل الكتلة content. ما تبقّى من الشيفرة البرمجية هو شيفرة معالجة الاستمارة المعيارية إلى حدٍ ما، والتي سنناقشها في مقال لاحق، ولكن كل ما تحتاج إلى معرفته الآن هو أن هذه الشيفرة البرمجية ستعرض استمارة يمكنك من خلالها إدخال اسم المستخدم وكلمة المرور الخاصين بك، وأنه سيُطلَب منك إدخال القيم الصحيحة عند تحديث الصفحة إذا أدخلت قيمًا غير صالحة. انتقل إلى صفحة تسجيل الدخول "http://127.0.0.1:8000/accounts/login/" بعد حفظ قالبك، وسترى شيئًا يشبه ما يلي: إذا سجّلت الدخول باستخدام اعتماديات صالحة، فسيعاد توجيهك إلى صفحة أخرى (هي "http://127.0.0.1:8000/accounts/profile/" افتراضيًا). تكمن المشكلة في أن جانغو يتوقع افتراضيًا أنه عند تسجيل الدخول سترغب في نقلك إلى الصفحة الشخصية profile، ويمكن ألّا تكون راغبًا في ذلك، وبما أنك لم تعرّف هذه الصفحة بعد، فستتلقى خطأ آخر. افتح إعدادات المشروع "/locallibrary/locallibrary/settings.py" وضِف إلى نهايته النص التالي، إذ يجب الآن إعادة توجيهك إلى صفحة الموقع الرئيسية افتراضيًا عند تسجيل الدخول: # إعادة التوجيه إلى عنوان URL للصفحة الرئيسية بعد تسجيل الدخول # تكون إعادة التوجيه الافتراضية إلى عنوان /accounts/profile/ LOGIN_REDIRECT_URL = '/' قالب تسجيل الخروج Logout إذا انتقلتَ إلى عنوان URL لتسجيل الخروج "http://127.0.0.1:8000/accounts/logout/"، فسترى بعض السلوكيات الغريبة، إذ سيُسجَّل خروج مستخدمك بالتاكيد، ولكن ستُنقَل إلى صفحة تسجيل خروج المدير Admin، وهذا ما لا تريده، لأن رابط تسجيل الدخول login link في تلك الصفحة ينقلك إلى شاشة تسجيل دخول المدير وهذا متاح فقط للمستخدمين الذين لديهم الإذن is_staff. أنشئ الملف التالي وافتحه، ثم انسخ الشيفرة التالية: الملف "/locallibrary/templates/registration/logged_out.html": {% extends "base_generic.html" %} {% block content %} <p>Logged out!</p> <a href="{% url 'login'%}">Click here to login again.</a> {% endblock %} يُعَد هذا القالب بسيطًا جدًا، إذ يعرض فقط رسالةً تخبرك بتسجيل خروجك، ويوفر رابطًا يمكنك الضغط عليه للعودة إلى شاشة تسجيل الدخول، وإذا ذهبت إلى عنوان URL لتسجيل الخروج مرة أخرى، فسترى الصفحة التالية: قوالب إعادة تعيين كلمة المرور يستخدم نظام إعادة تعيين كلمة المرور الافتراضي البريد الإلكتروني لإرسال رابط إعادة تعيين للمستخدم، لذا يجب أن تنشئ استمارات للحصول على عنوان بريد المستخدم الإلكتروني، وإرسال البريد الإلكتروني، والسماح له بإدخال كلمة مرور جديدة، وإعلامه عند اكتمال العملية. يمكنك استخدام القوالب التالية بمثابة بداية. استمارة إعادة تعيين كلمة المرور هذه هي الاستمارة المستخدمة للحصول على عنوان البريد الإلكتروني للمستخدم، لإرسال بريد إلكتروني لإعادة تعيين كلمة المرور. أنشئ الملف التالي، وضع فيه المحتويات التالية: الملف "/locallibrary/templates/registration/password_reset_form.html": {% extends "base_generic.html" %} {% block content %} <form action="" method="post"> {% csrf_token %} {% if form.email.errors %} {{ form.email.errors }} {% endif %} <p>{{ form.email }}</p> <input type="submit" class="btn btn-default btn-lg" value="Reset password"> </form> {% endblock %} اكتمال إعادة تعيين كلمة المرور تُعرَض هذه الاستمارة بعد جمع عنوان بريدك الإلكتروني. أنشئ الملف "/locallibrary/templates/registration/password_reset_done.html"، وضع فيه المحتويات التالية: {% extends "base_generic.html" %} {% block content %} <p>We've emailed you instructions for setting your password. If they haven't arrived in a few minutes, check your spam folder.</p> {% endblock %} البريد الإلكتروني لإعادة تعيين كلمة المرور يوفّر هذا القالب نص البريد الإلكتروني بتنسيق HTML الذي يحتوي على رابط إعادة التعيين الذي سنرسله إلى المستخدمين. أنشئ الملف التالي وضع فيه المحتويات التالية: الملف ""/locallibrary/templates/registration/password_reset_email.html: Someone asked for password reset for email {{ email }}. Follow the link below: {{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %} تأكيد إعادة تعيين كلمة المرور تُعَد هذه الصفحة المكان الذي تدخِل فيه كلمة المرور الجديدة بعد النقر على الرابط الموجود في صفحة البريد الإلكتروني لإعادة تعيين كلمة المرور. أنشئ الملف التالي، وضع فيه المحتويات التالية: الملف "/locallibrary/templates/registration/password_reset_confirm.html": {% extends "base_generic.html" %} {% block content %} {% if validlink %} <p>Please enter (and confirm) your new password.</p> <form action="" method="post"> {% csrf_token %} <table> <tr> <td>{{ form.new_password1.errors }} <label for="id_new_password1">New password:</label></td> <td>{{ form.new_password1 }}</td> </tr> <tr> <td>{{ form.new_password2.errors }} <label for="id_new_password2">Confirm password:</label></td> <td>{{ form.new_password2 }}</td> </tr> <tr> <td></td> <td><input type="submit" value="Change my password"></td> </tr> </table> </form> {% else %} <h1>Password reset failed</h1> <p>The password reset link was invalid, possibly because it has already been used. Please request a new password reset.</p> {% endif %} {% endblock %} اكتمال إعادة تعيين كلمة المرور هذا هو آخر قالب لإعادة تعيين كلمة المرور، إذ يُعرَض لإعلامك بنجاح إعادة تعيين كلمة المرور. أنشئ الملف التالي، وضع فيه المحتويات التالية: الملف "/locallibrary/templates/registration/password_reset_complete.html": {% extends "base_generic.html" %} {% block content %} <h1>The password has been changed!</h1> <p><a href="{% url 'login' %}">log in again?</a></p> {% endblock %} اختبار صفحات الاستيثاق الجديدة أضفتَ ضبط عناوين URL وأنشأتَ جميع القوالب، ويجب الآن أن تعمل صفحات الاستيثاق الجديدة، إذ يمكنك اختبارها من خلال محاولة تسجيل الدخول ثم تسجيل الخروج من حساب المستخدم المميز باستخدام عناوين URL التالية: /http://127.0.0.1:8000/accounts/login /http://127.0.0.1:8000/accounts/logout ستتمكّن من اختبار وظيفة إعادة تعيين كلمة المرور من الرابط الموجود في صفحة تسجيل الدخول، ولكن انتبه إلى أن جانغو لن يرسل رسائل البريد الإلكتروني لإعادة التعيين إلّا إلى العناوين (المستخدمين) المُخزَّنة في قاعدة بياناته مسبقًا. ملاحظة: يتطلب نظام إعادة تعيين كلمة المرور دعم موقعك للبريد الإلكتروني، وهو ما يتجاوز نطاق هذا المقال، وبالتالي لن يعمل هذا الجزء بعد، لذا ضع السطر التالي في نهاية الملف settings.py للسماح بالاختبار، إذ يسجل هذا السطر أيّ رسائل بريد إلكتروني مُرسَلة إلى الطرفية Console، بحيث يمكنك نسخ رابط إعادة تعيين كلمة المرور من الطرفية. EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' اطلع على صفحة إرسال بريد إلكتروني في توثيق جانغو لمزيد من المعلومات. اختبار المستخدمين المستوثقين سنوضَح في هذا القسم ما يمكننا تطبيقه للتحكم الانتقائي في المحتوى الذي يراه المستخدم بناءً على ما إذا كان قد سجل الدخول أم لا. اختبار القوالب يمكنك الحصول على معلومات حول المستخدم الذي سجّل الدخول حاليًا في القوالب باستخدام متغير القالب {{ user }}، إذ يُضاف هذا المتغير إلى سياق القالب افتراضيًا عند إعداد المشروع كما فعلنا سابقًا في الموقع الهيكلي. ستختبر أولًا متغير القالب {{ user.is_authenticated }} لتحديد ما إذا كان المستخدم مؤهلًا لمشاهدة محتوى معين، لذلك سنحدّث الشريط الجانبي لعرض رابط "تسجيل الدخول Login" إذا كان المستخدم قد سجّل الخروج، ورابط "تسجيل الخروج Logout" إذا سجّل الدخول. افتح القالب الأساسي "base_generic.html" من المسار "/locallibrary/catalog/templates/" وانسخ النص التالي في الكتلة sidebar قبل وسم القالب endblock مباشرةً: <ul class="sidebar-nav"> … {% if user.is_authenticated %} <li>User: {{ user.get_username }}</li> <li><a href="{% url 'logout' %}?next={{ request.path }}">Logout</a></li> {% else %} <li><a href="{% url 'login' %}?next={{ request.path }}">Login</a></li> {% endif %} </ul> نستخدم الوسوم if و else و endif لعرض نص بطريقة شرطية بناءً على ما إذا كان المتغير {{ user.is_authenticated }} صحيحًا أم لا؛ فإذا كان المستخدم مستوثقًا، فسنعلم أن لدينا مستخدمًا صالحًا، لذلك نستدعي {{ user.get_username }} لعرض اسمه. ننشئ عناوين URL لروابط تسجيل الدخول والخروج باستخدام وسم القالب url وأسماء عمليات ضبط عناوين URL ذات الصلة. لاحظ كيف ألحقنا ?next={{ request.path }} بنهاية عناوين URL، مما يؤدي إلى إضافة معامل URL وهو next (الذي يحتوي على عنوان URL للصفحة الحالية) إلى نهاية عنوان URL المتعلق بالرابط. ستستخدم العروض قيمة "next" هذه بعد أن يسجّل المستخدم الدخول أو الخروج بنجاح لإعادة توجيه المستخدم إلى الصفحة التي نقر فيها أولًا على ارتباط تسجيل الدخول أو الخروج. ملاحظة: جرب ما فعلناه في هذا القسم، وفي حال كنت في الصفحة الرئيسية ونقرتَ على تسجيل الدخول أو تسجيل الخروج Login/Logout في الشريط الجانبي، فيجب أن ينتهي بك الأمر في الصفحة نفسها بعد اكتمال العملية. اختبار العروض Views إذا استخدمتَ العروض التي تستند إلى الدوال، فإن أسهل طريقة لتقييد الوصول إلى دوالك هي تطبيق المزخرف decorator الذي يدعى login_required على دالة عرضك؛ فإذا سجّل المستخدم دخوله، فستُنفَّذ شيفرة العرض كما هو معتاد؛ وإذا لم يسجّل المستخدم دخوله، فسيُعاد توجيهه إلى عنوان URL لتسجيل الدخول المُعرَّف في إعدادات المشروع settings.LOGIN_URL، مع تمرير المسار المطلق الحالي بوصفه معامل URL الذي هو next. إذا نجح المستخدم في تسجيل الدخول، فسيُعاد إلى هذه الصفحة، ولكن سيكون مستوثقًا هذه المرة. from django.contrib.auth.decorators import login_required @login_required def my_view(request): # … ملاحظة: يمكنك تطبيق الشيء نفسه يدويًا من خلال اختبار request.user.is_authenticated، ولكن استخدام المزخرف يُعَد ملائمًا أكثر. وبالمثل، أسهل طريقة لتقييد وصول المستخدمين الذين سجلوا الدخول في عروضك المستندة إلى الأصناف هي الاشتقاق من LoginRequiredMixin، ولكن يجب أن تصرّح عن هذا المخلوط mixin أولًا في قائمة الصنف الأب قبل صنف العرض الرئيسي. from django.contrib.auth.mixins import LoginRequiredMixin class MyView(LoginRequiredMixin, View): # … وهذا له سلوك إعادة التوجيه نفسه للمزخرف login_required. يمكنك أيضًا تحديد موقع بديل لإعادة توجيه المستخدم إليه إن لم يكون مستوثقًا login_url، وتحديد اسم معامل URL بدلًا من المعامل "next" لإدخال المسار المطلق الحالي redirect_field_name. class MyView(LoginRequiredMixin, View): login_url = '/login/' redirect_field_name = 'redirect_to' اطلع على توثيق جانغو لمزيد من التفاصيل. تطبيق عملي: سرد كتب المستخدم الحالي تعرّفنا على كيفية تقييد صفحة لمستخدم معين، إذًا لننشئ عرضًا للكتب التي استعارها المستخدم الحالي. لسوء الحظ، ليس لدينا حتى الآن أيّ طريقة يمكن للمستخدمين من خلالها استعارة الكتب، لذا سنوسّع أولًا نموذج BookInstance لدعم مفهوم الاستعارة واستخدام تطبيق مدير جانغو لإعارة عدد من الكتب لمستخدمنا التجريبي قبل أن نتمكن من إنشاء قائمة الكتب. النماذج Models أولًا، يجب أن نجعل من الممكن للمستخدمين الحصول على نسخة كتاب BookInstance على سبيل الإعارة. لدينا فعليًا حالة status وتاريخ الاسترجاع due_back، ولكن ليس لدينا حتى الآن أي ارتباط association بين هذا النموذج والمستخدم، لذا سننشئ هذا الارتباط باستخدام حقل ForeignKey (علاقة واحد إلى متعدد). نحتاج أيضًا إلى آلية سهلة لاختبار ما إذا كان الكتاب المُعار متأخرًا عن تاريخ استرجاعه أم لا. افتح الملف catalog/models.py، واستورد نموذج المستخدم User من django.contrib.auth.models، وضِف ذلك مباشرةً بعد سطر الاستيراد السابق في بداية الملف، بحيث يكون User متاحًا للشيفرة البرمجية اللاحقة التي تستخدمه: from django.contrib.auth.models import User أضف بعد ذلك الحقل borrower إلى النموذج BookInstance كما يلي: borrower = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True) لنضِف أيضًا خاصية يمكننا استدعاؤها من قوالبنا لمعرفة ما إذا كان هناك نسخة لكتاب معين متأخرة عن تاريخ استرجاعها، إذ يمكننا حساب ذلك في القالب نفسه، ولكن يُعَد استخدام خاصية property كما هو موضح لاحقًا أكثر كفاءة، لذلك أضف ما يلي في مكان ما بالقرب من بداية الملف: from datetime import date ضِف بعد ذلك تعريف الخاصية الآتي إلى الصنف BookInstance. ملاحظة: تستخدم الشيفرة التالية دالة بايثون bool() التي تقيّم كائنًا أو كائنًا ناتجًا عن تعبيرٍ ما، وتعيد القيمة True إلّا إذا كانت النتيجة "خاطئة"، فتعيد في هذه الحالة القيمة False. يكون الكائن خاطئًا في بايثون (يُقيَّم على أنه False) إذا كان فارغًا (مثل [] و () و {}) أو 0 أو None أو إذا كان False. @property def is_overdue(self): """Determines if the book is overdue based on due date and current date.""" return bool(self.due_back and date.today() > self.due_back) ملاحظة: نتحقق أولًا ما إذا كان due_back فارغًا قبل إجراء موازنة، إذ يمكن أن يتسبب حقل due_back الفارغ في أن يعطي جانغو خطأً بدلًا من إظهار الصفحة، فالقيم الفارغة غير قابلة للموازنة، ولا نريد أن يظهر ذلك الخطأ لمستخدمينا. حدّثنا نماذجنا، ويجب الآن إجراء عمليات تهجير migrations جديدة في المشروع ثم تطبيقها كما يلي: python3 manage.py makemigrations python3 manage.py migrate المدير Admin افتح الملف "catalog/admin.py"، وضِف الحقل borrower إلى الصنف BookInstanceAdmin في كلٍ من list_display و fieldsets كما هو موضح فيما يلي، وسيجعل هذا الحقل مرئيًا في قسم المدير Admin، وبالتالي سيُسمَح لنا بإسناد مستخدم User إلى نسخة كتاب BookInstance عند الحاجة: @admin.register(BookInstance) class BookInstanceAdmin(admin.ModelAdmin): list_display = ('book', 'status', 'borrower', 'due_back', 'id') list_filter = ('status', 'due_back') fieldsets = ( (None, { 'fields': ('book', 'imprint', 'id') }), ('Availability', { 'fields': ('status', 'due_back', 'borrower') }), ) إعارة بعض الكتب أصبح من الممكن إعارة الكتب إلى مستخدم معين، لذا جرّب إعارة عددٍ من سجلات BookInstance. اضبط الحقل borrowed لمستخدمك التجريبي، واجعل الحالة status مُعار "On loan"، واضبط تواريخ الاسترجاع اللاحقة والسابقة. ملاحظة: لن نشرح العملية بالتفصيل، لأنك تعرف مسبقًا كيفية استخدام موقع المدير. عرض الإعارة On loan سنضيف الآن عرضًا للحصول على قائمة بجميع الكتب المُعارة للمستخدم الحالي، إذ سنستخدم عالعروض Views العامة والتفصيلية نفسه الذي نعرفه، ولكن سنستورد ونشتق LoginRequiredMixin هذه المرة، بحيث لا يتمكن سوى المستخدم الذي سجّل الدخول من استدعاء هذا العرض. سنصرّح عن اسم القالب template_name بدلًا من استخدام القيمة الافتراضية، لأنه يمكن أن يكون لدينا عدة قوائم مختلفة من سجلات BookInstance مع عروض وقوالب مختلفة. أضف ما يلي إلى الملف catalog/views.py: from django.contrib.auth.mixins import LoginRequiredMixin class LoanedBooksByUserListView(LoginRequiredMixin,generic.ListView): """Generic class-based view listing books on loan to current user.""" model = BookInstance template_name = 'catalog/bookinstance_list_borrowed_user.html' paginate_by = 10 def get_queryset(self): return ( BookInstance.objects.filter(borrower=self.request.user) .filter(status__exact='o') .order_by('due_back') ) يمكننا تقييد استعلامنا عن كائنات BookInstance للمستخدم الحالي فقط من خلال إعادة تقديم التابع get_queryset() كما هو موضح في الشيفرة السابقة. لاحظ أن الرمز "o" هو الرمز المُخزَّن الذي يمثل الإعارة "on loan" وتُرتب هذه الكتب المُعارة حسب تاريخ استرجاعها due_back بحيث تُعرَض العناصر الأقدم أولًا. ضبط عناوين URL للكتب المعارة افتح الملف "/catalog/urls.py" وأضِف التابع path() الذي يشير إلى العرض السابق. يمكنك نسخ النص التالي إلى نهاية الملف: urlpatterns += [ path('mybooks/', views.LoanedBooksByUserListView.as_view(), name='my-borrowed'), ] قالب الكتب المعارة ما نحتاجه الآن هو إضافة قالب لهذه الصفحة، لذا أنشئ أولًا ملف القالب وضع فيه المحتويات التالية: الملف "/catalog/templates/catalog/bookinstance_list_borrowed_user.html": {% extends "base_generic.html" %} {% block content %} <h1>Borrowed books</h1> {% if bookinstance_list %} <ul> {% for bookinst in bookinstance_list %} <li class="{% if bookinst.is_overdue %}text-danger{% endif %}"> <a href="{% url 'book-detail' bookinst.book.pk %}">{{ bookinst.book.title }}</a> ({{ bookinst.due_back }}) </li> {% endfor %} </ul> {% else %} <p>There are no books borrowed.</p> {% endif %} {% endblock %} يشبه هذا القالب إلى حدٍ كبير القوالب التي أنشأناها مسبقًا لكائنات Book و Author، ولكن الشيء الوحيد المختلف هنا هو أننا نتحقق من التابع الذي أضفناها في النموذج (bookinst.is_overdue) ونستخدمه لتغيير لون العناصر المتأخرة. يجب أن تكون الآن قادرًا على عرض القائمة الخاصة بالمستخدم الذي سجّل الدخول في متصفحك على العنوان "http://127.0.0.1:8000/catalog/mybooks/" عند تشغيل خادم التطوير. جرّب ذلك عندما يسجّل المستخدم الدخول وعندما يسجّل خروجه، إذ يجب إعادة توجيهك إلى صفحة تسجيل الدخول في الحالة الثانية. إضافة القائمة إلى الشريط الجانبي الخطوة الأخيرة هي إضافة رابط لهذه الصفحة الجديدة إلى الشريط الجانبي، إذ سنضعه في نفس القسم لعرض معلومات أخرى للمستخدم الذي سجّل الدخول. افتح القالب الأساسي "/locallibrary/catalog/templates/base_generic.html" وضِف سطر "My Borrowed" الذي يمثل الكتب التي استعارها المستخدم إلى الشريط الجانبي في الموضع الموضح فيما يلي: <ul class="sidebar-nav"> {% if user.is_authenticated %} <li>User: {{ user.get_username }}</li> <li><a href="{% url 'my-borrowed' %}">My Borrowed</a></li> <li><a href="{% url 'logout' %}?next={{ request.path }}">Logout</a></li> {% else %} <li><a href="{% url 'login' %}?next={{ request.path }}">Login</a></li> {% endif %} </ul> إذا سجل أيّ مستخدم الدخول، فسيرى رابط "My Borrowed" في الشريط الجانبي، وقائمة الكتب المعروضة على النحو التالي (الكتاب الأول ليس له تاريخ استرجاع، وهو خطأ يجب إصلاحه في مقال لاحق): الأذونات ترتبط الأذونات بالنماذج وتحدّد العمليات التي يمكن أن يجريها مستخدم لديه الإذن على نسخة النموذج، إذ يمنح جانغو افتراضيًا أذونات الإضافة والتغيير والحذف لجميع النماذج تلقائيًا، مما يسمح للمستخدمين الذين لديهم الأذونات بتنفيذ الإجراءات المرتبطة بها عبر موقع المدير. يمكنك تحديد أذونات النماذج الخاصة بك ومنحها لمستخدمين محددين، ويمكنك تغيير الأذونات المرتبطة بنسخ مختلفة من النموذج نفسه. يُعَد اختبار الأذونات في العروض والقوالب مشابهًا جدًا للاختبار في حالة الاستيثاق، بل ويختبر اختبارُ الأذونات الاستيثاقَ أيضًا. النماذج models تُعرَّف الأذونات في قسم النموذج "class Meta" باستخدام الحقل permissions، ويمكنك تحديد العديد من الأذونات التي تريدها ضمن صف tuple، إذ يُعرَّف كل إذن في صف متداخل nested tuple يحتوي على اسم الإذن وقيمة عرضه، فمثلًا يمكن أن نعرّف إذنًا للسماح للمستخدم بتمييز الكتاب بوصفه مُعادًا كما يلي: class BookInstance(models.Model): # … class Meta: # … permissions = (("can_mark_returned", "Set book as returned"),) يمكننا بعد ذلك إسناد الإذن لمجموعة "أمناء المكتبة Librarian" في موقع المدير. افتح الملف catalog/models.py، وضِف الإذن كما هو موضح سابقًا. ينبغي كذلك إعادة تشغيل عمليات التهجير (استدعِ الأمرين التاليين لتحديث قاعدة البيانات بصورة مناسبة: python3 manage.py makemigrations python3 manage.py migrate القوالب Templates تُخزَّن أذونات المستخدم الحالي في متغير قالب يسمى {{ perms }}، ويمكنك التحقق مما إذا كان المستخدم الحالي لديه إذن معين باستخدام اسم المتغير المحدد ضمن تطبيق جانغو المرتبط به، فمثلًا ستكون قيمة {{ perms.catalog.can_mark_returned }} هي True إذا كان المستخدم لديه هذا الإذن، و False في الحالات الأخرى. نختبر عادةً الإذن باستخدام وسم القالب {% if %} كما يلي: {% if perms.catalog.can_mark_returned %} <!-- We can mark a BookInstance as returned. --> <!-- Perhaps add code to link to a "book return" view here. --> {% endif %} العروض Views يمكن اختبار الأذونات في عرض مستند إلى الدوال باستخدام المزخرف permission_required أو في عرض مستند إلى الأصناف باستخدام الصنف PermissionRequiredMixin. يكون النمط هنا مماثلًا لاستيثاق تسجيل الدخول، بالرغم من أنه يمكن أن تضطر إلى إضافة عدة أذونات. إليك مزخرف عرض مستند إلى الدوال: from django.contrib.auth.decorators import permission_required @permission_required('catalog.can_mark_returned') @permission_required('catalog.can_edit') def my_view(request): # … إليك PermissionRequiredMixin للعروض المستندة إلى الأصناف: from django.contrib.auth.mixins import PermissionRequiredMixin class MyView(PermissionRequiredMixin, View): permission_required = 'catalog.can_mark_returned' # أو أذونات متعددة permission_required = ('catalog.can_mark_returned', 'catalog.can_edit') # لاحظ أن 'catalog.can_edit' هو مجرد مثال # تطبيق الدليل catalog ليس لديه هذا الإذن ملاحظة: يوجد اختلاف افتراضي بسيط في السلوك السابق، إذ يكون لمستخدم سجّل الدخول ولكنه انتهك إذنًا ما افتراضيًا ما يلي: @permission_required: الذي يعيد التوجيه إلى شاشة تسجيل الدخول (حالة HTTP التي هي 302). PermissionRequiredMixin: الذي يعيد قيمة الحالة 403 (وهي حالة HTTP التي تمثل أن الوصول ممنوع Forbidden). ستحتاج عادةً إلى استخدام سلوك PermissionRequiredMixin الذي يعيد القيمة 403 إذا سجّل المستخدم الدخول ولكنه لا يمتلك الإذن الصحيح، ويمكن تحقيق ذلك للعرض المستند إلى الدوال من خلال استخدام @login_required و @permission_required مع raise_exception=True كما يلي: from django.contrib.auth.decorators import login_required, permission_required @login_required @permission_required('catalog.can_mark_returned', raise_exception=True) def my_view(request): # … تحدى نفسك أوضحنا في هذا المقال كيفية إنشاء صفحة للمستخدم الحالي، مع سرد الكتب التي استعارها، ويتمثل التحدي الآن في إنشاء صفحة مماثلة تكون مرئية فقط لأمناء المكتبة وتعرض جميع الكتب المُستعارة وتتضمن اسم كل مستعير. يجب أن تكون قادرًا على اتباع النمط نفسه المُتبَع في العرض السابق الذي نفّذناه، ولكن الاختلاف الرئيسي هو أنك ستحتاج إلى تقييد العرض لأمناء المكتبة فقط، ويمكنك تحقيق ذلك بناءً على ما إذا كان المستخدم موظفًا (مزخرف الدالة هو staff_member_required ومتغير القالب هو user.is_staff)، ولكننا نوصي باستخدام الإذن can_mark_returned و PermissionRequiredMixin كما هو موضح في القسم السابق. ملاحظة: تذكّر عدم استخدام مستخدمك المميز للاختبار المستند إلى الأذونات، إذ تكون عمليات التحقق من الأذونات صحيحة للمستخدمين المميزين دائمًا، حتى إن لم يُعرَّف الإذن بعد، لذا أنشِئ مستخدمًا لأمين المكتبة، وأضِف له القدرات المطلوبة. يجب أن تبدو صفحتك كما يلي عند الانتهاء: الخلاصة أنشأتَ موقع ويب يمكن لأعضاء المكتبة فيه تسجيل الدخول وعرض محتواهم، ويمكن لأمناء المكتبة (مع الإذن الصحيح) عرض جميع الكتب المُعارة ومستعيري هذه الكتب. عرضنا المحتوى فقط، ولكن تُستخدَم المبادئ والأساليب نفسها عندما تريد تعديل البيانات وإضافتها. سنتعرّف في المقال التالي على كيفية استخدام استمارات جانغو لجمع مدخلات المستخدم، ثم البدء في تعديل بعض بياناتنا المُخزَّنة. ترجمة -وبتصرُّف- للمقال Django Tutorial Part 8: User authentication and permissions. اقرأ المزيد المقال التالي: تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms المقال السابق: تطبيق عملي لتعلم جانغو Django - الجزء السادس: إدارة الجلسات Sessions استيثاق المستخدم في جانغو (توثيق جانغو) استخدام نظام استيثاق جانغو الافتراضي (توثيق جانغو) زخرفة العروض المستندة إلى الأصناف (توثيق جانغو) بناء تطبيق يعرض أحوال الطقس باستخدام جانغو Django
-
سنوسّع في هذا المقال موقع المكتبة المحلية LocalLibrary من خلال إضافة عدّاد زيارات يعتمد على الجلسات إلى الصفحة الرئيسية. يُعَد هذا مثالًا بسيطًا نسبيًا، ولكنه يوضح كيفية استخدام إطار عمل الجلسة لتوفير سلوك دائم للمستخدمين المجهولين في مواقعك الخاصة. المتطلبات الأساسية: اطّلع على جميع المقالات السابقة من هذه السلسلة، بما في ذلك مقال العروض Views العامة والتفصيلية. الهدف: فهم كيفية استخدام الجلسات. يتيح موقع المكتبة المحلية LocalLibrary الذي أنشأناه سابقًا للمستخدمين تصفح الكتب والمؤلفين في صفحة الدليل Catalog، إذ يمكن لكل مستخدم الوصول إلى الصفحات وأنواع المعلومات نفسها عند استخدام الموقع أثناء توليد المحتوى ديناميكيًا من قاعدة البيانات. يمكن أن ترغب في مكتبة حقيقية بتزويد المستخدمين بتجربة مُخصَّصة لكلٍّ منهم بناءً على استخدامهم السابق للموقع وتفضيلاتهم وغير ذلك، فمثلًا يمكنك إخفاء رسائل التحذير التي أُعلِم المستخدم بها مسبقًا في المرة التالية التي يزور فيها الموقع أو تخزين واحترام تفضيلاتهم، مثل عدد نتائج البحث التي يريد عرضها في كل صفحة. يتيح لك إطار عمل الجلسة تنفيذ هذا النوع من السلوك، مما يسمح لك بتخزين واسترداد بيانات عشوائية على أساس كل زائر للموقع. تتألف هذه السلسلة الفرعية من السلسلة الأشمل تعلم تطوير الويب من المقالات التالية: مدخل إلى إطار عمل الويب جانغو Django إعداد بيئة تطوير تطبيقات جانغو تطبيق عملي لتعلم جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية تطبيق عملي لتعلم جانغو - الجزء السادس: إدارة الجلسات Sessions تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج تعرف على أمان تطبيقات جانغو ما هي الجلسات؟ تحدث جميع الاتصالات بين متصفحات الويب والخوادم باستخدام بروتوكول HTTP، وهو بروتوكول عديم الحالة Stateless، والذي يعني أن الرسائل بين العميل والخادم مستقلة تمامًا عن بعضها بعضًا، إذ لا توجد فكرة عن التسلسل أو السلوك المعتمد على الرسائل السابقة، لذا إذا أردتَ أن يكون لديك موقع يتعقّب العلاقات الجارية مع العميل، فيجب تطبيق ذلك بنفسك. تُعَد الجلسات آليةًَ يستخدمها إطار عمل جانغو Django ومعظم شبكة الإنترنت لتعقّب الحالة بين الموقع ومتصفحٍ معين، وتتيح الجلسات تخزين البيانات العشوائية لكل متصفح، وإتاحة هذه البيانات للموقع كلما اتصل المتصفح، ويُشَار بعد ذلك إلى عناصر البيانات الفردية المرتبطة بالجلسة باستخدام "مفتاح" يُستخدم لتخزين البيانات واستردادها. يستخدم جانغو ملف تعريف ارتباط Cookie يحتوي على معرّف جلسة خاص id لتحديد كل متصفح وجلسته المرتبطة بالموقع، ولكن تُخزَّن بيانات الجلسة الفعلية في قاعدة بيانات الموقع افتراضيًا، ويُعَد ذلك أكثر أمانًا من تخزين البيانات في ملف تعريف ارتباط، والتي تكون فيها أكثر عرضةً للمستخدمين الضارين. يمكنك ضبط جانغو لتخزين بيانات الجلسة في أماكن أخرى، مثل الذاكرة المخبئية cache والملفات وملفات تعريف الارتباط "الآمنة"، ولكن يُعَد الموقع الافتراضي خيارًا جيدًا وآمنًا نسبيًا. تفعيل الجلسات جرى تفعيل الجلسات تلقائيًا عندما أنشأنا موقع الويب الهيكلي. يجري إعداد الضبط Configuration في الأقسام INSTALLED_APPS و MIDDLEWARE من ملف المشروع "locallibrary/locallibrary/settings.py" كما يلي: INSTALLED_APPS = [ # … 'django.contrib.sessions', # … MIDDLEWARE = [ # … 'django.contrib.sessions.middleware.SessionMiddleware', # … استخدام الجلسات يمكنك الوصول إلى السمة session ضمن عرضٍ View ما من المعامل request، إذ يُمرَّر الوسيط HttpRequest بوصفه أول وسيط إلى العرض. تمثل سمة الجلسة session الاتصال المحدَّد بالمستخدم الحالي، أو الاتصال بالمتصفح الحالي بصورة أدق كما يحدّده معرّف الجلسة في ملف تعريف ارتباط المتصفح لهذا الموقع. تُعَد سمة session كائنًا يشبه القاموس dictionary يمكنك قراءته وكتابته عدة مرات في عرضك، مع تعديله بالطريقة التي تريدها، كما يمكنك تطبيق جميع عمليات القاموس العادية، بما في ذلك مسح جميع البيانات واختبار ما إذا كان المفتاح موجودًا والتكرار عبر البيانات وغير ذلك، ولكن ستستخدم غالبًا واجهة برمجة تطبيقات "القاموس" المعيارية فقط للحصول على القيم وضبطها. توضح أجزاء الشيفرة البرمجية الآتية كيفية الحصول على بعض البيانات وضبطها وحذفها باستخدام المفتاح "my_car" المرتبط بالجلسة الحالية (المتصفح). ملاحظة: أحد الأشياء الرائعة في جانغو هو أنك لست بحاجة إلى التفكير في الآليات التي تربط الجلسة بطلبك الحالي في عرضك، فإذا أردنا استخدام الأجزاء التالية من الشيفرة البرمجية في عرضنا، فسنعلم أن معلومات المفتاح my_car مرتبطة فقط بالمتصفح الذي أرسل الطلب الحالي. # الحصول على قيمة الجلسة باستخدام مفتاحها (مثل المفتاح 'my_car')، إذ سيحدث خطأ KeyError إن لم يكن المفتاح موجودًا my_car = request.session['my_car'] # الحصول على قيمة الجلسة، إذ تُضبَط القيمة الافتراضية إن لم تكن موجودة ('mini') my_car = request.session.get('my_car', 'mini') # ضبط قيمة الجلسة request.session['my_car'] = 'mini' # حذف قيمة الجلسة del request.session['my_car'] تقدّم واجهة برمجة التطبيقات API عددًا من التوابع الأخرى التي تُستخدَم غالبًا لإدارة ملف تعريف ارتباط الجلسة المرتبط، فمثلًا هناك توابع لاختبار أن ملفات تعريف الارتباط مدعومة في متصفح العميل، ولضبط والتحقق من تواريخ انتهاء صلاحية ملف تعريف الارتباط، ولمسح الجلسات منتهية الصلاحية من مخزن البيانات. يمكنك التعرف على واجهة برمجة التطبيقات الكاملة في توثيق جانغو الرسمي. حفظ بيانات الجلسة يحفظ جانغو افتراضيًا في قاعدة بيانات الجلسة فقط ويرسل ملف تعريف ارتباط الجلسة إلى العميل عند تعديل (تخصيص) الجلسة أو حذفها، فإذا أردتَ تحديث بعض البيانات باستخدام مفتاح الجلسة الخاص بها كما وضحنا سابقًا، فلا داعٍ للقلق بشأن ذلك، فمثلًا: # اُكتشِف السطر التالي بوصفه تحديثًا للجلسة، لذلك تُحفَظ بيانات الجلسة. request.session['my_car'] = 'mini' إذا حدّثتَ بعض المعلومات في بيانات الجلسة، فلن يدرك جانغو أنك أجريت تغييرًا على الجلسة ولن يحفظ البيانات، فإذا أردتَ مثلًا تغيير بيانات "wheels" في بيانات "my_car" كما يلي، فيجب تمييز الجلسة على أنها مُعدَّلة صراحةً: # لم يُعدَّل كائن الجلسة مباشرة، إذ عُدِّلت البيانات ضمن الجلسة فقط، إذًا لن تُحفَظ تغييرات الجلسة request.session['my_car']['wheels'] = 'alloy' # ضبط الجلسة بوصفها مُعدَّلة لفرض حفظ تحديثات البيانات أو ملفات تعريف الارتباط request.session.modified = True ملاحظة: يمكنك تغيير السلوك بحيث يحدّث الموقع قاعدة بيانات أو يرسل ملف تعريف الارتباط في كل طلب من خلال إضافة: SESSION_SAVE_EVERY_REQUEST = True في إعدادات مشروعك "locallibrary/locallibrary/settings.py". تطبيق عملي: الحصول على عدد الزيارات سنحدّث مكتبتنا لإخبار المستخدم الحالي بعدد المرات التي زار فيها الصفحة الرئيسية لموقع المكتبة المحلية ليكون بمثابة مثالٍ بسيط واقعي. افتح الملف /locallibrary/catalog/views.py وضِف الأسطر التي تحتوي على num_visits في التابع index() كما يلي: def index(request): # … num_authors = Author.objects.count() # 'all()' مُضمَّنة افتراضيًا # عدد الزيارات إلى هذا العرض، كما هو محسوب في متغير الجلسة num_visits = request.session.get('num_visits', 0) request.session['num_visits'] = num_visits + 1 context = { 'num_books': num_books, 'num_instances': num_instances, 'num_instances_available' : num_instances_available, 'num_authors': num_authors, 'num_visits': num_visits, } # عرض قالب index.html مع البيانات الموجودة في متغير السياق return render(request, 'index.html', context=context) سنحصل أولًا على قيمة مفتاح الجلسة 'num_visits' مع ضبطها على القيمة 0 إن لم تكن مضبوطةً مسبقًا، ثم نزيد القيمة في كل مرة نتلقى فيها طلبًا ونخزّنها في الجلسة (للمرة التالية التي يزور فيها المستخدم الصفحة)، ثم يُمرَّر المتغير num_visits إلى القالب في متغير السياق. ملاحظة: يمكن أن نختبر أيضًا ما إذا كانت ملفات تعريف الارتباط مدعومةً في المتصفح (اطّلع على كيفية استخدام الجلسات في توثيق جانغو للحصول على أمثلة)، أو أن نصمم واجهة المستخدم بحيث لا يهم ما إذا كانت ملفات تعريف الارتباط مدعومةً أم لا. أضف السطر الموضّح في نهاية الكتلة التالية إلى قالب HTML الرئيسي "/locallibrary/catalog/templates/index.html" في نهاية قسم "المحتوى الديناميكي Dynamic content" لعرض متغير السياق num_visits: <h2>Dynamic content</h2> <p>The library has the following record counts:</p> <ul> <li><strong>Books:</strong> {{ num_books }}</li> <li><strong>Copies:</strong> {{ num_instances }}</li> <li><strong>Copies available:</strong> {{ num_instances_available }}</li> <li><strong>Authors:</strong> {{ num_authors }}</li> </ul> <p> You have visited this page {{ num_visits }} time{{ num_visits|pluralize }}. </p> لاحظ أننا نستخدم وسم القالب pluralize المبني مسبقًا في جانغو لإضافة "s" (التي تمثل صيغة الجمع في اللغة الانجليزية) عند زيارة الصفحة مرات متعددة. احفظ التغييرات وأعِد تشغيل خادم الاختبار، إذ يجب تحديث عدد الزيارات في كل مرة تحدّث فيها الصفحة. الخلاصة تعرّفنا في هذا المقال على مدى سهولة استخدام الجلسات لتحسين تفاعلك مع مستخدمين مجهولين، وسنشرح في مقالاتنا القادمة إطار عمل الاستيثاق Authentication والترخيص Authorization (الإذن)، وسنوضح كيفية دعم حسابات المستخدمين. ترجمة -وبتصرُّف- للمقال Django Tutorial Part 7: Sessions framework. اقرأ المزيد المقال التالي: تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم المقال السابق: تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية بناء تطبيق ويب لإدارة معلومات العملاء باستخدام جانغو Django وريآكت React كيفية استخدام الجلسات في توثيق جانغو (توثيق جانغو)
-
سنوسّع في هذا المقال موقع المكتبة المحلية من خلال إضافة صفحات قائمة وتفاصيل للكتب والمؤلفين، إذ سنتعرف على العروض المُعمَّمة المستندة إلى الأصناف Generic Class-based Views، ونوضح كيف يمكنها تقليل كمية الشيفرة البرمجية التي يجب عليك كتابتها لحالات الاستخدام الشائعة. سننتقل أيضًا إلى معالجة عناوين URLs بمزيد من التفصيل، ونوضح كيفية إجراء مطابقة الأنماط الأساسية. المتطلبات الأساسية: أكمل جميع هذه السلسلة من المقالات بما في ذلك المقال السابق تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية لإنشاء الصفحة الرئيسية. الهدف: فهم مكان وكيفية استخدام العروض المعمَّمة المستندة إلى الأصناف، وكيفية استخراج الأنماط من عناوين URLs وتمرير المعلومات إلى العروض. سنكمل في هذا المقال النسخة الأولى من موقع المكتبة المحلية LocalLibrary من خلال إضافة صفحات قائمة وصفحات تفصيلية للكتب والمؤلفين، إذ سنعرض كيفية تقديم صفحات الكتاب، ونجعلك تنشئ صفحات المؤلفين بنفسك. تشبه هذه العملية إنشاء صفحة الفهرس التي تعلّمناها في المقال السابق، إذ سنحتاج إلى إنشاء روابط Maps لعناوين URL والعروض والقوالب. يتمثل الاختلاف الرئيسي في أننا سنواجه تحديًا إضافيًا بالنسبة لصفحات التفاصيل يتمثل في استخراج المعلومات من الأنماط الموجودة في عنوان URL وتمريرها إلى العرض، إذ سنعرض بالنسبة لهذه الصفحات نوعًا مختلفًا تمامًا من العروض هو عروض القائمة المُعمَّمة المستندة إلى الأصناف والعروض التفصيلية التي يمكن أن تقلل بصورة كبيرة من مقدار الشيفرة البرمجية للعرض المطلوب، مما يسهل كتابته وصيانته. سيوضح الجزء الأخير من هذا المقال كيفية ترقيم صفحات Pagination بياناتك عند استخدام عروض القائمة المُعمَّمة المستندة إلى الأصناف. تتألف هذه السلسلة الفرعية من السلسلة الأشمل تعلم تطوير الويب من المقالات التالية: مدخل إلى إطار عمل الويب جانغو Django إعداد بيئة تطوير تطبيقات جانغو تطبيق عملي لتعلم جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية تطبيق عملي لتعلم جانغو - الجزء السادس: إدارة الجلسات Sessions تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج تعرف على أمان تطبيقات جانغو صفحة قائمة الكتب ستعرض صفحة قائمة الكتب قائمةً بجميع سجلات الكتب المتاحة في الصفحة، والتي يمكن الوصول إليها باستخدام عنوان URL هو "catalog/books/"، وستعرض الصفحة عنوانًا ومؤلفًا لكل سجل على أن يكون العنوان رابطًا تشعبيًا لصفحة تفاصيل الكتاب المرتبطة به. سيكون للصفحة البنية والتنقل نفسه لجميع الصفحات الأخرى في الموقع، وبالتالي يمكننا توسيع القالب الأساسي base_generic.html الذي أنشأناه سابقًا. ربط عناوين URLs افتح الملف "/catalog/urls.py" وانسخ فيه السطر الذي يضبط مسار 'books/'. تعرّف الدالة path() -كما هو الحال بالنسبة لصفحة الفهرس Index- نمطًا لمطابقة عنوان URL الذي هو 'books/'، ودالة عرض ستُستدعَى عند التطابق مع عنوان URL وهي views.BookListView.as_view()، واسمًا لهذا الربط المحدَّد كما يلي: urlpatterns = [ path('', views.index, name='index'), path('books/', views.BookListView.as_view(), name='books'), ] يجب أن يطابق عنوان URL عنوان "/catalog" كما ناقشنا سابقًا، لذلك سيُستدعَى العرض لعنوان URL هو "/catalog/books/". لدالة العرض تنسيق مختلف عن ذي قبل، لأن هذا العرض سيُقدَّم بوصفه صنفًا، إذ سنرث من دالة عرض مُعمَّمة حالية تنفّذ معظم ما نريد أن تنفّذه دالة العرض بدلًا من كتابة دالتنا من الصفر. يمكننا الوصول إلى دالة عرض مناسبة من خلال استدعاء تابع الصنف as_view() بالنسبة لعروض جانغو المستندة إلى الأصناف، إذ يطبّق هذا التابع كل العمل لإنشاء نسخة من الصنف والتأكد من استدعاء التوابع المعالجة الصحيحة لطلبات HTTP الواردة. العرض المستند إلى الأصناف يمكننا بسهولة كتابة عرض قائمة الكتب بوصفه دالة عادية، مثل عرض الفهرس في المقال السابق، إذ يمكن لهذا العرض الاستعلام في قاعدة البيانات عن جميع الكتب، ثم استدعاء التابع render() لتمرير القائمة إلى قالب محدد، ولكن سنستخدم بدلًا من ذلك عرض قائمة مُعمَّمًا ومستندًا إلى الأصناف ListView، إذ يرث هذا الصنف من العرض الموجود مسبقًا. يقدّم العرض المُعمَّم معظم الوظائف التي نحتاجها ويتبع أفضل ممارسات جانغو، لذا سنكون قادرين على إنشاء عرض قائمة أقوى مع شيفرة وتكرار أقل وصيانة أقل. افتح الملف catalog/views.py، وانسخ الشيفرة البرمجية التالية في نهاية الملف: from django.views import generic class BookListView(generic.ListView): model = Book سيستعلم العرض المُعمَّم في قاعدة البيانات للحصول على جميع السجلات للنموذج المحدد Book، ثم يعرض قالبًا موجودًا في الملف "/locallibrary/catalog/templates/catalog/book_list.html"، الذي سننشئه لاحقًا. يمكنك ضمن القالب الوصول إلى قائمة الكتب باستخدام متغير القالب المسمى object_list أو book_list؛ أي "list_<اسم النموذج>" عمومًا. ملاحظة: ليس هذا المسار الغريب لموقع القالب خطأ مطبعيًا، إذ تبحث العروض المعمَّمة عن القوالب في الملف "/application_name/the_model_name_list.html" (في حالتنا "catalog/book_list.html") في المجلد "/application_name/templates/" الخاص بالتطبيق ("/catalog/templates/"). يمكنك إضافة سمات لتغيير السلوك الافتراضي السابق، إذ يمكنك مثلًا تحديد ملف قالب آخر إذا كنت بحاجة إلى عروض متعددة تستخدم النموذج نفسه، أو يمكن أن ترغب في استخدام اسم متغير قالب مختلف إذا لم يكن الاسم book_list معبرًا عن حالة استخدام قالبك. يُحتمَل أن يكون الاختلاف الأكثر فائدة هو تغيير أو ترشيح المجموعة الفرعية من النتائج المُعادة، لذا يمكنك سرد أفضل 5 كتب قرأها المستخدمون الآخرون بدلًا من سرد جميع الكتب. class BookListView(generic.ListView): model = Book context_object_name = 'book_list' # اسمك الخاص للقائمة بوصفه متغير قالب queryset = Book.objects.filter(title__icontains='war')[:5] # احصل على 5 كتب تحتوي على العنوان war template_name = 'books/my_arbitrary_template_name_list.html' # حدد اسم/موقع قالبك تعديل التوابع في العروض المستندة إلى الأصناف يمكنك تعديل بعض توابع الصنف بالرغم من أننا لا نحتاج إلى ذلك حاليًا، فمثلًا يمكننا تعديل التابع get_queryset() لتغيير قائمة السجلات المُعادة، إذ يُعَد ذلك أكثر مرونةً من ضبط السمة queryset كما فعلنا في جزء الشيفرة البرمجية السابق، بالرغم من عدم وجود فائدة حقيقية في هذه الحالة: class BookListView(generic.ListView): model = Book def get_queryset(self): # احصل على 5 كتب تحتوي على العنوان war return Book.objects.filter(title__icontains='war')[:5] يمكننا تعديل التابع get_context_data() لتمرير متغيرات سياق إضافية إلى القالب، مثل تمرير قائمة الكتب افتراضيًا. يوضح جزء الشيفرة التالي كيفية إضافة متغير بالاسم "some_data" إلى السياق، وسيكون متاحًا بعد ذلك بوصفه متغير قالب: class BookListView(generic.ListView): model = Book def get_context_data(self, **kwargs): # استدعِ التقديم الأساسي أولًا للحصول على السياق context = super(BookListView, self).get_context_data(**kwargs) # أنشئ بيانات وأضِفها إلى السياق context['some_data'] = 'This is just some data' return context من المهم اتباع النمط المستخدم السابق عند تطبيق ذلك كما يلي: احصل أولًا على السياق الحالي من الصنف الأب. ضِف بعد ذلك معلومات السياق الجديدة. ثم أعِد السياق الجديد (المُحدَّث). ملاحظة: اطلع على العروض المعمَّمة المستندة إلى الأصناف المبنية مسبقًا في توثيق جانغو للحصول على مزيد من الأمثلة لما يمكنك فعله باستخدامها. إنشاء قالب عرض القائمة أنشئ ملف HTML بالاسم التالي: /locallibrary/catalog/templates/catalog/book_list.html وانسخ فيه النص الآتي، وهو ملف القالب الافتراضي الذي يتوقعه عرض القائمة المُعمَّمة المستند إلى الأصناف (للنموذج Book في التطبيق catalog). تتشابه قوالب العروض المُعمَّمة مع أيّ قوالب أخرى بالرغم من اختلاف السياق أو المعلومات الممررة إلى القالب. سنوسّع القالب الأساسي في السطر الأول، ثم سنستبدل كتلة المحتوى content كما هو الحال في قالب الفهرس index. {% extends "base_generic.html" %} {% block content %} <h1>Book List</h1> {% if book_list %} <ul> {% for book in book_list %} <li> <a href="{{ book.get_absolute_url }}">{{ book.title }}</a> ({{book.author}}) </li> {% endfor %} </ul> {% else %} <p>There are no books in the library.</p> {% endif %} {% endblock %} يمرر العرضُ السياقَ (قائمة الكتب) افتراضيًا بوصفه اسمًا بديلًا لمتغير القالب object_list و book_list (سيلبي أيّ منهما الغرض). التنفيذ المشروط نستخدم وسوم القالب if و else و endif للتحقق من تعريف المتغير book_list وأنه ليس فارغًا. إذا كان book_list فارغًا، فستعرض تعليمة else نصًا يوضح عدم وجود كتب لسردها؛ وإذا لم يكن فارغًا، سنكرر المرور على قائمة الكتب. {% if book_list %} <!-- تسرد هذه الشيفرة البرمجية الكتب --> {% else %} <p>There are no books in the library.</p> {% endif %} يتحقق الشرط السابق من حالة واحدة فقط، ولكن يمكنك اختبار شروط إضافية باستخدام وسم القالب elif، مثل {% elif var2 %}. اطلع على if و ifequal/ifnotequal و ifchanged في وسوم ومرشحات القوالب المبنية مسبقًا في توثيق جانغو لمزيد من المعلومات حول المعاملات الشرطية. حلقات for يستخدم القالب وسمي القالب for و endfor للتكرار عبر قائمة الكتب كما هو موضح في المثال التالي، إذ يملأ كل تكرار متغير القالب book بمعلومات عن عنصر القائمة الحالية: {% for book in book_list %} <li> <!-- تحصل هذه الشيفرة البرمجية على المعلومات من كل عنصر كتاب --> </li> {% endfor %} يمكنك أيضًا استخدام وسم القالب {% empty %} لتحديد ما يحدث إذا كانت قائمة الكتب فارغة (بالرغم من أن قالبنا يختار استخدام شرط بدلًا من ذلك): <ul> {% for book in book_list %} <li><!-- تحصل هذه الشيفرة البرمجية على المعلومات من كل عنصر كتاب --></li> {% empty %} <p>There are no books in the library.</p> {% endfor %} </ul> سينشئ جانغو أيضًا ضمن الحلقة متغيرات أخرى يمكنك استخدامها لتتبع التكرار بالرغم من عدم استخدامه هنا، فمثلًا يمكنك اختبار المتغير forloop.last لإجراء معالجة شرطية لآخر مرة لتشغيل الحلقة. الوصول إلى المتغيرات تنشئ الشيفرة البرمجية الموجودة ضمن الحلقة عنصر قائمة لكل كتاب يُظهِر المؤلف والعنوان كأنه رابط للعرض التفصيلي الذي لم ننشئه بعد كما يلي: <a href="{{ book.get_absolute_url }}">{{ book.title }}</a> ({{book.author}}) يمكن الوصول إلى حقول سجل الكتاب المرتبط بها باستخدام "الصيغة النقطية Dot Notation" مثل book.title و book.author، إذ يكون النص الذي يأتي بعد العنصر book هو اسم الحقل كما هو مُحدَّد في النموذج. يمكننا أيضًا استدعاء دوال في النموذج من القالب، إذ نستدعي في هذه الحالة الدالة Book.get_absolute_url() للحصول على عنوان URL الذي يمكنك استخدامه لعرض سجل التفاصيل المرتبط به. تعمل هذه الدالة بشرط ألّا تحتوي على أي وسطاء، إذ لا توجد طريقة لتمريرها. ملاحظة: يجب أن نكون حذرين بعض الشيء من "الآثار الجانبية" لاستدعاء الدوال في القوالب، إذ حصلنا في مثالنا فقط على عنوان URL للعرض، ولكن الدالة يمكنها فعل أي شيء، فلا نريد حذف قاعدة بياناتنا عند عرض النموذج مثلًا. تحديث القالب الأساسي افتح النموذج الأساسي التالي وأدخل {% url 'books' %} في ارتباط عنوان URL لجميع الكتب All books، مما يؤدي إلى تفعيل هذا الارتباط في جميع الصفحات، ويمكننا وضع ذلك في مكانه الصحيح بنجاح الآن بعد أن أنشأنا رابط Mapper عنوان URL للكتب. الملف /locallibrary/catalog/templates/base_generic.html: <li><a href="{% url 'index' %}">Home</a></li> <li><a href="{% url 'books' %}">All books</a></li> <li><a href="">All authors</a></li> لن تتمكن من إنشاء قائمة الكتب بعد، لأننا ما زلنا نفتقد اعتمادية هي ربط عنوان URL لصفحات تفاصيل الكتب، والتي تُعَد ضروريةً لإنشاء ارتباطات تشعبية لكل كتاب. سنعرض كلًا من عروض القائمة والعروض التفصيلية بعد القسم التالي. صفحة تفاصيل الكتاب ستعرض صفحة تفاصيل الكتاب معلومات حول كتاب معين يمكن الوصول إليه باستخدام عنوان URL هو catalog/book/<id>، إذ يمثل <id> المفتاح الرئيسي للكتاب. سندرج أيضًا تفاصيل النسخ المتاحة BookInstances بما في ذلك الحالة وموعد الإعادة المتوقع والناشر والمعرّف، إضافةً إلى الحقول الموجودة في النموذج Book (المؤلف والملخص ورقم ISBN واللغة والنوع)، مما يسمح لقرائنا ليس فقط بالتعرف على الكتاب، ولكن لتأكيد ما إذا كان متوفرًا أو موعد توفره. ربط عناوين URLs افتح الملف /catalog/urls.py وضِف المسار المُسمَّى 'book-detail'، إذ تعرّف الدالة path() نمطًا، وعرضًا تفصيليًا معمَّمًا مستندًا إلى الأصناف مرتبطًا به، واسمًا. urlpatterns = [ path('', views.index, name='index'), path('books/', views.BookListView.as_view(), name='books'), path('book/<int:pk>', views.BookDetailView.as_view(), name='book-detail'), ] يستخدم نمط عنوان URL -بالنسبة للمسار book-detail- صيغةً خاصةً لالتقاط المعرّف المحدد للكتاب الذي نريد رؤيته، إذ تكون هذه الصيغة بسيطةً جدًا، إذ تحدّد أقواس الزاوية جزء عنوان URL الذي سيُلتقط، مع تضمين اسم المتغير الذي يمكن أن يستخدمه العرض للوصول إلى البيانات الملتقطة، فمثلًا سيلتقط النمط المحدد ويمرر القيمة إلى العرض بوصفه المتغير "something". يمكنك اختياريًا أن تسبق اسم المتغير بمواصفات المحوّل Converter Specification التي تحدد نوع البيانات (int و str و slug و uuid و path). نستخدم في حالتنا '<int:pk>' لالتقاط معرّف الكتاب، والذي يجب أن يكون سلسلةً نصيةً منسقةً بصورة خاصة ويجب تمريره إلى العرض بوصفه معاملًا بالاسم pk (اختصار المفتاح الرئيسي Primary Key)، وهذا هو المعرّف الذي يُستخدَم لتخزين الكتاب بصورة فريدة في قاعدة البيانات كما هو مُحدَّد في النموذج Book. ملاحظة: عنوان URL المطابق هو "catalog/book/"، لأننا في التطبيق catalog أو /catalog/ كما يُفترض. تحذير: يتوقع العرض التفصيلي المعمَّم المستند إلى الأصناف تمرير معاملٍ بالاسم pk، فإذا أردت كتابة دالة العرض الخاصة بك، فيمكنك استخدام أيّ اسم معامل تريده، أو تمرير المعلومات في وسيط بدون اسم. مطابقة المسار أو التعبير النمطي المتقدم ملاحظة: لن تحتاج هذا القسم لإكمال هذا المقال، ولكننا نقدّمه لأن معرفة هذا الخيار يُحتمَل أن يكون مفيدًا في مستقبلك المرتكز على إطار عمل جانغو. مطابقة النمط التي يوفرها التابع path() بسيطة ومفيدة للحالات الشائعة جدًا إذ تريد فقط التقاط أيّ سلسلة أو عدد صحيح، ولكن إذا كنت بحاجة إلى مزيدٍ من الترشيح المُحسَّن مثل ترشيح السلاسل النصية التي تحتوي على عدد معين من المحارف فقط، فيمكنك استخدام التابع re_path() الذي يُستخدَم تمامًا مثل التابع path() باستثناء أنه يسمح لك بتحديد نمط باستخدام تعبير نمطي Regular Expression، فمثلًا يمكن كتابة المسار السابق كما يلي: re_path(r'^book/(?P<pk>\d+)$', views.BookDetailView.as_view(), name='book-detail'), تُعَد التعابير النمطية أداةً فعالة جدًا لربط الأنماط، وهي غير بسيطة ويمكن أن تكون مخيفة للمبتدئين. سنوضّح فيما يلي مقدمةً تمهيديةً عنها، فأول شيء يجب معرفته هو أنه يجب التصريح عن التعابير النمطية باستخدام صياغة سلسلة نصية مجردة (أي تُضمَّن بالشكل: '<يظهر نص تعبيرك النمطي هنا>'r). الأجزاء الرئيسية من الصياغة التي ستحتاج إلى معرفتها للتصريح عن تطابقات الأنماط هي: الرمز معناه ^ يطابق بداية النص. $ يطابق نهاية النص. \d يطابق رقمًا (0 و 1 و 2 و ... 9). \w يطابق محرفًا بطول كلمة مثل أي حرف كبير أو صغير في الأبجدية أو رقمًا أو محرف الشرطة السفلية (_). + يطابق محرفًا أو أكثر من المحارف السابقة، فمثلًا يمكنك استخدام \d+ لمطابقة رقم واحد أو أكثر، ويمكنك استخدام a+ لمطابقة حرف "a" واحد أو أكثر. * يطابق صفرًا أو أكثر من المحارف السابقة، فمثلًا يمكنك مطابقة لا شيء أو كلمة من خلال استخدام \w* ( ) يلتقط جزء النمط الموجود بين الأقواس، وتُمرَّر أيّ قيم مُلتقَطة إلى العرض بوصفات معاملات دون اسم، فإن اُلتقِطت أنماطٌ متعددة، فستُوفَّر المعاملات المرتبطة بها بترتيب التصريح عن هذه الالتقاطات. (?P...) التقاط النمط (الذي تشير إليه ...) بوصفه متغيرًا مُسمًّى (في هذه الحالة "name")، وتُمرَّر القيم المُلتقَطة إلى العرض باستخدام الاسم المُحدَّد، لذلك يجب أن يصرّح العرض عن معامل بالاسم نفسه. [ ] يطابق محرفًا واحدًا في المجموعة، فمثلًا سيطابَق [abc] مع 'a' أو 'b' أو 'c'، وسيطابَق [-\w] مع المحرف '-' أو أي محرف بحجم كلمة. يمكن أن تؤخَذ معظم المحارف الأخرى حرفيًا. لنطّلع الآن على بعض الأمثلة الحقيقية عن الأنماط: النمط الوصف r'^book/(?P<pk>\d+)$' هذا هو التعبير النمطي RE المُستخدَم في رابط عنوان URL الخاص بنا، حيث يجري مطابقة مع سلسلة نصية تحتوي على book/ في بداية السطر (^book/)، ثم يحتوي على رقم واحد أو أكثر (\d+)، ثم ينتهي (بمحارف غير رقمية قبل علامة نهاية السطر). يلتقط هذا النمط أيضًا جميع الأرقام (?P<pk>\d+) ويمررها إلى العرض ضمن معامل يسمى 'pk'، إذ تُمرَّر دائمًا القيم المُلتقَطة بوصفها سلسلة نصية، فمثلًا سيطابق هذا النمط book/1234، ويرسل المتغير pk='1234' إلى العرض. r'^book/(\d+)$' يجري هذا النمط مطابقة مع عناوين URL للحالة السابقة نفسها، وستُرسَل المعلومات المُتقَطة بوصفها وسيطًا غير مُسمَّى إلى العرض. r'^book/(?P<stub>[-\w]+)$' يجري هذا النمط مطابقة مع سلسلة نصية تحتوي على book/ في بداية السطر (^book/)، ثم يحتوي على حرف واحد أو أكثر يكون إما '-' أو محرف بحجم كلمة ([-\w]+)، ثم ينتهي النمط، إذ يلتقط هذه المجموعة من المحارف ويمرّرها إلى العرض ضمن معامل بالاسم 'stub'. يُعَد هذا النمط نموذجيًا إلى حد ما لمفاتيح "Stub"، التي تُعَد مفاتيحًا رئيسية للبيانات وتعتمد على الكلمات ومناسبة لعناوين URL، إذ يمكنك استخدامها إذا أردتَ أن يكون عنوان URL للكتاب يتضمن معلومات أكثر مثل استخدام /catalog/book/the-secret-garden بدلًا من /catalog/book/33. يمكنك التقاط أنماط متعددة في تطابق واحد، مما يؤدي إلى تشفير الكثير من المعلومات المختلفة في عنوان URL. ملاحظة: ضع في حساباتك كتحدٍ لك كيفية تشفير عنوان URL لسرد جميع الكتب الصادرة في سنة وشهر ويوم محددين والتعبير النمطي RE الذي يمكن استخدامه لمطابقتها. تمرير خيارات إضافية في روابط URL إحدى الميزات التي لم نستخدمها حتى الآن -لكنها تُعَد قيّمة- هي أنه يمكنك تمرير قاموس Dictionary يحتوي على خيارات إضافية إلى العرض باستخدام الوسيط الثالث غير المسمَّى للدالة path(). يمكن أن يكون هذا الأسلوب مفيدًا إذا أردت استخدام العرض نفسه لموارد متعددة، وتمرير البيانات لإعداد سلوكها في كل حالة، فمثلًا سيستدعي جانغو: views.my_view(request, fish=halibut, my_template_name='some_path') بالنسبة إلى المسار التالي لطلب /myurl/halibut/: path('myurl/<int:fish>', views.my_view, {'my_template_name': 'some_path'}, name='aurl'), ملاحظة: تُمرَّر كلٌّ من الأنماط المُلتقَطة وخيارات القاموس إلى العرض بوصفها وسائطًا لها اسم، فإذا استخدمتَ الاسم نفسه لكل من نمط الالتقاط ومفتاح القاموس، فسيُستخدَم خيار القاموس. العرض المستند إلى الأصناف افتح "catalog/views.py"، وانسخ الشيفرة البرمجية التالية في أسفل الملف: class BookDetailView(generic.DetailView): model = Book هذا كل شيء، وكل ما عليك فعله الآن هو إنشاء قالب يُسمَّى: /locallibrary/catalog/templates/catalog/book_detail.html وسيمرّر العرض إليه معلومات قاعدة البيانات للسجل Book المحدد الذي يستخرجه رابط عنوان URL، إذ يمكنك الوصول إلى تفاصيل الكتاب ضمن النموذج باستخدام متغير القالب المسمى object أو book (أي "اسم النموذج" بصورة عامة). يمكنك تغيير القالب المُستخدَم واسم كائن السياق المُستخدَم للإشارة إلى الكتاب في القالب إذا احتجتَ إلى ذلك، ويمكنك تعديل التوابع لإضافة معلومات إضافية إلى السياق مثلًا. ماذا يحدث إذا كان السجل غير موجود؟ إذا لم يكن السجل المطلوب موجودًا، فسيرفع العرض التفصيلي المُعمَّم المستند إلى الأصناف استثناءَ Http404 تلقائيًا، وسيعرض في مرحلة الإنتاج صفحة مناسبة تحتوي على النص "لم يُعثَر على المورد resource not found"، والتي يمكنك تخصيصها إذا رغبت في ذلك. سنعطيك فكرةً عن كيفية عمل ذلك فقط، إذ يوضح جزء الشيفرة التالي كيفية تقديم العرض المستند إلى الأصناف بوصفه دالة إن لم تستخدم العرض التفصيلي المُعمَّم المستند إلى الأصناف: def book_detail_view(request, primary_key): try: book = Book.objects.get(pk=primary_key) except Book.DoesNotExist: raise Http404('Book does not exist') return render(request, 'catalog/book_detail.html', context={'book': book}) يحاول العرض أولًا الحصول على سجل الكتاب المحدد من النموذج، وإذا فشل في ذلك، فيجب أن يرفع العرض استثناء "Http404" للإشارة إلى أن الكتاب غير موجود، والخطوة الأخيرة هي كالعادة استدعاء الدالة render() مع اسم القالب وبيانات الكتاب في المعامل context بوصفه قاموسًا. يمكننا بدلًا من ذلك استخدام الدالة get_object_or_404() بوصفها اختصارًا لرفع استثناء "Http404" إذا لم يُعثَر على السجل كما يلي: from django.shortcuts import get_object_or_404 def book_detail_view(request, primary_key): book = get_object_or_404(Book, pk=primary_key) return render(request, 'catalog/book_detail.html', context={'book': book}) إنشاء نموذج العرض التفصيلي أنشئ ملف HTML بالاسم: /locallibrary/catalog/templates/catalog/book_detail.html وضع فيه المحتوى التالي، وهذا هو اسم ملف القالب الافتراضي الذي يتوقعه العرض التفصيلي المُعمَّم المستند إلى الأصناف (للنموذج Book في التطبيق المُسمى catalog). {% extends "base_generic.html" %} {% block content %} <h1>Title: {{ book.title }}</h1> <p><strong>Author:</strong> <a href="">{{ book.author }}</a></p> <!-- ارتباط تفاصيل المؤلف لم يُعرَّف بعد --> <p><strong>Summary:</strong> {{ book.summary }}</p> <p><strong>ISBN:</strong> {{ book.isbn }}</p> <p><strong>Language:</strong> {{ book.language }}</p> <p><strong>Genre:</strong> {{ book.genre.all|join:", " }}</p> <div style="margin-left:20px;margin-top:20px"> <h4>Copies</h4> {% for copy in book.bookinstance_set.all %} <hr /> <p class="{% if copy.status == 'a' %}text-success{% elif copy.status == 'm' %}text-danger{% else %}text-warning{% endif %}"> {{ copy.get_status_display }} </p> {% if copy.status != 'a' %} <p><strong>Due to be returned:</strong> {{ copy.due_back }}</p> {% endif %} <p><strong>Imprint:</strong> {{ copy.imprint }}</p> <p class="text-muted"><strong>Id:</strong> {{ copy.id }}</p> {% endfor %} </div> {% endblock %} ملاحظة: لارتباط المؤلف في النموذج السابق عنوان URL فارغ، لأننا لم ننشئ بعد صفحة تفاصيل المؤلف للارتباط بها. يمكننا الحصول على عنوان URL الخاص بصفحة التفاصيل بمجرد وجودها باستخدام أيٍّ من الطريقتين التاليتين: أولًا، استخدم وسم القالب url لعكس عنوان URL الخاص بتفاصيل المؤلف 'author-detail' المُعرَّف في رابط عنوان URL، ومرّره إلى نسخة المؤلف الخاص بالكتاب: <a href="{% url 'author-detail' book.author.pk %}">{{ book.author }}</a> استدعِ التابع get_absolute_url() الخاص بنموذج المؤلف، إذ يجري هذا التابع عملية الانعكاس نفسها: <a href="{{ book.author.get_absolute_url }}">{{ book.author }}</a> يطبّق كلا التابعين الشيء نفسه بفعالية، ولكن يُفضَّل استخدام التابع get_absolute_url() لأنه يساعدك على كتابة شيفرة برمجية أكثر تناسقًا وقابلية للصيانة، إذ يجب إجراء التغييرات في مكان واحد فقط هو نموذج المؤلف. شرحنا مسبقًا كل شيء تقريبًا في هذا القالب بالرغم من أنه أكبر قليلًا الآن: نوسّع القالب الأساسي ونعدّل كتلة "المحتوى content". نستخدم المعالجة المشروطة لتحديد عرض محتوًى معين أم لا. نستخدم حلقات for للتكرار عبر قوائم الكائنات. نصل إلى حقول السياق باستخدام الصيغة النقطية، إذ سُمِّي السياق بالاسم book لأننا استخدمنا العرض المُعمَّم التفصيلي، ولكن يمكننا أيضًا استخدام الاسم "object". أول شيء مهم لم نره من قبل هو التابع book.bookinstance_set.all() الذي ينشئه جانغو تلقائيًا لإعادة مجموعة سجلات BookInstance المرتبطة بكتاب Book معين: {% for copy in book.bookinstance_set.all %} <!-- شيفرة للتكرار على كل نسخة من الكتاب --> {% endfor %} يُعَد هذا التابع ضروريًا لأنك تصرّح عن حقل ForeignKey (واحد إلى متعدد) فقط في جانب "المتعدد" من العلاقة (جانب BookInstance). بما أنك لا تفعل أي شيء للتصريح عن العلاقة في النموذج الآخر ("واحد")، فلن يحتوي هذا الجانب (النموذج Book) على أيّ حقل للحصول على مجموعة السجلات المرتبطة به. يتغلب جانغو على هذه المشكلة من خلال بناء دالة "بحث عكسي" مسمّاة بطريقة مناسبة لاستخدامها، إذ يُبنَى اسم الدالة من خلال جعل حروف اسم النموذج حروفًا صغيرة في مكان التصريح عن ForeignKey ويتبعه _set، فمثلًا يكون اسم الدالة المُنشَأة في Book هي bookinstance_set(). ملاحظة: استخدمنا هنا الدالة all() للحصول على جميع السجلات افتراضيًا، ويمكنك استخدام الدالة filter() للحصول على مجموعة فرعية من السجلات في الشيفرة البرمجية، ولكن لا يمكنك ذلك مباشرةً في القوالب لأنه لا يمكن تحديد وسطاء للدوال. احذر أيضًا، ففي حال لم تحدّد طلبًا في العرض المستند إلى الأصناف أو النموذج، فسترى أخطاءً من خادم التطوير مثل الخطأ التالي: [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: <QuerySet [<Author: Ortiz, David>, <Author: H. McRaven, William>, <Author: Leigh, Melinda>]> allow_empty_first_page=allow_empty_first_page, **kwargs) يحدث هذا الخطأ لأن كائن ترقيم الصفحات يتوقع أن يرى تنفيذ تعليمة الترتيب ORDER BY على قاعدة بياناتك الأساسية، فبدونها لا يمكن التأكد من أن السجلات المُعادة بالترتيب الصحيح. لم يغطي هذا المقال ترقيم الصفحات Pagination حتى الآن، ولكن بما أنه لا يمكنك استخدام الدالة sort_by() وتمرير معامل كما هو الحال مع الدالة filter() سابقًا، فيجب عليك الاختيار من بين ثلاثة خيارات هي: أضف السمة ordering ضمن التصريح عن الصنف class Meta في نموذجك. أضف السمة queryset في عرضك المخصص المستند إلى الأصناف مع تحديد الدالة order_by(). أضف التابع get_queryset في عرضك المخصص المستند إلى الأصناف مع تحديد الدالة order_by(). إذا قررت استخدام الصنف class Meta للنموذج Author (ربما لن يكون مرنًا مثل تخصيص العرض المستند إلى الأصناف، ولكنه سهل بما فيه الكفاية)، فستحصل على ما يلي: class Author(models.Model): first_name = models.CharField(max_length=100) last_name = models.CharField(max_length=100) date_of_birth = models.DateField(null=True, blank=True) date_of_death = models.DateField('Died', null=True, blank=True) def get_absolute_url(self): return reverse('author-detail', args=[str(self.id)]) def __str__(self): return f'{self.last_name}, {self.first_name}' class Meta: ordering = ['last_name'] ليس ضروريًا أن يكون الحقل هو last_name، إذ يمكن أن يكون أيّ حقل آخر. أخيرًا، يجب عليك الفرز حسب السمة أو العمود الذي يحتوي على فهرس (فريد أو غير فريد) في قاعدة البيانات لتجنب مشاكل الأداء. لن يكون ذلك ضروريًا في هذا المشروع، فلن نحتاج مستقبلًا إلّا عددًا قليلًا من الكتب والمستخدمين، ولكنه شيء يستحق أن نضعه في الحسبان بالنسبة للمشاريع المستقبلية. الشيء الثاني المهم وغير الواضح في القالب هو المكان الذي نعرض فيه نص الحالة لكل نسخة كتاب ("متاح Available" و"في الصيانة Maintenance" …إلخ.). سيلاحظ القراء المتمرسون أن التابع BookInstance.get_status_display() الذي نستخدمه للحصول على نص الحالة لا يظهر في أي مكان آخر من الشيفرة البرمجية. <p class="{% if copy.status == 'a' %}text-success{% elif copy.status == 'm' %}text-danger{% else %}text-warning{% endif %}"> {{ copy.get_status_display }} </p> تُنشَأ هذه الدالة تلقائيًا لأن الحقل BookInstance.status هو حقل اختيارات choices، إذ ينشئ جانغو تلقائيًا التابع get_FOO_display() لكل حقل اختيارات "Foo" في النموذج، والذي يمكن استخدامه للحصول على قيمة الحقل الحالية. أنشأنا حتى الآن كل ما هو مطلوب لعرض كلِّ من صفحات قائمة الكتب وتفاصيل الكتاب. شغّل الخادم باستخدام الأمر python3 manage.py runserver وافتح المتصفح على العنوان "http://127.0.0.1:8000/". ملاحظة: لا تنقر على أيّ ارتباطات للمؤلف أو تفاصيله حتى الآن، إذ ستنشِئ هذه الارتباطات في التحدي الذي سنوكله إليك في نهاية المقال. انقر على ارتباط جميع الكتب All books لعرض قائمة الكتب. انقر بعد ذلك على ارتباط إلى أحد كتبك. إن سار كل شيء بصورة صحيحة، فسترى شيئًا مثل لقطة الشاشة التالية: ترقيم الصفحات Pagination إذا كان لديك عدد قليل من السجلات، فستبدو صفحة قائمة الكتب جيدة، ولكن ستستغرق الصفحة وقتًا أطول للتحميل وستحتوي على الكثير من المحتوى لتصفحه عندما تصل إلى عشرات أو مئات السجلات. يمكن حل هذه المشكلة من خلال إضافة ترقيم الصفحات إلى عروض القائمة، مما يقلل عدد العناصر المعروضة في كل صفحة. يتمتع جانغو بدعم ممتاز لترقيم الصفحات، وهو مُضمَّن في عروض القائمة المُعمَّمة المستندة إلى الأصناف، لذا لا يجب عليك فعل الكثير لتفعيله. العروض Views افتح الملف catalog/views.py، وأضِف سطر paginate_by التالي: class BookListView(generic.ListView): model = Book paginate_by = 10 سيبدأ العرض مع هذه الإضافة بترقيم صفحات البيانات التي يرسلها إلى القالب بمجرد أن يكون لديك أكثر من 10 سجلات. يمكن الوصول إلى الصفحات المختلفة باستخدام معاملات GET، فمثلًا ستستخدم /catalog/books/?page=2 للوصول إلى الصفحة 2. القوالب Templates يجب الآن إضافة دعم إلى القالب للتمرير عبر مجموعة النتائج بعد أن أصبحت البيانات ذات صفحات مرقَّمة، إذ سنضيف ذلك إلى القالب الأساسي لأننا يمكن أن نرغب في ترقيم صفحات جميع عروض القائمة. افتح الملف التالي وابحث عن "كتلة المحتوى content block" كما يلي: الملف /locallibrary/catalog/templates/base_generic.html: {% block content %}{% endblock %} انسخ فيه كتلة ترقيم الصفحات التالية بعد {% endblock %} مباشرةً، إذ تتحقق هذه الشيفرة البرمجية أولًا من تفعيل ترقيم الصفحات في الصفحة الحالية. إذا كان الأمر كذلك، فستضيف ارتباطات التالي next والسابق previous كما هو مناسب (ورقم الصفحة الحالية). {% block pagination %} {% if is_paginated %} <div class="pagination"> <span class="page-links"> {% if page_obj.has_previous %} <a href="{{ request.path }}?page={{ page_obj.previous_page_number }}">previous</a> {% endif %} <span class="page-current"> Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}. </span> {% if page_obj.has_next %} <a href="{{ request.path }}?page={{ page_obj.next_page_number }}">next</a> {% endif %} </span> </div> {% endif %} {% endblock %} يُعَد الكائن page_obj كائن ترقيم Paginator، والذي يكون موجودًا في حالة استخدام ترقيم الصفحات في الصفحة الحالية، ويسمح لك بالحصول على جميع المعلومات حول الصفحة الحالية والصفحات السابقة وعدد الصفحات الموجودة وغير ذلك. نستخدم {{ request.path }} للحصول على عنوان URL للصفحة الحالية لإنشاء ارتباطات ترقيم الصفحات، ويُعَد ذلك مفيدًا لأنه مستقل عن الكائن الذي نرقّم صفحاته. توضح لقطة الشاشة الآتية كيف يبدو ترقيم الصفحات؛ فإذا لم تدخِل أكثر من 10 عناوين في قاعدة بياناتك، فيمكنك اختبارها بسهولة أكبر من خلال تقليل العدد المُحدَّد في السطر paginate_by في الملف "catalog/views.py"، فمثلًا يمكنك الحصول على النتيجة التالية من خلال التغيير إلى paginate_by = 2، وتُعرَض ارتباطات ترقيم الصفحات في الجزء السفلي مع عرض ارتباطات next أو previous بناءً على الصفحة الموجود فيها: تحدى نفسك يتمثل التحدي في هذا المقال في إنشاء تفاصيل المؤلف وعروض القائمة المطلوبة لإكمال المشروع، إذ يجب توفيرها على عناوين URL التالية: catalog/authors/: قائمة جميع المؤلفين. catalog/author/<id>: العرض التفصيلي للمؤلف المُحدَّد باستخدام حقل المفتاح الرئيسي الذي يُسمَّى <id>. يجب أن تكون الشيفرة البرمجية المطلوبة لروابط عناوين URL والعروض متطابقة تقريبًا مع العروض التفصيلية وعروض قائمة Book التي أنشأناها سابقًا، وستكون القوالب مختلفة ولكنها ستشترك في سلوك مماثل. ملاحظة: ستحتاج إلى تحديث ارتباط جميع المؤلفين All authors في القالب الأساسي بعد إنشاء رابط عنوان URL لصفحة قائمة المؤلفين، لذا اتبع العملية نفسها عندما حدّثنا ارتباط جميع الكتب All books. يجب أيضًا تحديث قالب عرض تفاصيل الكتاب /locallibrary/catalog/templates/catalog/book_detail.html بعد إنشاء رابط عنوان URL لصفحة تفاصيل المؤلف، بحيث يؤشّر ارتباط المؤلف إلى صفحة تفاصيل المؤلف الجديدة بدلًا من أن يكون عنوان URL فارغ، والطريقة الموصى بها لذلك هي استدعاء التابع get_absolute_url() في نموذج المؤلف كما يلي: <p> <strong>Author:</strong> <a href="{{ book.author.get_absolute_url }}">{{ book.author }}</a> </p> يجب بعد ذلك أن تبدو صفحاتك كما يلي: الخلاصة تهانينا، لقد اكتملت وظائف مكتبتنا الأساسية، إذ تعلمنا في هذا المقال كيفية استخدام عروض القائمة المعمَّمة المستندة إلى الأصناف والعرض التفصيلية واستخدمناها لإنشاء صفحات لعرض الكتب والمؤلفين، وتعرّفنا على مطابقة الأنماط مع التعابير النمطية، وكيفية تمرير البيانات من عناوين URLs إلى العروض، وتعلمنا بعض الحيل الأخرى لاستخدام القوالب. أخيرًا، عرضنا كيفية ترقيم صفحات عروض القائمة بحيث يمكن إدارة القوائم حتى عند وجود العديد من السجلات. سنوسّع في المقالات القادمة هذه المكتبة لدعم حسابات المستخدمين، وبالتالي سنوضح استيثاق Authentication المستخدمين والأذونات والجلسات والاستمارات. ترجمة -وبتصرُّف- للمقال Django Tutorial Part 6: Generic list and detail views. اقرأ أيضًا المقال التالي: تطبيق عملي لتعلم جانغو - الجزء السادس: إدارة الجلسات Sessions المقال السابق: تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية العروض والقوالب الجزء الأول والجزء الثاني بناء تطبيق ويب لإدارة معلومات العملاء باستخدام جانغو Django وريآكت React العروض المُضمَّنة المُعمَّمة والقائمة على الأصناف (توثيق جانغو) العروض المُعمَّمة (توثيق جانغو) مدخل إلى العروض المستندة إلى الأصناف (توثيق جانغو) وسوم ومرشحات القوالب المُضمَّنة (توثيق جانغو) ترقيم الصفحات (توثيق جانغو) الكائنات ذات الصلة بإجراء الاستعلامات (توثيق جانغو)
-
أصبحنا الآن جاهزين لإضافة الشيفرة البرمجية التي تعرض أول صفحة كاملة، وهي الصفحة الرئيسية لموقع المكتبة المحلية LocalLibrary. ستعرض الصفحة الرئيسية عددًا من سجلاتنا لكل نوع نموذج وتوفر روابط التنقّل الجانبية إلى صفحاتنا الأخرى. سنكتسب في هذا المقال خبرةً عمليةً في كتابة روابط Maps وعروض Views عناوين URL الأساسية للحصول على السجلات من قاعدة البيانات واستخدام القوالب. المتطلبات الأساسية: الاطلاع على مقال مدخل إلى إطار عمل الويب جانغو، وأكمل موضوعات المقالات السابقة (بما في ذلك مقال موقع مدير جانغو). الهدف: تعلم كيفية إنشاء روابط وعروض بسيطة لعناوين URL (إذ لا توجد بيانات مُشفَّرة في عنوان URL) والحصول على البيانات من النماذج وإنشاء القوالب. عرّفنا نماذجنا وأنشأنا بعض سجلات المكتبة الأولية للعمل معها، لذا حان الوقت الآن لكتابة الشيفرة البرمجية التي تقدم هذه المعلومات للمستخدمين. أول شيء يجب علينا تطبيقه هو تحديد المعلومات التي نريد عرضها في صفحاتنا وتعريف عناوين URL المراد استخدامها لإعادة تلك الموارد، ثم سننشئ رابط Mapper عنوان URL والعروض والقوالب لعرض الصفحات. يوضح الشكل الآتي تدفق البيانات الرئيسي والمكونات المطلوبة عند معالجة طلبات واستجابات HTTP. بما أننا قدّمنا النموذج مسبقًا، فإن المكونات الرئيسية التي سننشئها هي: روابط عناوين URL لتوجيه عناوين URL المدعومة (وأي معلومات مُشفَّرة في عناوين URL) إلى دوال العرض المناسبة. دوال العرض للحصول على البيانات المطلوبة من النماذج وإنشاء صفحات HTML التي تعرض البيانات وإعادة الصفحات إلى المستخدم لعرضها في المتصفح. القوالب المراد استخدامها عند عرض البيانات في العروض. لدينا خمس صفحات لعرضها، وهي معلومات كثيرة جدًا لتوثيقها في مقال واحد، لذا سيركز هذا المقال على كيفية تقديم الصفحة الرئيسية وسنغطي الصفحات الأخرى في مقال لاحق، إذ يجب أن يمنحك ذلك فهمًا جيدًا شاملًا لكيفية عمل روابط عناوين URL والعروض والنماذج عمليًا. تتألف هذه السلسلة الفرعية من السلسلة الأشمل تعلم تطوير الويب من المقالات التالية: مدخل إلى إطار عمل الويب جانغو Django إعداد بيئة تطوير تطبيقات جانغو تطبيق عملي لتعلم جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية تطبيق عملي لتعلم جانغو - الجزء السادس: إدارة الجلسات Sessions تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج تعرف على أمان تطبيقات جانغو تعريف مورد عناوين URLs بما أن هذه النسخة من موقع المكتبة المحلية LocalLibrary للقراءة فقط للمستخدمين النهائيين، فيجب توفير صفحة هبوط للموقع، أي صفحة رئيسية وصفحات تعرض عروض القائمة والعروض التفصيلية للكتب والمؤلفين. عناوين URLs التي سنحتاجها لصفحاتنا، هي: catalog/: الصفحة الرئيسية (الفهرس index). catalog/books/: قائمة بجميع الكتب. catalog/authors/: قائمة بجميع المؤلفين. catalog/book/<id>: العرض التفصيلي لكتاب معين مع حقل المفتاح الرئيسي <id> (الافتراضي)، فمثلًا سيكون عنوان URL للكتاب الثالث المُضاف إلى القائمة هو /catalog/book/3. catalog/author/<id>: العرض التفصيلي للمؤلف المحدد مع حقل المفتاح الرئيسي <id>، فمثلًا سيكون عنوان URL الخاص بالمؤلف الحادي عشر المُضاف إلى القائمة هو /catalog/author/11. ستعرض عناوين URLs الثلاثة الأولى صفحة الفهرس وقائمة الكتب وقائمة المؤلفين. لا تشفّر عناوين URLs هذه أيّ معلومات إضافية، وستبقى الاستعلامات التي تجلب البيانات من قاعدة البيانات هي نفسها دائمًا، ولكن ستعتمد النتائج التي تعيدها الاستعلامات على محتويات قاعدة البيانات. بينما سيعرض عنوانا URL الأخيرين معلومات مُفصَّلة حول كتاب أو مؤلف معين. تشفّر عناوين URLs هذه هوية العنصر المُراد عرضه (يمثله <id>). سيستخرج رابط عنوان URL المعلومات المُشفَّرة ويمررها إلى العرض، وسيحدد العرض ديناميكيًا المعلومات التي سنحصل عليها من قاعدة البيانات، وسنستخدم مجموعة واحدة من ربط عنوان URL والعرض والقالب للتعامل مع جميع الكتب (أو المؤلفين) من خلال تشفير المعلومات في عنوان URL. ملاحظة: يمكنك باستخدام جانغو بناء عناوين URLs كما تشاء، إذ يمكنك تشفير المعلومات في متن عنوان URL كما هو موضح سابقًا، أو تضمين معاملات GET في عنوان URL مثل /book/?id=6. يجب أن تبقى عناوين URLs نظيفة ومنطقية وقابلة للقراءة على النحو الذي توصي به منظمة W3C بغض النظر عن الطريقة التي تستخدمها. يوصي توثيق جانغو بتشفير المعلومات في متن عنوان URL لتحقيق تصميم أفضل لعنوان URL. سنوضح في باقي هذا المقال كيفية إنشاء صفحة الفهرس أو الصفحة الرئيسية. إنشاء صفحة الفهرس الصفحة الأولى التي سننشئها هي صفحة الفهرس (catalog/)، إذ ستتضمن صفحة الفهرس بعض شيفرة HTML الثابتة إلى جانب "عدادات" ناتجة عن السجلات المختلفة في قاعدة البيانات. يمكن إنجاز ذلك من خلال إنشاء ربطٍ لعناوين URLs وعرض وقالب. ملاحظة: يستحق الأمر مزيدًا من الاهتمام في هذا القسم، إذ تنطبق معظم هذه المعلومات على الصفحات الأخرى التي سننشئها. ربط Mapping عناوين URLs حدّثنا الملف locallibrary/urls.py عندما أنشأنا موقع الويب الهيكلي للتأكد من أنه كلما اُستلِم عنوان URL له البادئة catalog/، فسيعالج الوحدة catalog.urls من النوع URLConf السلسلة الفرعية المتبقية. يتضمن جزء الشيفرة البرمجية التالي من الملف locallibrary/urls.py الوحدة catalog.urls: urlpatterns += [ path('catalog/', include('catalog.urls')), ] ملاحظة: إذا قابل جانغو دالة الاستيراد django.urls.include()، فسيقسم سلسلة عنوان URL عند المحرف النهائي المحدد ويرسل السلسلة الفرعية المتبقية إلى وحدة URLconf المُضمَّنة لمزيدٍ من المعالجة. أنشأنا أيضًا ملفًا بديلًا لوحدة URLConf بالاسم /catalog/urls.py، لذا أضِف الأسطر التالية إلى هذا الملف: urlpatterns = [ path('', views.index, name='index'), ] تعرّف الدالة path() ما يلي: نمط عنوان URL وهو سلسلة نصية فارغة: '' (سنناقش أنماط URL بالتفصيل عند العمل على العروض Views الأخرى). دالة عرض ستُستدعَى عند اكتشاف نمط URL: هي الدالة views.index التي تكون في الملف views.py بالاسم index(). تحدد الدالة path() أيضًا المعامل name، وهو معرّف فريد لهذا الربط لعنوان URL. يمكنك استخدام الاسم لعكس الرابط Mapper، أي لإنشاء عنوان URL ديناميكيًا الذي يؤشّر إلى المورد الذي صُمِّم الرابط للتعامل معه، فمثلًا يمكننا استخدام معامل الاسم للربط بصفحتنا الرئيسية من أيّ صفحة أخرى من خلال إضافة الارتباط التالي في القالب: <a href="{% url 'index' %}">Home</a>. ملاحظة: يمكننا كتابة الارتباط بصورة ثابتة كما هو الحال في <a href="/catalog/">Home</a>، ولكن إذا غيّرنا نمط صفحتنا الرئيسية إلى /catalog/index مثلًا، فلن تُربَط القوالب بصورة صحيحة، لذا يُعَد استخدام ربط عنوان URL المعكوس أفضل. العرض القائم على الدوال يُعَد العرض View دالةً تعالج طلب HTTP، وتجلب البيانات المطلوبة من قاعدة البيانات، وتعرض البيانات في صفحة HTML باستخدام قالب HTML، ثم تعيد صفحة HTML المُنشَأة في استجابة HTTP لعرض الصفحة للمستخدم. يتّبع عرض الفهرس هذا النموذج، إذ يجلب معلومات حول عدد سجلات Book و BookInstance و Author سجلات BookInstance المتوفرة في قاعدة البيانات، وتمرر هذه المعلومات إلى قالب لعرضها. افتح الملف catalog/views.py ولاحظ أن الملف يستورد دالة render() المُختصَرة لإنشاء ملف HTML باستخدام قالب وبيانات: from django.shortcuts import render # أنشئ عروضك هنا الصق الأسطر التالية في نهاية الملف: from .models import Book, Author, BookInstance, Genre def index(request): """View function for home page of site.""" # توليد عدّادات من بعض الكائنات الرئيسية num_books = Book.objects.all().count() num_instances = BookInstance.objects.all().count() # الكتب المتوفرة (status = 'a') num_instances_available = BookInstance.objects.filter(status__exact='a').count() # تُضمَّن 'all()' افتراضيًا num_authors = Author.objects.count() context = { 'num_books': num_books, 'num_instances': num_instances, 'num_instances_available': num_instances_available, 'num_authors': num_authors, } # عرض قالب HTML وهو index.html مع البيانات الموجودة في متغير السياق return render(request, 'index.html', context=context) يستورد السطر الأول أصناف النموذج التي سنستخدمها للوصول إلى البيانات في جميع عروضنا، إذ يجلب الجزء الأول من دالة العرض عدد السجلات باستخدام السمة objects.all() في أصناف النموذج، ويحصل على قائمة بكائنات BookInstance التي لها القيمة "a" (متاح Available) في حقل الحالة status. يمكنك العثور على مزيد من المعلومات حول كيفية الوصول إلى بيانات النموذج في قسم البحث عن السجلات من مقال استخدام النماذج. نستدعي الدالة render() في نهاية دالة العرض لإنشاء صفحة HTML وإعادة الصفحة بوصفها استجابة. تغلّف هذه الدالة المُختصَرة عددًا من الدوال الأخرى لتبسيط حالة الاستخدام الشائعة، وتقبل الدالة render() المعاملات التالية: كائن الطلب request الأصلي وهو HttpRequest. قالب HTML مع عناصر بديلة للبيانات. متغير context وهو قاموس بايثون الذي يحتوي على البيانات المطلوب إدخالها في العناصر البديلة. سنتحدث أكثر عن القوالب ومتغير context في القسم التالي، ولنبدأ الآن في إنشاء قالبنا لنتمكّن من عرض شيء ما للمستخدم. القالب Template القالب هو ملف نصي يعرّف بنية الملف أو تخطيطه، مثل صفحة HTML، ويستخدم عناصر بديلة لتمثيل المحتوى الفعلي. سيبحث تطبيق جانغو الذي أنشأه تطبيق البدء Startapp (مثل التطبيق الهيكلي) عن قوالب في مجلد فرعي له الاسم 'templates' لتطبيقاتك، فمثلًا ستتوقّع الدالة render() في عرض الفهرس الذي أضفناه للتو العثور على الملف index.html في المجلد /catalog/templates/ وستصدِر خطأً إذا لم يكن الملف موجودًا. يمكنك التحقق من ذلك من خلال حفظ التغييرات السابقة والوصول إلى العنوان "127.0.0.1:8000" في متصفحك، حيث ستظهر رسالة خطأ بديهية إلى حدٍ ما هي "TemplateDoesNotExist at /catalog/" وتفاصيل أخرى. ملاحظة: سيبحث جانغو عن قوالب في عدد من الأماكن بناءً على ملف إعدادات مشروعك، وسيبحث في التطبيقات المُثبَّتة افتراضيًا. يمكنك معرفة المزيد حول كيفية عثور جانغو على القوالب وتنسيقات القوالب التي يدعمها في قسم القوالب من توثيق جانغو. توسيع القوالب يحتاج قالب الفهرس إلى توصيف معياري في لغة HTML لقسم الرأس والمتن مع أقسام التنقل للربط بالصفحات الأخرى من الموقع (التي لم ننشئها بعد) والأقسام التي تعرض نصًا تقديميًا وبيانات الكتاب. سيكون الكثير من شيفرة HTML وبنية التنقل هي نفسها في صفحات موقعنا. يمكنك استخدام لغة قوالب جانغو للتصريح عن قالب أساسي ثم توسيعه ليحل محل الأجزاء المختلفة لكل صفحة محددة بدلًا من تكرار الشيفرة المتداولة في كل صفحة. يُعَد جزء الشيفرة البرمجية الآتية نموذجًا للقالب الأساسي من الملف base_generic.html، وسننشئ قالبًا لموقع المكتبة المحلية LocalLibrary قريبًا. يشتمل النموذج الآتي على شيفرة HTML مشتركة لأقسام العنوان والشريط الجانبي والمحتويات الرئيسية المميزة بوسوم القالب المُسمَّاة block و endblock. يمكنك ترك الكتل Blocks فارغة أو تضمين محتوًى افتراضي لاستخدامه عند عرض الصفحات المشتقة من القالب. ملاحظة: وسوم القالب Tags هي دوال يمكنك استخدامها في قالب لتكرارها على القوائم وإجراء عمليات شرطية بناءً على قيمة متغير وغير ذلك. تسمح صيغة القوالب بالإضافة إلى وسوم القالب بالإشارة إلى المتغيرات المُمرَّرة إلى القالب من العرض واستخدام مرشّحات القالب لتنسيق المتغيرات مثل تحويل سلسلة نصية إلى أحرف صغيرة. <!DOCTYPE html> <html lang="en"> <head> {% block title %}<title>Local Library</title>{% endblock %} </head> <body> {% block sidebar %}<!-- أدخِل نص التنقل الافتراضي لكل صفحة -->{% endblock %} {% block content %}<!-- نص المحتوى الافتراضي، ويكون فارغ عادة -->{% endblock %} </body> </html> نحدّد أولًا القالب الأساسي باستخدام وسم القالب extends عند تعريف قالب لعرض معين (اطّلع على نموذج الشيفرة البرمجية الآتي)، ثم نصرّح عن أقسام القالب التي نريد استبدالها -إن وجدت- باستخدام أقسام block أو endblock كما في القالب الأساسي. يوضح جزء الشيفرة البرمجية الآتي كيفية استخدام وسم القالب extends وتعديل الكتلة content. ستتضمن شيفرة HTML الشيفرة البرمجية والبنية المُعرَّفة في القالب الأساسي بما في ذلك المحتوى الافتراضي الذي عرّفته في الكتلة title، ولكن توضَع الكتلة content الجديدة مكان الكتلة الافتراضية. {% extends "base_generic.html" %} {% block content %} <h1>Local Library Home</h1> <p>Welcome to LocalLibrary, a website developed by <em>Mozilla Developer Network</em>!</p> {% endblock %} القالب الأساسي لموقع المكتبة المحلية LocalLibrary سنستخدم جزء الشيفرة الآتي بمثابة قالب أساسي لموقع المكتبة المحلية LocalLibrary، إذ يحتوي هذا الجزء على بعض شيفرة HTML ويعرّف كتل title و sidebar و content. لدينا عنوان افتراضي وشريط جانبي افتراضي يحتوي روابطًا لقوائم جميع الكتب والمؤلفين، وكلاهما مُضمَّن في كتل يمكن تغييرها بسهولة لاحقًا. ملاحظة: سنشرح أيضًا وسمي قالب إضافيين هما: url و load static في الأقسام اللاحقة. أنشئ ملفًا جديدًا هو base_generic.html في المجلد /locallibrary/catalog/templates/ والصق الشيفرة البرمجية التالية في هذا الملف: <!DOCTYPE html> <html lang="en"> <head> {% block title %} <title>Local Library</title> {% endblock %} <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous" /> <-- ضِف شيفرة CSS إضافية في ملف ثابت --!> {% load static %} <link rel="stylesheet" href="{% static 'css/styles.css' %}" /> </head> <body> <div class="container-fluid"> <div class="row"> <div class="col-sm-2"> {% block sidebar %} <ul class="sidebar-nav"> <li><a href="{% url 'index' %}">Home</a></li> <li><a href="">All books</a></li> <li><a href="">All authors</a></li> </ul> {% endblock %} </div> <div class="col-sm-10 ">{% block content %}{% endblock %}</div> </div> </div> </body> </html> يتضمن القالب شيفرة CSS من إطار عمل بوتستراب Bootstrap لتحسين تخطيط وعرض صفحة HTML؛ إذ يُعَد استخدام بوتستراب -أو إطار عمل ويب آخر من طرف العميل- طريقةً سريعةً لإنشاء صفحة جذابة تُعرَض بصورة جيدة على أحجام الشاشات المختلفة. يشير القالب الأساسي إلى ملف CSS محلي (styles.css) يوفر تنسيقًا إضافيًا. أنشئ ملف styles.css في المجلد /locallibrary/catalog/static/css/ والصق الشيفرة البرمجية التالية في الملف: .sidebar-nav { margin-top: 20px; padding: 0; list-style: none; } قالب الفهرس أنشئ ملف HTML جديد بالاسم index.html في المجلد /locallibrary/catalog/templates/ والصق الشيفرة البرمجية التالية فيه، إذ توسّع هذه الشيفرة البرمجية القالب الأساسي في السطر الأول، ثم تستبدل كتلة content الافتراضية في هذا القالب: {% extends "base_generic.html" %} {% block content %} <h1>Local Library Home</h1> <p> Welcome to LocalLibrary, a website developed by <em>Mozilla Developer Network</em>! </p> <h2>Dynamic content</h2> <p>The library has the following record counts:</p> <ul> <li><strong>Books:</strong> {{ num_books }}</li> <li><strong>Copies:</strong> {{ num_instances }}</li> <li><strong>Copies available:</strong> {{ num_instances_available }}</li> <li><strong>Authors:</strong> {{ num_authors }}</li> </ul> {% endblock %} نصرّح في قسم المحتوى الديناميكي Dynamic content عن العناصر البديلة، أي متغيرات القالب للمعلومات الواردة من العرض الذي نريد تضمينها، إذ تكون المتغيرات محاطة بأقواس معقوصة مزدوجة Handlebars. ملاحظة: يمكنك التعرف بسهولة على متغيرات القالب ووسوم القالب (الدوال)، إذ تكون المتغيرات محاطةً بأقواس معقوصة مزدوجة {{ num_books }}، وتكون الوسوم محاطة بأقواس معقوصة مفردة مع إشارات النسبة المئوية {% extends "base_generic.html" %}. الشيء المهم الذي يجب ملاحظته هنا هو تسمية المتغيرات بالمفاتيح Keys التي نمررها في قاموس context ضمن الدالة render() الخاصة بالعرض (اطلع على النموذج التالي)، وسنستبدل المتغيرات بالقيم المرتبطة بها عند عرض القالب: context = { 'num_books': num_books, 'num_instances': num_instances, 'num_instances_available': num_instances_available, 'num_authors': num_authors, } return render(request, 'index.html', context=context) الإشارة إلى الملفات الثابتة في القوالب يُحتمَل أن يستخدم مشروعك مواردًا ثابتة مثل ملفات جافاسكربت و CSS والصور، ويمكن أن يكون موقع هذه الملفات غير معروف (أو يمكن أن يتغير)، لذا يتيح لك جانغو تحديد الموقع في قوالبك المتعلقة بالإعداد العام STATIC_URL. يضبط موقع الويب الهيكلي الافتراضي الإعداد STATIC_URL على القيمة /static/، ولكن يمكنك اختيار استضافتها على شبكة توصيل المحتوى أو أيّ مكان آخر. استدعِ أولًا ضمن القالب وسم القالب load مع تحديد "static" لإضافة مكتبة القوالب كما هو موضح في نموذج الشيفرة البرمجية التالية، ويمكنك بعد ذلك استخدام وسم القالب static وتحديد عنوان URL النسبي للملف المطلوب: <-- إضافة شيفرة CSS إضافية في ملف ثابت --!> {% load static %} <link rel="stylesheet" href="{% static 'css/styles.css' %}" /> يمكنك إضافة صورة إلى الصفحة بطريقة مماثلة كما يلي: {% load static %} <img src="{% static 'catalog/images/local_library_model_uml.png' %}" alt="UML diagram" style="width:555px;height:540px;" /> ملاحظة: تحدد النماذج السابقة مكان وجود الملفات، ولكن جانغو لا يخدّمها افتراضيًا، لذا ضبطنا خادم الويب الخاص بالتطوير لتخديم الملفات من خلال تعديل رابط عنوان URL العام (/locallibrary/locallibrary/urls.py) عندما أنشأنا موقع الويب الهيكلي، ولكننا ما زلنا بحاجة إلى تفعيل تخديم الملفات في بيئة الإنتاج، إذ سنوضح ذلك لاحقًا. اطّلع على إدارة الملفات الثابتة في توثيق جانغو لمزيد من المعلومات. الارتباط بعناوين URLs قدّم القالب الأساسي السابق وسم القالب url. <li><a href="{% url 'index' %}">Home</a></li> يقبل هذا الوسم اسم الدالة path() المُستدعاة في الملف urls.py وقيم أيّ وسائط يتلقّاها العرض من هذه الدالة، ويعيد عنوان URL الذي يمكنك استخدامه للارتباط بالمورد. ضبط مكان العثور على القوالب يُحدَّد الموقع الذي يبحث فيه جانغو عن القوالب في الكائن TEMPLATES ضمن الملف settings.py، إذ يبدو هذا الملف افتراضيًا (كما أُنشِئ لهذه السلسلة من المقالات) كما يلي: TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] يُعَد إعداد 'APP_DIRS': True الأكثر أهمية من بين القوالب المُسمَّاة، لأنه يخبر جانغو بالبحث عن قوالب في مجلد فرعي لكل تطبيق في المشروع، مما يسهّل تجميع القوالب مع التطبيق المرتبط بها لسهولة إعادة الاستخدام. يمكننا أيضًا تحديد مواقع محددة لجانغو للبحث عن المجلدات باستخدام 'DIRS': [] لكنه ليس مطلوبًا حاليًا. ملاحظة: يمكنك معرفة المزيد حول كيفية عثور جانغو على القوالب وتنسيقات القوالب التي يدعمها في قسم القوالب ضمن توثيق جانغو. كيف تبدو صفحة موقعنا الرئيسية؟ أنشأنا حتى الآن جميع الموارد المطلوبة لعرض صفحة الفهرس. شغّل الخادم باستخدام الأمر python3 manage.py runserver وافتح العنوان "http://127.0.0.1:8000/" في متصفحك. إذا جرى ضبط كل شيء بصورة صحيحة، فيجب أن يبدو موقعك كما يلي: ملاحظة: لن تعمل ارتباطات جميع الكتب All books وجميع المؤلفين All authors حاليًا، لأن المسارات والعروض والقوالب الخاصة بهذه الصفحات لم تُعرَّف بعد، فقد أدخلنا فقط عناصرًا بديلة لتلك الارتباطات في القالب base_generic.html. تحدى نفسك إليك بعض المهام لاختبار مدى إلمامك باستعلامات النماذج والعروض والقوالب. أولًا، يتضمن القالب الأساسي لموقع المكتبة المحلية LocalLibrary الكتلة title. عدّل هذه الكتلة في قالب الفهرس وأنشئ عنوانًا جديدًا للصفحة. ملاحظة: يوضح القسم "توسيع القوالب" في هذا المقال كيفية إنشاء كتل وتوسيعها في قالب آخر. ثانيًا، عدّل العرض لتوليد أعداد من أنواع الكتب genres والكتب books التي تحتوي على كلمة معينة (غير حساسة لحالة الأحرف)، ومرّر النتائج إلى context. يمكنك تحقيق ذلك بطريقة مماثلة لإنشاء واستخدام المتغيرات num_books وnum_instances_available، ثم حدّث قالب الفهرس لتضمين هذه المتغيرات. الخلاصة لقد أنشأنا الصفحة الرئيسية لموقعنا، وهي صفحة HTML تعرض عددًا من السجلات من قاعدة البيانات وارتباطات لصفحات أخرى لم تُنشَأ بعد. تعلّمنا المعلومات الأساسية حول روابط Mappers عناوين URLs والعروض، والاستعلام في قاعدة البيانات باستخدام النماذج، وتمرير المعلومات إلى القالب من العرض، وإنشاء القوالب وتوسيعها. سننشئ في المقال التالي الصفحات الأربع المتبقية من موقعنا بناءً على هذه المعرفة. ترجمة -وبتصرُّف- للمقال Django Tutorial Part 5: Creating our home page. اقرأ أيضًا المقال التالي: تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية المقال السابق: تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin العروض والقوالب الجزء الأول والجزء الثاني البدء مع إطار العمل جانغو لإنشاء تطبيق ويب بناء تطبيق مهام باستخدام جانغو Django وريآكت React مرسل Dispatcher عناوين URL (توثيق جانغو) دوال العرض (توثيق جانغو) القوالب (توثيق جانغو) إدارة الملفات الثابتة (توثيق جانغو) دوال جانغو المُختصَرة (توثيق جانغو)
-
أنشأنا نماذجًا لموقع المكتبة المحلية LocalLibrary، وسنستخدم الآن موقع مدير جانغو Django Admin لإضافة بعض بيانات الكتب الحقيقية. سنوضّح أولًا كيفية تسجيل النماذج في موقع المدير، ثم سنوضّح كيفية تسجيل الدخول وإنشاء بعض البيانات، وسنعرض في نهاية المقال بعض الطرق التي يمكنك من خلالها تحسين عرض موقع المدير. المتطلبات الأساسية: أكمل أولًا مقال استخدام النماذج Models. الهدف: فهم فوائد وقيود موقع مدير جانغو واستخدامه لإنشاء بعض السجلات للنماذج. يمكن لتطبيق مدير جانغو استخدام نماذجك لإنشاء منطقة موقع تلقائيًا يمكنك استخدامها لإنشاء السجلات وعرضها وتحديثها وحذفها، مما يوفر لك الكثير من الوقت أثناء عملية التطوير، ويسهّل اختبار نماذجك ومعرفة ما إذا كان لديك البيانات الصحيحة. يمكن أن يكون تطبيق المدير مفيدًا لإدارة البيانات في عملية الإنتاج اعتمادًا على نوع موقع الويب، إذ يوصي مشروع جانغو به لإدارة البيانات الداخلية فقط، أي للاستخدام من طرف المديرين أو الأشخاص الداخليين في مؤسستك فقط، إذ لا يُعَد النهج المتمحور حول النماذج بالضرورة أفضل واجهة ممكنة لجميع المستخدمين، ويكشف الكثير من التفاصيل غير الضرورية عن النماذج. أُجرِي كل الإعداد المطلوب لتضمين تطبيق المدير في موقع الويب تلقائيًا عندما أنشأتَ المشروع الهيكلي (اطلع على توثيق جانغو للحصول على معلومات حول الاعتماديات Dependencies الفعلية المطلوبة)، فكل ما يجب عليك فعله لإضافة نماذجك إلى تطبيق المدير هو تسجيلها فقط. سنقدم في نهاية هذا المقال عرضًا موجزًا لكيفية إعداد منطقة المدير لعرض بيانات نموذجنا بصورة أفضل. سنتعرّف بعد تسجيل النماذج على كيفية إنشاء مستخدم مميز Superuser جديد وتسجيل الدخول إلى الموقع وإنشاء بعض الكتب والمؤلفين ونسخ الكتب وأنواعها، إذ سيكون ذلك مفيدًا لاختبار العروض والقوالب التي سننشئها في المقالات اللاحقة. تتألف هذه السلسلة الفرعية من السلسلة الأشمل تعلم تطوير الويب من المقالات التالية: مدخل إلى إطار عمل الويب جانغو Django إعداد بيئة تطوير تطبيقات جانغو تطبيق عملي لتعلم جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية تطبيق عملي لتعلم جانغو - الجزء السادس: إدارة الجلسات Sessions تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج تعرف على أمان تطبيقات جانغو تسجيل النماذج أولًا، افتح الملف admin.py في التطبيق catalog ضمن /locallibrary/catalog/admin.py، إذ يبدو هذا الملف حاليًا كما يلي: from django.contrib import admin # سجّل نماذجك هنا لاحظ أنه يستورد django.contrib.admin. سجّل النماذج من خلال نسخ النص التالي في نهاية الملف، إذ تستورد هذه الشيفرة البرمجية النماذج وتستدعي admin.site.register لتسجيل كلٍّ منها: from .models import Author, Genre, Book, BookInstance admin.site.register(Book) admin.site.register(Author) admin.site.register(Genre) admin.site.register(BookInstance) ملاحظة: إذا قبلت التحدي لإنشاء نموذج يمثل اللغة الطبيعية للكتاب في المقال السابق، فاستورد ذلك النموذج وسجّله. تُعَد هذه الطريقة أبسط طريقة لتسجيل نموذج واحد أو عدة نماذج في الموقع، إذ يُعَد موقع المدير قابلًا للتخصيص بدرجة كبيرة، وسنتحدث أكثر عن الطرق الأخرى لتسجيل نماذجك لاحقًا. إنشاء مستخدم مسؤول Superuser تحتاج حساب مستخدم مع تفعيل الحالة Staff (أي أن المستخدم من الموظفين) لتسجيل الدخول إلى موقع المدير، ويجب أن يكون لدى هذا المستخدم أذونات لإدارة جميع الكائنات لعرض السجلات وإنشائها. يمكنك إنشاء حساب مستخدم مميز يتمتع بوصول كامل إلى الموقع وجميع الأذونات المطلوبة باستخدام الملف manage.py. استدعِ الأمر التالي في مجلد الملف manage.py لإنشاء المستخدم المميز، إذ سيُطلَب منك إدخال اسم مستخدم وعنوان بريد إلكتروني وكلمة مرور قوية: python3 manage.py createsuperuser سيُضاف مستخدم مميز جديد إلى قاعدة البيانات بعد اكتمال الأمر السابق. أعِد تشغيل خادم التطوير لتتمكن من اختبار تسجيل الدخول كما يلي: python3 manage.py runserver تسجيل الدخول واستخدام الموقع يمكنك تسجيل الدخول إلى الموقع من خلال فتح عنوان URL للمدير "/admin"، مثل "http://127.0.0.1:8000/admin" وإدخال بيانات اعتماد المستخدم المميز وكلمة المرور الجديدة، إذ سيُعاد توجيهك إلى صفحة تسجيل الدخول ثم ستعود إلى عنوان URL للمدير "/admin" بعد إدخال تفاصيلك. يعرض هذا الجزء من الموقع جميع نماذجنا مُجمَّعةً حسب التطبيق المُثبَّت. يمكنك النقر على اسم النموذج للانتقال إلى شاشة تعرض قائمةً بجميع السجلات المرتبطة به، ثم النقر على هذه السجلات لتعديلها. يمكنك النقر مباشرةً على رابط الإضافة Add بجانب كل نموذج لإنشاء سجل من هذا النوع. انقر على رابط الإضافة Add الموجود على يمين النموذج Books لإنشاء كتاب جديد، إذ سيظهر مربع حوار يشبه إلى حد كبير الشكل الآتي. لاحظ كيف تتطابق عناوين كل حقل ونوع عنصر الواجهة المُستخدَم ونص التعليمات help_text -إن وجد- مع القيم التي حدّدتها في النموذج. أدخِل قيم الحقول، ويمكنك إنشاء مؤلفين Authors أو أنواع Genres جديدة بالضغط على الزر + بجانب الحقول أو تحديد قيم الموجودة من القوائم إذا أنشأتها مسبقًا. يمكنك عند الانتهاء الضغط على زر حفظ SAVE أو حفظ وإضافة آخر Save and add another أو حفظ ومتابعة التعديل Save and continue editing لحفظ السجل. ملاحظة: أضف بعض الكتب والمؤلفين والأنواع (مثل النوع الخيالي Fantasy) إلى تطبيقك، وتأكد من أن كل مؤلف ونوع يشتملان على عدة كتب، مما سيجعل قائمتك وتفاصيل عروضك أفضل عندما نقدّمها لاحقًا في المقالات القادمة. انقر على رابط الصفحة الرئيسية Home link في الأعلى للرجوع إلى صفحة المدير الرئيسية عند الانتهاء من إضافة الكتب، ثم انقر على رابط الكتب Books لعرض قائمة الكتب الحالية، أو انقر على أحد الروابط الأخرى لمشاهدة قوائم النماذج الأخرى. يمكن أن تبدو القائمة الآن مشابهة للقطة الشاشة التالية بعد أن أضفت بعض الكتب، إذ سيُعرَض عنوان كل كتاب، وهي القيمة المُعادة في التابع __str__() الخاص بالنموذج Book الذي حدّدناه في المقال السابق. يمكنك حذف الكتب من هذه القائمة من خلال تحديد مربع الاختيار بجانب الكتاب الذي لا تريده وتحديد إجراء الحذف delete… من القائمة المنسدلة Action، ثم الضغط على الزر Go. يمكنك إضافة كتب جديدة بالضغط على زر إضافة كتاب ADD BOOK. يمكنك تعديل كتاب من خلال اختيار اسمه في الارتباط، فصفحة تعديل الكتاب الموضَّحة في الشكل الآتي مطابقة تقريبًا لصفحة الإضافة، ولكن الاختلافات الرئيسية بينهما هي عنوان الصفحة "Change book" وإضافة الأزرار حذف Delete والسجل HISTORY وعرض على الموقع VIEW ON SITE، إذ يظهر هذا الزر الأخير لأننا عرّفنا التابع get_absolute_url() في نموذجنا. ملاحظة: يؤدي النقر فوق الزر VIEW ON SITE إلى ظهور استثناء NoReverseMatch بسبب محاولة التابع get_absolute_url() لعكس ()reverse رابط عنوان URL المُسمى ('book-detail') غير المُعرّف بعد. سنعرّف ربط العنوان URL والعرض المرتبط به في المقال السابع من هذه السلسلة. انتقل الآن مرةً أخرى إلى الصفحة الرئيسية Home باستخدام رابط Home في مسار التنقل ثم اعرض قوائم المؤلف Author والنوع Genre، إذ يجب أن يكون لديك عدد منها مُنشَأ مسبقًا عند إضافة الكتب الجديدة، ولكن لا تتردد في إضافة المزيد منها. لن يكون لديك أيّ نسخ كتب Book Instances، لأنها لم تُنشَأ من الكتب Books، بالرغم من أنه يمكنك إنشاء كتاب Book من BookInstance، فهذه هي طبيعة الحقل من النوع ForeignKey. انتقل مرةً أخرى إلى الصفحة الرئيسية واضغط على زر الإضافة Add المرتبط بعرض شاشة إضافة نسخة كتاب Add book instance التالية. لاحظ المعرّف الكبير والفريد بصورة عامة، والذي يمكن استخدامه لتحديد نسخة من كتاب في المكتبة بصورة منفصلة. أنشئ عددًا من هذه السجلات لكل كتاب من كتبك، واضبط الحالة على أنها متوفرة Available لبعض السجلات على الأقل وأنها مُعارة On Loan لسجلات أخرى. إذا كانت الحالة غير متوفرة، فاضبط أيضًا تاريخ استرجاع الكتاب Due back مستقبلًا. لقد تعلمت كيفية إعداد واستخدام موقع المدير، وأنشأت سجلات للنماذج Book و BookInstance و Genre و Author التي سنتمكن من استخدامها بمجرد إنشاء العروض والقوالب. الضبط المتقدم يطبّق جانغو عملًا جيدًا جدًا في إنشاء موقع مدير أولي باستخدام معلومات النماذج المسجلة، بحيث: يحتوي كل نموذج على قائمة من السجلات الفردية التي تحدّدها السلسلة النصية الناتجة باستخدام التابع __str__() الخاص بالنموذج وترتبط باستمارات أو عروض تفصيلية للتعديل، إذ يحتوي هذا العرض view افتراضيًا على قائمة إجراءات في الأعلى يمكنك استخدامها لإجراء مجموعة عمليات حذف على السجلات. تحتوي استمارات السجلات التفصيلية الخاصة بالنموذج لتعديل السجلات وإضافتها على جميع الحقول الموجودة في النموذج والموضوعة عموديًا في ترتيب التصريح عنها. يمكنك تخصيص الواجهة لتسهيل استخدامها، فبعض الأشياء التي يمكنك تطبيقها هي: عروض القائمة List Views: أضف الحقول أو المعلومات الإضافية المعروضة لكل سجل. أضف مرشّحات لتحديد السجلات المُدرجَة بناءً على التاريخ أو بعض قيم التحديد الأخرى، مثل حالة إعارة الكتاب. أضف خيارات إضافية إلى قائمة الإجراءات في عروض القائمة واختر مكان عرض هذه القائمة في الاستمارة. العروض التفصيلية Detail Views: اختر الحقول المراد عرضها (أو استبعادها) مع ترتيبها وتجميعها وما إذا كانت قابلة للتعديل وعنصر الواجهة المُستخدَم والاتجاه وغير ذلك. أضف الحقول المرتبطة بسجلٍ ما للسماح بالتعديل المُضمَّن مثل إضافة القدرة على إضافة وتعديل سجلات الكتاب أثناء إنشاء سجل المؤلف الخاص بها. سنلقي في هذا القسم نظرةً على بعض التغييرات التي من شأنها تحسين واجهة موقع مكتبتنا المحلية LocalLibrary، مثل إضافة المزيد من المعلومات إلى قوائم النموذج Book و Author، وتحسين تخطيط عروض التعديل الخاصة بها. لن نغير عرض النموذجين Language و Genre، إذ يحتوي كلٌ منهما على حقل واحد فقط، لذلك لا توجد فائدة حقيقية من ذلك. يمكنك العثور على مرجع كامل لجميع خيارات تخصيص موقع المدير في توثيق جانغو، ويمكنك على الاطلاع على مقال تنشيط واجهة مدير جانغو والاتصال بها لمعلومات أكثر. تسجيل الصنف ModelAdmin يمكنك تغيير كيفية عرض النموذج في واجهة المدير من خلال تعريف الصنف ModelAdmin (الذي يصف التخطيط) وتسجيله مع النموذج. لنبدأ بالنموذج Author. افتح الملف admin.py في التطبيق catalog ضمن /locallibrary/catalog/admin.py. ضع تعليقًا على التسجيل الأصلي (ابدأه بالرمز #) للنموذج Author كما يلي: # admin.site.register(Author) أضف صنف AuthorAdmin جديد وسجّله كما يلي: # تعريف صنف المدير class AuthorAdmin(admin.ModelAdmin): pass # تسجيل صنف المدير في النموذج المرتبط به admin.site.register(Author, AuthorAdmin) سنضيف الآن أصناف ModelAdmin للنموذجين Book و BookInstance. يجب التعليق على التسجيلات الأصلية مرةً أخرى كما يلي: # admin.site.register(Book) # admin.site.register(BookInstance) يمكننا الآن إنشاء وتسجيل النماذج الجديدة من خلال استخدام المزخرف @register لتسجيل النماذج، والذي يفعل الشيء نفسه تمامًا الذي تفعله صيغة admin.site.register(): # تسجيل أصناف المدير للنموذج Book باستخدام المزخرف @admin.register(Book) class BookAdmin(admin.ModelAdmin): pass # BookInstance تسجيل أصناف المدير للنموذج باستخدام المزخرف @admin.register(BookInstance) class BookInstanceAdmin(admin.ModelAdmin): pass جميع أصناف المدير فارغة حاليًا (لاحظ pass)، لذلك لن يتغير سلوك المدير، ويمكننا توسيعها لتحديد سلوك المدير الخاص بالنموذج. ضبط عروض القائمة تسرد المكتبة المحلية LocalLibrary حاليًا جميع المؤلفين الذين يستخدمون اسم الكائن الذي ينشئه التابع __str__() الخاص بالنموذج، وهذا جيد عندما يكون لديك عدد قليل من المؤلفين، ولكن يمكن أن ينتهي بك الأمر بالحصول على نسخ مكررة بمجرد أن يكون لديك العديد من المؤلفين. يمكنك التمييز بينها أو إظهار مزيد من المعلومات حول كل مؤلف من خلال استخدام السمة list_display لإضافة حقول إضافية إلى العرض. ضع الشيفرة البرمجية الآتية بدلًا من الصنف AuthorAdmin. يُصرَّح عن أسماء الحقول المعروضة في القائمة ضمن صف tuple بالترتيب المطلوب كما يلي (هذه هي الأسماء نفسها المُحدَّدة في نموذجك الأصلي): class AuthorAdmin(admin.ModelAdmin): list_display = ('last_name', 'first_name', 'date_of_birth', 'date_of_death') انتقل إلى قائمة المؤلفين في موقع الويب، إذ يجب الآن عرض الحقول السابقة على النحو التالي: سنعرض أيضًا حقول المؤلف author والنوع genre للنموذج Book، فالحقل author هو حقل من النوع ForeignKey للعلاقة (واحد إلى متعدد)، وبالتالي ستمثله قيمة التابع __str__() للسجل المرتبط به. ضع ما يلي بدلًا من الصنف BookAdmin: class BookAdmin(admin.ModelAdmin): list_display = ('title', 'author', 'display_genre') لسوء الحظ، لا يمكننا تحديد الحقل genre مباشرةً في السمة list_display، لأنه حقل من النوع ManyToManyField، إذ يمنع جانغو هذا النوع من الحقول لأنه سيكون هناك تكلفة كبيرة للوصول إلى قاعدة البيانات عند تطبيقه. لذا سنعرّف الدالة display_genre للحصول على المعلومات بوصفها سلسلة نصية، وهي الدالة التي استدعيناها سابقًا وسنعرّفها لاحقًا. ملاحظة: يمكن ألّا يكون الحصول على الحقل genre فكرةً جيدة هنا، بسبب تكلفة عملية قاعدة البيانات، ولكن يمكن أن تكون استدعاء الدوال في نماذجك مفيدة جدًا لأسباب أخرى مثل إضافة ارتباط الحذف Delete بجانب كل عنصر في القائمة. أضف الشيفرة البرمجية الآتية إلى النموذج Book في الملف models.py، إذ يؤدي هذا التابع إلى إنشاء سلسلة نصية من القيم الثلاث الأولى للحقل genre (إن وجدت) وإنشاء وصف قصير short_description يمكن استخدامه في موقع المدير لهذا التابع: def display_genre(self): """Create a string for the Genre. This is required to display genre in Admin.""" return ', '.join(genre.name for genre in self.genre.all()[:3]) display_genre.short_description = 'Genre' افتح موقع الويب بعد حفظ النموذج والمدير المُحدَّث وانتقل إلى صفحة قائمة الكتب، إذ يجب أن تشاهد قائمة كتب مشابهة للقائمة التالية: النموذج Genre (والنموذج Language إذا عرّفته) لهما حقل واحد، لذلك لا جدوى من إنشاء نموذج إضافي لهما لعرض حقول إضافية. ملاحظة: يجب تحديث قائمة النموذج BookInstance لعرض الحالة وتاريخ الإعادة المتوقع على الأقل، إذ أضفنا ذلك في نهاية هذا المقال. إضافة مرشحات القائمة يمكن أن يكون من المفيد أن تكون قادرًا على ترشيح العناصر المعروضة بعد حصولك على الكثير من العناصر في القائمة، إذ يمكن تحقيق ذلك من خلال سرد الحقول في السمة list_filter. ضع جزء الشيفرة البرمجية التالية بدلًا من الصنف BookInstanceAdmin الحالي: class BookInstanceAdmin(admin.ModelAdmin): list_filter = ('status', 'due_back') سيتضمن عرض القائمة الآن مربع ترشيح على اليمين. لاحظ كيف يمكنك اختيار التواريخ والحالة لترشيح القيم: تنظيم تخطيط العرض التفصيلي تضع العروض التفصيلية افتراضيًا كل الحقول عموديًا بترتيب التصريح عنها في النموذج، ولكن يمكنك تغيير ترتيب التصريح وما هي الحقول المعروضة (أو المستبعدة) وما إذا كانت الأقسام تُستخدَم لتنظيم المعلومات وما إذا كانت الحقول معروضة أفقيًا أو عموديًا وما هي عناصر واجهة التعديل المُستخدَمة في استمارات المدير. ملاحظة: تُعَد نماذج موقع المكتبة المحلية LocalLibrary بسيطةً نسبيًا، لذا لا توجد حاجة كبيرة لتغيير التخطيط، ولكن سنجري بعض التغييرات على أية حال لنوضّح لك كيفية تطبيق ذلك. التحكم في الحقول المعروضة وتنسيقها حدّث الصنف AuthorAdmin لإضافة السطر fields كما يلي: class AuthorAdmin(admin.ModelAdmin): list_display = ('last_name', 'first_name', 'date_of_birth', 'date_of_death') fields = ['first_name', 'last_name', ('date_of_birth', 'date_of_death')] تسرد السمة fields فقط الحقول التي ستُعرَض في الاستمارة وفق ترتيبها، إذ تُعرَض الحقول عموديًا افتراضيًا، ولكن ستُعرَض أفقيًا إذا جمّعتها ضمن صف كما هو موضح في حقول "date" السابقة. انتقل إلى عرض المؤلف التفصيلي في موقعك، إذ يجب أن يظهر الآن كما يلي: ملاحظة: يمكنك أيضًا استخدام السمة exclude للتصريح عن قائمة السمات التي ستُستبعَد من الاستمارة، إذ ستُعرَض جميع السمات الأخرى في النموذج. تقسيم العرض التفصيلي يمكنك إضافة أقسام Sections لتجميع معلومات النموذج ذات الصلة في الاستمارة التفصيلية باستخدام السمة fieldsets. لدينا في النموذج BookInstance معلومات تتعلق بما هو الكتاب (name و imprint و id) ومتى سيكون متاحًا (status و due_back)، إذ يمكننا إضافة هذه المعلومات إلى الصنف BookInstanceAdmin باستخدام الخاصية fieldsets كما يلي: @admin.register(BookInstance) class BookInstanceAdmin(admin.ModelAdmin): list_filter = ('status', 'due_back') fieldsets = ( (None, { 'fields': ('book', 'imprint', 'id') }), ('Availability', { 'fields': ('status', 'due_back') }), ) لكل قسم عنوانه الخاص، أو القيمة None إذا كنت لا تريد عنوانًا وصفٌ من الحقول المرتبطة به في القاموس، إذ يكون هذا التنسيق معقدًا لشرحه، ولكنه سهل الفهم إذا نظرت مباشرةً إلى جزء الشيفرة البرمجية السابقة. انتقل الآن إلى عرض نسخة الكتاب في موقعك، إذ يجب أن تظهر الاستمارة كما يلي: تعديل السجلات المضمن يكون في بعض الأحيان من المنطقي أن تكون قادرًا على إضافة سجلات في الوقت نفسه، فمن المنطقي مثلًا أن يكون لديك معلومات الكتاب ومعلومات نسخ محددة حصلت عليها في صفحة التفاصيل نفسها. يمكنك تطبيق ذلك من خلال التصريح عن السمة inlines من النوع TabularInline (تخطيط أفقي) أو StackedInline (تخطيط عمودي تمامًا مثل تخطيط النموذج الافتراضي). يمكنك إضافة معلومات نسخ الكتاب BookInstance مضمنة في تفاصيل الكتاب Book من خلال تحديد السمة inlines في الصنف BookAdmin: class BooksInstanceInline(admin.TabularInline): model = BookInstance @admin.register(Book) class BookAdmin(admin.ModelAdmin): list_display = ('title', 'author', 'display_genre') inlines = [BooksInstanceInline] انتقل الآن إلى عرض النموذج Book في موقعك، وسترى الآن في الأسفل نسخ الكتاب المتعلقة به أسفل حقول نوع الكتاب مباشرةً: كل ما فعلناه في هذه الحالة هو التصريح عن الصنف TabularInline المُضمَّن الذي يضيف فقط جميع الحقول من النموذج المُضمَّن inlined. يمكنك تحديد جميع أنواع المعلومات الإضافية للتخطيط بما في ذلك الحقول المراد عرضها وترتيبها وما إذا كانت للقراءة فقط أم لا وغير ذلك (اطلع على TabularInline لمزيد من المعلومات). ملاحظة: هناك بعض القيود في هذه الوظيفة، إذ لدينا في لقطة الشاشة السابقة ثلاث نسخ حالية من الكتاب متبوعة بثلاثة عناصر بديلة لنسخ كتب جديدة، والتي تبدو متشابهة جدًا. يُفضَّل عدم وجود نسخ كتب احتياطية افتراضيًا وإضافتها فقط باستخدام ارتباط إضافة نسخة كتاب أخرى Add another Book instance، أو أن تكون قادرًا فقط على سرد نماذج BookInstance بوصفها ارتباطات غير قابلة للقراءة من هنا. يمكن تحقيق الخيار الأول من خلال ضبط السمة extra على القيمة "0" في النموذج BooksInstanceInline (جرّبها بنفسك). تحدى نفسك لقد تعلمنا الكثير في هذا القسم، لذا حان الوقت الآن لتجربة بعض الأمور وهي: أضف لعرض قائمة BookInstance شيفرة برمجية لعرض الكتاب والحالة وتاريخ الاسترجاع والمعرّف (بدلًا من النص الافتراضي للتابع __str__()). أضف قائمة مُضمَّنة لعناصر الكتاب Book إلى عرض Author التفصيلي باستخدام الأسلوب نفسه الذي استخدمناه مع Book و BookInstance. الخلاصة لقد تعلمتَ الآن كيفية إعداد موقع المدير في شكله الأبسط والمحسّن وكيفية إنشاء مستخدم مميز وكيفية التنقل في موقع المدير وعرض السجلات وحذفها وتحديثها، وأنشأتَ مجموعة من الكتب ونسخ الكتب والأنواع والمؤلفين التي سنكون قادرين على سردها وعرضها بمجرد إنشاء العرض والقوالب الخاصة بنا. ترجمة -وبتصرُّف- للمقال Django Tutorial Part 4: Django admin site. اقرأ أيضًا المقال التالي: تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية المقال السابق: تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models تنشيط واجهة مدير جانغو والاتصال بها. العروض والقوالب في Django حزم بايثون الثمانية التي تسهل تعاملك مع Django كتابة أول تطبيق جانغو - الجزء الثاني: مقدمة إلى مدير جانغو (توثيق جانغو) موقع مدير جانغو (توثيق جانغو)
-
يوضح هذا المقال كيفية تعريف النماذج Models لموقع المكتبة المحلية LocalLibrary، ويشرح ما هو النموذج وكيفية التصريح عنه وبعض أنواع الحقول الرئيسية، ويعرض بإيجاز بعض الطرق الرئيسية التي يمكنك من خلالها الوصول إلى بيانات النموذج. المتطلبات الأساسية: الاطلاع على مقال إنشاء موقع ويب هيكلي لمكتبة محلية. الهدف: أن تكون قادرًا على تصميم وإنشاء نماذجك واختيار الحقول بصورة مناسبة. يمكن لتطبيقات جانغو Django الوصول إلى البيانات وإدارتها من خلال كائنات لغة بايثون Python المُشار إليها بالنماذج Models، إذ تعرِّف النماذج بنية البيانات المخزنة بما في ذلك أنواع الحقول وربما حجمها الأقصى وقيمها الافتراضية وخيارات قائمة الاختيار ونص التعليمات للتوثيق ونص التسمية للاستمارات Forms وغير ذلك. يُعَد تعريف النموذج مستقلًا عن قاعدة البيانات الأساسية، إذ يمكنك اختيار قاعدة بيانات واحدة من عدة قواعد بيانات بوصفها جزءًا من إعدادات مشروعك. لن تحتاج أبدًا إلى التواصل مع قاعدة البيانات التي تريد استخدامها مباشرةً بمجرد اختيارها، فما عليك سوى كتابة بنية نموذجك وشيفرة برمجية أخرى، وسيتولى إطار عمل جانغو كل الأعمال الأخرى للتواصل مع قاعدة البيانات نيابةً عنك. يوضح هذا المقال كيفية تعريف النماذج والوصول إليها في مثال موقع المكتبة المحلية LocalLibrary. تتألف هذه السلسلة الفرعية من السلسلة الأشمل تعلم تطوير الويب من المقالات التالية: مدخل إلى إطار عمل الويب جانغو Django إعداد بيئة تطوير تطبيقات جانغو تطبيق عملي لتعلم جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية تطبيق عملي لتعلم جانغو - الجزء السادس: إدارة الجلسات Sessions تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج تعرف على أمان تطبيقات جانغو تصميم نماذج المكتبة المحلية LocalLibrary يُفضَّل أخذ بضع دقائق للتفكير في البيانات التي يجب تخزينها والعلاقات بين الكائنات قبل الانتقال والبدء في كتابة شيفرة النماذج البرمجية. نعلم أننا نحتاج إلى تخزين معلومات حول الكتب (العنوان والملخص والمؤلف واللغة المكتوبة والفئة ورقم ISBN)، وأنه يمكن أن يكون لدينا نسخ متعددة متاحة (مع معرّف فريد عام وحالة التوفر وغير ذلك). يمكن أن نحتاج إلى تخزين مزيد من المعلومات حول المؤلف أكثر من مجرد اسمه، ويمكن أن يكون هناك عدة مؤلفين لهم الاسم نفسه أو أسماء متشابهة. نريد أن نكون قادرين على فرز المعلومات بناءً على عنوان الكتاب والمؤلف واللغة المكتوبة والفئة، إذ يُفضَّل أن يكون لديك نماذج منفصلة لكل كائن (مجموعة من المعلومات المتعلقة ببعضها) عند تصميم نماذجك، والأشياء الواضحة في هذه الحالة هي الكتب ونسخ الكتب والمؤلفون. قد ترغب في استخدام النماذج لتمثيل خيارات قائمة الاختيار، مثل قائمة اختيارات منسدلة بدلًا من الشيفرة الثابتة للخيارات في موقع الويب نفسه، إذ يُوصَى بذلك عندما لا تكون جميع الخيارات معروفة مسبقًا أو أنها تتغير باستمرار. يشمل المرشحون الواضحون للنماذج في هذه الحالة نوع الكتاب، مثل الخيال العلمي والشعر واللغة العربية والإنجليزية والفرنسية وغير ذلك. يجب التفكير في العلاقات بمجرد أن نقرر ما هي النماذج والحقول، إذ يسمح جانغو بتعريف العلاقات التي تكون من نوع واحد لواحد "OneToOneField" وواحد إلى متعدد "ForeignKey" ومتعدد إلى متعدد "ManyToManyField". يوضح مخطط الارتباط التالي باستخدام لغة UML النماذج التي سنعرّفها في هذه الحالة (على شكل مربعات): أنشأنا نماذجًا للكتاب (التفاصيل العامة للكتاب)، ونسخة الكتاب (حالة النسخ الحقيقية المُحدَّدة للكتاب المتاح في النظام)، والمؤلف، وقررنا أن يكون لدينا نموذج للنوع، بحيث يمكن إنشاء أو تحديد القيم من خلال واجهة المدير، وقررنا عدم وجود نموذج لحالة نسخة الكتاب BookInstance:status، إذ كتبنا شيفرة ثابتة للقيم (LOAN_STATUS) لأننا لا نتوقع تغييرها. يمكنك رؤية اسم النموذج وأسماء الحقول وأنواعها والتوابع وأنواع قيمها المُعادة ضمن كل مربع من المربعات. يوضح المخطط العلاقات بين النماذج بما في ذلك درجة تعدّدها Multiplicities، وهي الأعداد الموجودة على المخطط والتي توضح أعداد (الحد الأقصى والحد الأدنى) كل نموذج والتي يمكن أن تكون موجودةً في العلاقة، فمثلًا يوضّح الخط المتصل بين المربعات أن الكتاب Book والنوع Genre مرتبطان، وتوضح الأعداد القريبة من نموذج النوع أن الكتاب يجب أن يحتوي على نوع واحد أو أكثر (بقدر ما تريد)، بينما توضح الأعداد الموجودة على الطرف الآخر من الخط بجوار نموذج الكتاب أن النوع يمكن ألّا يكون له أيّ نوع مرتبط بالكتاب أو يمكن أن يكون له العديد من الأنواع المرتبطة بالكتب. ملاحظة: يوفر القسم التالي تمهيدًا يشرح كيفية تعريف النماذج واستخدامها، وضع في بالك أثناء قراءته كيفية بناء كل نموذج من النماذج الموضحة في المخطط السابق. مدخل إلى النماذج يقدم هذا القسم نظرةً عامة موجزة عن كيفية تعريف النموذج وبعضًا من أهم الحقول والوسطاء. تعريف النموذج تُعرَّف النماذج عادةً في الملف models.py الخاص بالتطبيق، وتُقدَّم بوصفها صنفًا فرعيًا من django.db.models.Model، ويمكن أن تشمل الحقول والتوابع والبيانات الوصفية. يُظهِر جزء الشيفرة البرمجية التالي نموذجًا قياسيًا بالاسم MyModelName: from django.db import models from django.urls import reverse class MyModelName(models.Model): """A typical class defining a model, derived from the Model class.""" # حقول my_field_name = models.CharField(max_length=20, help_text='Enter field documentation') # … # بيانات وصفية class Meta: ordering = ['-my_field_name'] # توابع def get_absolute_url(self): """Returns the URL to access a particular instance of MyModelName.""" return reverse('model-detail-view', args=[str(self.id)]) def __str__(self): """String for representing the MyModelName object (in Admin site etc.).""" return self.my_field_name سنستكشف في الأقسام التالية كل ميزة في النموذج بالتفصيل. الحقول Fileds يمكن أن يحتوي النموذج على عدد عشوائي من الحقول ومن أيّ نوع، إذ يمثل كل حقل عمودًا من البيانات التي نريد تخزينها في أحد جداول قاعدة بياناتنا، ويتألف كل سجل أو صف في قاعدة البيانات من قيمة واحدة لكل قيمة حقل. لنلقِ نظرةً على المثال التالي: my_field_name = models.CharField(max_length=20, help_text='Enter field documentation') يحتوي هذا المثال على حقل واحد يسمى my_field_name من النوع models.CharField، مما يعني أن هذا الحقل سيحتوي على سلاسل نصية من المحارف الأبْجَعَددية alphanumeric. تُسنَد أنواع الحقول باستخدام أصناف Classes معينة تحدد نوع السجل المُستخدَم لتخزين البيانات في قاعدة البيانات مع معايير التحقق لاستخدامها عند استلام القيم من استمارة HTML (أي ما يشكّل قيمة صالحة). يمكن أن تأخذ أنواع الحقول وسطاء تحدّد كيفية تخزين الحقل أو كيفية استخدامه، إذ سنعطي الحقل في حالتنا وسيطين، هما: max_length=20، الذي يشير إلى أن أقصى طول لقيمةٍ ما في هذا الحقل وهو 20 محرفًا. help_text='Enter field documentation'، وهو نص مفيد يمكن عرضه في استمارة لمساعدة المستخدمين على فهم كيفية استخدام الحقل. يُستخدَم اسم الحقل للإشارة إليه في الاستعلامات والقوالب، وتحتوي الحقول على تسمية Label يحدّدها الوسيط verbose_name (بقيمة افتراضية هي None). إذا لم يُضبَط الوسيط verbose_name، فستُنشَأ التسمية من اسم الحقل من خلال استبدال أيّ شرطات سفلية بمسافة وجعل الحرف الأول حرفًا كبيرًا، فمثلًا سيكون للحقل my_field_name تسمية افتراضية هي "My field name" عند استخدامه في الاستمارات. يؤثر ترتيب التصريح عن الحقول على ترتيبها الافتراضي إذا عُرِض نموذج ضمن استمارة (في موقع المدير مثلًا) بالرغم من أن هذا الترتيب يمكن تجاهله. يمكن استخدام الوسطاء الشائعة التالية عند التصريح عن أنواع الحقول: help_text: يوفر هذا الوسيط تسميةً نصيةً لاستمارات HTML (في موقع المدير مثلًا) كما وضّحنا سابقًا. verbose_name: اسم يمكن أن يقرأه الإنسان للحقل المُستخدَم في تسميات الحقل، وإذا لم يُحدَّد، فسيستنتج جانغو الاسم المُطوَّل Verbose Name الافتراضي من اسم الحقل. default: القيمة الافتراضية للحقل، ويمكن أن يكون هذا الوسيط قيمةً أو كائنًا يمكن استدعاؤه، إذ سيُستدعَى الكائن في هذه الحالة في كل مرة يُنشَأ فيها سجل جديد. null: إذا كانت قيمة هذا الوسيط True، فسيخزّن جانغو القيم الفارغة على أنها NULL في قاعدة البيانات للحقول التي يكون ذلك مناسبًا لها، وسيخزّن الحقل من النوع CharField سلسلة نصية فارغة بدلًا من ذلك. القيمة الافتراضية لهذا الوسيط هي False. blank: إذا كانت قيمة هذا الوسيط True، فسيُسمَح للحقل بأن يكون فارغًا في استماراتك. القيمة الافتراضية لهذا الوسيط هي False، أي سيجبرك التحقق من صحة استمارة جانغو على إدخال قيمة. يُستخدَم هذا الحقل عادةً مع القيمة null=True، لأنك إذا أردتَ السماح بقيم فارغة، فأنت تحتاج أيضًا أن تكون قاعدة البيانات قادرة على تمثيلها بصورة مناسبة. choices: مجموعة من الاختيارات للحقل، بحيث إذا توفّرت هذه الاختيارات، فستكون أداة الاستمارة المقابلة الافتراضية هي مربع تحديد لهذه الاختيارات بدلًا من حقل النص المعياري. primary_key: إذا كانت قيمة هذا الوسيط True، فسيُضبط الحقل الحالي بوصفه مفتاحًا رئيسيًا للنموذج؛ والمفتاح الرئيسي هو عمود خاص في قاعدة البيانات مخصَّص لتعريف جميع سجلات الجدول المختلفة بطريقة فريدة. إذا لم يُحدَّد أيّ حقل بوصفه مفتاحًا رئيسيًا، فسيضيف جانغو تلقائيًا حقلًا لهذا الغرض. يمكن تحديد نوع حقول المفاتيح الرئيسية المُنشَأة تلقائيًا لكل تطبيق في AppConfig.default_auto_field أو بصورة عامة في إعداد DEFAULT_AUTO_FIELD. ملاحظة: تضبط التطبيقات المُنشَأة باستخدام manage.py نوعَ المفتاح الرئيسي على النوع BigAutoField. يمكنك رؤية ما يلي في الملف catalog/apps.py الخاص بموقع المكتبة المحلية: class CatalogConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' هناك العديد من الخيارات الأخرى، إذ يمكنك الاطلاع القائمة الكاملة لخيارات الحقول في توثيق جانغو. توضح القائمة التالية بعض أنواع الحقول الأكثر استخدامًا: يُستخدَم النوع CharField لتعريف سلاسل نصية ذات طول ثابت قصير إلى متوسط الحجم. يجب عليك تحديد الطول الأقصى max_length للبيانات المراد تخزينها. يُستخدَم النوع TextField للسلاسل النصية الكبيرة ذات الطول العشوائي. يمكنك تحديد الطول الأقصى max_length للحقل، ولكنه لا يُستخدَم إلا عند عرض الحقل في استمارات (ليس إجباريًا على مستوى قاعدة البيانات). النوع IntegerField هو حقل لتخزين القيم الصحيحة (عدد صحيح)، وللتحقق من صحة القيم المدخَلة بوصفها أعدادًا صحيحة في الاستمارات. يُستخدَم النوعان DateField و DateTimeField لتخزين أو تمثيل التواريخ ومعلومات التاريخ/الوقت، مثل كائنات بايثون datetime.date و datetime.datetime على التوالي. يمكن أن تصرِّح هذه الحقول إضافةً لما سبق عن المعاملات (الحصرية فيما بينها) auto_now=True (لضبط الحقل على التاريخ الحالي في كل مرة يُحفَظ فيها النموذج) و auto_now_add (لضبط التاريخ عند إنشاء النموذج لأول مرة فقط) و default (لضبط تاريخ افتراضي يمكن للمستخدم تجاهله). يُستخدَم النوع EmailField لتخزين عناوين البريد الإلكتروني والتحقق من صحتها. يُستخدَم النوعان FileField و ImageField لتحميل الملفات والصور على التوالي، إذ يضيف الحقل ImageField تحققًا إضافيًا من أن الملف المُحمَّل هو صورة. يمتلك هذان النوعان من الحقول معاملات لتحديد كيفية ومكان تخزين الملفات التي جرى تحميلها. النوع AutoField هو نوع خاص من الحقل IntegerField الذي يزداد تلقائيًا. يُضاف مفتاح رئيسي من هذا النوع تلقائيًا إلى نموذجك إذا لم تحدده صراحةً. يُستخدَم النوع ForeignKey لتحديد علاقة واحد إلى متعدد بنموذج قاعدة بيانات آخر، فمثلًا للسيارة مُصنِّع واحد، ولكن يمكن للمصنِّع صنع العديد من السيارات. الجانب "واحد" من العلاقة هو النموذج الذي يحتوي على المفتاح، وتشير النماذج التي تحتوي على مفتاح خارجي Foreign Key إلى ذلك المفتاح، إذ تكون هذه النماذج في الجانب "متعدد" من هذه العلاقة. يُستخدَم النوع ManyToManyField لتحديد علاقة متعدد إلى متعدد، فمثلًا يمكن أن يكون للكتاب عدة أنواع ويمكن أن يحتوي كل نوع على عدة كتب. سنستخدم هذا النوع في تطبيق المكتبة بطريقة مشابهة جدًا للنوع ForeignKeys، ولكن يمكن استخدامه بطرق أكثر تعقيدًا لوصف العلاقات بين المجموعات. يحتوي هذا النوع على المعامل on_delete لتحديد ما يحدث عند حذف السجل المرتبط به مثل قيمة models.SET_NULL التي تضبط القيمة على NULL. هناك العديد من الأنواع الأخرى من الحقول بما في ذلك حقول أنواع مختلفة من الأعداد (الأعداد الصحيحة الكبيرة والأعداد الصحيحة الصغيرة والأعداد العشرية) والقيم المنطقية وعناوين URL والعناوين الفرعية Slugs والمعرّفات الفريدة وغيرها من المعلومات المتعلقة بالوقت (المدة والوقت وغير ذلك)، ويمكنك الاطلاع على القائمة الكاملة في توثيق جانغو. البيانات الوصفية Metadata يمكنك التصريح عن البيانات الوصفية على مستوى النموذج من خلال التصريح عن الصنف class Meta كما يلي: class Meta: ordering = ['-my_field_name'] تتمثل إحدى الميزات الأكثر فائدة لهذه البيانات الوصفية في التحكم في الترتيب الافتراضي للسجلات المُعادة عند الاستعلام عن نوع النموذج، ويمكنك تطبيق ذلك من خلال تحديد ترتيب المطابقة في قائمة أسماء الحقول مع السمة ordering كما وضّحنا سابقًا. يعتمد الترتيب على نوع الحقل، إذ تُفرَز الحقول المحرفية أبجديًا، بينما تُفرَز حقول التاريخ بترتيب زمني، كما يمكنك أن تسبق اسم الحقل بإشارة ناقص (-) لعكس ترتيب الفرز. إذا اخترنا مثلًا فرز الكتب افتراضيًا كما يلي: ordering = ['title', '-pubdate'] فستُفرَز الكتب أبجديًا حسب العنوان من A إلى Z، ثم حسب تاريخ النشر ضمن كل عنوان من الأحدث إلى الأقدم. هناك سمة شائعة أخرى هي verbose_name، وهي اسم مطوَّل للصنف بصيغة المفرد والجمع: verbose_name = 'BetterName' تسمح لك السمات المفيدة الأخرى بإنشاء وتطبيق أذونات وصول جديدة للنموذج (تُطبَّق الأذونات الافتراضية تلقائيًا)، أو السماح بالترتيب بناءً على حقل آخر، أو التصريح عن أن الصنف "مجرد Abstract"، أي صنف أساسي لا يمكنك إنشاء سجلات له، وستُشتَق بدلًا من ذلك لإنشاء نماذج أخرى. تتحكم العديد من خيارات البيانات الوصفية الأخرى في قاعدة البيانات التي يجب استخدامها مع النموذج وكيفية تخزين البيانات، وهي مفيدة حقًا إذا كنت بحاجة إلى ربط نموذج مع قاعدة بيانات موجودة مسبقًا. يمكنك الاطلاع على القائمة الكاملة لخيارات بيانات النموذج الوصفية في توثيق جانغو. التوابع Methods يمكن أن يحتوي النموذج أيضًا على توابع، إذ يجب عليك في كل نموذج على الأقل تعريف تابع لصنف بايثون المعياري __str__() لإعادة سلسلة نصية يمكن يقرأها الإنسان لكل كائن، إذ تُستخدَم هذه السلسلة النصية لتمثيل السجلات الفردية في موقع المدير وفي أيّ مكان آخر تحتاج فيه للإشارة إلى نسخة من النموذج. يعيد هذا التابع غالبًا حقل العنوان أو الاسم من النموذج. def __str__(self): return self.field_name هناك تابع آخر شائع لتضمينه في نماذج جانغو وهو التابع get_absolute_url() الذي يعيد عنوان URL لعرض سجلات النماذج على موقع الويب. إذا حدّدتَ هذا التابع، فسيضيف جانغو تلقائيًا زر "عرض على الموقع View on Site" إلى شاشات تعديل سجل النموذج في موقع المدير. يوضّح ما يلي نمطًا معياريًا للتابع get_absolute_url(): def get_absolute_url(self): """Returns the URL to access a particular instance of the model.""" return reverse('model-detail-view', args=[str(self.id)]) ملاحظة: يجب إنشاء رابط Mapper عنوان URL لتمرير الاستجابة والمعرّف إلى "عرض النموذج التفصيلي" (الذي سينفّذ العمل المطلوب لعرض السجل) بافتراض أنك ستستخدم عناوين URL، مثل "/myapplication/mymodelname/2" لعرض سجلات نموذجك، إذ يمثل "2" معرّف id سجل معين. الدالة reverse() السابقة قادرة على عكس رابط عنوان URL (المسماة في الحالة المذكورة سابقًا باسم نموذج-تفاصيل-عرض 'model-detail-View') لإنشاء عنوان URL بالتنسيق الصحيح، ويجب عليك كتابة ربط عنوان URL والعرض والقالب. يمكنك تعريف أيّ توابع أخرى تريدها واستدعاؤها من شيفرتك البرمجية أو قوالبك بشرط ألّا تأخذ أيّ معاملات. إدارة النموذج يمكنك استخدام أصناف نموذجك بعد تعريفها لإنشاء السجلات أو تحديثها أو حذفها ولتشغيل الاستعلامات للحصول على جميع السجلات أو مجموعات فرعية معينة من السجلات. سنوضّح لك كيفية تطبيق ذلك لاحقًا عندما نعرّف عروضنا، ولكن إليك ملخصًا موجزًا. إنشاء وتعديل السجلات يمكنك إنشاء سجل من خلال تعريف نسخة من النموذج ثم استدعاء الدالة save(). # أنشِئ سجلًا جديدًا باستخدام باني النموذج record = MyModelName(my_field_name="Instance #1") # احفظ الكائن في قاعدة البيانات record.save() ملاحظة: إذا لم تصرّح عن أي حقل primary_key، فسيُمنَح السجل الجديد حقلًا من هذا النوع تلقائيًا مع معرّف id اسم الحقل، إذ يمكنك الاستعلام عن هذا الحقل بعد حفظ السجل السابق، وسيكون له القيمة 1. يمكنك الوصول إلى الحقول في هذا السجل الجديد باستخدام الصيغة النقطية وتغيير القيم، ويجب عليك استدعاء الدالة save() لتخزين القيم المُعدَّلة في قاعدة البيانات. # الوصول إلى قيم حقل النموذج باستخدام سمات بايثون print(record.id) # يجب أن تعيد القيمة 1 للسجل الأول print(record.my_field_name) # يجب أن تطبع 'Instance #1' # غيّر السجل من خلال تعديل الحقول ثم استدعِ save() record.my_field_name = "New Instance Name" record.save() البحث عن السجلات يمكنك البحث عن السجلات التي تطابق معايير معينة باستخدام سمة النموذج objects، والتي يوفّرها الصنف الأساسي. ملاحظة: يمكن أن يكون شرح كيفية البحث عن السجلات باستخدام أسماء الحقول والنماذج المجردة مربكًا قليلًا، إذ سنشير في المناقشة الآتية إلى النموذج Book باستخدام الحقلين title و genre، بحيث يكون النوع genre نموذجًا له حقل name واحد. يمكننا الحصول على جميع سجلات النموذج بوصفها كائن QuerySet باستخدام الدالة objects.all()، إذ يُعَد QuerySet كائنًا تكراريًا، مما يعني أنه يحتوي على عدد من الكائنات الممكن تكرارها. all_books = Book.objects.all() تسمح لنا الدالة filter() من جانغو بترشيح الكائن QuerySet المُعاد لمطابقة نص محدّد، أو حقل عددي مع معايير معينة، فمثلًا يمكننا تطبيق ما يلي لترشيح الكتب التي تحتوي على الكلمة "wild" في عنوانها ثم عَدّها: wild_books = Book.objects.filter(title__contains='wild') number_wild_books = wild_books.count() تُعرَّف الحقول المراد مطابقتها ونوع التطابق في اسم معامل الترشيح باستخدام التنسيق field_name__match_type، ويمكنك ملاحظة الشرطة السفلية المزدوجة بين title و contains. رشّحنا title باستخدام مطابقة حساسة لحالة الأحرف، وهناك العديد من أنواع التطابقات الأخرى الممكن إجراؤها، مثل icontains (غير حساسة لحالة الأحرف) و iexact (مطابقة تامة غير حساسة لحالة الأحرف) و exact (مطابقة تامة حساسة لحالة الأحرف) و in و gt (أكبر من) و startswith وغير ذلك. يمكنك الاطلاع على القائمة الكاملة في توثيق جانغو. ستحتاج في بعض الحالات إلى ترشيح حقل يحدّد علاقة واحد إلى متعدد مع نموذج آخر، مثل ForeignKey، إذ يمكنك في هذه الحالة "فهرسة Index" الحقول ضمن النموذج المتعلق بها باستخدام شرطات سفلية مزدوجة إضافية، لذلك سيتعين عليك فهرسة الاسم name من خلال الحقل genre لترشيح كتب ذات نمط نوع معين كما يلي: # Will match on: Fiction, Science fiction, non-fiction etc. books_containing_genre = Book.objects.filter(genre__name__icontains='fiction') ملاحظة: يمكنك استخدام الشرطين السفليتين __ للتنقل عبر العديد من مستويات العلاقات ForeignKey/ManyToManyField كما تريد، فمثلًا يمكن أن يكون للكتاب Book الذي له أنواع مختلفة ومُعرَّف باستخدام علاقة "cover" معامل اسمٍ هو type__cover__name__exact='hard'. هناك الكثير من الأشياء الممكن فعلها باستخدام الاستعلامات، مثل عمليات البحث العكسية من النماذج ذات الصلة وتسلسل المرشّحات وإعادة مجموعة أصغر من القيم وغير ذلك. اطلع على إجراء الاستعلامات في توثيق جانغو لمزيد من المعلومات. تعريف نماذج المكتبة المحلية LocalLibrary سنبدأ في هذا القسم بتعريف نماذج المكتبة، لذا افتح الملف "models.py" ضمن المجلد "/locallibrary/catalog/". تستورد الشيفرة البرمجية المتداولة الموجودة في أعلى الصفحة الوحدة models التي تحتوي على صنف النموذج الأساسي models.Model الذي سترثه نماذجنا. from django.db import models # أنشِئ نماذجك هنا النموذج Genre انسخ شيفرة النموذج Genre البرمجية التالية والصقه في نهاية الملف "models.py" الخاص بك. يُستخدَم هذا النموذج لتخزين المعلومات حول فئة الكتاب مثل كونه خياليًا أو غير خيالي، أو عاطفيًا أو تاريخًا وغير ذلك. أنشأنا النوع genre بوصفه نموذجًا وليس نصًا حرًا أو قائمة اختيار بحيث يمكن إدارة القيم الممكنة عبر قاعدة البيانات بدلًا من أن تكون شيفرة ثابتة. class Genre(models.Model): """Model representing a book genre.""" name = models.CharField(max_length=200, help_text='Enter a book genre (e.g. Science Fiction)') def __str__(self): """String for representing the Model object.""" return self.name يحتوي النموذج على حقل واحد من النوع CharField هو name، بحيث يُستخدَم لوصف نوع الكتاب وهو مُحدَّد ليحتوي على 200 محرف ولديه بعض نصوص التعليمات help_text. صرّحنا في نهاية النموذج عن التابع __str__() الذي يعيد اسم النوع الذي يحدّده سجل معين. لم يُحدَّد أيّ اسم مطوَّل لذلك سيُسمَّى الحقل بالاسم Name في الاستمارات. النموذج Book انسخ النموذج Book التالي والصقه في نهاية ملفك، إذ يمثل هذا النموذج جميع المعلومات حول الكتاب المتاح بالمعنى العام، وليس مثيلًا أو نسخةً ماديةً معينة متاحة للإعارة. يستخدم النموذجُ النوعَ CharField لتمثيل الحقلين title و isbn الخاصَين بالكتاب، ولاحظ كيف يضبط المعامل الأول غير المُسمَّى للحقل isbn التسميةَ بصورة صريحة على القيمة "ISBN" وإلّا فستُضبَط افتراضيًا على القيمة "Isbn". ضبطنا المعامل unique على القيمة true لضمان حصول جميع الكتب على رقم ISBN فريد، إذ يجعل المعامل unique قيمة الحقل فريدةً بصورة عامة في الجدول. يستخدم النموذج النوع TextField للحقل summary، لأن هذا النص يمكن أن يكون طويلًا جدًا. # يُستخدَم لإنشاء عناوين URL من خلال عكس أنماط عنوان URL from django.urls import reverse class Book(models.Model): """Model representing a book (but not a specific copy of a book).""" title = models.CharField(max_length=200) # اُستخدِم المفتاح الخارجي لأنه لا يمكن أن يكون للكتاب سوى مؤلف واحد، ولكن يمكن أن يكون للمؤلفين عدة كتب # المؤلف هو سلسلة نصية وليس كائنًا بسبب عدم النصريح عنه بعد في الملف author = models.ForeignKey('Author', on_delete=models.SET_NULL, null=True) summary = models.TextField(max_length=1000, help_text='Enter a brief description of the book') isbn = models.CharField('ISBN', max_length=13, unique=True, help_text='13 Character <a href="https://www.isbn-international.org/content/what-isbn">ISBN number</a>') # استُخدم حقل ManyToManyField لأن النوع genre يمكن أن يحتوي على العديد من الكتب، ويمكن أن تغطي الكتب العديد من الأنواع. # عُرِّف الصنف Genre، وبالتالي يمكننا تحديد الكائن السابق. genre = models.ManyToManyField(Genre, help_text='Select a genre for this book') def __str__(self): """String for representing the Model object.""" return self.title def get_absolute_url(self): """Returns the URL to access a detail record for this book.""" return reverse('book-detail', args=[str(self.id)]) يكون الحقل genre من النوع ManyToManyField، بحيث يمكن أن يكون للكتاب أنواع متعددة ويمكن أن يحتوي النوع على العديد من الكتب. صُرِّح عن المؤلف على أنه من النوع ForeignKey، لذلك سيكون لكل كتاب مؤلف واحد فقط، ولكن يمكن أن يكون للمؤلف العديد من الكتب. يمكن أن يكون للكتاب مؤلفِين متعددين عمليًا، ولكن هذا غير ممكن في هذا التطبيق. يُصرَّح عن صنف النموذج ذي الصلة في كلا هذين النوعين من الحقول بوصفه أول معامل غير مسمًى باستخدام صنف النموذج أو سلسلة نصية تحتوي على اسم النموذج المرتبط بها، إذ يجب استخدام اسم النموذج بوصفه سلسلة نصية إذا لم يُعرَّف الصنف المرتبط به في هذا الملف قبل الإشارة إليه. المعاملات الأخرى ذات الأهمية في حقل المؤلف author هي null=True الذي يسمح لقاعدة البيانات بتخزين قيمة Null عند عدم تحديد أي مؤلف، و on_delete=models.SET_NULL الذي سيحدد قيمة حقل مؤلف الكتاب إلى Null عند حذف سجل المؤلف المرتبط به. تحذير: يكون المعامل on_delete=models.CASCADE افتراضيًا، مما يعني أنه إذا حُذِف المؤلف، فسيُحذَف هذا الكتاب أيضًا. استخدمنا SET_NULL هنا، ولكن يمكننا أيضًا استخدام PROTECT أو RESTRICT لمنع حذف المؤلف أثناء استخدام أيّ كتاب له. يعرّف النموذج أيضًا التابعَ __str__() باستخدام حقل عنوان الكتاب title لتمثيل سجل Book، إذ يعيد التابع الأخير get_absolute_url() عنوان URL يمكن استخدامه للوصول إلى سجل تفصيلي لهذا النموذج، إذ يجب لتحقيق ذلك تعريف ربط لعنوان URL له الاسم book-detail وتعريف عرض وقالب مرتبط به. النموذج BookInstance انسخ النموذج BookInstance الآتي بعد النماذج الأخرى، إذ يمثل هذا النموذج نسخةً محددةً من كتاب يمكن أن يستعيره شخص ما، ويتضمن معلومات حول ما إذا كانت النسخة متاحة، أو التاريخ المتوقع لاسترجاعها وتفاصيل الطبعة، أو الإصدار ومعرّف فريد للكتاب في المكتبة. ستصبح بعض هذه الحقول والتوابع مألوفةً الآن، إذ يستخدم النموذج BookInstance ما يلي: حقلًا من النوع ForeignKey لتحديد النموذج Book المرتبط به (يمكن أن يكون لكل كتاب نسخ متعددة، ولكن يمكن أن يكون للنسخة كتاب Book واحد فقط). يحدِّد المفتاح on_delete=models.RESTRICT لضمان عدم إمكانية حذف النموذج Book عندما يشير إليه النموذج BookInstance. حقلًا من النوع CharField لتمثيل الطبعة (إصدار محدد) للكتاب. import uuid # مطلوب لنسخ الكتاب الفريدة class BookInstance(models.Model): """Model representing a specific copy of a book (i.e. that can be borrowed from the library).""" id = models.UUIDField(primary_key=True, default=uuid.uuid4, help_text='Unique ID for this particular book across whole library') book = models.ForeignKey('Book', on_delete=models.RESTRICT, null=True) imprint = models.CharField(max_length=200) due_back = models.DateField(null=True, blank=True) LOAN_STATUS = ( ('m', 'Maintenance'), ('o', 'On loan'), ('a', 'Available'), ('r', 'Reserved'), ) status = models.CharField( max_length=1, choices=LOAN_STATUS, blank=True, default='m', help_text='Book availability', ) class Meta: ordering = ['due_back'] def __str__(self): """String for representing the Model object.""" return f'{self.id} ({self.book.title})' نصرّح إضافةً إلى ذلك عن بعض الأنواع الجديدة من الحقول وهي: يُستخدَم النوع UUIDField للحقل id لضبطه بوصفه مفتاحًا رئيسيًا primary_key لهذا النموذج. يخصِّص هذا النوع من الحقول قيمةً فريدةً عامة لكل نسخة (قيمة لكل كتاب تجده في المكتبة). يُستخدَم النوع DateField للتاريخ due_back، أي التاريخ المتوقع ليصبح فيه الكتاب متاحًا بعد استعارته أو صيانته، إذ يمكن أن تكون القيمة blank أو null (مطلوبة عندما يكون الكتاب متاحًا). تستخدم بيانات النموذج الوصفية (Class Meta) هذا الحقل لترتيب السجلات عند إعادتها ضمن استعلام. الحقل status من النوع CharField الذي يعرّف قائمة الاختيار أو الخيارات. نعرّف كما ترى صفًا يحتوي على صفوف من أزواج مفتاح-قيمة ونمرّرها إلى وسيط الاختيارات. تُعَد القيمة في زوج مفتاح/قيمة قيمة عرض display value يمكن للمستخدم تحديدها، بينما تكون المفاتيح هي القيم المحفوظة عند تحديد الخيار. ضبطنا قيمة افتراضية هي "m" (للصيانة Maintenance) عند إنشاء الكتب في البداية إذ تكون غير متوفرة قبل تخزينها على الرفوف. يمثل التابع __str__() كائن BookInstance باستخدام مجموعة من المعرّف الفريد وعنوان الكتاب Book المرتبط به. ملاحظة: إليك بعض المعلومات عن لغة بايثون: يمكنك بدءًا من الإصدار 3.6 للغة بايثون استخدام صيغة توليد السلاسل النصية (المعروفة أيضًا باسم f-strings) بالشكل التالي: f'{self.id} ({self.book.title})' استخدمنا سابقًا صيغة السلاسل النصية المُنسَّقة Formatted String التي تُعَد طريقةً صالحةً لتنسيق السلاسل النصية في لغة بايثون، مثل '{0} ({1})'.format(self.id,self.book.title). النموذج Author انسخ نموذج Author التالي بعد الشيفرة البرمجية الموجودة في الملف models.py: class Author(models.Model): """Model representing an author.""" first_name = models.CharField(max_length=100) last_name = models.CharField(max_length=100) date_of_birth = models.DateField(null=True, blank=True) date_of_death = models.DateField('Died', null=True, blank=True) class Meta: ordering = ['last_name', 'first_name'] def get_absolute_url(self): """Returns the URL to access a particular author instance.""" return reverse('author-detail', args=[str(self.id)]) def __str__(self): """String for representing the Model object.""" return f'{self.last_name}, {self.first_name}' يجب أن تكون جميع الحقول والتوابع مألوفةً الآن، إذ يعرِّف هذا النموذج مؤلفًا يحمل اسمًا أول واسم عائلة وتاريخ ميلاد ووفاة (كلاهما اختياري)، ويحدّد أنّ التابع __str__() افتراضيًا يعيد الاسم بالترتيب: اسم العائلة last name ثم الاسم الأول firstname. يعكس التابع get_absolute_url() ربط عنوان URL لتفاصيل المؤلف author-detail للحصول على عنوان URL لعرض مؤلف واحد. إعادة تشغيل عمليات تهجير قاعدة البيانات أنشأتَ حتى الآن جميع نماذجك، لذا أعِد تشغيل عمليات تهجير قاعدة البيانات لإضافتها إلى قاعدة بياناتك كما يلي: python3 manage.py makemigrations python3 manage.py migrate النموذج Language- تحدي تخيل أن أحد المتبرعين المحليين يتبرع بعدد من الكتب الجديدة المكتوبة بلغة أخرى (الفارسية مثلًا)، إذ يكمن التحدي في معرفة أفضل طريقة لتمثيلها في موقع مكتبتنا ثم إضافتها إلى النماذج. إليك بعض الأشياء التي يجب مراعاتها: هل يجب ربط اللغة بالنموذج Book أو BookInstance أو أيّ كائن آخر؟ هل يجب تمثيل اللغات المختلفة باستخدام نموذج، أم حقل نص حر، أم قائمة اختيار ثابتة؟ أضف الحقل بعد أن تقرر ما تريده، ويمكنك أن ترى ما قررناه على GitHub. لا تنسَ أنه يجب إعادة تشغيل عمليات تهجير قاعدة البيانات مرة أخرى لإضافة التغييرات بعد إجراء تغيير على نموذجك. python3 manage.py makemigrations python3 manage.py migrate الخلاصة تعلمنا في هذا المقال كيفية تعريف النماذج، ثم استخدمنا هذه المعلومات لتصميم وتقديم النماذج المناسبة لموقع المكتبة المحلية LocalLibrary. سنحوّل الآن اهتمامنا عن إنشاء الموقع لفترة وجيزة لنتعرّف على موقع إدارة جانغو الذي سيسمح لنا بإضافة بعض البيانات إلى المكتبة، والتي يمكننا عرضها بعد ذلك باستخدام العروض والقوالب التي لم ننشئها بعد. ترجمة -وبتصرُّف- للمقال Django Tutorial Part 3: Using models. اقرأ أيضًا المقال التالي: تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin المقال السابق: تطبيق عملي لتعلم إطار عمل جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية كتابة شيفرات بايثون: صيغ شائعة الاستخدام على نحو خاطئ إنشاء تطبيق جانغو وتوصيله بقاعدة بيانات النماذج Models والاستعلام عن البيانات في جانغو كتابة أول تطبيق جانغو - الجزء 2 (توثيق جانغو) إجراء الاستعلامات (توثيق جانغو) مرجع واجهة برمجة تطبيقات QuerySet (توثيق جانغو)
-
يشرح هذا المقال ما ستتعلمه لبناء موقع ويب باستخدام إطار عمل جانغو Django، ويوفر نظرةً عامةً على مثال موقع المكتبة المحلية الذي سنعمل عليه ونطوّره في المقالات اللاحقة، وسنوضّح كيفية إنشاء مشروع موقع ويب هيكلي skeleton يمثّل الأساس الذي يمكنك ملؤه لاحقًا بالإعدادات والمسارات والنماذج Models والعروض Views والقوالب Templates الخاصة بالموقع. المتطلبات الأساسية: قراءة مقال مدخل إلى إطار عمل جانغو، كما يجب إعداد بيئة تطوير جانغو. الهدف: مقدمة إلى التطبيق العملي الذي سنبنيه في المقالات اللاحقة، وفهم الموضوعات التي سنتناولها، والقدرة على استخدام أدوات جانغو لبدء مشروعات مواقع الويب الجديدة الخاصة بك. سنطور موقع ويب يمكن استخدامه لإدارة دليلٍ لمكتبة محلية، وسنتعلم في هذه السلسلة من المقالات ما يلي: استخدم أدوات جانغو لإنشاء موقع وتطبيق هيكلي. بدء وإيقاف خادم التطوير. إنشاء نماذج لتمثيل بيانات تطبيقك. استخدام موقع مدير جانغو لتعبئة بيانات موقعك. إنشاء عروض لاسترجاع بيانات محددة استجابةً لطلبات مختلفة، وإنشاء قوالب لعرض البيانات بتنسيق HTML في المتصفح. إنشاء روابط Mappers لربط أنماط عناوين URL المختلفة مع عروض محددة. إضافة تصريح المستخدم والجلسات للتحكم في سلوك الموقع والوصول إليه. العمل مع الاستمارات Forms. كتابة شيفرة اختبار تطبيقك البرمجية. استخدام أمان جانغو بفعالية. نشر تطبيقك في بيئة الإنتاج. تعلّمت مسبقًا عن بعض هذه الموضوعات، وتعرّفت إلى بعضها الآخر بإيجاز، ولكن يجب أن تعرف ما يكفي لتطوير تطبيقات جانغو بنفسك في نهاية هذه السلسلة من المقالات. تتألف هذه السلسلة الفرعية من السلسلة الأشمل تعلم تطوير الويب من المقالات التالية: مدخل إلى إطار عمل الويب جانغو Django إعداد بيئة تطوير تطبيقات جانغو تطبيق عملي لتعلم جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية تطبيق عملي لتعلم جانغو - الجزء السادس: إدارة الجلسات Sessions تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج تعرف على أمان تطبيقات جانغو موقع المكتبة المحلية LocalLibrary LocalLibrary هو اسم موقع الويب الذي سننشئه ونطوّره في المقالات اللاحقة، والغرض الأساسي من هذا الموقع هو توفير دليل Catalog عبر الإنترنت لمكتبة محلية صغيرة، إذ يمكن للمستخدمين تصفح الكتب المتاحة وإدارة حساباتهم. اختير هذا المثال بعناية لأنه يمكن تغيير حجمه لإظهار الكثير أو القليل من التفاصيل التي نحتاجها، ويمكن استخدامه لإظهار أي ميزة من ميزات جانغو تقريبًا، ويسمح بتوفير مسار إرشادي عبر أهم الوظائف في إطار عمل جانغو كما يلي: سنحدد في المقالات الأولى مكتبة بسيطة للتصفح فقط، إذ يمكن لأعضاء المكتبة استخدامها لمعرفة الكتب المتاحة، مما يتيح استكشاف العمليات المشتركة لكل مواقع الويب تقريبًا، وهي قراءة المحتوى وعرضه من قاعدة بيانات. يتوسّع مثال المكتبة في المقالات اللاحقة لإظهار ميزات جانغو الأكثر تقدمًا، فمثلًا يمكننا توسيع المكتبة للسماح للمستخدمين بحجز الكتب لتوضيح كيفية استخدام الاستمارات ودعم استيثاق المستخدم. سُمِّي هذا المثال بمكتبة محلية لسببٍ ما بالرغم من أنه قابل جدًا للتوسّع. هدفنا من هذا المثال هو إظهار الحد الأدنى من المعلومات التي ستساعدك على بدء استخدام جانغو وتشغيله بسرعة، لذا سنخزّن معلومات عن الكتب ونسخها والمؤلفين والمعلومات الأساسية الأخرى، ولكن لن نخزّن معلومات حول العناصر الأخرى التي يمكن أن تخزّنها المكتبة، ولن نوفّر البنية الأساسية اللازمة لدعم مواقع مكتبات متعددة، أو ميزات مكتبة كبيرة أخرى. سنوفر في هذه السلسلة من المقالات مقتطفات من الشيفرة البرمجية المناسبة لك لنسخها ولصقها في كل مرحلة، وستكون هناك شيفرة برمجية أخرى نأمل أن توسّعها بنفسك مع بعض الإرشادات. إذا واجهتك مشكلة، فيمكنك العثور على كامل النسخة المُطوَّرة من موقع الويب على غيت هب GitHub. حان الوقت لبدء إنشاء مشروع هيكلي بعد أن تعرّفت على موقع المكتبة المحلية وما ستتعلمه. إنشاء موقع ويب هيكلي سنوضح الآن كيفية إنشاء موقع ويب هيكلي يمكنك ملؤه لاحقًا بإعدادات ومسارات ونماذج وعروض وقوالب (سنناقشها في مقالات لاحقة) خاصة بالموقع. اتبع الخطوات التالية للبدء: استخدم أداة django-admin لإنشاء مجلد المشروع وقوالب الملفات الأساسية والملف manage.py الذي يعمل بوصفه سكربتًا لإدارة المشروع. استخدم manage.py لإنشاء تطبيق واحد أو أكثر. سجّل التطبيقات الجديدة لتضمينها في المشروع. صِل رابط Mapper عنوان url / المسار path لكل تطبيق. ملاحظة: يمكن أن يتكون موقع الويب من قسم واحد أو أكثر، مثل الموقع الرئيسي والمدونة والويكي wiki ومنطقة التنزيل download area وغيرها. يشجعك إطار عمل جانغو على تطوير هذه المكونات بوصفها تطبيقات منفصلة، بحيث يمكن إعادة استخدامها لاحقًا في مشاريع مختلفة إذا رغبت في ذلك. نسمّي موقع ويب المكتبة المحلية ومجلدات المشروع بالاسم "locallibrary" ويتضمن تطبيقًا واحدًا يسمى "catalog"، لذا ستكون بنية مجلد المستوى الأعلى كما يلي: locallibrary/ # مجلد موقع الويب manage.py # سكربت لتشغيل أدوات جانغو لهذا المشروع (أُنشِئ باستخدام django-admin) locallibrary/ # مجلد الموقع أو المشروع (أُنشِئ باستخدام django-admin) catalog/ # مجلد التطبيق (أُنشِئ باستخدام manage.py) تناقش الأقسام التالية خطوات العملية بالتفصيل، وتوضح كيفية اختبار تعديلاتك، وسنناقش في نهاية هذا المقال إعدادًا آخر على مستوى الموقع يمكن تطبيقه حاليًا. إنشاء المشروع يمكنك إنشاء المشروع باتباع الخطوات التالية: أولًا، افتح صدفة الأوامر Command Shell أو نافذة طرفية Terminal، وتأكد من أنك في بيئتك الافتراضية. ثانيًا، انتقل إلى المكان الذي تريد تخزين تطبيقات جانغو فيه (اجعله في مكان يسهل العثور عليه مثل مجلد المستندات)، وأنشئ مجلدًا لموقع الويب الجديد (django_projects في هذه الحالة)، ثم انتقل إلى المجلد الذي أنشأته كما يلي: mkdir django_projects cd django_projects ثالثًا، أنشئ المشروع الجديد باستخدام الأمر django-admin startproject، ثم انتقل إلى مجلد المشروع كما يلي: django-admin startproject locallibrary cd locallibrary تنشئ الأداة django-admin بنية مجلد أو ملف على النحو التالي: locallibrary/ manage.py locallibrary/ __init__.py settings.py urls.py wsgi.py asgi.py يجب أن يبدو مجلد العمل الحالي كما يلي: ../django_projects/locallibrary/ يُعَد مجلد المشروع الفرعي locallibrary نقطة الدخول إلى موقع الويب، إذ يحتوي على الملفات التالية: "init.py"، وهو ملف فارغ يوجّه لغة بايثون للتعامل مع هذا المجلد بوصفه حزمة بايثون. يحتوي الملف "settings.py" على جميع إعدادات موقع الويب بما في ذلك تسجيل أيّ تطبيقات ننشئها، وموقع ملفاتنا الساكنة، وتفاصيل إعداد قاعدة البيانات وغير ذلك. يحدد الملف "urls.py" الروابط الخاصة بالموقع لعنوان URL مع العرض URL-to-View، ويمكن أن يحتوي هذا الملف على كامل شيفرة ربط عناوين URL، ولكن من الشائع تفويض بعض الروابط إلى تطبيقات معينة كما سترى لاحقًا. يُستخدَم الملف "wsgi.py" لمساعدة تطبيق جانغو على التواصل مع خادم الويب، إذ يمكنك التعامل مع هذا الملف على أنه نموذج أولي Boilerplate. يُعَد الملف "asgi.py" معيارًا لتطبيقات وخوادم الويب غير المتزامنة الخاصة بلغة بايثون للتواصل مع بعضها بعضًا. واجهة بوابة الخادم غير المتزامنة Asynchronous Server Gateway Interface -أو اختصارًا ASGI- هي الواجهة التالية غير المتزامنة لواجهة بوابة خادم الويب Web Server Gateway Interface -أو اختصارًا WSGI، وتوفر معيارًا لكل من تطبيقات بايثون غير المتزامنة والمتزامنة، بينما قدمت واجهة WSGI معيارًا للتطبيقات المتزامنة فقط. ASGI متوافقة مع الإصدارات السابقة من واجهة WSGI وتدعم العديد من الخوادم وأطر عمل التطبيقات. يُستخدَم سكربت manage.py لإنشاء التطبيقات والعمل مع قواعد البيانات وبدء خادم تطوير الويب. إنشاء تطبيق الدليل catalog شغّل الأمر التالي لإنشاء تطبيق دليل المكتبة catalog الذي سيكون ضمن مشروع locallibrary، وتأكّد من تشغيل هذا الأمر من المجلد نفسه مثل manage.py الخاص بمشروعك: # في نظام لينكس وماك python3 manage.py startapp catalog # في نظام ويندوز py manage.py startapp catalog ملاحظة: تستخدم هذه السلسلة من المقالات صياغة نظامي لينكس وماك أو إس macOS، فإذا كنت تعمل على نظام ويندوز، فيجب عليك استخدام py (أو py -3) بدلًا من python3 عندما ترى أمرًا يبدأ بها. تنشئ الأداة مجلدًا جديدًا وتعبّئه بملفات لأجزاء مختلفة من التطبيق كما هو موضح في المثال الآتي. تُسمَّى معظم الملفات وفقًا للغرض منها، فمثلًا يجب تخزين العروض في الملف views.py والنماذج في الملف models.py والاختبارات في الملف tests.py وإعداد موقع المدير في الملف admin.py وتسجيل التطبيق في الملف apps.py، وتحتوي الملفات على الحد الأدنى من الشيفرة البرمجية المعيارية للعمل مع الكائنات المرتبطة بها. يجب أن يبدو مجلد المشروع المُحدَّث كما يلي: locallibrary/ manage.py locallibrary/ catalog/ admin.py apps.py models.py tests.py views.py __init__.py migrations/ لدينا أيضًا ما يلي: المجلد "migrations" الذي يُستخدَم لتخزين عمليات التهجير Migrations، وهي الملفات التي تتيح لك تحديث قاعدة البيانات تلقائيًا عند تعديل نماذجك. الملف الفارغ "init.py" الذي أُنشِئ ليتعرّف جانغو أو بايثون على المجلد بوصفه حزمة بايثون ويسمح باستخدام كائناته في أجزاء أخرى من المشروع. ملاحظة: توجد العروض والنماذج في قائمة الملفات السابقة، ولكن لا يوجد مكان يمكنك من خلاله وضع روابط عناوين URL والقوالب والملفات الساكنة، إذ سنوضح كيفية إنشائها لاحقًا، فهي ليست مطلوبة في جميع مواقع الويب ولكنها مطلوبة في مثالنا. تسجيل التطبيق catalog يجب الآن بعد إنشاء التطبيق تسجيلُه في المشروع بحيث يُضمَّن عند تشغيل أيّ أدوات، مثل إضافة نماذج إلى قاعدة البيانات، إذ تُسجَّل التطبيقات من خلال إضافتها إلى القائمة INSTALLED_APPS في إعدادات المشروع. افتح ملف إعدادات المشروع (django_projects/locallibrary/locallibrary/settings.py) وابحث عن تعريف القائمة INSTALLED_APPS، ثم أضِف سطرًا جديدًا في نهاية القائمة كما يلي: INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', # أضِف تطبيقنا الجديد 'catalog.apps.CatalogConfig', #أُنشئ هذا الكائن في /catalog/apps.py ] يحدد السطر الجديد كائن إعداد التطبيق (CatalogConfig) الذي أُنشِئ في "/locallibrary/catalog/apps.py" عندما أنشأتَ التطبيق. ملاحظة: ستلاحظ أن هناك الكثير من قوائم INSTALLED_APPS الأخرى وقوائم MIDDLEWARE أسفل ملف الإعدادات، مما يتيح دعم موقع مدير جانغو والوظائف التي يستخدمها بما في ذلك الجلسات والاستيثاق وغيرها. تحديد قاعدة البيانات يجب الآن تحديد قاعدة البيانات التي ستُستخدَم للمشروع. يُفضَّل استخدام قاعدة البيانات نفسها للتطوير والإنتاج إن أمكن ذلك، لتجنب اختلافات السلوك البسيطة. يمكنك التعرف على الخيارات المختلفة في قواعد البيانات في توثيق جانغو. سنستخدم قاعدة بياناتSQLite في مثالنا، لأننا لا نتوقع أن نطلب الكثير من الوصول المتزامن إلى قاعدة بيانات توضيحية Demonstration Database التي لا يتطلب إعدادها أيّ عمل إضافي. يمكنك رؤية كيفية إعداد قاعدة البيانات هذه في الملف settings.py: DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', } } لا نحتاج إلى إجراء أيّ إعداد إضافي لأننا نستخدم قاعدة بيانات SQLite. إعدادات المشروع الأخرى يُستخدَم الملف settings.py لضبط عددٍ من الإعدادات الأخرى، ولكن نريد حاليًا فقط تغيير المنطقة الزمنية TIME_ZONE التي يجب أن تكون مساوية لسلسلة نصية من القائمة المعيارية للمناطق الزمنية في قاعدة بيانات TZ، إذ يحتوي العمود TZ في الجدول على القيم التي تريدها. عدّل قيمة المنطقة الزمنية TIME_ZONE إلى إحدى هذه السلاسل النصية المناسبة لمنطقتك الزمنية كما يلي: TIME_ZONE = 'Europe/London' هناك إعدادان آخران لن تغيّرهما الآن، ولكن يجب أن تكون على دراية بهما، وهما: SECRET_KEY: هو مفتاح سري يُستخدَم بوصفه جزءًا من استراتيجية أمان موقع جانغو. إذا لم تحمي هذا الرمز في مرحلة التطوير، فيجب استخدام رمز مختلف (ربما يُقرَأ من متغير بيئة أو ملف) عند وضعه في مرحلة الإنتاج. DEBUG الذي يتيح عرض سجلات تنقيح الأخطاء Debugging عند حدوث الخطأ بدلًا من استجابات رمز حالة HTTP، ويجب ضبطه على القيمة False في مرحلة الإنتاج عندما تكون معلومات تنقيح الأخطاء مفيدة للمهاجمين، ولكن يمكننا حاليًا الاحتفاظ به مضبوطًا على القيمة True. وصل رابط عنوان URL يُنشَأ موقع الويب باستخدام ملف رابط عنوان URL (هو urls.py) في مجلد المشروع، إذ يمكنك استخدام هذا الملف لإدارة جميع روابط عناوين URL الخاصة بك، ولكن من المعتاد تأجيل هذه الروابط للتطبيق المرتبط به. افتح الملف locallibrary/locallibrary/urls.py ولاحظ النص الإرشادي الذي يشرح بعض طرق استخدام رابط عنوان URL. """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')) """ from django.contrib import admin from django.urls import path urlpatterns = [ path('admin/', admin.site.urls), ] تُدار روابط عنوان URL من خلال المتغير urlpatterns، وهو قائمة بايثون لدوال path()، بحيث إما تربط هذه الدالة نمط عنوان URL بعرض معين يُعرَض عند مطابقة النمط، أو تربطه بقائمة أخرى من شيفرة اختبار نمط عنوان URL؛ إذ يصبح النمط في الحالة الثانية "عنوان URL الأساسي" للأنماط المُعرَّفة في الوحدة الهدف. تعرِّف قائمة urlpatterns في البداية دالةً واحدة تربط جميع عناوين URL باستخدام النمط "admin/" مع الوحدة admin.site.urls التي تحتوي على تعريفات ربط عناوين URL الخاصة بتطبيق المدير. ملاحظة: الوجهة Route في الدالة path() هي سلسلة نصية تعرِّف نمط عنوان URL لمطابقته، إذ يمكن أن تحتوي هذه السلسلة النصية على متغير مُسمَّى بين قوسي زاوية، مثل 'catalog/<id>/'. سيطابق هذا النمط عنوان URL مثل العنوان catalog/any_chars/ ويمرِّر any_chars إلى العرض بوصفه سلسلة نصية مع معاملٍ بالاسم id. سنناقش توابع المسار Path Methods وأنماط الوجهة Route Patterns لاحقًا. يمكن إضافة عنصر قائمة جديد إلى القائمة urlpatterns من خلال إضافة الأسطر الآتية إلى أسفل الملف، إذ يتضمن هذا العنصر الجديد الدالة path() التي توجّه الطلبات مع النمط catalog/ إلى الوحدة catalog.urls (الملف الذي يحتوي على عنوان URL النسبي catalog/urls.py). # استخدم التابع include() لإضافة مسارات من تطبيق Catalog from django.urls import include urlpatterns += [ path('catalog/', include('catalog.urls')), ] ملاحظة: لاحظ أننا ضمّنا سطر الاستيراد from django.urls import include مع الشيفرة البرمجية التي يستخدمها، لذلك يمكن بسهولة رؤية ما أضفناه، ولكن من الشائع تضمين جميع سطور الاستيراد في أعلى ملف بايثون. لنعيد الآن توجيه عنوان URL الجذر لموقعنا (أي 127.0.0.1:8000) إلى العنوان .127.0.0.1:8000/catalog/ هذا هو التطبيق الوحيد الذي سنستخدمه في هذا المشروع، ويمكن تحقيق ذلك من خلال استخدام دالة عرض خاصة هي RedirectView، التي تأخذ عنوان URL النسبي الجديد لإعادة التوجيه إلى /catalog/ بوصفه وسيطها الأول عند مطابقة نمط عنوان URL المُحدَّد في الدالة path() (عنوان URL الجذر في حالتنا). ضِف الأسطر التالية في نهاية الملف: #ضِف روابط عنوان URL لإعادة توجيه عنوان URL الأساسي إلى تطبيقنا from django.views.generic import RedirectView urlpatterns += [ path('', RedirectView.as_view(url='catalog/', permanent=True)), ] اترك المعامل الأول لدالة المسار path() فارغًا للإشارة إلى '/'. إذا كتبتَ المعامل الأول بالشكل '/'، فسيعطيك جانغو التحذير التالي عند بدء تشغيل خادم التطوير: 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 '/'. لا يخدّم جانغو ملفات ساكنة مثل ملفات CSS وجافا سكريبت والصور افتراضيًا، ولكن يمكن أن يكون مفيدًا لخادم الويب للتطوير لفعل ذلك أثناء إنشاء موقعك. يمكنك تفعيل تخديم الملفات الساكنة أثناء التطوير من خلال إضافة الأسطر الآتية وذلك بمثابة إضافةٍ أخيرة إلى رابط عنوان URL. أضف الكتلة النهائية التالية إلى أسفل الملف: # استخدم الدالة static() لإضافة رابط عنوان URL لتخديم الملفات الساكتة أثناء التطوير (فقط) from django.conf import settings from django.conf.urls.static import static urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) ملاحظة: هناك عدد من الطرق لتوسيع قائمة urlpatterns؛ إذ ألحقنا سابقًا عنصر قائمة جديد باستخدام المعامل =+ للفصل بوضوح بين الشيفرة البرمجية القديمة والجديدة، لكن كان بإمكاننا بدلًا من ذلك تضمين ربط النمط الجديد في تعريف القائمة الأصلية كما يلي: urlpatterns = [ path('admin/', admin.site.urls), path('catalog/', include('catalog.urls')), path('', RedirectView.as_view(url='catalog/')), ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) أخيرًا، أنشِئ ملفًا ضمن المجلد catalog يُسمَّى urls.py، وضِف النص التالي لتعريف قائمة urlpatterns المستوردة (الفارغة)، وهذا هو المكان الذي سنضيف فيه أنماطنا عند بناء التطبيق. from django.urls import path from . import views urlpatterns = [ ] اختبار إطار عمل الموقع لدينا الآن مشروع هيكلي كامل. لا يفعل موقع الويب أيّ شيء حتى الآن، ولكن الأمر يستحق تشغيله للتأكد من أن أيّ تغييرات أجريناها لم تعطّل شيئًا ما. يجب علينا أولًا قبل ذلك تشغيل تهجير قاعدة البيانات Database Migration، مما يؤدي إلى تحديث قاعدة بياناتنا (لتضمين أيّ نماذج في تطبيقاتنا المُثبَّتة) ويزيل بعض تحذيرات البناء. تشغيل تهجيرات قاعدة البيانات يستخدم جانغو رابط كائنات علائقي Object-Relational-Mapper -أو ORM اختصارًا- لربط تعريفات النموذج في شيفرة جانغو البرمجية مع بنية البيانات التي تستخدمها قاعدة البيانات الأساسية. بما أننا نغيّر تعريفات نماذجنا، فسيتتبّع جانغو التغييرات ويمكنه إنشاء سكربتات تهجير قاعدة البيانات (في /locallibrary/catalog/migrations/) لتهجير بنية البيانات الأساسية تلقائيًا في قاعدة البيانات لمطابقة النموذج. أضاف جانغو تلقائيًا عددًا من النماذج عندما أنشأنا موقع الويب ليستخدمها قسم المدير في الموقع (الذي سنلقي نظرةً عليه لاحقًا). شغّل الأوامر التالية لتعريف الجداول لتلك النماذج في قاعدة البيانات، وتأكد من أنك في المجلد الذي يحتوي على الملف manage.py: python3 manage.py makemigrations python3 manage.py migrate تحذير: يجب تشغيل هذه الأوامر في كل مرة تتغير فيها نماذجك بطريقة تؤثر على بنية البيانات التي يجب تخزينها بما في ذلك إضافة وإزالة النماذج الكاملة والحقول الفردية. ينشئ الأمر makemigrations -لكنه لا يُطبِّق- تهجيرات لجميع التطبيقات المثبتة في مشروعك، إذ يمكنك تحديد اسم التطبيق لتشغيل تهجير لمشروع واحد فقط، مما يمنحك فرصةً للتحقق من الشيفرة البرمجية لهذه التهجيرات قبل تطبيقها. إذا كنت خبيرًا باستخدام جانغو، فيمكنك اختيار تعديلها بعض الشيء. يطبّق الأمر migrate التهجيرات على قاعدة بياناتك، ويتتبّع جانغو التهجيرات المُضافة إلى قاعدة البيانات الحالية. ملاحظة: اطّلع على عمليات التهجير Migrations في توثيق جانغو للحصول على معلومات إضافية حول أوامر التهجير الأقل استخدامًا. تشغيل الموقع يمكنك أثناء التطوير تخديم موقع الويب أولًا باستخدام خادم الويب الخاص بالتطوير، ثم عرضه في متصفح الويب المحلي. ملاحظة: لا يُعَد خادم الويب الخاص بالتطوير قويًا أو ذا أداء كافٍ للاستخدام في الإنتاج، ولكنه طريقة سهلة لتنشيط موقع جانغو وتشغيله أثناء التطوير لمنحه اختبارًا سريعًا مناسبًا، إذ سيخدّم هذا الخادم افتراضيًا الموقعَ لحاسوبك المحلي (http://127.0.0.1:8000/)، ولكن يمكنك تحديد حواسيب أخرى على شبكتك لتخديمها. اطلع على django-admin و manage.py: runserver في توثيق جانغو لمزيد من المعلومات. شغّل خادم الويب الخاص بالتطوير من خلال استدعاء الأمر runserver (في مجلد الملف manage.py نفسه): $ 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. يمكنك عرض الموقع بمجرد تشغيل الخادم بالانتقال إلى http://127.0.0.1:8000/ في متصفح الويب المحلي. يُفترَض أن ترى صفحة تظهِر خطأ في الموقع وتبدو كما يلي: لا تقلق فهذه الصفحة متوقعة لأنه ليس لدينا أي صفحات أو عناوين URLs معرّفة في الوحدة catalog.urls التي أُعيد توجيهنا إليها عندما حصلنا على عنوان URL لجذر الموقع. ملاحظة: يوضح هذا المثال ميزة جانغو الرائعة، وهي تسجيل تنقيح الأخطاء الآلي، إذ يعرض جانغو شاشة خطأ تحتوي على معلومات مفيدة أو أيّ خطأ ناتج عن الشيفرة البرمجية عندما يتعذر العثور على صفحة، إذ يمكننا في هذه الحالة أن نرى أن عنوان URL المتوفر لا يتطابق مع أيٍّ من أنماط عناوين URL الخاصة بنا كما هو موضَّح. يُوقَف التسجيل في مرحلة الإنتاج (وهو عندما نعرض الموقع على الويب مباشرة)، إذ ستُخدَّم في هذه الحالة صفحة ذات معلومات أقل ولكنها أسهل في الاستخدام. نعلم في هذه المرحلة أن تطبيق جانغو يعمل. ملاحظة: يجب إعادة تشغيل عمليات التهجير وإعادة اختبار الموقع كلما أجريت تغييرات مهمة، ولا يستغرق الأمر وقتًا طويلًا. تحدى نفسك يحتوي المجلد "catalog/" على ملفات للعروض والنماذج وأجزاء أخرى من التطبيق. افتح هذه الملفات وافحص النموذج المعياري. أُضيف ربط عناوين URL لموقع المدير في الملف urls.py الخاص بالمشروع كما رأيت سابقًا. انتقل إلى منطقة المدير في متصفحك وشاهد ما يحدث (يمكنك استنتاج عنوان URL الصحيح من الربط). الخلاصة أنشأت الآن مشروعًا هيكليًا كاملًا لموقع الويب، والذي يمكنك الاستمرار في ملؤه بعناوين URL والنماذج والعروض والقوالب. اكتمل هيكل موقع المكتبة المحلية وجرى تشغيله، وحان الوقت الآن لبدء كتابة الشيفرة البرمجية التي تجعل هذا الموقع يفعل ما يفترض به أن يفعل. ترجمة -وبتصرُّف- للمقالين Django Tutorial: The Local Library website و Django Tutorial Part 2: Creating a skeleton website. اقرأ أيضًا المقال التالي: تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models المقال السابق: إعداد بيئة تطوير تطبيقات جانغو Django مدخل إلى إطار عمل الويب جانغو من طرف الخادم تثبيت إطار العمل جانغو على أوبنتو البدء مع إطار العمل جانغو لإنشاء تطبيق ويب كتابة أول تطبيق جانغو - الجزء 1 (توثيق جانغو). التطبيقات (توثيق جانغو) الذي يحتوي على معلومات حول إعداد التطبيقات.
-
عرفت حتى الآن الغرض من إطار عمل جانغو Django، وسنوضّح الآن كيفية إعداد واختبار بيئة تطوير جانغوعلى أنظمة التشغيل ويندوز ولينكس (أوبنتو) وماك أو إس macOS، إذ يجب أن يعطيك هذا المقال ما تحتاجه لتكون قادرًا على البدء في تطوير تطبيقات جانغو بغض النظر عن نظام التشغيل الذي تستخدمه. المتطلبات الأساسية: معرفة أساسية باستخدام سطر الأوامر أو الطرفية Terminal وكيفية تثبيت الحزم البرمجية على نظام تشغيل حاسوب التطوير خاصتك. الهدف: تشغيل بيئة تطوير جانغو على حاسوبك. تتألف هذه السلسلة الفرعية من السلسلة الأشمل تعلم تطوير الويب من المقالات التالية: مدخل إلى إطار عمل الويب جانغو Django إعداد بيئة تطوير تطبيقات جانغو تطبيق عملي لتعلم جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية تطبيق عملي لتعلم جانغو - الجزء السادس: إدارة الجلسات Sessions تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج تعرف على أمان تطبيقات جانغو نظرة عامة إلى بيئة تطوير جانغو يسهّل جانغو Django عملية إعداد حاسوبك للبدء في تطوير تطبيقات الويب. سيشرح هذا القسم ما ستحصل عليه في بيئة التطوير، ويقدّم نظرة عامة على بعض خيارات الضبط والإعداد، وسيوضح الجزء المتبقي من المقال الطريقة الموصَى بها لتثبيت بيئة تطوير جانغو على أنظمة تشغيل أوبنتو وماك أو إس وويندوز وكيفية اختبارها. ما هي بيئة تطوير جانغو؟ بيئة التطوير هي تثبيت جانغو على حاسوبك المحلي الذي يمكنك استخدامه لتطوير واختبار تطبيقات جانغو قبل نشرها في بيئة الإنتاج. الأدوات الرئيسية التي يوفرها جانغو بنفسه هي مجموعة من سكربتات بايثون لإنشاء مشاريع جانغو والعمل معها وخادم ويب بسيط للتطوير يمكنك استخدامه لاختبار تطبيقات جانغو المحلية (أي على حاسوبك وليس على خادم ويب خارجي) على متصفح الويب في حاسوبك. هناك أدوات طرفية أخرى تشكل جزءًا من بيئة التطوير، ولكن لن نتحدث عنها، ويتضمن ذلك أشياءً مثل محرر النصوص، أو بيئة تطوير شاملة IDE لتحرير الشيفرة البرمجية، وأداة إدارة التحكم بالمصادر مثل غيت Git لإدارة النسخ المختلفة من شيفرتك البرمجية بأمان. سنفترض أن محرر نصوص مُثبَّت مسبقًا. خيارات إعداد جانغو يُعَد جانغو مرنًا جدًا من حيث كيفية ومكان تثبيته وإعداده، إذ يمكن أن يكون جانغو: مثبتًا على أنظمة تشغيل مختلفة. مثبتًا من المصدر من نظام إدارة الحزم Python Package Index -أو PyPi اختصارًا- ومن تطبيق مدير الحزم للحاسوب المضيف في كثير من الحالات. مُعَدًا لاستخدام واحدة من عدة قواعد بيانات، والتي يمكن أن تحتاج إلى التثبيت والإعداد بصورة منفصلة. يُشغَّل في بيئة نظام بايثون الرئيسي، أو ضمن بيئات بايثون الافتراضية المنفصلة. يتطلب كل خيار من هذه الخيارات إعدادًا وضبطًا مختلفًا قليلًا، إذ ستوضح الأقسام الفرعية التالية بعض اختياراتك، وسنوضّح لاحقًا كيفية إعداد جانغو على عدد من أنظمة التشغيل، وسنعتمد هذا الإعداد في بقية هذه السلسلة من المقالات. ملاحظة: خيارات التثبيت الممكنة الأخرى موضَّحة في توثيق جانغو الرسمي، وسنقدّم روابطًا إلى التوثيق المناسب لاحقًا. أنظمة التشغيل المدعومة يمكن تشغيل تطبيقات جانغو على أيّ جهاز تقريبًا يمكنه تشغيل لغة بايثون 3 مثل أنظمة ويندوز وماك أو إس ولينكس/ يونيكس وسولاريس Solaris. يجب أن يتمتع أي حاسوب تقريبًا بالأداء اللازم لتشغيل جانغو أثناء عملية التطوير. سنقدم في هذا المقال إرشادات لأنظمة ويندوز وماك أو إس ولينكس/ يونيكس. نسخة بايثون التي يجب استخدامها يمكنك استخدام أيّ نسخة من بايثون يدعمها إصدار جانغو المستهدف. النسخ المسموح بها بالنسبة لإصدار جانغو 4.0.2 هي إصدار بايثون 3.8 إلى 3.10 (اطلع على نسخة بايثون التي يمكن استخدامها مع جانغو). يوصي مشروع جانغو (ويدعم رسميًا) باستخدام أحدث إصدار مدعوم من بايثون. مكان تنزيل جانغو هناك ثلاثة أماكن لتنزيل جانغو، هي: مستودع حزم بايثون Python Package Repository -أو اختصارًا PyPi- باستخدام أداة Pip، وهي أفضل طريقة للحصول على أحدث نسخة مستقرة من جانغو. استخدم نسخةً من مدير حزم حاسوبك، إذ تقدم توزيعات جانغو المُجمَّعة مع أنظمة التشغيل آلية تثبيت مألوفة. يمكن أن تكون النسخة المُضمَّنة قديمة جدًا، ولا يمكن تثبيتها إلا في بيئة بايثون الخاصة بالنظام (وهذا ما لا تريده). التثبيت من المصدر، إذ يمكنك الحصول على أحدث نسخة من جانغو وتثبيتها من المصدر، وهذا غير موصَى به للمبتدئين ولكنه ضروري عندما تكون مستعدًا لبدء المساهمة في جانغو. يوضح هذا المقال كيفية تثبيت جانغو من مستودع حزم PyPi للحصول على أحدث نسخة مستقرة. قاعدة البيانات المستخدمة يدعم جانغو رسميًا قواعد بيانات PostgreSQL و MariaDB و MySQL و Oracle و SQLite، وهناك مكتبات مجتمعية توفر مستويات مختلفة من الدعم لقواعد بيانات SQL و NoSQL الشائعة الأخرى. نوصيك باختيار قاعدة البيانات نفسها لكل من الإنتاج والتطوير، إذ لا تزال هناك مشاكل محتملة يُفضَّل تجنبها بالرغم من أن جانغو يجرّد العديد من الاختلافات في قاعدة البيانات باستخدام رابط الكائنات العلائقي Object-Relational Mapper -أو ORM اختصارًا. سنستخدم في هذا المقال قاعدة بيانات SQLite التي تخزن بياناتها في ملف؛ إذ صُمِّمت قاعدة بيانات SQLite للاستخدام بوصفها قاعدة بيانات خفيفة الوزن ولا يمكنها دعم مستوى عالٍ من التزامن، ولكنها تُعَد اختيارًا ممتازًا للتطبيقات التي تكون للقراءة فقط. ملاحظة: أُعِّد جانغو لاستخدام قاعدة بيانات SQLite افتراضيًا عند بدء مشروع موقع الويب باستخدام الأدوات المعيارية (django-admin)، وتُعَد خيارًا رائعًا عند البدء لأنها لا تتطلب أي إعداد أو ضبط إضافي. التثبيت على مستوى النظام أم في بيئة بايثون الافتراضية تحصل بتثبيت بايثون 3 على بيئة عامة واحدة تشترك فيها جميع شيفرات بايثون البرمجية، ويمكنك تثبيت أيّ حزمة من حزم بايثون التي تريدها في هذه البيئة، ولكن يمكنك تثبيت نسخة معينة واحدة فقط من كل حزمة في الوقت نفسه. ملاحظة: يُحتمَل أن تتعارض تطبيقات بايثون المثبتة في البيئة العامة مع بعضها بعضًا، أي إذا كانت معتمدة على نسخ مختلفة من الحزمة نفسها. إذا ثبَّتَ جانغو في البيئة الافتراضية أو العامة، فستتمكّن فقط من استهداف نسخة واحدة من جانغو على الحاسوب، ويمكن أن يشكّل ذلك مشكلة إذا أردتَ إنشاء مواقع ويب جديدة (باستخدام أحدث نسخة من جانغو) مع الاستمرار في الحفاظ على مواقع الويب التي تعتمد على النسخ القديمة، لذا يشغّل مطورو بايثون/جانغو ذوو الخبرة تطبيقات بايثون في بيئات بايثون الافتراضية المستقلة، مما يتيح العديد من بيئات جانغو المختلفة على حاسوب واحد. يوصي فريق مطوري جانغو باستخدام بيئات بايثون الافتراضية. تفترض هذه السلسلة من المقالات أنك ثبَّتَ جانغو في بيئة افتراضية، وسنوضح فيما يلي كيفية ذلك. تثبيت لغة بايثون 3 يجب تثبيت بايثون على نظام التشغيل لاستخدام جانغو، وإذا كنت تستخدم بايثون 3، فستحتاج أداة الخاصة بمستودع حزم بايثون Python Package Index وهي pip3 التي تُستخدَم لإدارة (تثبيت وتحديث وحذف) حزم أو مكتبات بايثون التي تستخدمها تطبيقات جانغو وتطبيقات بايثون الأخرى. يشرح هذا القسم بإيجاز كيفية التحقق من نسخ بايثون الموجودة مسبقًا ويوضح طريقة تثبيت نسخ جديدة حسب الحاجة لنظام التشغيل أوبنتو لينكس 20.04 ونظام ماك أو إس ونظام ويندوز 10. ملاحظة: يمكن أن تتمكن من تثبيت بايثون وأداة pip من مدير الحزم الخاص بنظام التشغيل، أو عبر آليات أخرى اعتمادًا على منصتك التي تستخدمها، إذ يمكنك تنزيل ملفات التثبيت المطلوبة من موقع بايثون وتثبيتها باستخدام الطريقة المناسبة الخاصة بالمنصة. تثبيت لغة بايثون على نظام التشغيل لينكس أوبنتو يتضمن نظام أوبنتو لينكس LTS 20.04 بايثون 3.8.10 افتراضيًا، ويمكنك التأكّد من ذلك من خلال تشغيل الأمر التالي في طرفية باش Bash: python3 -V Python 3.8.10 ليست الأداة pip3 الخاصة بمستودع حزم بايثون التي ستحتاجها لتثبيت حزم بايثون 3 (بما في ذلك جانغو) متوفرة افتراضيًا، إذ يمكنك تثبيت pip3 في طرفية باش باستخدام الأمر التالي: sudo apt install python3-pip ملاحظة: تُعَد بايثون 3.8 أقدم نسخة يدعمها جانغو 4.0. يوصي إطار عمل جانغو بالتحديث إلى أحدث نسخة، ولكنك لا تحتاج إلى استخدام أحدث نسخة في هذه السلسلة من المقالات. إذا أردت تحديث بايثون، فيمكنك البحث عن التعليمات على الإنترنت. تثبيت لغة بايثون على نظام ماك أو إس لا تتضمن النسخة إل كابيتان El Capitan من نظام ماك أو إس والنسخ الأحدث الأخرى لغةَ بايثون 3، ويمكنك التأكّد من ذلك من خلال تشغيل الأوامر التالية في طرفية zsh أو طرفية باش: $ python3 -V python3: command not found يمكنك بسهولة تثبيت بايثون 3 (إضافةً إلى أداة pip3) من موقع بايثون كما يلي: نزّل برنامج التثبيت المطلوب: انتقل إلى موقع بايثون الرسمي. نزّل أحدث نسخة مدعومة تعمل مع جانغو 4.0.2 (وهي نسخة بايثون 3.10.2 في وقت كتابة هذا المقال). حدّد موقع الملف باستخدام مدير الملفات فايندر Finder، وانقر نقرًا مزدوجًا على ملف الحزمة، ثم اتبع خطوات التثبيت. يمكنك الآن تأكيد التثبيت الناجح من خلال التحقق من نسخة بايثون 3 كما يلي: python3 -V Python 3.10.2 يمكنك التحقق من تثبيت أداة pip3 من خلال سرد الحزم المتاحة: pip3 list تثبيت لغة بايثون على نظام ويندوز 10 أو 11 لا يتضمن نظام ويندوز لغة بايثون افتراضيًا، ولكن يمكنك تثبيتها بسهولة (إضافةً إلى أداة pip3) من موقع بايثون كما يلي: نزّل برنامج التثبيت المطلوب بالطريقة التالية: انتقل إلى موقع بايثون. نزّل أحدث نسخة مدعومة تعمل مع جانغو 4.0.2 (وهي نسخة بايثون 3.10.2 في وقت كتابة هذا المقال). ثبّت بايثون بالنقر المزدوج على الملف الذي نزّلته واتبّع خطوات التثبيت. تأكد من تحديد المربع المسمَّى "إضافة بايثون إلى المسار Add Python to PATH". يمكنك بعد ذلك التحقق من تثبيت بايثون 3 من خلال إدخال النص التالي في موجّه الأوامر: py -3 -V Python 3.10.2 يضمّن مثبِّت نظام ويندوز الأداة pip3 (مدير حزمة بايثون) افتراضيًا، ويمكنك سرد الحزم المُثبَّتة كما يلي: pip3 list ملاحظة: يجب على المثبِّت إعداد كل ما تحتاجه حتى يعمل الأمر السابق. إذا تلقيتَ رسالةً مفادها أنه لا يمكن العثور على بايثون، فيمكن أن تكون قد نسيت إضافتها إلى مسار نظامك، ويمكنك تطبيق ذلك من خلال تشغيل المثبت مرةً أخرى وتحديد الخيار "تعديل Modify" وتحديد المربع المسمى "إضافة بايثون إلى متغيرات البيئة Add Python to Environment Variables" في الصفحة الثانية. استخدام جانغو ضمن بيئة بايثون الافتراضية المكتبات التي سنستخدمها لإنشاء بيئاتنا الافتراضية هي virtualenvwrapper (لينكس وماك أو إس) وvirtualenvwrapper-win (ويندوز)، إذ تستخدم هاتان المكتبتان أداة virtualenv. تنشئ الأدوات المغلِّفة واجهة متناسقة لإدارة الواجهات على جميع المنصات. تثبيت برمجيات البيئة الافتراضية سنتعرّف الآن على كيفية إعداد البيئة الافتراضية في كل من أنظمة تشغيل أوبنتو وماك وويندوز. إعداد بيئة أوبنتو الافتراضية يمكنك بعد تثبيت بايثون وأداة pip تثبيت المكتبة virtualenvwrapper التي تتضمن المكتبة virtualenv. يمكنك الاطلاع على دليل التثبيت الرسمي أو اتباع الإرشادات التالية. ثبّت الأداة أو المكتبة باستخدام الأداة pip3 كما يلي: sudo pip3 install virtualenvwrapper أضف بعد ذلك الأسطر التالية إلى نهاية ملف بدء تشغيل الصدفة Shell، وهو اسم ملف مخفي ".bashrc" في مجلدك الرئيسي. تحدد الأسطر التالية الموقع الذي توجد فيه البيئات الافتراضية وموقع مجلدات مشروع التطوير وموقع السكربت المُثبَّت مع هذه الحزمة: export WORKON_HOME=$HOME/.virtualenvs export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3 export VIRTUALENVWRAPPER_VIRTUALENV_ARGS=' -p /usr/bin/python3 ' export PROJECT_HOME=$HOME/Devel source /usr/local/bin/virtualenvwrapper.sh ملاحظة: تشير المتغيرات VIRTUALENVWRAPPER_PYTHON و VIRTUALENVWRAPPER_VIRTUALENV_ARGS إلى موقع التثبيت العادي لبايثون 3، ويشير المصدر /usr/local/bin/virtualenvwrapper.sh إلى الموقع الاعتيادي للسكربت virtualenvwrapper.sh. إذا لم تعمل مكتبة virtualenv عند اختبارها، فالشيء الوحيد الذي يجب التحقق منه هو أن بايثون والسكربت موجودان في الموقع المتوقع، ثم غيّر ملف بدء التشغيل بطريقة مناسبة. يمكنك العثور على المواقع الصحيحة لنظامك باستخدام الأوامر which virtualenvwrapper.sh و which python3. أعِد بعد ذلك تحميل ملف بدء التشغيل من خلال تشغيل الأمر التالي في الطرفية: source ~/.bashrc يجب الآن أن ترى مجموعة من السكربتات التي تُشغَّل كما يلي: virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/premkproject virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/postmkproject # … virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/preactivate virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/postactivate virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/get_env_details يمكنك الآن إنشاء بيئة افتراضية جديدة باستخدام الأمر mkvirtualenv. إعداد البيئة الافتراضية لنظام ماك أو إس يطابق إعداد المكتبة virtualenvwrapper على نظام ماك أو إس تقريبًا إعدادها على نظام أوبنتو، ويمكنك اتباع التعليمات إما من دليل التثبيت الرسمي، أو من الخطوات التي سنوضحها فيما يلي. ثبّت المكتبة Virtualenvwrapper (وتجميع المكتبة virtualenv) باستخدام الأداة pip كما يلي: sudo pip3 install virtualenvwrapper أضف بعد ذلك الأسطر الآتية إلى نهاية ملف بدء تشغيل الصدفة، وهي السطور في نظام أوبنتو نفسها. إذا كنت تستخدم صدفة zsh، فسيكون ملف بدء التشغيل ملفًا مخفيًا باسم ".zshrc" في مجلدك الرئيسي، وإذا كنت تستخدم صدفة باش Bash، فسيكون ملفًا مخفيًا باسم ".bash_profile"، وقد تحتاج إلى إنشاء الملف إن لم يكن موجودًا بعد. export WORKON_HOME=$HOME/.virtualenvs export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3 export PROJECT_HOME=$HOME/Devel source /usr/local/bin/virtualenvwrapper.sh ملاحظة: يشير المتغير VIRTUALENVWRAPPER_PYTHON إلى موقع التثبيت العادي لبايثون 3، ويشير المصدر /usr/local/bin/virtualenvwrapper.sh إلى الموقع الاعتيادي للسكربت virtualenvwrapper.sh. إذا لم تعمل المكتبة virtualenv عند اختبارها، فالشيء الوحيد الذي يجب التحقق منه هو أن بايثون والسكربت موجودان في الموقع المتوقع، ثم غيّر ملف بدء التشغيل بطريقة مناسبة. انتهى اختبار أحد التثبيتات على نظام ماك أو إس مثلًا بالأسطر التالية التي تُعَد ضرورية في ملف بدء التشغيل: export WORKON_HOME=$HOME/.virtualenvs export VIRTUALENVWRAPPER_PYTHON=/Library/Frameworks/Python.framework/Versions/3.7/bin/python3 export PROJECT_HOME=$HOME/Devel source /Library/Frameworks/Python.framework/Versions/3.7/bin/virtualenvwrapper.sh يمكنك العثور على المواقع الصحيحة لنظامك باستخدام الأوامر which virtualenvwrapper.sh و which python3. أعِد بعد ذلك تحميل ملف بدء التشغيل من خلال إجراء الاستدعاء التالي في الطرفية: source ~/.bash_profile يمكن أن ترى الآن مجموعة من السكربتات التي تكون قيد التشغيل (السكربتات الخاصة بتثبيت نظام أوبنتو نفسها)، ويجب أن تكون الآن قادرًا على إنشاء بيئة افتراضية جديدة باستخدام الأمر mkvirtualenv. ملاحظة: إذا لم تتمكن من العثور على ملف بدء التشغيل لتعديله في أداة البحث، فيمكنك فتحه في الطرفية باستخدام الأمر nano. تبدو الأوامر كما يلي بافتراض أنك تستخدم باش Bash: cd ~ # الانتقال إلى المجلد الرئيسي ls -la #قائمة محتويات المجلد. يجب أن ترى .bash_profile nano .bash_profile # افتح الملف في محرر نصوص نانو nano ضمن الطرفية # انتقل إلى نهاية الملف، وانسخ الأسطر السابقة # استخدم Ctrl + X للخروج من نانو، واختر Y لحفظ الملف. إعداد بيئة ويندوز الافتراضية يُعَد تثبيت مكتبة virtualenvwrapper-win أبسط من إعداد مكتبة virtualenvwrapper، لأنك لا تحتاج إلى إعداد مكان تخزين الأداة لمعلومات البيئة الافتراضية، فهناك قيمة افتراضية، وكل ما عليك فعله هو تشغيل الأمر التالي في موجّه الأوامر: pip3 install virtualenvwrapper-win يمكنك الآن إنشاء بيئة افتراضية جديدة باستخدام الأمر mkvirtualenv. إنشاء بيئة افتراضية يكون العمل مع البيئات الافتراضية متشابهًا جدًا على جميع المنصات بعد تثبيت المكتبة virtualenvwrapper أو virtualenvwrapper-win. يمكنك الآن إنشاء بيئة افتراضية جديدة باستخدام الأمر mkvirtualenv، إذ سترى أثناء تشغيل هذا الأمر البيئة التي يجري إعدادها (يكون ما تراه خاصًا بالمنصة). ستكون البيئة الافتراضية الجديدة نشطةً عندما يكتمل الأمر، ويمكنك رؤية ذلك لأن بداية موجّه الأوامر ستكون اسم البيئة بين قوسين، إذ سنعرض ذلك لنظام أوبنتو، ولكن السطر الأخير مشابه لنظام ويندوز وماك أو إس. $ mkvirtualenv my_django_environment Running virtualenv with interpreter /usr/bin/python3 # … virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/t_env7/bin/get_env_details (my_django_environment) ubuntu@ubuntu:~$ أصبحت الآن ضمن البيئة الافتراضية، ويمكنك تثبيت جانغو والبدء في التطوير. ملاحظة: يُرجَى من الآن فصاعدًا في هذا المقال (وهذه السلسلة من المقالات) افتراض أن أيّ أوامر مُشغَّلة ضمن بيئة بايثون الافتراضية تشبه الأمر الذي ضبطناه سابقًا. استخدام البيئة الافتراضية لا يوجد سوى عدد قليل من الأوامر المفيدة الأخرى التي يجب أن تعرفها، إذ يوجد المزيد في توثيق الأدوات، ولكن الأوامر التالية هي التي سنستخدمها بانتظام: deactivate: الخروج من بيئة بايثون الافتراضية الحالية. workon: سرد البيئات الافتراضية المتاحة. workon name_of_environment: تنشيط بيئة بايثون الافتراضية المحددة. rmvirtualenv name_of_environment: إزالة البيئة المحددة. تثبيت جانغو يمكنك استخدام الأداة pip3 لتثبيت جانغو بعد إنشاء بيئة افتراضية واستدعاء الأمر workon للدخول إليها كما يلي: pip3 install django~=4.0 يمكنك اختبار تثبيت جانغو من خلال تشغيل الأمر التالي، وهو مجرد اختبار لإمكانية أن تعثر بايثون على وحدة جانغو: # Linux/macOS python3 -m django --version 4.0.2 # Windows py -3 -m django --version 4.0.2 ملاحظة: إن لم يُظهِر أمر ويندوز السابق وجود وحدة جانغو، فجرّب الأمر التالي: py -m django --version تُشغَّل سكربتات بايثون 3 في ويندوز من خلال أن نضع py -3 في بداية الأمر، بالرغم من أن ذلك يمكن أن يختلف اعتمادًا على التثبيت المحدد، وحاول حذف المعدِّل -3 إذا واجهت أي مشاكل مع الأوامر. بينما يكون الأمر هو python3 في لينكس وماك. ملاحظة: تستخدم هذه السلسلة من المقالات أمر لينكس python3 لاستدعاء بايثون 3، فإذا كنت تعمل على نظام ويندوز، فاستبدل هذه البادئة بالبادئة py -3. أدوات بايثون الأخرى يمكن أن يثبّت مطورو بايثون ذوو الخبرة أدوات إضافية مثل منقحات الصياغة linters التي تساعد في اكتشاف الأخطاء الشائعة في الشيفرة البرمجية. لاحظ أنه يجب عليك استخدام منقّح صياغة جانغو مثل pylint-django. يمكن أن تبلّغ منقحات صياغة بايثون شائعة الاستخدام مثل pylint بصورة غير صحيحة عن أخطاء في الملفات المعيارية التي جرى إنشاؤها لجانغو. اختبار تثبيتك لجانغو Django يعمل الاختبار السابق بطريقة جيدة، ولكنه ليس ممتعًا كثيرًا، فالاختبار الأكثر إثارة للاهتمام هو إنشاء مشروع هيكلي ورؤيته يعمل، ويمكن ذلك من خلال الانتقال أولًا في موجه الأوامر أو الطرفية إلى المكان الذي تريد تخزين تطبيقات جانغو فيه. أنشِئ مجلدًا لموقع اختبارك وانتقل إليه كما يلي: mkdir django_test cd django_test يمكنك بعد ذلك إنشاء موقع هيكلي جديد بالاسم "mytestsite" باستخدام الأداة django-admin كما هو موضح. يمكنك -بعد إنشاء الموقع- الانتقال إلى المجلد حيث ستجد السكربت الرئيسي لإدارة المشاريع والذي يُسمَّى manage.py. django-admin startproject mytestsite cd mytestsite يمكننا تشغيل خادم الويب الخاص بالتطوير من داخل هذا المجلد باستخدام manage.py والأمر runserver كما يلي: $ python3 manage.py runserver 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. March 01, 2022 - 01:19:16 Django version 4.0.2, using settings 'mytestsite.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C. ملاحظة: يعرض الأمرُ السابق الأمرَ الخاص بنظام لينكس أو ماك. يمكنك تجاهل التحذيرات بشأن عمليات التهجير غير المُطبَّقة "18 unapplied migration(s)" حاليًا. يمكنك بمجرد تشغيل الخادم عرض الموقع بالانتقال إلى عنوان URL التالي على متصفح الويب المحلي الخاص بك: http://127.0.0.1:8000/، ويجب أن تشاهد موقعًا يشبه ما يلي: الخلاصة لديك الآن بيئة تطوير جانغو التي تعمل على حاسوبك. رأيتَ في قسم الاختبار بإيجاز كيف يمكننا إنشاء موقع جانغو جديد باستخدام الأمر django-admin startproject وتشغيله في متصفحك باستخدام خادم تطوير الويب (python3 manage.py runserver). سنتوسّع في هذه العملية ونبني تطبيق ويب بسيط وكامل في المقال التالي. اطّلع أيضًا على المقالات التالية: تثبيت إطار العمل جانغو على أوبنتو. دليل التثبيت السريع (توثيق جانغو). كيفية تثبيت جانغو - الدليل الكامل (توثيق جانغو): يغطي كيفية إزالة جانغو أيضًا. كيفية تثبيت جانغو على نظام ويندوز (توثيق جانغو). ترجمة -وبتصرُّف- للمقال Setting up a Django development environment. اقرأ أيضًا المقال التالي: تطبيق عملي لتعلم جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية المقال السابق: مدخل إلى إطار عمل الويب جانغو من طرف الخادم إعداد تطبيق جانغو جاهز للنشر مع قاعدة بيانات Postgres وخادم Nginx و Gunicorn البدء مع إطار العمل جانغو لإنشاء تطبيق ويب حزم بايثون الثمانية التي تسهل تعاملك مع Django
-
يُعد جانغو Django إطار عمل ويب شائع جدًا من طرف الخادم ويمتلك ميزات كاملة وهو مكتوب بلغة البرمجة بايثون. سنوضح في هذه السلسلة من المقالات سبب كون جانغو أحد أكثر إطارات عمل خادم الويب شيوعًا وكيفية إعداد بيئة التطوير والبدء في استخدامه لإنشاء تطبيقات الويب. تتألف هذه السلسلة الفرعية من السلسلة الأشمل تعلم تطوير الويب من المقالات التالية: مدخل إلى إطار عمل الويب جانغو Django إعداد بيئة تطوير تطبيقات جانغو تطبيق عملي لتعلم جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية تطبيق عملي لتعلم جانغو - الجزء الثاني: استخدام النماذج Models تطبيق عملي لتعلم جانغو - الجزء الثالث: موقع مدير جانغو Django Admin تطبيق عملي لتعلم جانغو - الجزء الرابع: إنشاء صفحة المكتبة الرئيسية تطبيق عملي لتعلم جانغو - الجزء الخامس: العروض Views العامة والتفصيلية تطبيق عملي لتعلم جانغو - الجزء السادس: إدارة الجلسات Sessions تطبيق عملي لتعلم جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms تطبيق عملي لتعلم جانغو - الجزء التاسع: اختبار تطبيق جانغو تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج تعرف على أمان تطبيقات جانغو المتطلبات الأساسية لست بحاجة إلى معرفة إطار عمل جانغو Django قبل البدء، ولكنك تحتاج إلى فهم ماهية برمجة الويب من طرف الخادم وأطر الويب من خلال الاطلاع على سلسلة مقالات الخطوات الأولى لبرمجة موقع الويب من طرف الخادم. يُوصَى بمعرفة مفاهيم البرمجة ولغة بايثون، ولكنها ليست ضرورية لفهم المفاهيم الأساسية. ملاحظة: تُعَد لغة بايثون إحدى أسهل لغات البرمجة ليقرأها ويفهمها المبتدئون، ولكن إذا أردت فهم هذه السلسلة من المقالات بصورة أفضل، فيمكنك الاطلاع على توثيق لغة بايثون في موسوعة حسوب، وقراءة كتاب البرمجة بلغة بايثون من أكاديمية حسوب. تتألف هذه السلسلة الفرعية من السلسلة الأشمل تعلم تطوير الويب من المقالات التالية: مدخل إلى إطار عمل الويب جانغو من طرف الخادم: سنجيب في مقالنا الأول على سؤال "ما هو إطار عمل جانغو؟" وسنقدّم نظرةً عامة على ما يجعله إطار عمل مميز، وسنحدّد الميزات الرئيسية بما في ذلك بعض الوظائف المتقدمة التي لن يكون لدينا الوقت لتغطيتها بالتفصيل. سنوضّح أيضًا بعض أساسيات تطبيق جانغو لإعطائك فكرة عمّا يمكنه تطبيقه قبل إعداده والبدء به. إعداد بيئة تطوير جانغو: سنوضّح بعد معرفة الغرض من إطار عمل جانغو كيفية إعداد واختبار بيئة تطويره على أنظمة تشغيل ويندوز ولينكس (أوبونتو Ubuntu) وماك أو إس MacOS. يجب أن يعطيك هذا المقال ما تحتاج إليه لتكون قادرًا على البدء في تطوير تطبيقات جانغو بغض النظر عن نظام التشغيل الشائع الذي تستخدمه. تطبيق عملي لتعلم إطار عمل جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية: يشرح هذا المقال ما ستتعلمه، ويوفر نظرةً عامة على موقع "المكتبة المحلية Local Library"، وهو مثال لموقع الويب الذي سنعمل من خلاله ونطوّره في مقالات لاحقة. يوضح هذا المقال كيفية إنشاء مشروع موقع الويب الهيكلي، والذي يمكنك بعد ذلك ملؤه بالإعدادات الخاصة بالموقع وعناوين URL والنماذج والعروض والقوالب. تطبيق عملي لتعلم إطار عمل جانغو - الجزء الثاني: استخدام النماذج Models: يوضح هذا المقال كيفية تحديد النماذج Models لموقع ويب المكتبة المحلية؛ إذ تمثل النماذج بنى البيانات التي نريد تخزين بيانات التطبيق فيها، وتسمح لجانغو بتخزين البيانات في قاعدة بياناتنا وتعديلها لاحقًا. يشرح هذا المقال أيضًا مفهوم النموذج وكيفية التصريح عنه وبعض أنواع الحقول الرئيسية، ويعرض بإيجاز بعض الطرق الرئيسية التي يمكنك من خلالها الوصول إلى بيانات النموذج. تطبيق عملي لتعلم إطار عمل جانغو - الجزء الثالث: موقع مدير جانغو: أنشأنا نماذج موقع ويب المكتبة المحلية، وسنستخدم الآن موقع مدير جانغو Django Admin لإضافة بعض بيانات الكتب الحقيقية، إذ سنوضح أولًا كيفية تسجيل النماذج في موقع المدير، ثم سنوضح كيفية تسجيل الدخول وإنشاء بعض البيانات، وسنعرض بعض الطرق التي يمكنك من خلالها تحسين عرض موقع المدير. تطبيق عملي لتعلم إطار عمل جانغو - الجزء الرابع: إنشاء الصفحة الرئيسية: نحن الآن جاهزون لإضافة الشيفرة البرمجية لعرض صفحتنا الأولى الكاملة، وهي الصفحة الرئيسية للمكتبة المحلية التي تعرض عددًا من السجلات لكل نوعٍ من النماذج وتوفر روابط تنقّل جانبية إلى صفحاتنا الأخرى، وسنكتسب خبرةً عملية في كتابة روابط عناوين URL والعروض الأساسية والحصول على السجلات من قاعدة البيانات واستخدام القوالب. تطبيق عملي لتعلم إطار عمل جانغو - الجزء الخامس: عروض القائمة المعممة والعروض التفصيلية: سنوسّع في هذا المقال موقع المكتبة المحلية من خلال إضافة قائمة وصفحات تفصيلية للكتب والمؤلفين، إذ سنتعرف على العروض المعمَّمة Generic Views المستندة إلى الأصناف، وسنوضّح كيف يمكنها تقليل مقدار الشيفرة البرمجية التي يجب عليك كتابتها لحالات الاستخدام الشائعة، وسننتقل إلى معالجة عناوين URL بمزيد من التفصيل مع توضيح كيفية إجراء مطابقة الأنماط الأساسية. تطبيق عملي لتعلم إطار عمل جانغو - الجزء السادس: إطار عمل الجلسة: سنوسّع في هذا المقال موقع المكتبة المحلية من خلال إضافة عدّاد زيارات مستند إلى جلسات الصفحة الرئيسية، إذ يُعَد ذلك مثالًا بسيطًا نسبيًا، ولكنه يوضّح كيفية استخدام إطار عمل الجلسة لتوفير سلوك مستمر للمستخدمين المجهولين على مواقعك الخاصة. تطبيق عملي لتعلم إطار عمل جانغو - الجزء السابع: استيثاق المستخدمين وأذوناتهم: سنشرح في هذا المقال كيفية السماح للمستخدمين بتسجيل الدخول إلى موقعك بحساباتهم الخاصة وكيفية التحكم بما يمكنهم فعله ومشاهدته بناءً على ما إذا قد سجّلوا الدخول أم لا وبناءً على أذوناتهم، إذ سنوسّع موقع ويب المكتبة المحلية من خلال إضافة صفحات تسجيل الدخول والخروج وصفحات خاصة بالمستخدمين والموظفين لعرض الكتب المستعارة. تطبيق عملي لتعلم إطار عمل جانغو - الجزء الثامن: التعامل مع الاستمارات: سنشرح في هذا المقال كيفية التعامل مع استمارات HTML في إطار عمل جانغو وخاصة استخدام أسهل طريقة لكتابة الاستمارات Forms لإنشاء نسخ من النماذج Model وتحديثها وحذفها، إذ سنوسّع موقع الويب للمكتبة المحلية بحيث يمكن لأمناء المكتبة تجديد الكتب وإنشاء المؤلفين وتحديثهم وحذفهم باستخدام الاستمارات بدلًا من استخدام تطبيق المدير. تطبيق عملي لتعلم إطار عمل جانغو - الجزء التاسع: اختبار تطبيق جانغو: يصبح الاختبار اليدوي لمواقع الويب أصعب مع زيادة حجمها، بسبب وجود المزيد لاختباره، إضافةً إلى زيادة تعقيد التفاعلات بين المكونات، إذ يمكن أن يتطلب تغيير بسيط في منطقة واحدة العديدَ من الاختبارات الإضافية للتحقق من تأثيره على مناطق أخرى. تتمثل إحدى طرق التخفيف من هذه المشاكل في كتابة الاختبارات الآلية التي يمكن تشغيلها بسهولة وموثوقية في كل مرة تُجري فيها تغييرًا. يوضّح هذا المقال كيفية جعل اختبار الوحدة Unit Testing لموقع الويب آليًا باستخدام إطار اختبار جانغو. تطبيق عملي لتعلم إطار عمل جانغو - الجزء العاشر: نشر تطبيق جانغو في بيئة الإنتاج: أنشأتَ واختبرتَ موقعًا رائعًا للمكتبة المحلية، ويجب الآن تثبيته على خادم ويب عام حتى يصل إليه موظفو المكتبة وأعضاؤها عبر الإنترنت. يقدّم هذا المقال نظرةً عامةً حول كيفية البحث عن مضيف لنشر موقع الويب وما يجب فعله لتجهيز موقعك للإنتاج. أمان تطبيق جانغو: تُعَد حماية بيانات المستخدم جزءًا أساسيًا من تصميم أيّ موقع ويب، إذ شرحنا سابقًا بعض الهجمات الأمنية الأكثر شيوعًا في مقال أمان مواقع الويب، وسيقدّم هذا المقال شرحًا عمليًا لكيفية تعامل أدوات الحماية المُضمَّنة في إطار عمل جانغو مع هذه الهجمات. يمكنك اختبار فهمك لكيفية إنشاء موقع ويب باستخدام جانغو كما هو موضح في هذه السلسلة من المقالات من خلال إنشاء مدونتك الخاصة. لنبدأ بمقالنا الأول من هذه السلسلة من خلال التعرف على إطار عمل جانغو. مقدمة إلى إطار العمل جانغو سنجيب في مقالنا الأول على سؤال "ما هو جانغو؟" وسنقدّم نظرةً عامةً على ما يجعله إطار عمل ويب مميز، إذ سنحدد الميزات الرئيسية بما في ذلك بعض الوظائف المتقدمة التي لن يكون لدينا الوقت الكافي لتغطيتها بالتفصيل، وسنعرض بعض أساسيات بناء تطبيق جانغو بالرغم من أنه لن يكون لديك بيئة تطوير لاختبارها بعد. المتطلبات الأساسية: المهارات الحاسوبية الأساسية، وفهم عام لبرمجة مواقع الويب من طرف الخادم وخاصةً تفاعلات الخادم مع العميل في مواقع الويب. الهدف: التعرف على إطار عمل جانغو والوظائف التي يوفرها وأساسيات بناء تطبيق جانغو. ما هو جانغو؟ يُعَد جانغو إطار عمل ويب عالي المستوى يستخدم لغة بايثون ويتيح التطوير السريع لمواقع الويب الآمنة والقابلة للصيانة. صمّم مطورون ذوو خبرة إطار عمل جانغو الذي يخلصك من الكثير من متاعب تطوير الويب، بحيث يمكنك التركيز على كتابة تطبيقك دون الحاجة إلى إعادة اختراع العجلة، وهو إطار عمل مجاني ومفتوح المصدر وله مجتمع مزدهر ونشط وتوثيق رائع والعديد من خيارات الدعم المجاني والمدفوع. يساعدك جانغو في كتابة برمجيات تتمتع بالمواصفات التالية: مكتملة: يتبع جانغو فلسفة أنه يتضمن كل شيء "Batteries Included" ويوفر تقريبًا كل ما يرغب المطوّرون في تطبيقه. بما أن كل ما تحتاجه هو جزء من منتج واحد، فإن كل شيء يعمل مع بعضه بعضًا بسلاسة ويتبع مبادئ تصميم متناسقة ويحتوي على توثيق شامل ومُحدَّث. متعددة الاستعمالات: يمكن استخدام جانغو -وقد اُستخدِم فعليًا- لبناء أيّ نوع من مواقع الويب تقريبًا بدءًا من أنظمة إدارة المحتوى ومواقع الويكي مرورًا بشبكات التواصل الاجتماعي والمواقع الإخبارية. يمكن لجانغو العمل مع أيّ إطار عمل من طرف العميل، ويمكنه تقديم المحتوى بأي تنسيق تقريبًا، مثل HTML، و RSS Feeds، و JSON، و XML. يوفّر جانغو داخليًا خيارات لأي وظيفة تقريبًا ترغب فيها مثل دعم العديد من أنواع قواعد البيانات الشائعة ومحركات القوالب وغيرها، ولكن يمكن توسيعه لاستخدام مكونات أخرى إذا لزم الأمر. آمنة: يساعد جانغو المطورين على تجنب العديد من الأخطاء الأمنية الشائعة من خلال توفير إطار عمل مُصمَّم لتطبيق الأشياء الصحيحة بهدف حماية الموقع آليًا، فمثلًا يوفر جانغو طريقةً آمنةً لإدارة حسابات المستخدمين وكلمات المرور وتجنب الأخطاء الشائعة مثل وضع معلومات الجلسة في ملفات تعريف الارتباط Cookies، حيث تكون معرضةً للخطر، وذلك من خلال جعل ملفات تعريف الارتباط cookies تحتوي على مفتاح فقط وتخزين البيانات الفعلية في قاعدة البيانات، أو تخزين كلمات المرور مباشرةً بدلًا من تعمية Hash كلمة المرور التي هي قيمة ذات طول ثابت تُنشَأ من خلال إرسال كلمة المرور عبر دالة تعمية مُشفَّرة Cryptographic Hash Function. يمكن لجانغو التحقق مما إذا كانت كلمة المرور المدخَلة صحيحة من خلال تشغيلها عبر دالة التعمية وموازنة الخرج مع قيمة التعمية المخزنة، ولكن حتى إذا اُخترِقت قيمة التعمية المخزنة، فمن الصعب على المهاجم معرفة كلمة المرور الأصلية نظرًا لطبيعة الدالة أحادية الاتجاه. يتيح جانغو الحماية ضد العديد من الثغرات افتراضيًا بما في ذلك حقن استعلامات SQL وهجمات السكربتات العابرة للمواقع وتزوير الطلبات عبر المواقع والاختطاف بالنقر clickjacking (اطلع على مقال أمان مواقع الويب لمزيد من التفاصيل حول هذه الهجمات). قابلة للتوسّع: يستخدم جانغو معمارية "لا شيء مشترك shared-nothing" القائمة على المكونات، إذ يكون كل جزء من المعمارية مستقلًا عن الأجزاء الأخرى، وبالتالي يمكن استبداله أو تغييره حسب الحاجة. يعني وجود فصل واضح بين الأجزاء المختلفة أنه يمكن توسيع حركة الزوار المتزايدة من خلال إضافة عتاد على أي مستوى من خوادم التخزين المؤقت، أو خوادم قواعد البيانات، أو خوادم التطبيقات. وقد نجحت بعض المواقع الأكثر ازدحامًا في توسيع جانغو لتلبية مطالبهم مثل إنستغرام وديسكاس Disqus. قابلة للصيانة: كُتِبت شيفرة جانغو البرمجية باستخدام مبادئ وأنماط التصميم التي تشجع على إنشاء شيفرة برمجية قابلة للصيانة وإعادة الاستخدام؛ إذ يستخدم جانغو مبدأ لا تكرر نفسك Don't Repeat Yourself -أو DRY اختصارًا، لذلك لا يوجد تكرار غير ضروري، مما يقلل من كمية الشيفرة البرمجية. كما يدعم جانغو تجميع الوظائف المرتبطة ببعضها ضمن تطبيقات قابلة لإعادة الاستخدام، وتجميع الشيفرة البرمجية المرتبطة ببعضها ضمن وحدات على مستوى أدنى مثل نمط نموذج-عرض-مُتحكِّم Model View Controller -أو MVC اختصارًا. قابلة للنقل: كُتِب إطار عمل جانغو باستخدام لغة بايثون التي تعمل على العديد من المنصات، وهذا يعني أنك لست مرتبطًا بأي منصة خوادم معينة، ويمكنك تشغيل تطبيقاتك على العديد من إصدارات لينكس وويندوز وماك أو إس. كما يدعم العديد من مزودي استضافة الويب إطار عمل جانغو بصورة جيدة، ويوفّر مزودو استضافة الويب في أغلب الأحيان بنيةً أساسيةً محدّدة وتوثيقًا لاستضافة مواقع جانغو. تاريخ إطار عمل جانغو طوّر فريقُ ويب كان مسؤولًا عن إنشاء مواقع الصحف وصيانتها إطارَ عمل جانغو في البداية بين عامي 2003 و2005، ثم بدأ الفريق -بعد إنشاء عدد من المواقع- في تحليل وإعادة استخدام الكثير من أنماط التصميم والشيفرة البرمجية المشتركة. تطورت هذه الشيفرة البرمجية المشتركة إلى إطار عمل عام لتطوير الويب، والذي أصبح مشروع جانغو مفتوح المصدر في الشهر 7 من عام 2005. استمر إطار عمل جانغو في النمو والتحسن من الإصدار الأول 1.0 في الشهر التاسع من عام 2008 وحتى الإصدار 4.0 الصادر مؤخرًا في عام 2022. أضاف كل إصدار وظائفًا جديدة وإصلاحات للأخطاء بدءًا من دعم الأنواع الجديدة من قواعد البيانات ومحركات القوالب والتخزين المؤقت وإضافة دوال وأصناف العرض المعمَّمة التي تقلل من مقدار الشيفرة البرمجية التي يجب أن يكتبها المطورون لعددٍ من المهام البرمجية. ملاحظة: تحقَّق من ملاحظات الإصدارات على موقع جانغو لترى ما الذي تغير في الإصدارات الأخيرة ومقدار العمل المبذول في تحسين جانغو. يُعَد جانغو الآن مشروعًا تعاونيًا مزدهرًا ومفتوح المصدر، ولديه عدة آلاف من المستخدمين والمساهمين، وقد تطوّر إلى إطار عمل متعدد الاستخدامات قادر على تطوير أيّ نوع من مواقع الويب، بالرغم من أنه لا يزال يحتوي على بعض الميزات التي تعكس أصله. ما مدى شعبية جانغو؟ لا يوجد أي مقياس نهائي ومتوفر حاليًا لشعبية أطر العمل من طرف الخادم، بالرغم من أنه يمكنك تقدير الشعبية باستخدام آليات، مثل حساب عدد مشاريع غيت هَب GitHub وأسئلة موقع StackOverflow لكل منصة، لذا يُفضَّل أن تسأل ما إذا كان جانغو يتمتع بشعبية كافية لتجنب مشاكل المنصات التي لا تحظى بشعبية، وما إذا كان مستمرًا في التطور ويمكنك الحصول على المساعدة إذا احتجت إليها، وإذا كان هناك فرصة لك للحصول على عمل مدفوع الأجر إذا تعلمت جانغو. يمكن القول أن جانغو إطار عمل شائع الاستخدام استنادًا إلى عدد المواقع البارزة التي تستخدمه وعدد الأشخاص المساهمين في قاعدة الشيفرة البرمجية وعدد الأشخاص الذين يقدمون الدعم المجاني والمدفوع. تشمل المواقع البارزة التي تستخدم جانغو: ديسكاس Disqus وإنستغرام و Knight Foundation و MacArthur Foundation وموزيلا Mozilla وناشيونال جيوغرافيك National Geographic و Open Knowledge Foundation وينترست Pinterest و Open Stack (المصدر: موقع جانغو الرسمي). هل جانغو إطار عمل متشبث برأيه؟ تشير أطر عمل الويب إلى نفسها في أغلب الأحيان على أنها "قائمة على رأيها أو متشبثة برأيها Opinionated" أو أنها "غير قائمة على رأيها Unopinionated"، فأطر العمل القائمة على رأيها هي أطر العمل التي لديها آراء حول الطريقة الصحيحة للتعامل مع أيّ مهمة معينة، وتدعم التطور السريع أو حل المشاكل في مجال معين، إذ تكون الطريقة الصحيحة لفعل أي شيء عادةً مفهومة ومُوثَّقة جيدًا، لكن يمكن أن تكون أقل مرونة في حل المشاكل خارج مجالها الرئيسي، وتميل إلى تقديم خيارات أقل للمكونات والأساليب التي يمكن استخدامها. يكون لأطر العمل غير القائمة على رأيها قيود أقل بكثير على أفضل طريقة لربط المكونات مع بعضها بعضًا لتحقيق هدفٍ ما أو حتى لتحديد المكونات التي يجب استخدامها، وتسهّل على المطورين استخدام أنسب الأدوات لإكمال مهمة معينة وإن كان ذلك على حساب الجهد الذي تحتاجه للعثور على تلك المكونات بنفسك. يُعَد إطار عمل جانغو قائمًا على رأيه إلى حدٍ ما، وبالتالي يقدم أفضل ما في هذين النوعين، إذ يوفر مجموعةً من المكونات للتعامل مع معظم مهام تطوير الويب وطريقة أو طريقتين مفضلتين لاستخدامهما، ولكن تعني معمارية جانغو المنفصلة أنه يمكنك الانتقاء والاختيار من بين عدد من الخيارات المختلفة، أو إضافة دعم لخيارات جديدة تمامًا إذا رغبت في ذلك. كيف تبدو شيفرة جانغو البرمجية؟ ينتظر تطبيق الويب طلبات HTTP من متصفح الويب (أو عميل آخر) في موقع ويب تقليدي مُوجَّه بالبيانات، إذ يعمل التطبيق عند تلقي طلب على تحديد ما هو مطلوب بناءً على عنوان URL وربما على المعلومات الموجودة في بيانات طلب POST أو بيانات طلب GET، ثم يمكنه بعد ذلك اعتمادًا على ما هو مطلوب قراءة المعلومات، أو كتابتها من قاعدة بيانات، أو أداء مهام أخرى مطلوبة لتلبية الطلب، ثم سيعيد التطبيق استجابةً إلى متصفح الويب، وينشئ غالبًا صفحة HTML ديناميكيًا ليعرضها المتصفح من خلال إدخال البيانات المُسترجَعة ضمن عناصر بديلة في قالب HTML. تجمّع تطبيقاتُ ويب جانغو الشيفرةَ البرمجية التي تعالج كل خطوة من الخطوات السابقة ضمن ملفات منفصلة كما يلي: عناوين URL: يمكن معالجة الطلبات من كل عنوان URL باستخدام دالة واحدة، ولكن تُعَد كتابة دالة عرض منفصلة للتعامل مع كل مورد أمرًا قابلًا للصيانة بصورة أفضل، إذ يُستخدَم رابط Mapper عنوان URL لإعادة توجيه طلبات HTTP إلى العرض المناسب بناءً على عنوان URL الخاص بالطلب، ويمكن لرابط عنوان URL مطابقة أنماط معينة من السلاسل النصية أو الأرقام التي تظهر في عنوان URL وتمريرها إلى دالة عرض بوصفها بيانات. العرض View: العرض هو دالة معالج الطلب والتي تتلقى طلبات HTTP وتعيد استجابات HTTP. تصل العروض إلى البيانات اللازمة لتلبية الطلبات باستخدام النماذج وتكلّف القوالب Templates بتنسيق الاستجابة. النموذج Model: النماذج هي كائنات بايثون تعرّف بنية بيانات التطبيق وتوفّر آليات لإدارة (إضافة وتعديل وحذف) السجلات والاستعلام عنها في قاعدة البيانات. القالب Template: القالب هو ملف نصي يعرّف بنية أو تخطيط ملف، مثل صفحة HTML، مع استخدام العناصر البديلة لتمثيل المحتوى الفعلي. يمكن للعرض أن ينشئ صفحة HTML ديناميكيًا باستخدام قالب HTML وتعبئتها ببيانات من نموذج، ويمكن استخدام القالب لتعريف بنية أيّ نوع من الملفات، فليس بالضرورة أن يكون ملف HTML. ملاحظة: يشير جانغو إلى هذا التنظيم بأنه معمارية نموذج-عرض-قالب Model View Template -أو MVT اختصارًا، إذ تمتلك هذه المعمارية العديد من أوجه التشابه مع معمارية نموذج-عرض-مُتحكِّم MVC الأكثر شيوعًا. ستمنحك الأقسام الآتية فكرةً عمّا تبدو عليه هذه الأجزاء الرئيسية من تطبيق جانغو، وسنعرض مزيدًا من التفاصيل لاحقًا بعد إعداد بيئة التطوير. إرسال الطلب إلى العرض الصحيح: الملف urls.py يُخزَّن رابط عنوان URL في ملف يسمى urls.py، إذ يعرّف الرابط urlpatterns في المثال الآتي قائمةً من الروابط بين الوجهات Routes (وهي أنماط Patterns عنوان URL مُحدَّدة) ودوال العرض المقابلة لها. إذا اُستلِم طلب HTTP يحتوي على عنوان URL يطابق نمطًا محددًا، فستُستدعَى دالة العرض المرتبطة به ويُمرَّر الطلب. urlpatterns = [ path('admin/', admin.site.urls), path('book/<int:id>/', views.book_detail, name='book_detail'), path('catalog/', include('catalog.urls')), re_path(r'^([0-9]+)/$', views.best), ] كائن urlpatterns هو قائمة من دوال path() و/ أو re_path(). تُعرَّف قوائم بايثون باستخدام أقواس مربعة، إذ تُفصَل العناصر بفواصل ويمكن أن تحتوي على فاصلة لاحقة اختيارية مثل [item1, item2, item3,]. الوسيط الأول لكلا التابعين هو الوجهة أو النمط الذي ستجري مطابقته. يستخدم التابع path() أقواس زاوية لتحديد أجزاء من عنوان URL التي ستُلتقَط وتُمرَّر إلى دالة العرض بوصفها وسائطًا مُسمَّاة، بينما تستخدم الدالة re_path() أسلوبًا مرنًا لمطابقة الأنماط يُعرَف بالتعبير النمطي Regular Expression الذي سنتحدث عنه لاحقًا. الوسيط الثاني هو دالة أخرى تُستدعَى عند مطابقة النمط، إذ تشير الصيغة views.book_detail إلى أن الدالة تسمى book_detail() ويمكن العثور عليها في الوحدة views، أي ضمن الملف views.py. معالجة الطلب: الملف views.py تُعَد العروض قلب تطبيق الويب، فهي تتلقى طلبات HTTP من عملاء الويب وتعيد استجابات HTTP، وتنظّم الموارد الأخرى لإطار العمل للوصول إلى قواعد البيانات وعرض القوالب وغير ذلك. وتُخزَّن العروض عادةً في ملف يسمى views.py. يوضّح المثال الآتي أصغر دالة عرض index() التي كان من الممكن أن يستدعيها رابط عنوان URL في القسم السابق؛ إذ تتلقى هذه الدالة مثل جميع دوال العرض كائن HttpRequest بوصفه معاملًا request وتعيد كائن HttpResponse، إذ لا نفعل أيّ شيء مع الطلب في هذه الحالة، وتعيد استجابتنا سلسلةً نصيةً ثابتة Hard-coded، وسنعرض لاحقًا طلبًا يفعل شيئًا آخر أكثر إثارة للاهتمام. # اسم الملف: views.py (دوال عرض جانغو) from django.http import HttpResponse def index(request): # الحصول على HttpRequest - معامل الطلب # إجراء العمليات باستخدام المعلومات الواردة في الطلب. # إعادة HttpResponse return HttpResponse('Hello from Django!') ملاحظة: إليك بعض المعلومات عن لغة بايثون: وحدات بايثون هي مكتبات من الدوال مُخزَّنة في ملفات منفصلة، والتي يمكن أن نرغب في استخدامها في شيفرتنا البرمجية. نستورد في مثالنا كائن HttpResponse من الوحدة django.http حتى نتمكّن من استخدامه في عرضنا from django.http import HttpResponse، وهناك طرق أخرى لاستيراد بعض الكائنات أو جميعها من وحدةٍ ما. يُصرَّح عن الدوال باستخدام الكلمة المفتاحية def كما هو موضح سابقًا، مع وجود معاملات مُسمَّاة بين قوسين بعد اسم الدالة وينتهي السطر بأكمله بنقطتين. لاحظ وضع مسافة بادئة لجميع الأسطر التالية، إذ تُعَد المسافة البادئة مهمة، لأنها تحدد أن سطور الشيفرة البرمجية موجودة ضمن تلك الكتلة المحددة. تُعَد المسافة البادئة الإلزامية ميزةً رئيسية في لغة بايثون، وهي أحد أسباب سهولة قراءة شيفرة بايثون البرمجية. تعريف نماذج البيانات: الملف models.py تدير تطبيقات ويب جانغو البيانات وتستعلم عنها من خلال كائنات بايثون التي يُشَار إليها باسم النماذج، التي تحدّد بنية البيانات المُخزَّنة بما في ذلك أنواع الحقول وحجمها الأقصى وقيمها الافتراضية وخيارات قائمة الاختيار ونص تعليمات التوثيق ونص التسمية في الاستمارات وغير ذلك. يُعَد تعريف النموذج مستقلًا عن قاعدة البيانات الأساسية، إذ يمكنك اختيار قاعدة بيانات من عدة قواعد بيانات بوصفها جزءًا من إعدادات مشروعك. لن تحتاج إلى التواصل مع قاعدة البيانات التي تريد استخدامها مباشرةً بمجرد اختيارها، فما عليك سوى كتابة بنية النموذج والشيفرة البرمجية الأخرى، وسيتولى جانغو جميع الأعمال الأخرى للتواصل مع قاعدة البيانات. يُظهِر مقتطف الشيفرة البرمجية الآتي نموذج جانغو بسيط جدًا للكائن Team، فالصنف Team مُشتَق من صنف جانغو models.Model، ويعرّف اسم الفريق ومستوى الفريق بوصفها حقولًا محرفية ويحدد الحد الأقصى لعدد المحارف التي ستُخزَّن لكل سجل. يمكن أن يكون مستوى الفريق team_level قيمةً من عدة قيم، لذلك نحدّده بوصفه حقلًا اختياريًا ونوفّر ربطًا بين الخيارات المراد عرضها والبيانات المراد تخزينها إلى جانب القيمة الافتراضية. # اسم الملف: models.py from django.db import models class Team(models.Model): team_name = models.CharField(max_length=40) TEAM_LEVELS = ( ('U09', 'Under 09s'), ('U10', 'Under 10s'), ('U11', 'Under 11s'), # … # قائمة مستويات الفريق الأخرى ) team_level = models.CharField(max_length=3, choices=TEAM_LEVELS, default='U11') ملاحظة: إليك بعض المعلومات عن لغة بايثون: تدعم لغة بايثون البرمجة كائنية التوجه التي تُعَد نمطًا من البرمجة، إذ ننظم شيفرتنا البرمجية ضمن كائنات، والتي تتضمن بيانات ودوال متعلقة بها للعمل على تلك البيانات. يمكن للكائنات أن ترث، أو توسّع، أو تُشتَق من كائنات أخرى، مما يسمح بمشاركة السلوك المشترك بين الكائنات المتعلقة ببعضها. نستخدم في لغة بايثون الكلمة المفتاحية class لتعريف المخطط الأولي للكائن. يمكننا إنشاء نسخ متعددة محددة لنوع الكائن بناءً على النموذج في الصنف؛ فلدينا في مثالنا الصنف Team مُشتَق من الصنف Model، وهذا يعني أنه نموذج وسيحتوي على جميع توابعه، ولكن يمكننا منحه ميزات خاصة به أيضًا. نحدد في نموذجنا الحقول التي ستحتاجها قاعدة البيانات لتخزين بياناتنا من خلال منحها أسماءً محددة. يستخدم جانغو هذه التعريفات -بما في ذلك أسماء الحقول- لإنشاء قاعدة البيانات الأساسية. الاستعلام عن البيانات: الملف views.py يوفر نموذج جانغو واجهة برمجة تطبيقات استعلام بسيطة للبحث في قاعدة البيانات المرتبطة به، ويمكن أن تتطابق هذه الواجهة مع عدد من الحقول في وقت واحد باستخدام معايير مختلفة، مثل التطابق التام exact وغير الحساس لحالة الأحرف case-insensitive وأكبر من وغير ذلك، ويمكن أن يدعم العبارات المعقدة، فمثلًا يمكنك تحديد بحث عن فرق U11 التي يبدأ اسم فريقها بالحرفين "Fr" أو ينتهي بالحرفين "al". يُظهِر مقتطف الشيفرة البرمجية التالية دالة العرض (معالج المورد) لعرض جميع فرق U09، إذ يوضح السطر التالي: list_teams = Team.objects.filter(team_level__exact="U09") كيف يمكننا استخدام واجهة API للاستعلام عن النموذج لترشيح جميع السجلات، إذ يحتوي الحقل team_level على النص "U09" حرفيًا (لاحظ كيف تُمرَّر هذه المعايير إلى الدالة filter() بوصفها وسيطًا مع فصل اسم الحقل ونوع المطابقة بشرطة سفلية مزدوجة: team_level__exact). ## اسم الملف: views.py from django.shortcuts import render from .models import Team def index(request): list_teams = Team.objects.filter(team_level__exact="U09") context = {'youngest_teams': list_teams} return render(request, '/best/index.html', context) تستخدم الدالةُ السابقة الدالةَ render() لإنشاء الكائن HttpResponse الذي يُرسَل إلى المتصفح، إذ تُعَد هذه الدالة اختصارًا Shortcut ينشئ ملف HTML من خلال الجمع بين قالب HTML محدَّد وبعض البيانات لإدخالها في القالب (تتوفر هذه البيانات في المتغير المُسمَّى "context"). سنوضّح في القسم التالي كيفية إدخال البيانات في القالب لإنشاء ملف HTML. عرض البيانات: قوالب HTML تسمح لك أنظمة القوالب بتحديد بنية مستند الخرج باستخدام عناصر بديلة للبيانات التي ستُملَأ عند إنشاء الصفحة، إذ تُستخدَم القوالب غالبًا لإنشاء مستندات HTML، ولكن يمكنها إنشاء أنواع أخرى من المستندات أيضًا. يدعم جانغو كلًا من نظام القوالب الأصيل Native ومكتبة بايثون الشهيرة الأخرى التي تسمى Jinja2، ويمكن جعله يدعم أنظمة أخرى إن لزم الأمر. يوضح مقتطف الشيفرة البرمجية التالية الشكل الذي يمكن أن يبدو عليه قالب HTML الذي تستدعيه الدالة render() في القسم السابق. كُتِب هذا القالب على أساس الافتراض بأنه يمكنه الوصول إلى متغير قائمة يُسمَّى youngest_teams عند عرضه (يوجد هذا المتغير ضمن المتغير context في الدالة render()). يوجد ضمن بنية HTML تعبيرٌ يتحقق أولًا من وجود المتغير youngest_teams، ثم يكرره في حلقة for، إذ يعرض القالب في كل تكرار قيمة team_name لكل فريق في عنصر <li>. ## اسم الملف: best/templates/best/index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Home page</title> </head> <body> {% if youngest_teams %} <ul> {% for team in youngest_teams %} <li>{{ team.team_name }}</li> {% endfor %} </ul> {% else %} <p>No teams are available.</p> {% endif %} </body> </html> ميزات جانغو الأخرى أظهرت الأقسام السابقة الميزات الرئيسية التي ستستخدمها في كل تطبيق ويب تقريبًا وهي: ربط عناوين URL والعروض والنماذج والقوالب. تشمل بعض الأشياء الأخرى التي يقدمها إطار عمل جانغو الميزاتِ التالية: الاستمارات Forms: تُستخدَم استمارات HTML لتجميع بيانات المستخدم لمعالجتها على الخادم. يبسّط جانغو إنشاء الاستمارات والتحقق من صحتها ومعالجتها. استيثاق Authentication المستخدم وأذوناته: يتضمن جانغو نظامًا قويًا لاستيثاق المستخدم وأذوناته، إذ أُنشئ هذا النظام مع وضع الأمان في الحسبان. التخزين المؤقت Caching: يتطلب إنشاء المحتوى ديناميكيًا عمليات حسابية أكثر وأبطأ من تقديم محتوى ساكن، لذا يوفر جانغو تخزينًا مؤقتًا مرنًا بحيث يمكنك تخزين الصفحة المعروضة بالكامل أو جزء منها حتى لا يُعاد عرضها إلّا عند الضرورة. موقع المدير Administration site: يُضمَّن موقع مدير جانغو افتراضيًا عند إنشاء تطبيق باستخدام الهيكل الأساسي، مما يسهّل توفير صفحة مدير لمديري الموقع لإنشاء أي نماذج بيانات في موقعك وتعديلها وعرضها. سَلسَلة البيانات Serializing data: يسهّل جانغو من سَلسَلة بياناتك وتقديمها بتنسيق XML أو JSON، ويمكن أن يكون ذلك مفيدًا عند إنشاء خدمة ويب (موقع ويب يقدّم البيانات فقط لتستهلكها تطبيقات أو مواقع أخرى ولا يعرض أيّ شيء بنفسه)، أو عند إنشاء موقع ويب تعالج فيه الشيفرة البرمجية من طرف العميل كل عملية عرض أو تصيير البيانات. الخلاصة أكملنا الخطوة الأولى في رحلة تعلم جانغو، ويجب أن تفهم الآن الفوائد الرئيسية لتطبيق جانغو وتاريخه والشكل الذي يمكن أن يبدو عليه كل جزء من الأجزاء الرئيسية لتطبيق جانغو، ويجب أن تكون قد تعلمت بعض الأشياء حول لغة برمجة بايثون بما في ذلك صيغة القوائم والدوال والأصناف. لقد رأيتَ بعض شيفرات جانغو الحقيقية، ولكن تحتاج -بخلاف الشيفرة البرمجية من طرف العميل- إلى إعداد بيئة تطوير لتشغيلها، وهذه خطوتنا التالية. ترجمة -وبتصرُّف- للمقالين Django Web Framework (Python) و Django introduction. اقرأ أيضًا المقال التالي: إعداد بيئة تطوير تطبيقات جانغو المقال السابق: تعرف على أمان مواقع الويب أطر عمل الويب من طرف الخادم المرجع الشامل إلى تعلم لغة بايثون برمجة عملاء ويب باستخدام بايثون
-
يتطلب أمان مواقع الويب اليقظة في جميع جوانب تصميم الموقع واستخدامه. لن يجعلك هذا المقال التمهيدي خبيرًا في أمان مواقع الويب، ولكنه سيساعدك على فهم مصدر الهجمات وما يمكنك فعله لتقوية تطبيق الويب ضد الهجمات الأكثر شيوعًا. المتطلبات الأساسية: المهارات الحاسوبية الأساسية. الهدف: فهم الهجمات الأكثر شيوعًا التي تهدّد أمان تطبيقات الويب وما يمكنك فعله لتقليل مخاطر اختراق موقعك. ما هو أمان موقع الويب؟ تُعَد شبكة الإنترنت مكانًا خطيرًا، إذ نسمع بانتظام عن عدم توفّر مواقع الويب بسبب هجمات حجب الخدمة أو عرض معلومات مُعدَّلة وضارة غالبًا على صفحاتها الرئيسية، وكانت قد سُرِّبت الملايين من كلمات المرور وعناوين البريد الإلكتروني وتفاصيل بطاقة الائتمان إلى النطاق العام في حالات أخرى عالية المستوى، مما عرّض مستخدمي مواقع الويب للإحراج الشخصي والمخاطر المالية، فالغرض من أمان موقع الويب هو منع هذه الهجمات أو أيّ نوع آخر منها. التعريف الرسمي لأمان موقع الويب هو فعل أو ممارسة تهدف إلى حماية مواقع الويب من الوصول، أو الاستخدام، أو التعديل، أو التدمير، أو التعطيل غير المُصرَّح به. يتطلب أمان موقع الويب الفعّال جهدًا في التصميم على كامل موقع الويب بما في ذلك تطبيق الويب وإعداد خادم الويب وسياساتك لإنشاء كلمات المرور وتجديدها والشيفرة البرمجية من طرف العميل. يمكن أن يبدو ذلك صعبًا، إلا أن الخبر السار هو أنه إذا استخدمتَ إطار عمل ويب من طرف الخادم، فسيفعِّل افتراضيًا آليات دفاع قوية ومدروسة جيدًا ضد عدد من الهجمات الأكثر شيوعًا، ويمكن تخفيف الهجمات الأخرى عبر إعداد خادم الويب من خلال تفعيل بروتوكول HTTPS مثلًا. أخيرًا، هناك أدوات فحص للثغرات الأمنية عامة يمكنها مساعدتك في معرفة ما إذا كنت مرتكبًا لأي أخطاء واضحة. دورة علوم الحاسوب دورة تدريبية متكاملة تضعك على بوابة الاحتراف في تعلم أساسيات البرمجة وعلوم الحاسوب اشترك الآن سيمنحك هذا المقال مزيدًا من التفاصيل حول بعض الهجمات الشائعة وبعض الخطوات البسيطة الممكن اتخاذها لحماية موقعك. ملاحظة: يُعَد هذا المقال موضوعًا تمهيديًا، وهو مُصمَّم لمساعدتك على البدء في التفكير في أمان موقع الويب، ولكنه ليس شاملًا. هجمات أمان مواقع الويب سنوضّح عددًا من هجمات مواقع الويب الأكثر شيوعًا وكيفية التخفيف منها. لاحظ كيف تكون الهجمات أنجح عندما يثق تطبيق الويب بالبيانات القادمة من المتصفح، أو عندما لا يكون متشككًا بها بالقدر الكافي. هجمات السكربتات العابرة للمواقع Cross-Site Scripting أو XSS هجمات السكربتات العابرة للمواقع هو مصطلح يُستخدَم لوصف فئة من الهجمات التي تسمح للمهاجمين بحقن سكربتات من طرف العميل من خلال موقع الويب في متصفحات المستخدمين الآخرين. تأتي الشيفرة البرمجية المحقونة إلى المتصفح من الموقع، لذلك تُعَد هذه الشيفرة البرمجية موثوقة ويمكنها تطبيق أمور، مثل إرسال ملف تعريف ارتباط تصريح الموقع الخاص بالمستخدم إلى المهاجم، وبالتالي يمكنه تسجيل الدخول إلى الموقع كما لو أنه المستخدم ويفعل أي شيء يمكن للمستخدم فعله مثل الوصول إلى تفاصيل بطاقته الائتمانية، أو الاطلاع على تفاصيل جهات الاتصال، أو تغيير كلمات المرور. ملاحظة: كانت ثغرات XSS سابقًا أكثر شيوعًا من أي نوع آخر من الهجمات الأمنية. تنقسم ثغرات XSS إلى ثغرات منعكسة Reflected وأخرى دائمة Persistent بناءً على كيفية إعادة الموقع للسكربتات البرمجية المحقونة في المتصفح كما يلي: تحدث ثغرة XSS المنعكسة عند إعادة محتوى المستخدم المُمرَّر إلى الخادم مباشرةً مع عدم تعديله لعرضه في المتصفح، إذ ستُشغّل أيّ سكربتات برمجية في محتوى المستخدم الأصلي عند تحميل الصفحة الجديدة. ضع في بالك مثلًا وظيفة البحث في الموقع إذ تُرمَّز مصطلحات البحث بوصفها معاملات في عنوان URL، وتُعرَض هذه المصطلحات مع النتائج. يمكن للمهاجم إنشاء رابط بحث يحتوي على سكربت ضار بوصفه معاملًا مثل الرابط الآتي ويرسله بالبريد الإلكتروني إلى مستخدم آخر. إذا نقر المستخدم المستهدف على هذا "الرابط المهم"، فسيُنفَّذ السكربت عند عرض نتائج البحث، مما يمنح المهاجم جميع المعلومات التي يحتاجها للدخول إلى الموقع بصفته المستخدم المستهدف، ويُحتمَل أن ينفّذ عمليات شراء بصفته المستخدم، أو يشارك معلومات جهات اتصاله: http://developer.mozilla.org?q=beer<script%20src="http://example.com/tricky.js"></script> تحدث ثغرة XSS الدائمة عند تخزين السكربت الضار على موقع الويب ثم يعاد عرضه لاحقًا دون تعديل لينفّذه المستخدمون الآخرون دون علمهم. يمكن أن تخزّن مثلًا لوحة المناقشة التي تقبل تعليقات تحتوي على شيفرة HTML غير معدلة سكربتًا ضارًا من المهاجم، ويُنفَّذ السكربت عند عرض التعليقات ويمكن أن يرسل إلى المهاجم المعلومات المطلوبة للوصول إلى حساب المستخدم. يُعَد هذا النوع من الهجمات شائعًا وقويًا جدًا، لأن المهاجم يمكن ألّا يكون له أي ارتباطٍ مباشر مع الضحايا. تُعَد البيانات الواردة من طلبات من النوع "POST" أو "GET" المصدر الأكثر شيوعًا لثغرات XSS، ولكن يُحتمَل أن تكون أيّ بيانات من المتصفح عرضةً للخطر، مثل بيانات ملفات تعريف الارتباط التي يعرضها المتصفح، أو ملفات المستخدم المُحمَّلة والمعروضة. أفضل دفاع ضد ثغرات XSS هو إزالة أو تعطيل أي شيفرة HTML يمكن أن تحتوي على تعليمات لتشغيل الشيفرة البرمجية، ويتضمن ذلك في لغة HTML عناصرًا، مثل العناصر <script> و <object> و <embed> و <link>. تُعرَف عملية تعديل بيانات المستخدم بحيث لا يمكن استخدامها لتشغيل السكربتات أو التأثير بطريقة أخرى على تنفيذ شيفرة الخادم البرمجية باسم تعقيم الإدخال Input Sanitization، إذ تطبّق العديد من أطر عمل الويب تلقائيًا عملية تعقيم مدخلات المستخدم من نماذج HTML افتراضيًا. حقن استعلامات SQL تمكّن الثغرات الأمنية الخاصة بحقن استعلامات SQL المستخدمين الضارين من تنفيذ شيفرة SQL عشوائية على قاعدة بيانات، مما يسمح بالوصول إلى البيانات أو تعديلها أو حذفها بغض النظر عن أذونات المستخدم. يمكن أن يؤدي هجوم الحقن الناجح إلى انتحال الهويات، أو إنشاء هويات جديدة لديها حقوق إدارة، أو الوصول إلى جميع البيانات الموجودة على الخادم، أو تخريب، أو تعديل البيانات لجعلها غير قابلة للاستخدام. تتضمن أنواع حقن استعلامات SQL حقن استعلامات SQL المستندة إلى الأخطاء وحقن استعلامات SQL المستندة إلى الأخطاء المنطقية وحقن استعلامات SQL المستندة إلى الوقت. تظهر هذه الثغرة الأمنية إذا كان من الممكن أن يغيّر مُدخل المستخدم المُمرَّر إلى تعليمة SQL الأساسية معنى التعليمة، فالغرض من الشيفرة التالية مثلًا هو سرد كافة المستخدمين الذين يملكون اسمًا معينًا userName يوفّره نموذج HTML: statement = "SELECT * FROM users WHERE name = '" + userName + "';" إذا حدّد المستخدم اسمًا حقيقيًا، فستعمل التعليمة السابقة كما هو متوقع، ولكن يمكن لمستخدم ضار تغيير سلوك تعليمة SQL هذه بالكامل إلى التعليمة الجديدة في المثال الآتي من خلال تحديد التعليمة a';DROP TABLE users; SELECT * FROM userinfo WHERE 't' = 't للحقل userName. SELECT * FROM users WHERE name = 'a';DROP TABLE users; SELECT * FROM userinfo WHERE 't' = 't'; تنشِئ التعليمة المُعدَّلة تعليمة SQL صالحة تحذف جدول المستخدمين users وتحدّد جميع البيانات من جدول معلومات المستخدم userinfo الذي يكشف عن معلومات كل مستخدم. تعمل هذه التعليمة لأن الجزء الأول من النص المحقون (a';) يكمل التعليمة الأصلية. يمكنك تجنب هذا النوع من الهجوم من خلال التأكد من أن بيانات المستخدم المُمرَّرة إلى استعلام SQL لا يمكنها تغيير طبيعته، ويمكن تحقيق ذلك باستخدام محرف الهروب Escape Character للهروب من جميع المحارف في مدخلات المستخدم التي لها معنًى خاص في لغة SQL. ملاحظة: تعالج تعليمة SQL المحرف (') بوصفه بداية ونهاية سلسلة محرفية، وإذا وضعت شرطة مائلة عكسية أمام هذا المحرف (')، فسنهرّب الرمز ونطلب من لغة SQL التعامل معه بوصفه محرفًا، أي مجرد جزء من السلسلة النصية. سنهرّب في التعليمة التالية المحرف (')، إذ ستفسّر لغة SQL الآن الاسم name على أنه سلسلة نصية كاملة بالخط العريض bold (حسنًا إنه اسم غريب جدًا، ولكنه ليس ضارًا): SELECT * FROM users WHERE name = 'a\';DROP TABLE users; SELECT * FROM userinfo WHERE \'t\' = \'t'; تهتم أطر عمل الويب بمحرف الهروب نيابةً عنك، إذ يضمن إطار عمل جانغو Django مثلًا هروب أيّ بيانات مستخدم مُمرَّرة إلى مجموعات الاستعلام (استعلامات النموذج). ملاحظة: يمكنك الاطلاع على مقال كيفية تأمين الخوادم السحابية ضد هجمات حقن SQL لمعلومات أكثر. تزوير الطلبات عبر المواقع Cross-Site Request Forgery أو CSRF تسمح هجمات CSRF لمستخدمٍ ضار بتنفيذ إجراءات باستخدام ثبوتيات مستخدم آخر دون عِلم هذا المستخدم أو موافقته. لنفترض مثلًا أن سامي مستخدم ضار يعرف أن موقعًا معينًا يسمح للمستخدمين الذين سجّلوا الدخول بإرسال أموال إلى حسابٍ محدد باستخدام طلب HTTP من النوع "POST"، بحيث يتضمن هذا الطلب اسم الحساب ومبلغًا من المال. ينشئ سامي نموذجًا يتضمن التفاصيل المصرفية الخاصة به ومبلغًا من المال بوصفها حقولًا مخفية، ويرسله بالبريد الإلكتروني إلى مستخدمي الموقع الآخرين مع زر الإرسال Submit المخفي بوصفه رابطًا إلى موقع "الثراء السريع get rich quick". إذا نقر المستخدم على زر الإرسال، فسيُرسَل طلب HTTP من النوع "POST" إلى الخادم الذي يحتوي على تفاصيل المعاملة وأيّ ملفات تعريف ارتباط من طرف العميل يرتبط من خلالها المتصفح بالموقع، إذ تُعَد إضافة ملفات تعريف الارتباط المرتبطة بالموقع إلى الطلبات سلوك المتصفح العادي. سيتحقق الخادم من ملفات تعريف الارتباط، ويستخدمها لتحديد ما إذا كان المستخدم قد سجّل الدخول ولديه إذن لإجراء المعاملة أم لا. النتيجة هي أن أيّ مستخدم ينقر على زر الإرسال أثناء تسجيل الدخول إلى موقع التعاملات المالية سيجري المعاملة، وسيصبح سامي ثريًا. ملاحظة: الحيلة هنا هي أن سامي لا يحتاج إلى الوصول إلى ملفات تعريف الارتباط الخاصة بالمستخدم أو الوصول إلى ثبوتياته، إذ يخزّن متصفح المستخدم هذه المعلومات ويضمّنها تلقائيًا في جميع الطلبات إلى الخادم المرتبط به. تتمثل إحدى طرق منع هذا النوع من الهجوم في أن يطلب الخادم أن تتضمن طلبات "POST" سرًا خاصًا بالمستخدم يولّده الموقع، إذ سيوفّر الخادم هذا السر عند إرسال نموذج الويب المُستخدَم لإجراء التحويلات. يمنع هذا الأسلوب سامي من إنشاء نموذجه الخاص، لأنه سيتعين عليه معرفة السر الذي يوفره الخادم للمستخدم، وحتى إذا اكتشف السر وأنشأ نموذجًا لمستخدم معين، فلن يكون قادرًا على استخدام النموذج نفسه لمهاجمة جميع المستخدمين. تتضمن أطر عمل الويب آليات منع هجمات CSRF في أغلب الأحيان. هجمات أخرى تشمل الهجمات أو الثغرات الشائعة الأخرى ما يلي: هجمات الاختطاف بالنقر Clickjacking: يختطف مستخدم ضار في هذا الهجوم نقراتٍ مُوجَّهة إلى موقع مرئي من المستوى الأعلى ويوجّهها إلى صفحة مخفية. يمكن استخدام هذه التقنية مثلًا لعرض موقع مصرفي شرعي ولكنه يلتقط ثبوتيات تسجيل الدخول ضمن عنصر <iframe> غير مرئي يتحكم فيه المهاجم. يمكن استخدام هجمات الاختطاف بالنقر لجعل المستخدم ينقر على زر في موقع مرئي، ولكنه بذلك ينقر على زر مختلف تمامًا من غير عِلمه. يمكن تجنب ذلك من خلال أن يمنع موقعك نفسه من أن يُدمَج في عنصر <iframe> في موقع آخر عبر ضبط ترويسات HTTP المناسبة. هجمات حجب الخدمة Denial of Service -أو اختصارًا DoS: تتحقق هجمات DoS من خلال إغراق الموقع المستهدف بطلبات مزيفة بحيث يتعطل الوصول إلى الموقع للمستخدمين الشرعيين. يمكن أن تكون هذه الطلبات كثيرةً، أو يستهلك كل طلب منها كميات كبيرة من الموارد، مثل عمليات القراءة البطيئة، أو تحميل ملفات كبيرة. تعمل دفاعات DoS من خلال تحديد وحظر حركة المرور السيئة مع السماح بمرور الرسائل المشروعة، وتوجد هذه الدفاعات عادةً قبل خادم الويب أو فيه، فهي ليست جزءًا من تطبيق الويب نفسه. هجوم اجتياز شجرة الملفات Directory Traversal (الملف ومعلوماته): يحاول مستخدم ضار في هذا الهجوم الوصول إلى أجزاء من نظام ملفات خادم الويب التي لا ينبغي أن يتمكن من الوصول إليها. تحدث هذه الثغرة عندما يكون المستخدم قادرًا على تمرير أسماء الملفات التي تتضمن محارف التنقل في نظام الملفات، مثل /../..، والحل هو تعقيم مدخلات المستخدم قبل استخدامها. تضمين الملفات File Inclusion: يكون المستخدم في هذا الهجوم قادرًا على تحديد ملف عشوائي لعرض أو تنفيذ البيانات المُمرَّرة إلى الخادم. يمكن عند التحميل تنفيذ هذا الملف على خادم الويب أو من طرف العميل، مما يؤدي إلى هجوم XSS، والحل هو تعقيم مدخلات المستخدم قبل استخدامها. حقن الأوامر Command Injection: تسمح هجمات حقن الأوامر للمستخدم الضار بتنفيذ أوامر نظام عشوائية على نظام التشغيل المضيف. الحل هو تعقيم مدخلات المستخدم قبل استخدامها في استدعاءات النظام. اطّلع أيضًا على الهجمات الأمنية Security Attacks في الشبكات الحاسوبية. بعض النصائح الأساسية تنجح تقريبًا جميع عمليات استغلال الأمان الموضَّحة سابقًا عندما يثق تطبيق الويب في البيانات الواردة من المتصفح، لذا يجب عليك تعقيم جميع البيانات التي ينشئها المستخدم قبل عرضها في المتصفح، أو استخدامها في استعلامات SQL، أو تمريرها إلى نظام التشغيل، أو استدعاء نظام الملفات مهما كان ما تفعله لتحسين أمان موقعك الويب. تحذير: الدرس الوحيد الأكثر أهمية الذي يمكنك تعلّمه حول أمان موقع الويب هو عدم الوثوق أبدًا في البيانات الواردة من المتصفح مثل البيانات الموجودة في معاملات عنوان URL لطلبات "GET" وطلبات "POST" وترويسات وملفات تعريف ارتباط HTTP والملفات التي يرفعها المستخدم. تحقق دائمًا من جميع البيانات الواردة وعقّمها، وافترض الأسوأ دائمًا. إليك عددًا من الخطوات الأخرى التي يمكنك اتخاذها: استخدام إدارة أكثر فاعلية لكلمات المرور: شجّع على استخدام كلمات المرور القوية، واستخدم الاستيثاق الثنائي two-factor authentication في موقعك، بحيث يجب على المستخدم إدخال رمز استيثاق آخر بالإضافة إلى كلمة المرور، إذ يمكن تسليم رمز الاستيثاق باستخدام العتاد الحقيقي الذي يمتلكه المستخدم فقط مثل رمز في رسالة SMS مرسَلة إلى هاتف المستخدم. إعداد خادمك الويب لاستخدام بروتوكولات HTTPS وأمن HTTP للنقل الصارم HTTP Strict Transport Security -أو اختصارًا HSTS: يشفّر بروتوكول HTTPS البيانات المرسَلة بين العميل والخادم، مما يضمن أن تكون ثبوتيات تسجيل الدخول وملفات تعريف الارتباط وبيانات طلبات "POST" ومعلومات الترويسة غير متاحة بسهولة للمهاجمين. يجب تتبّع الهجمات الأكثر شيوعًا (قائمة OWASP الحالية) ومعالجة الثغرات الأكثر شيوعًا أولًا. استخدام أدوات فحص الثغرات لإجراء اختبار أمان آلي على موقعك. يمكن أن يجد موقع الويب الناجح الخاص بك لاحقًا أخطاءً من خلال تقديم مكافآت على اكتشاف الأخطاء كما يفعل موقع موزيلا. تخزين وعرض البيانات التي تحتاجها فقط، فمثلًا إذا كان يجب على المستخدمين تخزين معلومات حساسة مثل تفاصيل البطاقة الائتمانية، فاعرض فقط أرقامًا كافية من رقم البطاقة التي يمكن للمستخدم تحديدها ولا يكفي أن ينسخها المهاجم ويستخدمها على موقع آخر، والنمط الأكثر شيوعًا عندها هو عرض آخر 4 أرقام فقط من رقم البطاقة الائتمانية. يمكن أن تساعد أطر عمل الويب في التخفيف من العديد من الثغرات الأكثر شيوعًا. الخلاصة أوضحنا في هذا المقال مفهوم أمان الويب وبعض الهجمات الأكثر شيوعًا التي يجب أن يحاول موقع الويب الحماية منها، ويجب أن تفهم أن تطبيق الويب لا يمكنه الوثوق بأيّ بيانات من متصفح الويب، لذا يجب تعقيم جميع بيانات المستخدم قبل عرضها أو استخدامها في استعلامات SQL واستدعاءات نظام الملفات. وصلنا مع هذا المقال إلى نهاية هذه السلسلة من المقالات والتي تغطي خطواتك الأولى في برمجة مواقع الويب من طرف الخادم، وبذلك تعلّمتَ المفاهيم الأساسية، وأصبحتَ جاهزًا لتحديد إطار عمل الويب وبدء البرمجة. ترجمة -وبتصرُّف- للمقال Website security. اقرأ أيضًا المقال السابق: أطر عمل الويب من طرف الخادم تأمين متصفحات الويب في العالم الرقمي ممارسات الأمن والحماية في تطبيقات PHP رفع مستوى أمان تطبيقات جانغو في بيئة الإنتاج
-
وضّحنا في المقال السابق كيف يبدو الاتصال بين عملاء وخوادم الويب، وطبيعة طلبات واستجابات HTTP، وما يحتاج تطبيق الويب من طرف الخادم لتنفيذه بهدف الاستجابة للطلبات القادمة من متصفح الويب. حان الوقت الآن لاستكشاف كيف يمكن لأطر عمل الويب تبسيط هذه المهام، وإعطائك فكرةً عن كيفية اختيار إطار عمل لتطبيقك الأول من طرف الخادم. المتطلبات الأساسية: المهارات الحاسوبية الأساسية، والفهم الأساسي لكيفية معالجة الشيفرة البرمجية من طرف الخادم لطلبات HTTP والاستجابة لها (اطّلع على تفاعلات الخادم مع العميل في موقع ويب ديناميكي). الهدف: فهم كيف يمكن لأطر عمل الويب تبسيط تطوير أو صيانة الشيفرة البرمجية من طرف الخادم وجعل القارئ يفكر في اختيار إطار عمل لعملية التطوير الخاصة به. سنوضّح في الأقسام التالية بعض النقاط باستخدام أجزاء من الشيفرة البرمجية المأخوذة من أطر عمل ويب حقيقية. لا تقلق إن لم تفهم كل شيء الآن، إذ سنعمل معك لاحقًا باستخدام شيفرة برمجية عند التعرّف على أطر عمل الويب من طرف الخادم. مقدمة عامة تُعَد أطر عمل الويب من طرف الخادم -المعروفة أيضًا باسم "أطر عمل تطبيقات الويب"- أطر عمل برمجية تسهّل كتابة تطبيقات الويب وصيانتها وتوسيعها، وتوفّر الأدوات والمكتبات التي تبسّط المهام الشائعة لتطوير الويب، مثل توجيه عناوين URL إلى المعالجات المناسبة والتفاعل مع قواعد البيانات ودعم الجلسات والتصريح للمستخدمين وتنسيق الخرج، مثل ملفات HTML و JSON و XML، وتحسين الأمان في مواجهة هجمات الويب. يوفّر القسم التالي مزيدًا من التفاصيل حول طريقة أطر عمل الويب لتسهيل تطوير تطبيقات الويب، ثم سنشرح بعض المعايير التي يمكنك استخدامها لاختيار إطار عمل ويب، وسنعرض بعض الخيارات المتاحة أمامك. دورة تطوير تطبيقات الويب باستخدام لغة PHP احترف تطوير النظم الخلفية والووردبريس وتطبيقات الويب من الألف إلى الياء دون الحاجة لخبرة برمجية مسبقة اشترك الآن فوائد أطر عمل الويب توفّر أطر عمل الويب أدوات ومكتبات لتبسيط عمليات تطوير الويب الشائعة. لا يُعَد استخدام إطار عمل ويب من طرف الخادم أمرًا ضروريًا، ولكن يُنصح بشدة بذلك، لأنه يسهّل الأمور عليك كثيرًا. سنناقش في هذا القسم بعض الوظائف التي توفرها أطر عمل الويب في أغلب الأحيان، إذ لن توفر جميع أطر العمل بالضرورة جميع هذه الميزات. العمل المباشر مع طلبات واستجابات HTTP تتواصل خوادم الويب والمتصفحات باستخدام بروتوكول HTTP، إذ تنتظر الخوادم طلبات HTTP من المتصفح ثم تعيد المعلومات في استجابات HTTP. تسمح لك أطر عمل الويب بكتابة صيغة مبسّطة من شأنها أن تنشِئ شيفرةً برمجيةً من طرف الخادم للعمل مع هذه الطلبات والاستجابات، وهذا يعني أنه سيكون لديك وظيفة أسهل، وستتفاعل مع شيفرة برمجية أسهل وأعلى مستوًى بدلًا من أساسيات الشبكات ذات المستوى الأدنى. يوضح المثال الآتي كيفية عمل ذلك في إطار عمل جانغو Django باستخدام لغة بايثون Python، إذ تتلقى كل دالة عرض (أو معالج طلب) الكائن HttpRequest الذي يحتوي على معلومات الطلب، وتكون هذه الدالة مطلوبة لإعادة الكائن HttpResponse مع الخرج المُنسَّق (سلسلة نصية في هذه الحالة). # دالة عرض جانغو from django.http import HttpResponse def index(request): # الحصول على كائن HttpRequest (طلب) # إجراء العمليات باستخدام المعلومات الواردة في الطلب. # إعادة الكائن HttpResponse return HttpResponse('Output string to return') توجيه الطلبات إلى المعالج المناسب ستوفِّر معظم المواقع عددًا من الموارد المختلفة التي يمكن الوصول إليها من خلال عناوين URL مميزة. يمكن أن يكون الحفاظ على التعامل مع جميع هذه الموارد باستخدام دالة واحدة أمرًا صعبًا، لذلك توفر أطر عمل الويب آليات بسيطة لربط أنماط عنوان URL مع دوال معالجة محدَّدة. لهذا النهج فوائد من حيث الصيانة، لأنه يمكنك تغيير عنوان URL المُستخدَم لتقديم ميزة معينة دون الحاجة إلى تغيير الشيفرة البرمجية الأساسية. تستخدم أطر العمل المختلفة آليات مختلفة للربط Mapping، فمثلًا يضيف إطار عمل الويب فلاسك Flask باستخدام لغة بايثون مسارات لعرض الدوال باستخدام مزخرِف Decorator. @app.route("/") def hello(): return "Hello World!" بينما يتوقع إطار عمل جانغو من المطورين تحديد قائمة بروابط عناوين URL بين نمط URL ودالة عرض. urlpatterns = [ url(r'^$', views.index), # مثال : /best/myteamname/5/ url(r'^best/(?P<team_name>\w.+?)/(?P<team_number>[0-9]+)/$', views.best), ] تسهيل الوصول إلى البيانات في الطلب يمكن تشفير البيانات في طلب HTTP بعدة طرق، إذ يمكن أن يشفِّر طلب HTTP من النوع "GET" البيانات المطلوبة في معاملات عنوان URL أو ضمن بنية عنوان URL للحصول على ملفات أو بيانات من الخادم؛ بينما سيتضمن طلب HTTP من النوع "POST" معلومات التحديث بوصفها "بيانات POST" ضمن متن الطلب لتحديث موردٍ على الخادم. كما يمكن أن يتضمن طلب HTTP معلومات حول الجلسة أو المستخدم الحالي في ملف تعريف الارتباط Cookie من طرف العميل. توفر أطر عمل الويب آليات مناسبة للغة البرمجة للوصول إلى هذه المعلومات، فمثلًا يحتوي كائن HttpRequest الذي يمرره إطار عمل جانغو إلى كل دالة عرض على توابع وخاصيات للوصول إلى عنوان URL الهدف ونوع الطلب، مثل طلب HTTP من النوع "GET"، ومعاملات "GET" أو "POST" وملف تعريف الارتباط وبيانات الجلسة وغير ذلك. كما يمكن لإطار عمل جانغو تمرير المعلومات المُشفَّرة في بنية عنوان URL من خلال تحديد "الأنماط الملتقَطة" في رابط Mapper عنوان URL (ألقِ نظرةً على الجزء الأخير من الشيفرة البرمجية في القسم السابق). تجريد وتبسيط الوصول إلى قاعدة البيانات تستخدم مواقع الويب قواعد البيانات لتخزين معلومات عن المستخدمين ومعلومات لمشاركتها معهم. توفّر أطر عمل الويب في أغلب الأحيان طبقة قاعدة بيانات تجرّد عمليات القراءة والكتابة والاستعلام والحذف لقاعدة البيانات، إذ يُشار إلى طبقة التجريد هذه باسم رابط الكائنات بالعلاقات Object-Relational Mapper -أو اختصارًا ORM. توجد فائدتان لاستخدام رابط ORM، هما: يمكنك استبدال قاعدة البيانات الأساسية دون الحاجة بالضرورة إلى تغيير الشيفرة البرمجية التي تستخدمها، مما يتيح للمطورين تحسين خصائص قواعد البيانات المختلفة بناءً على استخدامها. يمكن تطبيق التحقق validation الأساسي من صحة البيانات ضمن إطار العمل، مما يجعل من الأسهل والأكثر أمانًا التحقق من تخزين البيانات في النوع الصحيح لحقل قاعدة البيانات وبالتنسيق الصحيح (مثل عنوان البريد الإلكتروني) وكذلك التحقق من أنها غير ضارة بأيّ شكل من الأشكال، إذ يمكن للمخترقين استخدام أنماط معينة من الشيفرة البرمجية ليفعلوا أمورًا سيئة مثل حذف سجلات قاعدة البيانات. يوفر إطار عمل الويب جانغو رابط ORM مثلًا، ويشير إلى الكائن المُستخدَم لتعريف بنية السجل بوصفه نموذجًا Model، بحيث يحدّد هذا النموذج أنواع الحقول المراد تخزينها والتي يمكن أن توفر تحققًا على مستوى الحقل لصحة المعلومات الممكن تخزينها، فمثلًا سيسمح حقل البريد الإلكتروني بعناوين البريد الإلكتروني الصالحة فقط، كما يمكن أن تحدّد تعريفات الحقول حجمها الأقصى وقيمها الافتراضية وخيارات قائمة التحديد ونص التعليمات للوثائق ونص التسمية للاستمارات وإلخ. لا يذكر النموذج أيّ معلومات حول قاعدة البيانات الأساسية لأنه يُعَد ضبطًا لإعدادٍ يمكن تغييره بصورة منفصلة عن شيفرتنا البرمجية. يُظهر مقتطف الشيفرة البرمجية الأول التالي نموذج جانغو بسيط جدًا للكائن Team الذي يخزن اسم الفريق ومستوى الفريق بوصفهما حقولًا محرفية ويحدّد الحد الأقصى لعدد المحارف التي ستُخزَّن لكل سجل. يُعَد الحقل team_level حقل اختيار، لذلك سنقدّم ربطًا بين الاختيارات المُراد عرضها والبيانات المراد تخزينها إلى جانب القيمة الافتراضية. #best/models.py from django.db import models class Team(models.Model): team_name = models.CharField(max_length=40) TEAM_LEVELS = ( ('U09', 'Under 09s'), ('U10', 'Under 10s'), ('U11', 'Under 11s'), # قائمة فرقنا الأخرى ) team_level = models.CharField(max_length=3,choices=TEAM_LEVELS,default='U11') يوفّر نموذج جانغو واجهة برمجة تطبيقات API بسيطة للاستعلام بهدف البحث في قاعدة البيانات، إذ يمكن أن تتطابق هذه الواجهة مع عدد من الحقول في وقت واحد باستخدام معايير مختلفة، مثل التطابق التام، وعدم الحساسية لحالة الأحرف، وأكبر من وإلخ، ويمكن أيضًا دعم العبارات المعقدة، فمثلًا يمكنك تحديد بحثٍ عن فِرق U11 التي اسمها يبدأ بالحرفين "Fr"، أو ينتهي بالحرفين "al". يوضّح مقتطف الشيفرة البرمجية الثاني دالة عرض (معالج موارد) لعرض جميع فِرق U09، إذ نحدّد في هذه الحالة أننا نريد ترشيح جميع السجلات عندما يحتوي الحقل team_level على النص "U09" تمامًا. لاحظ فيما يلي كيفية تمرير هذه المعايير إلى الدالة filter() بوصفها وسيطًا مع اسم الحقل ونوع التطابق ومفصولٌ بينها شرطة سفلية team_level__exact. #best/views.py from django.shortcuts import render from .models import Team def youngest(request): list_teams = Team.objects.filter(team_level__exact="U09") context = {'youngest_teams': list_teams} return render(request, 'best/index.html', context) تصيير البيانات توفر أطر عمل الويب أنظمة إنشاء قوالب تتيح لك تحديد بنية مستند الخرج باستخدام العناصر البديلة للبيانات التي ستُضاف عند إنشاء صفحة. تُستخدم القوالب في أغلب الأحيان لإنشاء مستند HTML، ولكن يمكنها إنشاء أنواع أخرى من المستندات. توفر أطر عمل الويب آلية لتسهيل إنشاء تنسيقات أخرى من البيانات المخزنة بما في ذلك تنسيقات JSON و XML، فمثلًا يتيح لك نظام قوالب جانغو تحديد المتغيرات باستخدام صيغة "تعابير موضوعة بين قوسين معقوصين Double-handlebars"، مثل {{ variable_name }}، والتي ستُستبدَل بالقيم المُمرَّرة من دالة العرض عند عرض الصفحة. كما يوفّر نظام القوالب دعمًا للتعابير ذات الصيغة {% expression %} التي تسمح للقوالب بإجراء عمليات بسيطة، مثل تكرار قيم القائمة المُمرَّرة في القالب. ملاحظة: تستخدم العديد من أنظمة إنشاء القوالب الأخرى البنية نفسها مثل Jinja2 بلغة بايثون و Handlebars بلغة جافا سكريبت و Moustache بلغة جافا سكريبت وغيرها. يوضّح مقتطف الشيفرة البرمجية التالي كيف يعمل ذلك، فاستمرارًا لمثال "فريق الناشئين" من القسم السابق، يُمرَّر قالب HTML إلى متغير قائمة يسمى youngest_teams باستخدام العرض view. لدينا ضمن بنية ملف HTML تعبير يتحقق أولًا من وجود المتغير youngest_teams، ثم يكرّره في حلقة for. يعرض القالب في كل تكرار قيمة اسم الفريق team_name الخاص بالفريق في عنصر قائمة. #best/templates/best/index.html <!DOCTYPE html> <html lang="en"> <body> {% if youngest_teams %} <ul> {% for team in youngest_teams %} <li>{{ team.team_name }}</li> {% endfor %} </ul> {% else %} <p>No teams are available.</p> {% endif %} </body> </html> كيفية اختيار إطار عمل الويب توجد العديد من أطر عمل الويب لكل لغة برمجة تقريبًا ترغب في استخدامها، وسنوضّح عددًا من أطر العمل الأكثر شيوعًا في القسم التالي، ولكن يمكن أن يصبح تحديد إطار العمل الذي يوفر أفضل نقطة بداية لتطبيقك الويب الجديد أمرًا صعبًا مع وجود العديد من الخيارات. إليك بعض العوامل التي يمكن أن تؤثر على قرارك: حجم الجهد المبذول للتعلم: يعتمد الجهد المبذول لتعلم إطار عمل الويب على مدى معرفتك بلغة البرمجة الأساسية وتناسق واجهة برمجة التطبيقات الخاصة به وجودة توثيقه وحجم ونشاط مجتمعه. إذا لم تكن لديك خبرةٌ في البرمجة إطلاقًا، ففكر في إطار عمل جانغو الذي يُعَد من أسهل أطر العمل للتعلم بناءً على المعايير التي ذكرناها سابقًا؛ بينما إذا كنت جزءًا من فريق تطوير لديه خبرةٌ فعلية كبيرة في إطار عمل ويب معين أو لغة برمجة معينة، فلا بُد أنك ستتمسّك بها طبعًا. الإنتاجية: هي مقياس لمدى السرعة التي يمكنك بها إنشاء ميزات جديدة بمجرد أن تكون على دراية بإطار العمل، وتتضمن كلًا من الجهد المبذول لكتابة الشيفرة البرمجية وصيانتها، إذ لا يمكنك كتابة ميزات جديدة عندما تكون الميزات القديمة معطلة. تتشابه العديد من العوامل التي تؤثر على الإنتاجية مع عوامل حجم الجهد المبذول للتعلم، مثل التوثيق والمجتمع والخبرة في البرمجة وغيرها من العوامل التي نوضّحها فيما يلي: هدف أو أصل إطار العمل: اُنشِأت بعض أطر عمل الويب في البداية لحل أنواع معينة من المشاكل والعمل بصورة أفضل على إنشاء تطبيقات الويب ذات القيود المماثلة؛ فمثلًا أُنشِئ إطار عمل جانغو لدعم تطوير موقع ويب لصحيفة، لذا يُعَد جيدًا للمدونات والمواقع الأخرى التي تتضمن عمليات نشر؛ بينما يُعَد إطار عمل فلاسك أخف بكثير وهو رائع لإنشاء تطبيقات الويب التي تعمل على الأجهزة المُضمَّنة. إطار العمل القائم على رأيه Opinionated وغير القائم على رأيه Unopinionated: إطار العمل القائم على رأيه هو إطار العمل الذي توجد فيه طرق أفضل موصَى بها لحل مشكلة معينة، وتميل إلى أن تكون أكثر إنتاجية عندما تحاول حل المشاكل الشائعة، لأنها تقودك في الاتجاه الصحيح، ولكنها تكون أقل مرونة أحيانًا. إطار العمل الذي يتضمن كل شيء Batteries included وإطار العمل الذي تضطر للبحث فيه عن كل شيء بنفسك get it yourself: تتضمن بعض أطر عمل الويب أدوات أو مكتبات تعالج كل مشكلة يمكن لمطوريها التفكير فيها افتراضيًا، مثل إطار عمل جانغو، بينما تتوقع الأطر الأخف وزنًا أن يحدد ويختار مطورو الويب حلًا للمشاكل من مكتبات منفصلة مثل إطار عمل فلاسك خفيف الوزن. يكون البدء في أطر العمل التي تتضمن كل شيء أسهل لأن لديك كل ما تحتاجه فعليًا، ويُحتمَل أن تكون متكاملة وذات توثيق جيد، لكن إذا احتوى إطار العمل الأصغر على كل ما تحتاجه، فيمكن تشغيله في بيئات أكثر تقييدًا وسيكون لديه مجموعةً أصغر وأسهل من الأشياء لتعلّمها. إطار العمل الذي يشجع ممارسات التطوير الجيدة وإطار العمل الذي لا يشجع ذلك، إذ ينتج مثلًا عن إطار العمل -الذي يشجّع بنية نموذج-عرض-مُتحكِّم Model-View-Controller لفصل الشيفرة البرمجية إلى دوال منطقية- شيفرةٌ برمجية أكثر قابلية للصيانة من الشيفرة البرمجية التي ليس لديها أيّ توقعات بشأن المطورين، كما يمكن أن يكون لتصميم إطار العمل تأثير كبير على مدى سهولة اختبار وإعادة استخدام الشيفرة البرمجية. أداء لغة البرمجة أو إطار العمل: لا تُعَد السرعة عادةً العامل الأكبر في الاختيار لأن أوقات التشغيل البطيئة نسبيًا مثل لغة بايثون تكون أكثر من "جيدة" للمواقع متوسطة الحجم التي تعمل على عتاد متوسط. يمكن تعويض فوائد هذه السرعة المحسوسة للغة أخرى مثل لغة C++ أو جافا سكريبت من خلال تكاليف التعلم والصيانة. دعم التخزين المؤقت Caching: يمكن أن يصبح موقعك الويب أنجح، وبالتالي ستجد أنه لم يعد قادرًا على التعامل مع عدد الطلبات التي يتلقاها عندما يصل المستخدمون إليه، وبالتالي يمكن أن تفكر في إضافة دعم للتخزين المؤقت الذي يمثّل تحسينًا عندما تخزّن كل استجابة الويب أو جزءًا منها بحيث لا يلزم إعادة حسابها في الطلبات اللاحقة. تُعَد استعادة استجابة مُخزَّنة مؤقتًا أسرع بكثير من حساب الاستجابة. يمكن تقديم التخزين المؤقت في شيفرتك البرمجية أو الخادم (اطّلع على الوسيط العكسي Reverse Proxy)، وسيكون لأطر عمل الويب مستويات مختلفة من الدعم لتحديد المحتوى الممكن تخزينه مؤقتًا. قابلية التوسع: إذا أصبح موقعك ناجحًا جدًا، فستُستنفَد فوائد التخزين المؤقت ويمكن أن تصل إلى حدود التوسع الرأسي Vertical Scaling أي تشغيل تطبيق الويب على عتاد أقوى، وبالتالي ستحتاج إلى التوسع أفقيًا (مشاركة الحِمل من خلال توزيع موقعك على عدد من خوادم الويب وقواعد البيانات)، أو التوسع جغرافيًا، إذ يقيم بعض عملائك بعيدًا عن خادمك. يمكن أن يُحدِث إطار عمل الويب الذي تختاره فرقًا كبيرًا في مدى سهولة توسيع نطاق موقعك. أمان الويب: توفر بعض أطر عمل الويب دعمًا أفضل للتعامل مع هجمات الويب الشائعة. يعقّم Sanitize إطار عمل جانغو مثلًا جميع مدخلات المستخدم من قوالب HTML، وبالتالي لا يمكن تشغيل شيفرة جافا سكريبت التي أدخلها المستخدم. توفر أطر العمل الأخرى حمايةً مماثلة، ولكن لا تُفعَّل دائمًا افتراضيًا. هناك عدة عوامل محتملة أخرى بما في ذلك منح التراخيص Licensing سواءً كان إطار العمل قيد التطوير النشط أم لا وغير ذلك. إذا كنت مبتدئًا في البرمجة، فيُحتمَل أن تختار إطار عملك بناءً على أساس سهولة التعلم. يُعَد التوثيق أو البرامج التعليمية عالية الجودة والمجتمع النشط الذي يساعد المستخدمين الجدد أكثر مواردك قيمةً، إضافةً إلى سهولة استخدام اللغة نفسها. اخترنا إطار عمل جانغو بلغة بايثون و Express باستخدام Node/JavaScript لكتابة أمثلتنا، ويرجع ذلك أساسًا إلى أنها سهلة التعلم ولديها دعمٌ جيد. ملاحظة: يمكننا زيارة مواقع الويب الرسمية لإطار عمل جانغو بلغة بايثون و Express باستخدام Node/JavaScript للتحقق من توثيقها ومجتمعها. انتقل إلى المواقع الرسمية. انقر على روابط قائمة التوثيق التي تحتوي أشياءً بأسماء مثل "Documentation و Guide و API Reference و Getting Started" وغير ذلك. هل يمكنك رؤية موضوعات توضّح كيفية إعداد توجيه عناوين URL والقوالب وقواعد البيانات والنماذج؟ هل التوثيق واضح؟ انتقل إلى القوائم البريدية الخاصة بكل موقع (يمكن الوصول إليها من روابط المجتمع). كم عدد الأسئلة المنشورة في الأيام القليلة الماضية؟ كم عدد الردود؟ هل لديها مجتمع نشط؟ أطر عمل الويب الجيدة لننتقل الآن ونناقش بعض أطر عمل الويب من طرف الخادم، إذ تمثل أطر العمل من طرف الخادم الآتية بعضًا من أكثر أطر العمل المتاحة شيوعًا، فلديها كل ما تحتاجه لتكون منتِجًا، فهي مفتوحة المصدر وقيد التطوير النشط ولديها مجتمعات نشطة تنشِئ توثيقًا وتساعد المستخدمين في لوحات المناقشة، وتُستخدَم في عدد كبير من مواقع الويب رفيعة المستوى. هناك العديد من أطر العمل الرائعة الأخرى من طرف الخادم التي يمكنك اكتشافها بنفسك. إطار عمل جانغو Django باستخدام لغة بايثون يُعَد جانغو إطار عمل ويب بلغة بايثون عالي المستوى ويشجّع التطوير السريع والتصميم النظيف والعملي، وقد أنشأه مطورون ذوو خبرة، وهو يعتني بالكثير من متاعب تطوير الويب، بحيث يمكنك التركيز على كتابة تطبيقك دون الحاجة إلى إعادة اختراع العجلة، وهو مجاني ومفتوح المصدر. يتبع جانغو فلسفة "أنه يتضمن كل شيء Batteries Included" التي تنص على أن إطار عمل جانغو يتضمن جميع الأجزاء الممكنة والمطلوبة للاستخدام الكامل، ويوفر تقريبًا كل شيء يمكن أن يرغب معظم المطورين في تطبيقه. بما أنه يتضمّن كل شيء، سيعمل كل شيء مع بعضه بعضًا. يتبع إطار عمل جانغو أيضًا مبادئ تصميم متناسقة، ويحتوي على توثيق شامل وحديث، وهو سريع وآمن وقابل للتوسّع. تُعَد شيفرة جانغو البرمجية سهلة القراءة والصيانة لأنه يعتمد على لغة بايثون. تشمل المواقع الشهيرة التي تستخدم إطار عمل جانغو (من صفحته الرئيسية): ديسكاس Disqus وإنستغرام Instagram و Knight Foundation و MacArthur Foundation وموزيلا Mozilla وناشونال جيوغرافيك National Geographic و Open Knowledge Foundation وبنترست Pinterest وأوبن ستاك Open Stack. إطار عمل فلاسك باستخدام لغة بايثون يُعَد فلاسك إطار عمل مصغّر للغة بايثون، وهو إطار عمل بسيط، إلّا أنه يمكنه إنشاء مواقع ويب جادة ومميزة. يحتوي فلاسك على خادم تطوير ومنقح أخطاء، ويتضمن دعمًا لإنشاء قوالب Jinja2 وملفات تعريف الارتباط الآمنة واختبار الوحدات وإرسال طلبات RESTful، ولديه توثيق جيد ومجتمع نشط. أصبح فلاسك شائعًا جدًا خاصة للمطورين الذين يحتاجون إلى تقديم خدمات الويب على أنظمة صغيرة محدودة الموارد مثل تشغيل خادم ويب على راسبيري باي Raspberry Pi وأجهزة التحكم بدون طيار وغير ذلك. إطار عمل Express باستخدام Node.js/JavaScript يُعَد Express إطار عمل ويب سريع وغير قائم على رأيه ومرن وبسيط لبيئة Node.js التي تمثل بيئةً دون متصفح لتشغيل شيفرة جافا سكريبت، ويوفر مجموعةً قويةً من الميزات لتطبيقات الويب والهاتف المحمول ويوفر طرقًا لاستخدام HTTP وبرامجًا وسيطة مفيدة. إطار عمل Express شائع الاستخدام، ويرجع ذلك إلى أنه يسهل عملية التهجير migration على مبرمجي الويب باستخدام لغة جافا سكريبت من طرف العميل إلى التطوير من طرف الخادم، ولأنه فعّال في استخدام الموارد، إذ تستخدم بيئة Node الأساسية مهامًا متعددة خفيفة الوزن ضمن خيط Thread بدلًا من إنتاج عمليات منفصلة لكل طلب ويب جديد. نظرًا لبساطة إطار عمل Express، فإنه لا يدمج كل مكوِّن ترغب في استخدامه، فهو يوفّر مثلًا الوصول إلى قاعدة البيانات والدعم للمستخدمين والجلسات من خلال مكتبات مستقلة. هناك العديد من المكونات المستقلة الممتازة، ولكن يمكن أن يكون في بعض الأحيان من الصعب تحديد أيها الأفضل لغرض معين. تعتمد العديد من أطر العمل الشائعة من طرف الخادم والشاملة (التي تتضمن أطر عمل من طرف الخادم والعميل) على إطار عمل Express بما في ذلك أطر عمل Feathers و ItemsAPI و KeystoneJS و Kraken و LoopBack و MEAN و Sails، كما تستخدم الكثير من الشركات المرموقة إطار عمل Express بما في ذلك شركات أوبر Uber و Accenture و IBM وغيرها. اطّلع على قائمة هذه الشركات الكاملة. إطار عمل دينو Deno باستخدام لغة جافا سكريبت دينو هو إطار عمل بسيط وحديث وآمن في وقت التنفيذ خاص بلغتي جافا سكريبت و TypeScript، وأُنشِئ على محرك Chrome V8 ولغة رست Rust. إطار عمل دينو مدعوم من طرف مكتبة Tokio التي توفّر وقت تنفيذ غير متزامن قائم على لغة رست وتتيح لإطار عمل دينو خدمة صفحات الويب بصورة أسرع، وتدعم WebAssembly داخليًا، والذي يتيح تصريف Compilation الشيفرة الثنائية لاستخدامها من طرف العميل. يهدف دينو إلى سد بعض الثغرات في بيئة Node.js من خلال توفير آلية تحافظ على أمان أفضل. تشمل ميزات إطار عمل دينو ما يلي: الأمان افتراضيًا، إذ تقيّد وحدات دينو أذونات الوصول إلى الملفات، أو الشبكة، أو البيئة ما لم يُسمَح بذلك صراحةً. يدعم لغة TypeScript. آلية انتظار من الدرجة الأولى. أداة اختبار ومنسّق شيفرة برمجية مُدمَجَين (deno fmt). التوافق مع المتصفحات (جافا سكريبت): يجب أن تعمل برامج دينو المكتوبة كاملًا بلغة جافا سكريبت باستثناء فضاء الأسماء Deno، أو ميزاتها التي جرى اختبارها، مباشرةً في أيّ متصفح حديث. تجميع السكريبت في ملف جافا سكريبت واحد. يوفّر إطار عمل دينو طريقةً سهلةً لكنها قويةً لاستخدام لغة جافا سكريبت لكل من البرمجة من طرف العميل وطرف الخادم. إطار عمل روبي أون ريلز Ruby on Rails باستخدام لغة روبي Ruby ريلز -أو كما يشار إليه عادةً باسم روبي أون ريلز- هو إطار عمل ويب مكتوب للغة البرمجة روبي. يتبع ريلز فلسفة تصميم مشابهة جدًا لفلسفة إطار عمل جانغو من خلال توفير آليات معيارية لتوجيه عناوين URL والوصول إلى البيانات من قاعدة بيانات وإنشاء شيفرة HTML من القوالب وتنسيق البيانات بتنسيق JSON أو XML. يشجّع إطار عمل ريلز على استخدام أنماط التصميم، مثل نمط DRY "لا تكرر نفسك Don't Repeat Yourself" -أي كتابة الشيفرة البرمجية مرة واحدة فقط إذا كان ذلك ممكنًا- ونمط MVC وهو نموذج-عرض-متحكِّم Model-View-Controller وغير ذلك. هناك طبعًا عدة اختلافات بسبب قرارات التصميم وطبيعة لغات البرمجة. اُستخدِم إطار عمل ريلز للمواقع البارزة مثل Basecamp وغيت هب GitHub و Shopify و Airbnb و Twitch وساوند كلاود SoundCloud وهولو Hulu و Zendesk و Square و Highrise. إطار عمل لارافيل Laravel باستخدام لغة PHP لارافيل هو إطار عمل لتطبيق ويب يستخدم صياغةً معبرةً وأنيقة، فهو يحاول التخلص من مصاعب التطوير من خلال تسهيل المهام الشائعة المُستخدَمة في غالبية مشاريع الويب مثل: محرّك توجيه بسيط وسريع. حاوية حقن اعتماديات قوية. نهايات خلفية متعددة لتخزين الجلسة والذاكرة المؤقتة. قاعدة بيانات ORM معبّرة وبديهية. عمليات تهجير حيادية لمخطط قاعدة البيانات. معالجة خلفية قوية. بث الحدث في الوقت الفعلي. يُعَد إطار عمل لارافيل شاملًا، وهو قوي ويوفر الأدوات اللازمة للتطبيقات الكبيرة والقوية. إطار عمل ASP.NET ASP.NET هو إطار عمل ويب مفتوح المصدر طوّرته مايكروسوفت لبناء تطبيقات وخدمات الويب الحديثة. يمكنك باستخدام إطار عمل ASP.NET إنشاء مواقع ويب تعتمد على لغات HTML و CSS وجافا سكريبت بسرعة وتوسيع نطاقها ليستخدمها ملايين المستخدمين وإضافة إمكانات أكثر تعقيدًا بسهولة مثل واجهات Web API، أو النماذج عبر البيانات، أو الاتصالات في الوقت الفعلي. أحد العوامل المميزة لإطار عمل ASP.NET هو أنه مبني على وقت التنفيذ المشترك للغات Common Language Runtime -أو CLR اختصارًا، مما يسمح للمبرمجين بكتابة شيفرة ASP.NET باستخدام أي لغة ".NET" مدعومة مثل C# وفيجوال بيسك Visual Basic وغيرها. يستفيد إطار عمل ASP.NET -مثل العديد من منتجات مايكروسوفت- من أدوات ممتازة ومجانية غالبًا ومجتمع مطورين نشط وتوثيق جيد، وتستخدم العديد من الشركات إطار عمل ASP.NET مثل مايكروسوفت و Xbox.com و Stack Overflow وغيرها الكثير. إطار عمل Mojolicious باستخدام لغة بيرل Perl Mojolicious هو إطار عمل ويب من الجيل التالي للغة برمجة بيرل Perl. تعلّم الكثير من الناس لغة بيرل بسبب مكتبتها الرائعة التي تُدعَى CGI في الأيام الأولى للويب، وكان الأمر بسيطًا بما يكفي للبدء دون معرفة الكثير عن اللغة ومناسبًا بما يكفي للاستمرار، إذ يطبّق إطار عمل Mojolicious هذه الفكرة باستخدام تقنيات حد النزيف Bleeding Edge، وهي نوع من التقنيات التي تصدر للجميع بالرغم من عدم اكتمال اختبارها ويمكن أن تكون غير موثوقة. بعض الميزات التي يوفرها إطار عمل Mojolicious هي: إطار عمل ويب في الوقت الفعلي لتنمية نماذج الملف الواحد الأولية بسهولة إلى تطبيقات ويب MVC جيدة التنظيم. وجهات RESTful، وإضافات، وأوامر، وقوالب Perl-ish، ومفاوضات المحتوى، وإدارة الجلسات وكذلك التحقق من صحة النموذج، واختبار إطار العمل، وخادم الملفات الساكن، واكتشاف CGI/PSGI، ودعم الترميز الموحّد Unicode من الدرجة الأولى. تقديم بروتوكول HTTP الشامل وبروتوكول WebSocket للعميل/الخادم مع دعم بروتوكولات IPv6 و TLS و SNI و IDNA، ووسيط HTTP/SOCKS5، ومقبس Socket نطاق يونيكس UNIX، وتقنية Comet (الاستطلاع المفتوح Long Polling)، واستمرار النشاط Keep-alive، وتجمّع الاتصالات Connection Pooling، والمهلة الزمنية، وملف تعريف الارتباط Cookie، وتعدد الأجزاء، وطريقة الضغط Gzip. محللات ومولّدات ملفات JSON و HTML و XML مع دعم محدّدات CSS. واجهة برمجة تطبيقات Pure-Perl النقية والقابلة للنقل وكائنية التوجّه دون أيّ سحر خفي. شيفرة برمجية حديثة تعتمد على سنوات من الخبرة، وهي مجانية ومفتوحة المصدر. إطار عمل Spring Boot باستخدام لغة جافا يُعَد إطار عمل Spring Boot أحد المشاريع التي قدمتها منصة Spring، وهو نقطة انطلاق جيدة لتطوير الويب من طرف الخادم باستخدام لغة جافا. ليس إطار عمل Spring Boot إطار العمل الوحيد الذي يعتمد على لغة جافا، ولكن من السهل استخدامه لإنشاء تطبيقات قائمة بذاتها في مرحلة الإنتاج وتعتمد على منصة Spring، إذ يمكنك تشغيل هذه التطبيقات مباشرةً. كما يُعَد بمثابة وجهة نظر راسخة مستندة إلى منصة Spring ومكتبات الجهات الخارجية، ولكنه يسمح بالبدء بأقل قدر من الاضطراب والإعداد. يمكن استخدام إطار عمل Spring Boot للمشاكل الصغيرة، ولكن تكمن قوته في بناء تطبيقات واسعة النطاق تستخدم نهج السحابة، ويمكن تشغيل تطبيقات متعددة بالتوازي مع بعضها بعضًا، إذ يوفّر بعضها تفاعل المستخدم ويطبّق البعض الآخر أعمال الواجهة الخلفية مثل الوصول إلى قواعد البيانات أو الخدمات الأخرى. تساعد موازنات الأحمال على ضمان وجود قدرة تعويضية Redundancy وموثوقية أو السماح بمعالجة محددة جغرافيًا لطلبات المستخدم بهدف ضمان الاستجابة. الخلاصة أظهر هذا المقال أن أطر عمل الويب يمكن أن تسهّل تطوير الشيفرة البرمجية من طرف الخادم وصيانتها، وقدّم نظرةً عامةً عالية المستوى على عدد من أطر العمل الشائعة، وناقش معايير اختيار إطار عمل تطبيق الويب. يجب أن يكون لديك الآن على الأقل فكرة عن كيفية اختيار إطار عمل الويب لتطوير تطبيقك من طرف الخادم. إذا لم يكن الأمر كذلك، فلا داعٍ للقلق، إذ سنقدم لك لاحقًا دروسًا تفصيلية حول إطاري عمل جانغو و Express لمنحك بعض الخبرة العملية مع إطار عمل ويب. سنغير الاتجاه قليلًا في المقال التالي وسنتعمّق أكثر في مجال أمان الويب. ترجمة -وبتصرُّف- للمقال Server-side web frameworks. اقرأ أيضًا المقال السابق: نظرة على تفاعلات الخادم مع العميل في موقع ويب ديناميكي تعرف على مفهوم إطار العمل Framework وأهميته في البرمجة استخدام أطر العمل في برمجة تطبيقات الويب: فلاسك نموذجا
-
تعرّفنا في المقال السابق من هذه السلسلة تعلم تطوير الويب على الغرض والفوائد المُحتمَلة من البرمجة من طرف الخادم، وسنوضّح الآن تفصيليًا ما يحدث عندما يتلقى الخادم طلبًا ديناميكيًا من المتصفح. تتعامل معظم الشيفرات البرمجية من طرف خادم موقع الويب مع الطلبات والاستجابات بطرق مماثلة، فسيساعدك ذلك على فهم ما عليك تطبيقه عند كتابة شيفرتك البرمجية. المتطلبات الأساسية: المهارات الحاسوبية الأساسية، وفهم أساسي لخادم الويب. الهدف: فهم تفاعلات الخادم مع العميل في موقع ويب ديناميكي وخاصةً العمليات التي يجب أن تجريها الشيفرة البرمجية من طرف الخادم. لن نضع شيفرة برمجية حقيقية في هذه المناقشة لأننا لم نختر بعد إطار عمل ويب لاستخدامه في كتابة شيفرتنا البرمجية، ولكن لا تزال هذه المناقشة وثيقة الصلة بالموضوع، لأنه يجب تقديم السلوك الموصوف من خلال شيفرة برمجية من طرف الخادم بغض النظر عن لغة البرمجة أو إطار عمل الويب الذي تحدده. مفهوم خوادم الويب وبروتوكول HTTP تتواصل متصفحات الويب مع خوادم الويب باستخدام بروتوكول نقل النص التشعبي HyperText Transfer Protocol -أو اختصارًا HTTP. إذا نقرتَ على رابط في صفحة ويب أو أرسلتَ نموذجًا أو أجريتَ بحثًا، سيرسل المتصفح طلب HTTP إلى الخادم، ويشمل هذا الطلب ما يلي: عنوان URL الذي يعرِّف الخادم والمورد الهدف مثل ملف HTML، أو نقطة بيانات معينة على الخادم، أو أداة للتشغيل. طريقة تحدد الإجراء المطلوب للحصول على ملف، أو حفظ، أو تحديث بعض البيانات مثلًا. الطرق أو الأفعال المختلفة والإجراءات المرتبطة بها هي ما يلي: GET: الحصول على مورد معين مثل ملف HTML يحتوي على معلومات حول منتج أو قائمة منتجات. POST: إنشاء مورد جديد مثل إضافة مقال جديد إلى مواقع ويكي Wiki، أو إضافة جهة اتصال جديدة إلى قاعدة بيانات. HEAD: الحصول على معلومات البيانات الوصفية حول مورد معين دون الحصول على المتن كما تفعل الطريقة GET. يمكنك مثلًا استخدام طلب الطريقة HEAD لمعرفة آخر تحديث لأحد الموارد، ثم استخدام طلب الطريقة GET (الأكثر تكلفة) لتنزيل المورد عند تغييره فقط. PUT: تحديث مورد موجود مسبقًا أو إنشاء مورد جديد إن لم يكن موجودًا مسبقًا. DELETE: حذف المورد المحدد. TRACE و OPTIONS و CONNECT و PATCH: تُستخدَم هذه الأفعال لمهام أقل شيوعًا وتقدمًا، لذلك لن نشرحها هنا. يمكن ترميز المعلومات الإضافية مع الطلب مثل بيانات نموذج HTML، إذ يمكن ترميز هذه المعلومات على النحو التالي: معاملات URL: تطلب الطريقة GET تشفير البيانات في عنوان URL المُرسَل إلى الخادم من خلال إضافة أزواج الاسم/القيمة في نهايته مثل العنوان http://example.com?name=Fred&age=11. لديك دائمًا علامة استفهام (?) تفصل بقية عنوان URL عن معاملات URL، وإشارة مساواة (=) تفصل كل اسم عن القيمة المرتبطة به، وعلامة العطف (&) التي تفصل بين الأزواج. تُعَد معاملات URL غير آمنة بطبيعتها، إذ يمكن للمستخدمين تغييرها ثم إعادة إرسالها، ولذلك لا تُستخدَم معاملات عنوان URL أو طلبات GET مع الطلبات التي تحدّث البيانات على الخادم. بيانات POST: تضيف طلبات POST مواردًا جديدة، وتُشفَّر بياناتها ضمن متن الطلب. ملفات تعريف الارتباط Cookies من طرف العميل: تحتوي ملفات تعريف الارتباط على بيانات جلسة العميل بما في ذلك المفاتيح التي يمكن للخادم استخدامها لتحديد حالة تسجيل الدخول والأذونات أو الوصول إلى الموارد. تنتظر خوادم الويب رسائل طلب العميل وتعالجها عند وصولها، وترد على متصفح الويب برسالة استجابة HTTP، إذ تحتوي الاستجابة على رمز حالة استجابة HTTP الذي يشير إلى نجاح الطلب من عدمه مثل "200 OK" للنجاح و "404 Not Found" إذا تعذر العثور على المورد و "403 Forbidden" إذا كان المستخدم غير مصرَّح له برؤية الموارد وغير ذلك. يمكن أن يحتوي متن الاستجابة الناجحة لطلب "GET" على المورد المطلوب. يعرض متصفح الويب صفحة HTML المُعادة، ويمكن أن يكتشف المتصفح روابطًا إلى موارد أخرى بمثابة جزء من المعالجة، إذ تشير صفحة HTML عادةً إلى صفحات جافا سكريبت و CSS مثلًا، وسيرسِل طلبات HTTP منفصلة لتنزيل هذه الملفات. تستخدم كل من مواقع الويب الساكنة والديناميكية التي سنناقشها لاحقًا بروتوكول أو أنماط الاتصال نفسها تمامًا. دورة تطوير تطبيقات الويب باستخدام لغة PHP احترف تطوير النظم الخلفية والووردبريس وتطبيقات الويب من الألف إلى الياء دون الحاجة لخبرة برمجية مسبقة اشترك الآن مثال عن طلب/استجابة GET يمكنك تقديم طلب GET بسيط من خلال النقر على رابط أو البحث في موقع (مثل الصفحة الرئيسية لمحرّك البحث)، فمثلًا سيبدو طلب HTTP المُرسَل عند إجراء بحث على موقع MDN للمصطلح "client-server overview" إلى حد كبير مثل النص الذي سنوضّحه لاحقًا (لن يكون متطابقًا لاعتماد أجزاء من الرسالة على متصفحك أو إعداداتك). ملاحظة: يُحدَّد تنسيق رسائل HTTP في معيار الويب RFC9110. لست بحاجة إلى معرفة هذا المستوى من التفاصيل، ولكنك تعرف الآن على الأقل من أين أتى كل ذلك. الطلب request يحتوي كل سطر من الطلب على معلومات عنه. يُطلَق على الجزء الأول اسم الترويسة header، ويحتوي على معلومات مفيدة حول الطلب بالطريقة نفسها التي تحتوي بها ترويسة HTML على معلومات مفيدة حول مستند HTML، ولكن ليس المحتوى الفعلي نفسه الموجود في المتن. GET /en-US/search?q=client+server+overview&topic=apps&topic=html&topic=css&topic=js&topic=api&topic=webdev HTTP/1.1 Host: developer.mozilla.org Connection: keep-alive Pragma: no-cache Cache-Control: no-cache Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Referer: https://developer.mozilla.org/en-US/ Accept-Encoding: gzip, deflate, sdch, br Accept-Charset: ISO-8859-1,UTF-8;q=0.7,*;q=0.7 Accept-Language: en-US,en;q=0.8,es;q=0.6 Cookie: sessionid=6ynxs23n521lu21b1t136rhbv7ezngie; csrftoken=zIPUJsAZv6pcgCBJSCj1zU6pQZbfMUAT; dwf_section_edit=False; dwf_sg_task_completion=False; _gat=1; _ga=GA1.2.1688886003.1471911953; ffo=true يحتوي السطران الأول والثاني على معظم المعلومات التي تحدثنا عنها سابقًا وهي: نوع الطلب (GET). عنوان URL للمورد الهدف (/en-US/search). معاملات عنوان URL: q=client%2Bserver%2Boverview&topic=apps&topic=html&topic=css&topic=js&topic=api&topic=webdev موقع الويب الهدف أو المضيف (developer.mozilla.org). تتضمن نهاية السطر الأول سلسلة نصية قصيرة تحدد إصدار البروتوكول المُحدَّد (HTTP/1.1). يحتوي السطر الأخير على معلومات حول ملفات تعريف الارتباط من طرف العميل، إذ يمكنك أن ترى في هذه الحالة أن ملف تعريف الارتباط يتضمن معرّفًا لإدارة الجلسات: sessionid=6ynxs23n521lu21b1t136rhbv7ezngie; … تحتوي الأسطر المتبقية على معلومات حول المتصفح المُستخدَم ونوع الاستجابات التي يمكنه التعامل معها، إذ يمكنك أن ترى هنا مثلًا ما يلي: المتصفح (User-Agent) هو موزيلا فايرفوكس (Mozilla/5.0). يمكن للمتصفح قبول معلومات Gzip المضغوطة (Accept-Encoding: gzip). يمكنه قبول مجموعة محدَّدة من المحارف (Accept-Charset: ISO-8859-1,UTF-8;q=0.7,*;q=0.7) واللغات (Accept-Language: en-US,en;q=0.8,es;q=0.6). يشير السطر Referer إلى عنوان صفحة الويب التي تحتوي على رابط هذا المورد (مثل أصل الطلب https://developer.mozilla.org/en-US/). يمكن أن تحتوي طلبات HTTP على متن، ولكنه فارغ في هذه الحالة. الاستجابة سنعرض لاحقًا الجزء الأول من الاستجابة لهذا الطلب، إذ تحتوي الترويسة على معلومات كما يلي: يتضمن السطر الأول رمز الاستجابة "200 OK" الذي يمثل نجاح الطلب. يمكننا أن نرى أن الاستجابة بتنسيق "text/html" (نوع المحتوى Content-Type). يمكننا أن نرى استخدام مجموعة محارف UTF-8 بالشكل: Content-Type: text/html; charset=utf-8. تخبرنا الترويسة عن حجمها (Content-Length: 41823). نرى في نهاية الرسالة محتوى المتن الذي يحتوي على شيفرة HTML الفعلية التي يعيدها الطلب. HTTP/1.1 200 OK Server: Apache X-Backend-Server: developer1.webapp.scl3.mozilla.com Vary: Accept, Cookie, Accept-Encoding Content-Type: text/html; charset=utf-8 Date: Wed, 07 Sep 2016 00:11:31 GMT Keep-Alive: timeout=5, max=999 Connection: Keep-Alive X-Frame-Options: DENY Allow: GET X-Cache-Info: caching Content-Length: 41823 <!DOCTYPE html> <html lang="en-US" dir="ltr" class="redesign no-js" data-ffo-opensanslight=false data-ffo-opensans=false > <head prefix="og: http://ogp.me/ns#"> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=Edge"> <script>(function(d) { d.className = d.className.replace(/\bno-js/, ''); })(document.documentElement);</script> … يتضمن الجزء المتبقي من ترويسة الاستجابة معلومات حول الاستجابة (مثل وقت إنشائها) والخادم وكيف يتوقع أن يتعامل المتصفح مع الصفحة (مثل السطر X-Frame-Options: DENY الذي يخبر المتصفح بعدم السماح لتلك الصفحة بتضمينها ضمن العنصر <iframe> في موقع آخر). مثال عن طلب/استجابة POST يمكن إجراء الطريقة "POST" الخاصة ببروتوكول HTTP عند إرسال نموذج يحتوي على معلومات لحفظها على الخادم. الطلب يوضّح النص التالي طلب HTTP، الذي يُجرَى عندما يرسل المستخدم تفاصيل جديدة للملف الشخصي على هذا الموقع. يماثل تنسيق الطلب تقريبًا طلب "GET" الموضح سابقًا، بالرغم من أن السطر الأول يعرِّف هذا الطلب على أنه طلب "POST". POST /en-US/profiles/hamishwillee/edit HTTP/1.1 Host: developer.mozilla.org Connection: keep-alive Content-Length: 432 Pragma: no-cache Cache-Control: no-cache Origin: https://developer.mozilla.org Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Content-Type: application/x-www-form-urlencoded Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Referer: https://developer.mozilla.org/en-US/profiles/hamishwillee/edit Accept-Encoding: gzip, deflate, br Accept-Language: en-US,en;q=0.8,es;q=0.6 Cookie: sessionid=6ynxs23n521lu21b1t136rhbv7ezngie; _gat=1; csrftoken=zIPUJsAZv6pcgCBJSCj1zU6pQZbfMUAT; dwf_section_edit=False; dwf_sg_task_completion=False; _ga=GA1.2.1688886003.1471911953; ffo=true csrfmiddlewaretoken=zIPUJsAZv6pcgCBJSCj1zU6pQZbfMUAT&user-username=hamishwillee&user-fullname=Hamish+Willee&user-title=&user-organization=&user-location=Australia&user-locale=en-US&user-timezone=Australia%2FMelbourne&user-irc_nickname=&user-interests=&user-expertise=&user-twitter_url=&user-stackoverflow_url=&user-linkedin_url=&user-mozillians_url=&user-facebook_url= لكن الاختلاف الرئيسي بينهما هو عدم احتواء عنوان URL على أية معاملات، وتُرمَّز المعلومات من النموذج في متن الطلب، فمثلًا يُضبَط الاسم الكامل للمستخدم الجديد باستخدام: &user-fullname=Hamish+Willee الاستجابة response يوضّح النص التالي استجابة الطلب، إذ يخبر رمز الحالة "302 Found" المتصفح بنجاح الإرسال، وأنه يجب عليه إصدار طلب HTTP ثانٍ لتحميل الصفحة المُحدَّدة في الحقل Location. تكون معلومات الاستجابة مشابهة للمعلومات الخاصة بالاستجابة على طلب "GET". HTTP/1.1 302 FOUND Server: Apache X-Backend-Server: developer3.webapp.scl3.mozilla.com Vary: Cookie Vary: Accept-Encoding Content-Type: text/html; charset=utf-8 Date: Wed, 07 Sep 2016 00:38:13 GMT Location: https://developer.mozilla.org/en-US/profiles/hamishwillee Keep-Alive: timeout=5, max=1000 Connection: Keep-Alive X-Frame-Options: DENY X-Cache-Info: not cacheable; request wasn't a GET or HEAD Content-Length: 0 ملاحظة: اُلتقِطت استجابات وطلبات HTTP الموضَّحة في هذه الأمثلة باستخدام تطبيق Fiddler، ولكن يمكنك الحصول على معلومات مماثلة باستخدام أدوات التعرّف على متصفحات الويب Web Sniffers (مثل الأداة Websniffer)، أو أدوات تحليل الحزم مثل Wireshark التي يمكنك تجربتها بنفسك. استخدم أيًا من هذه الأدوات، ثم تنقّل عبر الموقع وعدّل معلومات الملف الشخصي لمشاهدة الطلبات والاستجابات المختلفة. تحتوي معظم المتصفحات الحديثة على أدوات مراقبة طلبات الشبكة مثل أداة مراقبة الشبكة Network Monitor في فايرفوكس. المواقع الساكنة الموقع الساكن Static Site هو الموقع الذي يعيد المحتوى الثابت نفسه من الخادم كلما طُلِب مورد معين. لذلك، إذا كانت لديك صفحةٌ عن منتجٍ ما في الوجهة "/static/myproduct1.html" مثلًا، ستُعاد هذه الصفحة نفسها إلى جميع المستخدمين. إذا أضفت منتجًا مشابهًا آخر إلى موقعك، فيجب إضافة صفحة أخرى (مثل الصفحة "myproduct2.html") وهكذا. يمكن لذلك أن يفقد فعاليته عندما يكون لديك آلاف صفحات المنتجات، إذ ستكِّرر الكثير من الشيفرة البرمجية عبر كل صفحة (قالب الصفحة الأساسي وبنيتها وغير ذلك)، وإذا أردت تغيير أي شيء يتعلق ببنية الصفحة مثل إضافة قسم جديد للمنتجات ذات الصلة، فيجب تغيير كل صفحة على حدة. ملاحظة: تُعَد المواقع الساكنة ممتازة عندما يكون لديك عددٌ قليلٌ من الصفحات وتريد إرسال المحتوى نفسه إلى جميع المستخدمين، ولكن يمكن أن يكون لها تكلفة صيانة كبيرة عندما يصبح عدد الصفحات أكبر. لنلخص كيفية عمل ذلك من خلال الاطلاع مرةً أخرى على مخطط معمارية الموقع الساكن الذي تحدثنا عنه في المقال السابق. إذا أراد المستخدم الانتقال إلى صفحةٍ ما، فسيرسل المتصفح طلب HTTP من النوع "GET" الذي يحدّد عنوان URL لصفحة HTML. يسترجع الخادم المستند المطلوب من نظام ملفاته ويعيد استجابة HTTP تحتوي على المستند ورمز حالة استجابة HTTP الذي يشير إلى النجاح "200 OK". يمكن أن يعيد الخادم رمز حالة مختلف مثل "404 Not Found" إذا لم يكن الملف موجودًا على الخادم أو الرمز "301 Moved Permanently" إذا كان الملف موجودًا ولكن أُعيد توجيهه إلى موقع مختلف. يحتاج خادم الموقع الساكن فقط إلى معالجة طلبات GET، لأن الخادم لا يخزّن أيّ بيانات قابلة للتعديل، ولا يغيّر استجاباته بناءً على بيانات طلب HTTP (مثل معاملات URL أو ملفات تعريف الارتباط)، لكن يُعَد فهم كيفية عمل المواقع الساكنة مفيدًا عند تعلم البرمجة من طرف الخادم، لأن المواقع الديناميكية تتعامل مع طلبات الملفات الساكنة (ملفات CSS وجافا سكريبت والصور الساكنة وغير ذلك) بالطريقة نفسها تمامًا. المواقع الديناميكية الموقع الديناميكي Dynamic Site هو الموقع الذي يمكنه إنشاء المحتوى وإعادته بناءً على عنوان URL والبيانات المحدَّدة للطلب بدلًا من إعادة الملف الثابت نفسه لعنوان URL معين. يخزّن الخادم في مثال موقع المنتجات بيانات المنتج في قاعدة بيانات بدلًا من ملفات HTML الفردية، ويحدّد معرّف المنتج ويجلب البيانات من قاعدة البيانات، ثم يبني صفحة HTML للاستجابة من خلال إدراج البيانات في قالب HTML عند تلقي طلب HTTP من النوع "GET" لمنتجٍ ما. للموقع الديناميكي مزايا كبيرة يتفوق بها على الموقع الساكن، وهي: يسمح استخدام قاعدة البيانات بتخزين معلومات المنتج بكفاءة بطريقة قابلة للتوسّع والتعديل والبحث بسهولة. يسهّل استخدام قوالب HTML تغيير بنية ملف HTML، لأنه يُطبَّق في مكان واحد فقط وفي قالب واحد، وليس عبر آلاف الصفحات الساكنة المُحتمَلة. تحليل الطلب الديناميكي يوضّح هذا القسم دورة طلب واستجابة HTTP الديناميكية بناءً على ما تحدثنا عنه في المقال السابق بمزيد من التفاصيل. سنستخدم سياق موقع ويب مدير فريق رياضي، إذ يمكن للمدرب تحديد اسم فريقه وحجم الفريق في نموذج HTML والحصول على أفضل تشكيلة مقترحة للمباراة القادمة. يوضح الشكل التالي العناصر الرئيسية لموقع مدرب الفريق مع تسميات مرقَّمة لتسلسل العمليات عندما يصل المدرب إلى قائمة "أفضل فريق". أجزاء الموقع التي تجعله ديناميكيًا هي تطبيق الويب Web Application (وهو الطريقة التي سنشير بها إلى الشيفرة البرمجية من طرف الخادم الذي يعالج طلبات HTTP ويعيد استجابات HTTP)، وقاعدة البيانات التي تحتوي على معلومات حول اللاعبين والفرق والمدربين وعلاقاتهم، وقوالب HTML. يكون تسلسل العمليات كما يلي بعد أن يرسل المدرب النموذج مع اسم الفريق وعدد اللاعبين: ينشئ متصفح الويب طلب HTTP من النوع "GET" للخادم باستخدام عنوان URL الأساسي للمورد (/best) ويرمّز الفريق ورقم اللاعب إما مثل معاملات URL (مثل /best?team=my_team_name&show=11) أو مثل جزء من عنوان URL للنمط (مثل /best/my_team_name/11/). يُستخدَم طلب "GET" لأن الطلب يجلب البيانات فقط ولا يعدّلها. يكتشف خادم الويب أن الطلب ديناميكي ويوجّهه إلى تطبيق الويب للمعالجة. يحدّد خادم الويب كيفية التعامل مع عناوين URL المختلفة بناءً على قواعد مطابقة الأنماط المُعرّفة في ضبطه. يحدد تطبيق الويب أن القصد من الطلب هو الحصول على "قائمة أفضل فريق" استنادًا إلى عنوان URL (وهو /best/) ويكتشف اسم الفريق المطلوب وعدد اللاعبين من عنوان URL. يحصل تطبيق الويب بعد ذلك على المعلومات المطلوبة من قاعدة البيانات باستخدام معاملات داخلية إضافية لتحديد اللاعبين الأفضل، ويمكن أن يحصل على هوية المدرب الذي سجّل الدخول من ملف تعريف الارتباط من طرف العميل. ينشئ تطبيق الويب صفحة HTML ديناميكيًا من خلال وضع البيانات (من قاعدة البيانات) في عناصر بديلة ضمن قالب HTML. يعيد تطبيق الويب ملف HTML المُنشَأ إلى متصفح الويب (عبر خادم الويب) مع رمز حالة HTTP التي تمثل النجاح وهي 200. إذا كان هناك أي شيء يمنع إعادة ملف HTML، فسيعيد تطبيق الويب رمزًا آخر مثل الرمز "404" للإشارة إلى أن الفريق غير موجود. يبدأ متصفح الويب بعد ذلك بمعالجة ملف HTML المُعاد، وإرسال طلبات منفصلة للحصول على أيّ ملفات CSS أو جافا سكريبت أخرى يشير إليها (راجع الخطوة 7). يحمّل خادم الويب الملفات الساكنة من نظام الملفات ويعيدها إلى المتصفح مباشرةً. تعتمد المعالجة الصحيحة للملفات على قواعد الإعداد ومطابقة نمط عنوان URL. تُعالَج عملية تحديث سجل في قاعدة البيانات بصورة مشابهة باستثناء أنه يجب ترميز طلب HTTP من المتصفح بوصفه طلب "POST" مثل أيّ تحديث لقاعدة البيانات. تنفيذ أعمال أخرى تتمثل مهمة تطبيق الويب في استقبال طلبات HTTP وإعادة استجابات HTTP. يكون التفاعل مع قاعدة بيانات للحصول على المعلومات أو تحديثها مهامًا شائعة جدًا، ولكن يمكن أن تطبّق الشيفرة البرمجية أشياءً أخرى في الوقت نفسه أو يمكن ألّا تتفاعل مع قاعدة بيانات إطلاقًا. من الأمثلة الجيدة على المهمة الإضافية التي يمكن أن يؤدّيها تطبيق الويب إرسال بريد إلكتروني إلى المستخدمين لتأكيد تسجيلهم في الموقع، كما يمكن أن يطبّق الموقع عمليات التسجيل، أو عمليات أخرى. إعادة شيء آخر غير ملف HTML لا يتعين على شيفرة موقع الويب البرمجية من طرف الخادم إعادة أجزاء أو ملفات HTML في الاستجابة، بل يمكنها بدلًا من ذلك إنشاء وإعادة أنواع أخرى من الملفات ديناميكيًا (نصوص وملفات PDF و CSV وغيرها) أو حتى بيانات (JSON و XML وغير ذلك). كانت فكرة إعادة البيانات إلى متصفح الويب ليتمكّن من تحديث محتواه AJAX ديناميكيًا موجودةٌ منذ فترة طويلة، ولكن أصبحت "التطبيقات أحادية الصفحة Single-page Apps" شائعةً في الآونة الأخيرة، إذ يُكتَب كامل موقع الويب باستخدام ملف HTML واحد يُحدَّث ديناميكيًا عند الحاجة. تدفع مواقع الويب المُنشَأة باستخدام هذا الأسلوب من التطبيقات الكثير من التكلفة الحوسبية من الخادم إلى متصفح الويب، ويمكن أن تؤدي إلى ظهور مواقع الويب التي يبدو أنها تتصرف كثيرًا مثل التطبيقات الأصيلة native (سريعة الاستجابة وما إلى ذلك). تبسيط أطر عمل الويب لبرمجة الويب من طرف الخادم تسهّل أطر عمل الويب من طرف الخادم كثيرًا كتابة الشيفرة البرمجية للتعامل مع العمليات التي وضّحناها سابقًا، وتتمثل إحدى أهم العمليات التي تطبّقها أطر عمل الويب في توفير آليات بسيطة لربط عناوين URL لمصادر أو صفحات مختلفة مع دوال معالجة محددة، مما يسهّل الاحتفاظ بالشيفرة البرمجية المرتبطة بكل نوعٍ من الموارد بصورة منفصلة، كما توجد فوائد من حيث الصيانة، إذ يمكنك تغيير عنوان URL المستخدَم لتقديم ميزة معينة في مكانٍ واحد دون الحاجة إلى تغيير دالة المعالجة. افترض مثلًا الشيفرة البرمجية التالية باستخدام إطار عمل جانغو Django (باستخدام لغة بايثون Python)، الذي يربط نمطي عنوان URL مع دالتي عرض. يضمن النمط الأول تمرير طلب HTTP مع عنوان URL للمورد /best إلى الدالة index() في الوحدة views. بينما سيُمرَّر الطلب الذي يحتوي على النمط /best/junior إلى دالة العرض junior(). # الملف: best/urls.py # from django.conf.urls import url from . import views urlpatterns = [ # مثال: /best/ url(r'^$', views.index), # مثال: /best/junior/ url(r'^junior/$', views.junior), ] ملاحظة: يمكن أن تبدو المعاملات الأولى في دوال url() غريبةً بعض الشيء (مثل المعامل r'^junior/$') لأنها تستخدم تقنية مطابقة النمط التي تسمى التعابير النمطية Regular Expressions -أو RegEx أو RE اختصارًا. لست بحاجة إلى معرفة كيفية عمل التعابير النمطية حاليًا، ولكن يجب أن تعرف أنها تسمح بمطابقة الأنماط في عنوان URL (عوضًا عن القيم الثابتة) وتُستخدَم بوصفها معاملات في دوال العرض. يمكن أن يقول التعبير النمطي RegEx البسيط: "التطابق مع حرف كبير واحد متبوع بما بين 4 إلى 7 أحرف صغيرة" مثلًا. يسهّل إطار عمل الويب على دالة العرض إحضار المعلومات من قاعدة البيانات. تُعرّف بنية بياناتنا في النماذج وهي أصناف بايثون تعرّف الحقول المراد تخزينها في قاعدة البيانات الأساسية. إذا كان لدينا نموذج يسمى "Team" مع الحقل "team_type"، فيمكننا استخدام صيغة استعلام بسيطة لاستعادة جميع الفرق التي لديها نوع معين. يحصل المثال التالي على قائمة بجميع الفرق التي يكون لديها الحقل team_type له القيمة "junior" تمامًا (حساسة لحالة الأحرف). لاحظ تنسيق اسم الحقل team_type متبوع بشرطة سفلية مزدوجة ثم نوع التطابق المراد استخدامه وهو exact في هذه الحالة. هناك العديد من أنواع التطابقات الأخرى ويمكننا ترتيبها ضمن سلسلة تعاقبية والتحكم في ترتيبها وعدد النتائج المُعادة. #best/views.py from django.shortcuts import render from .models import Team def junior(request): list_teams = Team.objects.filter(team_type__exact="junior") context = {'list': list_teams} return render(request, 'best/index.html', context) تستدعي الدالة junior() الدالة render() بعد أن تحصل على قائمة الفرق الناشئة، وتمرّر كائن HttpRequest الأصلي وقالب HTML وكائن السياق الذي يعرّف المعلومات التي ستُضمَّن في القالب. تُعَد الدالة render() دالةً ملائمةً تنشئ ملف HTML باستخدام سياق وقالب HTML وتعيده ضمن كائن HttpResponse. يمكن أن تساعدك أطر عمل الويب في كثيرٍ من المهام الأخرى، وسنناقش الكثير من الفوائد وبعض خيارات أطر عمل الويب الشائعة في المقال القادم. دورة تطوير تطبيقات الويب باستخدام لغة PHP احترف تطوير النظم الخلفية والووردبريس وتطبيقات الويب من الألف إلى الياء دون الحاجة لخبرة برمجية مسبقة اشترك الآن الخلاصة يجب أن يكون لديك الآن فهمٌ جيدٌ للعمليات التي يجب أن تطبّقها الشيفرة البرمجية من طرف الخادم، ومعرفة بعض الطرق التي يمكن من خلالها لإطار عمل الويب من طرف الخادم تسهيل ذلك. سنساعدك في المقال التالي في اختيار أفضل إطار عمل ويب لموقعك الأول. ترجمة -وبتصرُّف- للمقال Client-Server Overview. اقرأ أيضًا المقال السابق: مدخل إلى برمجة مواقع الويب من طرف الخادم دليل إعداد خادم ويب محلي خطوة بخطوة إنشاء خادم ويب في Node.js باستخدام الوحدة HTTP