عبدالهادي الديوري

الأعضاء
  • المساهمات

    354
  • تاريخ الانضمام

  • تاريخ آخر زيارة

  • Days Won

    19

السُّمعة بالموقع

159 Excellent
  1. الوسيط العكسي عبارة عن نوع من الخواديم الوسيطة Proxy servers تستقبل طلبات HTTP(S) وتوزّعها بصورة شفّافة على خادوم خلفيّ Backend server واحد أو أكثر. الوسيط العكسي مُفيد لأنّ مُعظم تطبيقات الويب في أيّامنا هذه تستعمل خواديم لم تكن قد صُمّمت لأخذ الطّلبات مُباشرة من المُستخدم وفي العادة لا تدعم إلّا ميزات HTTP بدائيّة. يُمكنك استخدام وسيط عكسيّ لتجنّب وصول المستخدم إلى الخواديم الخلفيّة مُباشرة. يُمكن استخدامها كذلك لتوزيع الحمل Load balancing من الطّلبات على عدّة خواديم، ما يُحسّن الأداء ويُوفّر حماية من تعطّل التّطبيق. يُمكها كذلك أن تمنحك ميزات لا تمنحها خواديم التّطبيقات مثل التّخبئة Caching، ضغط الملفّات Compression، وحتى تشفير SSL. سنضبُط في هذا الدّرس Apache ليعمل على وسيطًا عكسيًّا بسيطًا باستخدام وحدة mod_proxy لإعادة توجيه الطّلبات القادمة إلى خادوم خلفيّ أو أكثر من خادوم يعمل على نفس الشّبكة. سنستعمل في هذا الدّرس واجهة خلفيّة بسيطة مكتوبة بإطار العمل Flask، لكنّك تستطيع استعمال أي خادوم خلفيّ تُريده وأي لغة برمجيّة مُناسبة لك. مُلاحظة: أبدِل your_server_ip في عناوين URL التي تجدها في هذا الدّرس بعنوان IP الخاصّ بخادومك. المُتطلّبات لاتّباع هذا الدّرس، ستحتاج إلى: خادوم أوبونتو 16.04 مضبوط باتّباع الخطوات في هذا الدّرس، وذلك يشمل مُستخدما ذا صلاحيّات sudo مع ضبط مُسبق لتمكينه من تنفيذ مهام إداريّة، غير المستخدم الجذر root، بالإضافة إلى جدار ناري. Apache 2 مُنصّب على خادومك باتّباع الخطوة الأولى من هذا الدّرس. الخطوة الأولى – تفعيل وحدات Apache اللازمة يمتلك Apache الكثير من الوحدات المبنيّة مُسبقا لكنّها لا تكون مُفعّلة عند التّنصيب. لذا سيتوجّب علينا تفعيل الوحدات التي سنستخدمها في هذا الدّرس. الوحدات التّي نحتاج إليها هي mod_proxy وبضعة إضافات خاصّة بها للحصول على بضعة ميزات أخرى لدعم بروتوكولات شبكيّة مُتعدّدة. سنستعمل ما يلي بالتّحديد: mod_proxy، الوحدة الرّئيسيّة لإعادة توجيه الطّلبات، وتُمكّننا من تحويل Apache إلى بوابّة لخواديم التّطبيقات المُعتمدة. وحدة mod_proxy_http لتمكيننا من توسيط اتّصالات HTTP. وحدتا mod_proxy_balancer وmod_lbmethod_byrequests لإضافة ميّزات مُوازنة الحمل على عدّة خواديم. لتفعيل الوحدات الأربع، نفّذ الأوامر التّاليّة: sudo a2enmod proxy sudo a2enmod proxy_http sudo a2enmod proxy_balancer sudo a2enmod lbmethod_byrequests لتطبيق التّغييرات، أعد تشغيل Apache: sudo systemctl restart apache2 يُمكن الآن استخدام Apache ليعمل وسيطًا عكسيًّا لطلبات HTTP. في الخطوة – الاختياريّة - التّاليّة، سننشئ خادومين خلفيّين بسيطين. ما سيُخوّلنا التّحقّق من أنّ الإعدادات تعمل جيّدا، لكن إن كانت لديك تطبيقات تعمل في الواجهة الخلفيّة، يُمكنك تخطي الخطوة التّاليّة والانتقال مُباشرة إلى الخطوة الثّالثة. الخطوة الثّانيّة – إعداد خواديم خلفيّة Backend Servers للاختبار إنشاء خواديم خلفيّة بسيطة طريقة سهلة لاختبار ما إذا كانت إعدادات Apache الخاصّة بك تعمل جيّدًا أو لا. في هذه الفقرة، سنعدّ خادومين يجيبان على طلبات HTTP بمقطع نصّي صغير. أحد الخادومين سيُجيب بالمقطع Hello world! والآخر بالمقطع Howdy world!. مُلاحظة: في الحالات غير الاختباريّة، عادة ما يجب على الخواديم أن تُجيب بنفس الإجابة. لكن بالنّسبة لهذا الاختبار، فامتلاك إجابتين مُختلفتين يُمكّننا من التّأكّد من أنّ خاصيّة مُوازنة الحمل تستخدم كلا الخادومين. Flask إطار عمل مُصغّر مبني بلغة بايثون لبناء تطبيقات الويب. استعملنا Flask لإنشاء خادوميْ الاختبار لأنّ تطبيقا بسيطا باستخدامه لا يتطلّب سوى بضعة أسطر برمجيّة. ليس من الضّروري أن تكون لديك معرفة بلغة بايثون لإعداد الخادومين، لكن إن أردت تعلّمها، فيُمكنك الاطّلاع على هذه الدّروس. أولا، حدّث قائمة الحزم: sudo apt-get update بعدها، نصّب أداة pip لإدارة حزم لغة بايثون: sudo apt-get -y install python3-pip استخدام pip لتنصيب أداة Flask: sudo pip3 install flask بعد تنصيب المُكوّنات المطلوبة، ابدأ بإنشاء ملفّ جديد ليحتوي على شفرة الخادوم الأول في المُجلّد الشخصي للمستخدم الحاليّ: nano ~/backend1.py انسخ ما يلي إلى الملفّ ثمّ احفظه وأغلقه: from flask import Flask app = Flask(__name__) @app.route('/') def home(): return 'Hello world!' السّطران الأول والثّاني عبارة عن تهيئة لإطار العمل Flask. لدينا دالّة واحدة باسم home() تُرجع النّص Hello world!. السّطر @app.route('/') المتواجد فوق الدّالة home() يُخبر Flask بالإجابة على أي طلب يصل إلى العنوان الجذر / بما تُرجعه الدّالّة. الخادوم الثّاني مُشابه للأول، الاختلاف الوحيد أنّ الجواب مُختلف قليلا. لذا انسخ الملفّ الأول: cp ~/backend1.py ~/backend2.py عدّل الملفّ الجديد: nano ~/backend2.py عدّل ما تُرجعه الدّالة من Hello world! إلى Howdy world! ثمّ احفظ وأغلق الملفّ. from flask import Flask app = Flask(__name__) @app.route('/') def home(): return 'Howdy world!' استخدم الأمر التّالي لتشغيل الخادوم الأول على المنفذ رقم 8080. سيقوم هذا الأمر بتحويل المخرجات إلى /dev/null كذلك لتفادي مُقاطعة المُخرج للطّرفيّة. FLASK_APP=~/backend1.py flask run --port=8080 >/dev/null 2>&1 & في السّطر أعلاه، عرّفنا مُتغيّر البيئة FLASK_APP ثمّ نفّذنا الأمر flask لتشغيل الخادوم في نفس السّطر. مُتغيّرات البيئة طريقة سهلة لتمرير المعلومات إلى العمليّات المبدوءة على الطّرفيّة. في هذه الحالة، باستعمال مُتغيّرات البيئة فإنّنا نتأكّد من أنّ الإعداد يُطبّق على الأمر الذي سيكون قيد التّشغيل فقط ولن يكون مُتوفّرا بعد ذلك، وهذا مُناسب لنا لأنّنا سنمرّر اسم ملفّ آخر بنفس الطّريقة لإخبار flask بتشغيل الخادوم الثّاني. استعمل الأمر التّالي لتشغيل الخادوم الثّاني على المنفذ 8081 بطريقة مُشابهة لما سبق. لاحظ بأنّ قيمة مُتغيّر البيئة FLASK_APP مُختلفة في هذه الحالة: FLASK_APP=~/backend2.py flask run --port=8081 >/dev/null 2>&1 & يُمكنك اختبار عمل الخادومين باستخدام أداة curl. اختبار الخادوم الأول: curl http://127.0.0.1:8080/ يجب على المُخرج أن يساوي Hello world!. الخادوم الثّاني: curl http://127.0.0.1:8081/ يجب على المُخرج أن يكون Howdy world!. مُلاحظة: لإغلاق كلا الخادومين بعد أن تنتهي من استخدامهما، عند إنهائك لهذا الدّرس مثلا، فيُمكنك تنفيذ الأمر killall flask. في الخطوة التّاليّة، سنعدّل ملفّ إعدادات Apache لتمكيننا من استخدامه وسيطًا عكسيًّا. الخطوة الثّالثة – تعديل الإعدادات المبدئيّة لجعل Apache يعمل وسيطًا عكسيًّا سنعدّ في هذه الفقرة مُضيفًا افتراضيًّا Virtual host على Apache للعمل وسيطًا عكسيًّا لخادوم خلفي واحد أو عدّة خواديم موزونة الحمل. مُلاحظة: سنُطبّق الإعدادات في هذا الدّرس على مُستوى المُضيف الافتراضي. يوجد في الإعداد المبدئي لـApache مُضيف افتراضي واحد مُفعّل. لكنّك تستطيع استعمال جميع أجزاء هذه الإعدادات على أي مُضيف افتراضي آخر. إن كان خادوم Apache الخاص بك يجيب على طلبات HTTP و HTTPS، فسيتوجّب عليك وضع إعدادات الوسيط العكسي في كلا المُضيفَين الافتراضيّيْن لـHTTP وHTTPS. افتح ملفّ إعدادات Apache المبدئي باستخدام nano أو أي مُحرّر مُفضّل لديك: sudo nano /etc/apache2/sites-available/000-default.conf ستجد داخل هذا الملفّ الجزء <VirtualHost *:80> بدءا من السّطر الأول. يشرح المثال الأول أسفله كيفيّة ضبط هذا الجزء لإعداد وسيط عكسي لخادوم واحد، وفي المثال الثّاني سنضبط وسيطا عكسيّا يوازن الحمل على أكثر من خادوم. المثال الأول – إعداد وسيط عكسي لخادوم خلفيّ واحد أبدل جميع المُحتويات داخل الوسم VirtualHost بما يلي ليصير ملفّ الإعدادات الخاصّ بك كما يلي: <VirtualHost *:80> ProxyPreserveHost On ProxyPass / http://127.0.0.1:8080/ ProxyPassReverse / http://127.0.0.1:8080/ </VirtualHost> إن تابعت الأمثلة في الخطوة الثّانيّة أعلاه، فاستخدم العنوان 127.0.0.1:8080 كما هو مكتوب أعلاه. إن كان لديك تطبيق ويب خاصّ بك، فاستعمل عنوان التّطبيق عوضا عمّا فعلنا. لدينا ثلاثة تعليمات في هذا الإعداد: ProxyPreserveHost مسؤولة عن جعل Apache يُمرّر الترويسة Header المسمَّاة Host الأصليّة إلى الخادوم الخلفيّ. هذا الأمر مُفيد لأنّه يُخبر الخادوم بالعنوان المُستخدَم للوصول إلى التّطبيق. ProxyPass عبارة عن تعليمة الإعداد الرّئيسيّة للوسيط. في هذه الحالة، نحدّد كلّ شيء تحت عنوان URL الجذر / ليُربط مع الخادوم الخلفيّ المرتبط بالعنوان المُعطى. على سبيل المثال، إن استقبل Apache طلبا للموجّه /example، فسيقوم بالاتّصال بالعنوان http://your_backend_server/example وإرجاع الإجابة إلى العميل. ينبغي على ProxyPassReverse أن يحمل نفس الإعداد الذي يحمله ProxyPass. ويُخبر Apache بتعديل ترويسة الجواب من طرف الخادوم الخلفيّ. بهذه الطّريقة سنتأكّد من إعادة توجيه العميل إلى عنوان الوسيط وليس الخادوم الخلفيّ في حالة وُجدت ترويسة إعادة توجيه في جواب الخادوم لتجنّب أخطاء إعادة التّوجيه. لتطبيق هذه التّغييرات، أعد تشغيل Apache: sudo systemctl restart apache2 إن حاولت الآن الوصول إلى http://your_server_ip في مُتصفّح ويب، ستجد جواب الخادوم الخلفيّ عوضا عن رسالة ترحيب Apache المألوفة. إن تابعت الخطوة الثّانيّة، فهذا يعني بأنّ النّتيجة ستكون Hello world!. المثال الثّاني – موازنة الحمل على عدّة خواديم خلفيّة إن كان لديك أكثر من خادوم خلفيّ واحد، فاستخدام ميزة مُوازنة الحمل في mod_proxy طريقة جيّدة لتوزيع الطّلبات على الخواديم. استبدل جميع مُحتويات الجزء VirtualHost بما يلي ليبدو ملفّ الإعدادات كما يلي: <VirtualHost *:80> <Proxy balancer://mycluster> BalancerMember http://127.0.0.1:8080 BalancerMember http://127.0.0.1:8081 </Proxy> ProxyPreserveHost On ProxyPass / balancer://mycluster/ ProxyPassReverse / balancer://mycluster/ </VirtualHost> الإعدادات هنا مُشابهة لما سبق، لكن عوضا عن تخصيص خادوم واحد مُباشرة، استعملنا وسم Proxy إضافيّا لتعيين خواديم مُتعدّدة.سمّينا الوسم بـbalancer://mycluster (يُمكنك تغيير الاسم إن شئت) ليحتوي على أكثر من تعليمة BalancerMember، والذي يُحدّد عناوين الخواديم الخلفيّة. هكذا تستعمل التّعليمتان ProxyPass وProxyPassReverse مجموعة موازنة الحمل المُسمّاة mycluster عوضا عن خادم مُحدّد. إن تابعت الخطوة الثّانيّة من هذا الدّرس، فاستعمل العنوانين 127.0.0.1:8080 و127.0.0.1:8081 للتّعليمة BalancerMember كما هو مُبيّن في الإعدادات أعلاه. أمّا إن كانت لديك خواديم خاصّة بك فاستعمل عناوينها. لتطبيق هذه التّغييرات، أعد تشغيل Apache: sudo systemctl restart apache2 إن حاولت الآن الوصول إلى http://your_server_ip في مُتصفّح ويب، ستجد جواب الخواديم الخلفيّة عوضا عن رسالة ترحيب Apache المألوفة. إن تابعت الخطوة الثّانيّة، فعند إعادة تحميل الصّفحة عدّة مرّات، ستُلاحظ بأنّ النّتيجة ستكون إمّا Hello world! أو Howdy world!، ما يعني بأنّ الوسيط العكسي يعمل على مُوازنة الحمل بين الخادومين. خاتمة لديك الآن معرفة بكيفيّة إعداد خادوم Apache ليعمل وسيطًا عكسيًّا لخادوم تطبيق أو أكثر. يُمكن استعمال mod_proxy لضبط وسيط عكسي لخواديم تطبيقات مكتوبة بلغات مثل Python مع Django أو Ruby مع Ruby on Rails. يُمكن كذلك استخدامها لموازنة الحمل على عدّة خواديم للتّطبيقات التي تستقبل العديد من الزّوار، بالإضافة إلى إمكانيّة استخدامه لتوفير حماية SSL للخواديم الخلفيّة التي لا تدعم SSL. رغم أنّ mod_proxy وmod_proxy_http أكثر تركيب يتمّ استخدامه، إلّا أنّ هناك العديد من الوحدات الأخرى التّي تدعم بروتوكولات اتّصال أخرى. إليك بعضا من الوحدات الشّهيرة التي لم نستخدمها في هذا الدّرس: mod_proxy_ftp لبروتوكول FTP. mod_proxy_connect لإعداد أنفاق SSL. mod_proxy_ajp لبروتوكول AJP (Apache JServ Protocol) لخواديم خلفيّة مبنيّة على Tomcat. mod_proxy_ftp لمقابس الويب Web sockets. يُمكنك قراءة التّوثيق الرّسمي للاستزادة حول mod_proxy. ترجمة – بتصرّف - للمقال How To Use Apache as a Reverse Proxy with mod_proxy on Ubuntu 16.04 لكاتبه Mateusz Papiernik.
  2. تعرفنا في درس سابق على كيفية تفعيل وحدة mod_rewrite وضبطها لإعادة كتابة عناوين URL في خادوم Apache.ا سنعتمد في هذا الشرح على ما تعلمناه سابقًا ونتوسّع - مع مثاليْن عمليّيْن - في شرحٍ بعض من أكثر التّعليمات استخداما في ضبط mod_rewrite. المثال الأول – تبسيط نصوص الاستعلام Query strings باستخدام RewriteRule تستعمل تطبيقات الوِب في العادة نصوص الاستعلام التي يُمكن إضافتها إلى عنوان URL باستخدام علامة استفهام (?) بعد العنوان. تُمرَّر المُعاملات المُختلفة باستخدام علامة &. يُمكن استخدام نصوص الاستعلام لتمرير معلومات إضافيّة بين صفحات تطبيق الوِب. على سبيل المثال، يُمكن لصفحة نتائج بحث مكتوبة بلغة PHP أن تستعمل عنوانا مثل http://example.com/results.php?item=shirt&season=summer. في هذا المثال، هناك مُعاملان إضافيّان، المُعامل item مع القيمة shirt، والمُعامل season ذو القيمة summer. سيستعمل التّطبيق هذه المعلومات لتقديم أكثر نتيجة مناسبة للمُستخدم. تُكتب قواعد إعادة الكتابة في Apache عادة لتبسيط روابط طويلة مثل التي أعلاه إلى عناوين URL بسيطة سهلة القراءة والفهم. في هذا المثال، نريد تبسيط ما سبق ليكون على شكل http://example.com/shirt/summer. كما تُلاحظ، فالقيمتان shirt و summer لا تزالان مُتواجدتيْن داخل العنوان، لكن دون نصّ استعلام واسم ملفّ PHP. إليك قاعدة لتحقيق مُرادنا: RewriteRule ^shirt/summer$ results.php?item=shirt&season=summer [QSA] المقطع shirt/summer واضح وقد أخبرنا Apache بتوجيه أي طلبات مطابقة إلى العنوان results.php?item=shirt&season=summer. تُستخدم الخيارات [QSA] عادة في قواعد إعادة الكتابة. وتُخبر Apache بإضافة أي نصّ استعلام إضافي إلى عنوان URL المُقدَّم، لذا لو كتب زائر http://example.com/shirt/summer?page=2 فسيجيب الخادوم بـ results.php?item=shirt&season=summer&page=2. إن لم تُضف هذه الخيارات فسيُتجاهل نصّ الاستعلام الإضافي. رغم أنّ هذه الطّريقة تُحقّق هدفنا، إلّا أنّنا كتبنا القيمتين بصراحة داخل الملفّ. لذا فالقاعدة لن تعمل لأي قيم أخرى مثل pants وwinter. لجعل القاعدة عامّة أكثر، يُمكننا استخدام التّعابير النّمطيّة لمُطابقة أجزاء من العنوان الأصلي واستعمالها في مقطع بدل نمطي. بحيث تكون القاعدة المُعدّلة كما يلي: RewriteRule ^([A-Za-z0-9]+)/(summer|winter|fall|spring) results.php?item=$1&season=$2 [QSA] التّعبير النمطي الأول داخل القوسين يُطابق أي مقطع نصّ مكوّن من أحرف وأرقام مثل shirt و pants ويحفظ المقطع المُطابق في المُتغيّر $1. التّعبير النمطي الذي يتبعه يُطابق حرفيًّا كلّا من summer، winter، fall و spring، ويحفظ أي مُطابق في المُتغيّر $2. تُستعمل المقاطع المُطابقة بعدها في عنوان URL النّاتج لتمريرها إلى كل من المُعاملين item وseason عوضا عن كتابة القيمتين على نحو صريح كما فعلنا سابقا. القاعدة أعلاه ستستبدل على سبيل المثال العنوان http://example.com/pants/summer إلى http://example.com/results.php?item=pants&season=summer. هذا المثال قابل للتماشي مع التّطويرات المُستقبليّة كذلك، بحيث يُمكن إعادة كتابة العناوين لعدّة قيم باستخدام قاعدة واحدة فقط. المثال الثّاني – إضافة شروط منطقيّة باستخدام RewriteConds قواعد إعادة الكتابة ليست محصورة في قواعد تُترجم واحدة تلو الأخرى دون أية قيود. تُمكّننا تعليمة RewriteCond من إضافة شروط لقواعد إعادة الكتابة للتحكم في وقت ترجمة القواعد. تتّبع RewriteCond التّنسيق التّالي: RewriteCond TestString Condition [Flags] RewriteCond: تعليمة الشرط. TestString النّص الذي سيُجرى عليه الاختبار. Condition نمط أو شرط للاختبار به. Flags عبارة عن مُعاملات إضافيّة يُمكن لها تعديل الشّرط وقواعد التّحويل. إذا أرجع شرط في RewriteCond القيمة المنطقيّة true (أي أنّ الشرط قد تحقّق)، فستُنفَّذ قاعدة RewriteRule التي تلي الشرط مُباشرة. إن لم يتحقّق الشّرط فتُتجاهل القاعدة. يُمكن استخدام أكثر من شرط. يجب مبدئيًّا على جميع الشّروط أن تتحقّق لتطبيق القاعدة التي تليها. على سبيل المثال، لنفترض بأنّك تريد إعادة توجيه جميع الطّلبات المُوجّهة إلى ملفّات أو مُجلّدات غير موجودة إلى الصّفحة الرّئيسيّة عوضا عن عرض صفحة الخطأ 404. يُمكن تحقيق هذا الهدف بالشّروط التّاليّة: RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . / في المثال أعلاه: %{REQUEST_FILENAME} يُمثّل النصّ المُختبَر. في هذه الحالة، القيمة ستكون عبارة عن اسم الملفّ الذي طلبه المُستخدم وهو مُتغيّر نظام خاص يتوفّر في كل طلب. f- شرط متوفّر مُسبقا في وحدة mod_rewrite للتّحقّق من أنّ الاسم الذي طلبه المُستخدم مُتواجد على القرص وبأنّه ملفّ فعلا. أمّا ! فهي علامة نفي. بجمعهما، فـf-! تتحقّق من أنّ الاسم الذي طلبه الاستعلام غير مُتواجد أو أنّه ليس ملفّا. d-! يعمل بطريقة مُشابهة ولكن بالنسبة للمجلّدات، إذ يُساوي القيمة true فقط إن كان الاسم المطلوب غير موجود أو أنّه ليس مجلّدا. ستُنفَّذ قاعدة RewriteRule الموجودة في السطر الأخير فقط إن تلقى الخادوم طلبا لملفّ أو مُجلّد غير موجود. القاعدة بسيطة وكلّ ما تفعله هو إعادة توجيه الطّلب إلى موجّه الجذر / في الموقع (والذي يكون عادة صفحة رئيسيّة). خاتمة mod_rewrite وحدة مُفيدة جدّا في Apache يُمكن استخدامها لتوفير عناوين URL سهلة القراءة. في هذا الدّرس، تعلّمتَ كيفيّة استخدام تعليمة RewriteRule لإعادة توجيه المُستخدمين حتى ولو كانت تتضمّن نصوص استعلام. تعلّمت كذلك كيفيّة إعادة التّوجيه حسب شروط باستعمال التّعليمة RewriteCond. للتّعرف أكثر على mod_rewrite، ألق نظرة على هذه الصّفحة والتّوثيق الرّسمي لـmod_rewrite من Apache. ترجمة – بتصرّف - للمقال How To Rewrite URLs with mod_rewrite for Apache on Ubuntu 16.04 لكاتبه Mateusz Papiernik.
  3. مُقدّمة سنتعرّف في هذا الدّرس على كيفيّة إعادة كتابة عناوين URL باستخدام وحدة mod_rewrite الخاصّة بـApache 2. تمنحنا هذه الوحدة حريّة إعادة كتابة روابط URL لتكون أكثر نظافة وتنسيقا عبر ترجمة مسارات قابلة للقراءة إلى استعلامات مُوجّهة لتطبيق الوِب أو إعادة توجيه المُستخدم حسب شروط إضافيّة. هذا الدّرس مُقسّم إلى جزأين. بحيث نجهّز في الجزء الأول موقعا إلكترونيا بسيطا مع مثال بسيط لإعادة كتابة عنوان URL. يُغطّي الجزء الثّاني مثالين مُعمّقين لأكثر قواعد إعادة الكتابة شيوعا. المُتطلّبات لاتّباع هذا الدّرس، ستحتاج إلى: خادوم أوبونتو 16.04 مضبوط باتّباع الخطوات الواردة في درس الإعداد الابتدائي لخادوم أوبونتو، وذلك يشمل مُستخدما ذا صلاحيّات sudo مع ضبط مُسبق لتمكينه من تنفيذ مهام إداريّة، غير المستخدم الجذر root، بالإضافة إلى جدار ناري. Apache 2 مُنصّب على خادومك باتّباع الخطوة الأولى من درس تثبيت حزم LAMP على أوبونتو. الخطوة 1 – تفعيل mod_rewrite أولا، نحتاج إلى تفعيل mod_rewrite. مبدئيًّا، الوحدة متوفّرة لكنّها غير مُفعّلة عند تنصيب Apache 2. sudo a2enmod rewrite سيقوم هذا الأمر بتفعيل الوحدة أو إخبارك بأنّ الوحدة قد سبق تفعيلها. لتطبيق التّغييرات، أعد تشغيل Apache: sudo systemctl restart apache2 وحدة mod_rewrite مُفعّلة الآن. سنضبُط في الخطوة التّاليّة ملفّ .htaccess لاستخدامه لتحديد قواعد إعادة الكتابة لإعادات التّوجيه Redirects. الخطوة 2 – ضبط htaccess. يسمح لنا ملفّ .htaccessبتعديل قواعد إعادة الكتابة دون الحاجة إلى الوصول إلى ملفّات إعدادات الخادوم. لهذا السّبب، فملفّ .htaccess مُهمّ جدّا لحماية تطبيق الوِب الخاصّ بك. النّقطة في أول الاسم تُشير إلى أنّ الملفّ مخفي. مُلاحظة: يُمكن لأي قواعد تضعها داخل ملفّ .htaccess أن توضع كذلك داخل ملفّات إعدادات الخادوم، في الحقيقة، ينصح التوثيق الرّسمي لـApache ينصح باستعمال ملفات إعدادات الخادوم عوضا عن ملفّ .htaccess لقدرة Apache على مُعالجتها بسرعة أعلى. مع ذلك، في مثالنا البسيط، الفرق في الأداء لن يكون ملحوظا. بالإضافة إلى أنّ وضع القواعد داخل ملفّ .htaccess مُريح أكثر، خاصّة إن كان لديك الكثير من المواقع الإلكترونيّة في خادوم واحد. إذ لا تتطلّب التّغييرات إعادة تشغيل الخادوم ولا تحتاج إلى صلاحيّات المُستخدم الجذر Root لتعديل الملفّ، ما يُسهّل إجراء التّغييرات بحساب مُستخدم عاديّ. بعض البرمجيّات مفتوحة المصدر مثل جوملا ، ووردبريس ولارافل تعتمد على ملفّ .htaccess لإجراء التّعديلات وإضافة قواعد إضافيّة حسب الطّلب. سنحتاج إلى ضبط وتأمين بعض الإعدادات قبل أن نبدأ. يمنع Apache مبدئيًّا استخدام ملفّ .htaccess لقواعد إعادة كتابة روابط URL، لذا سيتوجّب عليك أولا تفعيل إمكانيّة استخدام الملفّ. افتح ملفّ إعدادات Apache المبدئية باستخدام nano أو مُحرّرك المفضّل. sudo nano /etc/apache2/sites-available/000-default.conf ستجد داخل هذا الملفّ الجزء <VirtualHost *:80> في أول سطر. داخل هذا الجزء، أضف الجزء التّالي مباشرة بعد السطر الذي يبدأ بـDocumentRoot: <Directory /var/www/html> Options Indexes FollowSymLinks MultiViews AllowOverride All Require all granted </Directory> ليُصبح ملفّ الإعدادات كما يلي (حذفنا - للاختصار - التعليقات، وهي الأسطر التي تبدأ بـ# من المُقتَطع أدناه). تأكّد من أنّ الإزاحة صحيحة: <VirtualHost *:80> ServerAdmin webmaster@localhost DocumentRoot /var/www/html <Directory /var/www/html> Options Indexes FollowSymLinks MultiViews AllowOverride All Require all granted </Directory> ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost> احفظ وأغلق الملفّ. ملحوظة: يفترض هذا الدليل وجودَ موقع واحد على خادومك، في هذه الحالة يكفي التعديل على ملف المضيف الافتراضي Virtual host المبدئي (000-default.conf) بالطريقة أعلاه لتفعيل إمكانيّة إعادة التوجيه. إن كان لديك أكثر من موقع فستحتاج لإجراء التعديل أعلاه على ملفّ المضيف الافتراضي الخاصّ بالموقع الذي تريد. توجد ملفات المضيفات الافتراضية على المسار /etc/apache2/sites-available/. لتطبيق التّغييرات، أعد تشغيل خادوم Apache: sudo systemctl restart apache2 الآن، أنشئ ملفّ .htaccess داخل مجلّد الوب الجذر: sudo nano /var/www/html/.htaccess أضف السّطر التّالي إلى أعلى الملفّ لتفعيل مُحرّك إعادة الكتابة Rewrite engine: RewriteEngine on احفظ وأغلق الملفّ. يُمكنك الآن استعمال الملفّ .htaccess للتّحكم بقواعد المُوجّهات في تطبيق الوِب الخاصّ بك. الخطوة 3 – ضبط إعادات كتابة روابط URL سنعدّ في هذه الفقرة إعادة كتابة بسيطة لرابط URL، بحيث نستطيع تحويل عناوين URL جميلة إلى مسارات يُمكن للشفرة فهمها. سنسمح بالخصوص للمُستخدمين بالوصول إلى العنوان http://your_server_ip/about. لنبدأ بإنشاء ملفّ باسم about.php داخل مُجلّد الوب: sudo nano /var/www/html/about.php انسخ شفرة HTML التّاليّة إلى الملفّ واحفظه ثمّ أغلقه: <html> <head> <title>About Us</title> </head> <body> <h1>About Us</h1> </body> </html> ملحوظة: تأكّد من أن خادوم الوِب لديه صلاحيات الوصول إلى الملف الذي أنشأته للتو. مثلا، بإعطاء الأذون 755على مجلد الوِب: sudo chmod -R 755 /var/www يُمكنك الوصول إلى هذه الصّفحة على العنوان http://your_server_ip/about.html، لكن لو حاولت الوصول إلى العنوان http://your_server_ip/about، فستُلاحظ خطأ 404 Not Found، إن أردت تمكين مُستخدميك من استعمال هذا العنوان عوضا عن العنوان السّابق (أي دون الجزء .html) فقواعد إعادة الكتابة كفيلة بتوفير هذه الوظيفة. تتّبع جميع قواعد الكتابة التّنسيق التّالي: RewriteRule pattern substitution [flags] بحيثُ: RewriteRule يُحدّد التّعليمة. pattern تعبير نمطي Regular Expression يُحدّد عنوان URL المرغوب به، هذا هو ما سيكتبه المُستخدم في شريط عنوان URL. substitution: البدل، وهو مسار عنوان URL الأصلي، (مسار الملفّ الذي يقوم Apache بتقديمه). flags عبارة عن مُعاملات اختياريّة لتعديل آليّة عمل القاعدة. افتح ملفّ .htaccess: sudo nano /var/www/html/.htaccess بعد السّطر الأول، أضف السّطر الثّاني ممّا يلي: RewriteEngine on RewriteRule ^about$ about.html [NC] في هذه الحالة، المقطع ^about$ هو التّعبير النّمطي، about.html يُعبّر عن البدل، و [NC] هي المُعاملات. استخدمنا في هذا المثال عدّة محارف تحمل معان خاصّة: ^ يُشير إلى بداية العنوان بعد المقطع your_server_ip/. $ يُشير إلى نهاية عنوان URL. about المقطع الذي يجب على التّعبير النّمطي مُطابقته. about.html اسم الملفّ الأصلي الذي يصل إليه المُستخدم [NC] خيار لجعل القاعدة تتجاهل حالة الأحرف Case insensitive. ينبغي الآن أن تستطيع الوصول إلى العنوان http://your_server_ip/about في مُتصّفح الويب الخاصّ بك. في الحقيقة، بالقاعدة التي كتبناها أعلاه، فالعناوين التّاليّة ستؤدّي جميعها إلى الملفّ about.html: http://your_server_ip/about، بسبب تعريفنا للقاعدة. http://your_server_ip/About، لأنّ القاعدة تتجاهل حالة الأحرف. http://your_server_ip/about.html، لأنّ اسم الملفّ الأصلي سيعمل دائما. العناوين التّاليّة لن تعمل: http://your_server_ip/about/، لأنّ القاعدة تنصّ بوضوح بأنّه لا يجوز على أي شيء أن يكون بعد about باستخدام المحرف $. http://your_server_ip/contact، لأنّ العنوان لن يُطابق المقطع about. تمتلك الآن ملفّ .htaccess مع قاعدة بسيطة، يُمكنك الآن تعديله وتوسيعه حسب حاجاتك. سنتعرّف في الجزء الثاني من هذا الدرس على أمثلة إضافية لإعادة كتابة الروابط والتعليمات الأكثر استخداما مع mod_rewrite. ترجمة – بتصرّف - للمقال How To Rewrite URLs with mod_rewrite for Apache on Ubuntu 16.04 لكاتبه Mateusz Papiernik.
  4. تعرّفنا في الجزأين السابقيْن من هذا الدليل على كيفية عرض معلومات عن مساحات التخزين وإنشاء مساحات تخزين جديدة وتحجيمها. سنكمل في هذا الجزأ - الأخير من الدليل - ما تعلمناه سابقا ونتعرّف على المهمة الأخيرة من بين المهمّات الأساسية في إدارة التخزين بآلية LVM. تنبيه: تأكّد من أنّ الأجهزة التّي تودّ تطبيق الأوامر المذكورة في هذا الدرس عليها لا تحتوي على بيانات مُهمّة. استخدام هذه الأجهزة مع LVM سيؤدّي إلى الكتابة فوق المحتويات الحاليّة. يُفضَّل أن تختبر الخطوات المعروضة أدناه في آلة افتراضية أو على خادوم خاصّ بأغراض التجربة والاختبار. إزالة أو تقليص مُكوّنات LVM بما أن تقليص مساحة التّخزين قد يؤدي إلى فقدان البيانات، فإجراءات تقليص المساحة المتوفّرة، سواءٌ بتقليص حجم أو حذف المكوّنات، أكثر تعقيدًا من بقية المهامّ. تخفيض حجم وحدة تخزين منطقيّة لتقليص وحدة تخزين منطقيّة، يجب عليك أولا أخذ نسخة احتياطية من بياناتك. فبما أن هذه العمليّة تقلّص مساحة التّخزين المُتوفّرة فإن أي خطأ يُمكن له أن يُؤدي إلى فقدان البيانات. إذا كنت جاهزا، تأكّد من حجم المساحة المُستخدمة حاليّا: df -h المُخرج: Filesystem Size Used Avail Use% Mounted on . . . /dev/mapper/LVMVolGroup-test 4.8G 521M 4.1G 12% /mnt/test في هذا المثال، يبدو بأنّنا نستخدم حاليّا حوالي 521M من المساحة. استعمل هذه المعلومة لتحديد الحجم الذي تريد تقليص وحدة التخزين إليه. تاليّا، أزل تركيب نظام الملفّات. فعلى عكس التوسيعات، يجب عليك تقليص مساحة نظام الملفّات أثناء إزالة التركيب: cd ~ sudo umount /dev/LVMVolGroup/test بعد إزالة التركيب، تأكّد من أن نظام الملفّات في حالة جيّدة. مرّر نوع نظام الملفّات عبر الخيار -t. سنستعمل الخيار -f للتّحقّق من أن كل شيء على ما يرام حتى ولو بدا كذلك: sudo fsck -t ext4 -f /dev/LVMVolGroup/test بعد التّحقّق من سلامة نظام الملفّات، يُمكنك تقليص مساحته باستخدام الأدوات الخاصّة، في حالة Ext4، فالأمر سيكون resize2fs. مرّر الحجم النّهائي لنظام الملفّات. تنبيه: أكثر خيار أمانا هو تمرير حجم نهائي أكبر بكثير من الحجم المُستخدم حاليا. أعط نفسك مساحة أمان لتجنّب فقدان البيانات وتأكّد من إنشاء نسخ احتياطيّة. sudo resize2fs -p /dev/LVMVolGroup/test 3G حالما تنتهي العمليّة، قلّص حجم وحدة التّخزين المنطقيّة عبر تمرير نفس الحجم (3G في هذه الحالة) إلى الأمر lvresize عبر الخيار -L. sudo lvresize -L 3G LVMVolGroup/test ستستقبل تنبيها حول فقدان البيانات، إن كنت جاهزا، فاكتب y للاستمرار. بعد تقليص حجم وحدة التّخزين المنطقيّة، تحقّق من سلامة نظام الملفّات مُجدّدا: sudo fsck -t ext4 -f /dev/LVMVolGroup/test إن سار كلّ شيء على ما يرام، فستستطيع إعادة وصل نظام الملفّات بالأمر المُعتاد: sudo mount /dev/LVMVolGroup/test /mnt/test ينبغي الآن على وحدة التّخزين المنطقيّة أن تُقلّص إلى الحجم المُحدّد. حذف وحدة تخزين منطقيّة إن لم تعد بحاجة إلى وحدة تخزين منطقيّة، يُمكنك حذفها باستعمال الأمر lvremove. أولا، أزل تركيب وحدة التخزين: cd ~ sudo umount /dev/LVMVolGroup/test بعدها، احذف وحدة التخزين بالأمر التّالي: sudo lvremove LVMVolGroup/test سيُطلب منك تأكيد العمليّة، إن كنت متأكّدا من رغبتك في حذف وحدة التّخزين، فاكتب y. حذف مجموعة تخزين لحذف مجموعة تخزين كاملة، بما في ذلك جميع وحدات التّخزين المنطقيّة المتواجدة بها، استعمل الأمر vgremove. قبل حذف مجموعة تخزين، عليك حذف وحدات التّخزين بداخلها بالعمليّة أعلاه. أو على الأقل، تأكّد من إزالة تركيب جميع وحدات التّخزين بداخل المجموعة: sudo umount /dev/LVMVolGroup/www sudo umount /dev/LVMVolGroup/projects sudo umount /dev/LVMVolGroup/db بعدها يُمكنك حذف مجموعة التّخزين عبر تمرير اسمها إلى الأمر vgremove: sudo vgremove LVMVolGroup سيُطلب منك تأكيد عمليّة حذف المجموعة. إذا كانت هناك أية وحدات تخزين منطقيّة مُتبقيّة، ستُسأل عن تأكيد حذفها واحدة واحدة قبل حذف المجموعة. حذف وحدة تخزين ماديّة إن أردت إزالة وحدة تخزين ماديّة من إدارة LVM، فسيعتمد الإجراء المطلوب على ما إذا كانت الوحدة مُستخدمة من طرف LVM أو لا. إن كانت وحدة التّخزين الماديّة مُستخدَمة، سيتوجّب عليك نقل المداءات الماديّة على الجهاز إلى مكان آخر. سيتطلّب ذلك وجود وحدات تخزين ماديّة أخرى لتحتضن المداءات الماديّة. إن كنت تستعمل أنواعا مُعقّدة من وحدات التّخزين المنطقيّة، فقد يتوجّب عليك الحصول على المزيد من وحدات التّخزين الماديّة حتى ولو كانت لديك مساحة تخزين كافيّة. إن كان لديك عدد كاف من وحدات التّخزين الماديّة في مجموعة التّخزين لاحتضان المداءات الماديّة، فانقلها إلى خارج وحدة التّخزين الماديّة التي ترغب في إزالتها عبر كتابة ما يلي: sudo pvmove /dev/sda يُمكن لهذه العمليّة أن تأخذ وقتا طويلا حسب حجم وحدات التّخزين وكميّة البيانات التي يتوجب نقلها. حالما تنتقل المداءات إلى وحدات تخزين أخرى، يُمكنك حذف وحدة التّخزين الماديّة من مجموعة التّخزين عبر الأمر التّالي: sudo vgreduce LVMVolGroup /dev/sda هذه العمليّة كفيلة بإزالة وحدات التّخزين التي أُخلِيت من مجموعة التّخزين. بعد انتهاء العمليّة، يُمكنك حذف علامة وحدة التّخزين الماديّة من جهاز التّخزين عبر الأمر: sudo pvremove /dev/sda ينبغي الآن أن تتمكّن من استعمال جهاز التّخزين المُزال لأغراض أخرى أو إزالته من النّظام بالكامل. ختاما إلى هذه النّقطة، يجب أن يكون لديك فهم لكيفيّة إدارة أجهزة التّخزين على أوبونتو 16.04 مع LVM. يجب أن تعرف كيفيّة الحصول على معلومات عن مكوّنات LVM، كيفيّة استخدام LVM لتركيب نظام تخزين خاص بك، وكيفيّة تعديل وحدات التّخزين لتلبيّة حاجاتك. يُمكنك اختبار هذه المبادئ في بيئة آمنة لفهم أعمق حول آليّة عمل LVM ومكوّناته. ترجمة – بتصرّف - للمقال How To Use LVM To Manage Storage Devices on Ubuntu 16.04 لكاتبه Justin Ellingwood.
  5. بعد أن تعرّفنا في الجزء السابق على كيفية عرض معلومات عن مختلف العناصر في LVM، سنتطرّق في هذا الجزء من الدليل إلى كيفية التحكم في هذه المكوّنات بإنشاء مكوّنات جديدة أو إعادة تحجيم (توسيع أو تقليص) مكوّنات موجودة. إنشاء أو توسيع مكوّنات LVM سنتحدّث في هذه الفقرات عن كيفيّة إنشاء وتوسيع وحدات التّخزين الماديّة والمنطقيّة وكذا مجموعات التّخزين. إنشاء وحدات تخزين ماديّة من أجهزة تخزين خام لاستعمال أجهزة التّخزين مع LVM، يجب عليها أولا أن تُعلّم على أنها وحدات تخزين ماديّة. ما يُحدّد إمكانيّة استخدام الجهاز داخل مجموعة تخزين. أولا، استعمل الأمر lvmdiskscan لإيجاد جميع أجهزة التّخزين التي يُمكن لـLVM رؤيتها واستخدامها: sudo lvmdiskscan المُخرج: /dev/ram0 [ 64.00 MiB] /dev/sda [ 200.00 GiB] /dev/ram1 [ 64.00 MiB] . . . /dev/ram15 [ 64.00 MiB] /dev/sdb [ 100.00 GiB] 2 disks 17 partitions 0 LVM physical volume whole disks 0 LVM physical volumes في المُخرج أعلاه، بتجاهل أجهزة /dev/ram*، يُمكننا رؤية الأجهزة التّي يُمكن تحويلها إلى وحدات تخزين ماديّة لـLVM. تنبيه: تأكّد من أنّ الأجهزة التّي ترغب باستعمالها مع LVM لا تحتوي على أيّة بيانات مُهمّة. استخدام هذه الأجهزة مع LVM سيؤدّي إلى الكتابة فوق المحتويات الحاليّة. إذا كنت تمتلك بيانات مهمّة على خادومك فأنشئ نسخا احتياطية قبل الاستمرار في تطبيق الدّرس. لجعل أجهزة التّخزين وحدات تخزين ماديّة خاصّة بـLVM، استعمل الأمر pvcreate. يُمكنك تمرير عدّة أجهزة في نفس الوقت: sudo pvcreate /dev/sda /dev/sdb هذا الأمر سيكتب ترويسة LVM على جميع الأجهزة الهدف لتخصيصها كوحدات تخزين LVM ماديّة. إنشاء مجموعة تخزين من وحدات التّخزين الماديّة لإنشاء مجموعة تخزين جديدة من وحدات التّخزين الماديّة، استعمل الأمر vgcreate. سيتوجّب عليك تمرير اسم للمجموعة متبوعا بوحدة تخزين ماديّة واحدة على الأقل: sudo vgcreate volume_group_name /dev/sda استبدل volume_group_name باسم من اختيارك لمجموعة التّخزين. سينشئ المثال أعلاه مجموعة تخزين انطلاقا من وحدة تخزين ماديّة واحدة فقط. يُمكنك تمرير أكثر من وحدة تخزين ماديّة عند إنشاء المجموعة إن أردت ذلك: sudo vgcreate volume_group_name /dev/sda /dev/sdb /dev/sdc عادة ستحتاج إلى مجموعة تخزين واحدة فقط لكل خادوم. بحيث يُمكنك إضافة أي مساحة تخزين إضافيّة لمنطقة مُوحّدة ثمّ تخصيص وحدات تخزين منطقيّة منها. الحاجة إلى استخدام أحجام مداءات مُختلفة من الأسباب التّي قد تدفعك إلى إنشاء أكثر من مجموعة تخزين واحدة. في العادة، لن يتوجّب عليك تحديد حجم المدى (الحجم المبدئي هو 4M ويُعدّ كافيّا لمُعظم الاستخدامات)، لكنّ إن أردت، يُمكنك تحديد حجم المداءات عند إنشاء مجموعة التّخزين باستعمال الخيار -s: sudo vgcreate -s 8M volume_group_name /dev/sda سينشئ هذا الأمر مجموعة تخزين مع حجم مداءات يُساوي 8M. إضافة وحدة تخزين ماديّة إلى مجموعة تخزين موجودة مُسبقا لتوسيع مجموعة تخزين عبر إضافة وحدات تخزين ماديّة، استخدم الأمر vgextend. يأخذ هذا الأمر مجموعة تخزين متبوعة بوحدات التّخزين الماديّة التّي ترغب بإضافتها إلى المجموعة. يُمكنك كذلك تمرير أكثر من جهاز في نفس الوقت إن أردت: sudo vgextend volume_group_name /dev/sdb ستُضاف وحدة التّخزين الماديّة إلى مجموعة التّخزين موسّعة بذلك مساحة التّخزين المُتوفّرة على المجموعة. إنشاء وحدة تخزين منطقيّة بحجم مُحدّد لإنشاء وحدة تخزين منطقيّة من مجموعة تخزين، استعمل الأمر lvcreate. حدّد حجم وحدة التّخزين المنطقيّة باستخدام الخيار -L، وحدّد اسما عبر الخيار -n، ومرّر مجموعة التّخزين الأم. على سبيل المثال، لإنشاء وحدة تخزين منطقيّة تُسمّى test من مجموعة تخزين تُسمّى LVMVolGroup، اكتب: sudo lvcreate -L 10G -n test LVMVolGroup على افتراض أنّ بمجموعة التّخزين مساحة تخزين فارغة توافق حجم وحدة التّخزين، فإنشاء وحدة التّخزين الجديدة سيتمّ بنجاح. إنشاء وحدة تخزين منطقيّة من كامل باقي المساحة الفارغة إن أردت إنشاء وحدة تخزين من باقي المساحة الفارغة على مجموعة تخزين، استعمل الأمر vgcreate مع الخيار -n لتحديد اسم لها ثمّ مرّر مجموعة التّخزين كما في السّابق. عوضا عن تمرير حجم مُعيّن، استعمل الخيار -l 100%FREE لتحديد بقيّة المساحة الفارغة في المجموعة: sudo lvcreate -l 100%FREE -n test2 LVMVolGroup إنشاء وحدات تخزين منطقيّة مع خيارات مُتقدّمة يُمكنك إنشاء وحدات تخزين منطقيّة مع خيارات مُتقدّمة كذلك. التّالي بعض من الخيارات التي يُمكن أن تأخذها بعين الاعتبار: --type: يقوم هذا الخيار بتحديد نوع وحدة التّخزين المنطقيّة ما يُحدّد كيفيّة تخصيص مساحة له. بعض من هذه الأنواع لن تكون مُتوفّرة إن لم يكن لديك الحدّ الأدنى من وحدات التّخزين الماديّة التي يتطلّبها النّوع. بعض من أكثر هذه الأنواع شيوعا هي كما يلي: linear: النّوع المبدئي. أجهزة التّخزين المُستخدمة ستُضاف بعضها على بعض واحدا تلو الآخر. striped: مُشابه لـRAID 0، تُقسّم البيانات إلى قطع صغيرة وتُنشر على شكل قوس على وحدات التّخزين الماديّة المُستعملة. ما يؤدّي إلى تحسينات في الأداء، لكن يُمكن أن يؤدي إلى زيادة في قابليّة إصابة البيانات. لتحديد هذا النّوع، يجب عليك تمرير الخيار -i ويتطلّب وحدتي تخزين ماديّتين على الأقل. raid1: إنشاء وحدة تخزين RAID 1 مُنعكسة Mirrored. مبدئيًّا، سيكون للانعكاس نُسختان، لكن يُمكنك تحديد عدد أكبر عبر الخيار -m المشروح أسفله. هذا الخيار يتطلّب وحدتي تخزين ماديّتين على الأقل. raid5: إنشاء وحدة تخزين RAID 5. يتطلّب ثلاثة وحدات تخزين ماديّة على الأقل. raid6: إنشاء وحدة تخزين RAID 6. يتطلّب أربعة وحدات تخزين ماديّة على الأقل. -m: يحدّد عدد نُسخ البيانات الإضافيّة. إن مرّرت القيمة 1 فهذا يعني بأنّ نُسخة واحدة إضافيّة من البيانات ستُصان، بحيث يكون لديك مجموعتان من نفس البيانات. -i: يُحدّد عدد الشّرائط Stripes. هذا الخيار مطلوب للنوع striped، ويُمكن أن يُؤثّر على بعض خيارات RAID الأخرى. -s: يُحدّد بأنّ أخذ لقطة يجب أن يتمّ على مُستوى وحدة التّخزين المنطقيّة الحاليّة عوضا عن إنشاء وحدة تخزين منطقيّة جديدة ومُستقلّة. سنلقي نظرة على بضعة أمثلة لهذه الخيارات لنرى حالات الاستخدام الشّائعة. لإنشاء وحدة تخزين شريطيّة Striped volume، يجب عليك تحديد شريطين على الأقل. هذه العمليّة تحتاج إلى وحدتي تخزين ماديّتين على الأقل مع مساحة تخزين متوافقة مع الحجم المُراد: sudo lvcreate --type striped -i 2 -L 10G -n striped_vol LVMVolGroup لإنشاء مساحة تخزين مُنعكسة Mirrored volume، استخدم النّوع raid1. إن أردت أكثر من مجموعتي بيانات، استعمل الخيار -m. المثال التّالي يستعمل -m 2 لإنشاء ثلاث مجموعات من البيانات (يعتبر LVM بأنّها مجموعة واحدة من البيانات مع انعكاسين). ستحتاج إلى ثلاثة وحدات تخزين ماديّة على الأقل لنجاح العمليّة: sudo lvcreate --type raid1 -m 2 -L 20G -n mirrored_vol LVMVolGroup لإنشاء لقطة لوحدة تخزين ما، يجب عليك تحديد وحدة التّخزين المنطقيّة الأصل التي ستأخذ منها اللقطة وليس كامل مجموعة التّخزين. لا تحجز اللقطات الكثير من المساحة في البدء، لكنّ حجمها يزداد كلّما أُجرِيَت تغييرات على وحدة التّخزين المنطقيّة الأصليّة. عند إنشاء لقطة ما، فالحجم المُخصّص يكون أقصى حدّ يُمكن للقطة أن تصل إليه (اللقطات التي تزيد عن هذا الحجم مُعطّلة وغير قابلة للاستخدام؛ لكن يُمكن توسيع اللقطات التي تقترب من هذا الحجم): sudo lvcreate -s -L 10G -n snap_test LVMVolGroup/test تنبيه: لإعادة وحدة تخزين إلى الحالة التي تتواجد بها اللقطة، استخدم الأمر lvconvert --merge: sudo lvconvert --merge LVMVolGroup/snap_test سيقوم هذا الأمر بإعادة اللقطة إلى الحالة التي كانت عليها عندما تمّ إنشاؤها. كما ترى، هناك العديد من الخيارات التي يُمكن لها أن تُغيّر طريقة عمل وحدة التخزين المنطقيّة بشكل كبير. زيادة حجم وحدة تخزين منطقيّة المرونة في التّعامل مع وحدات التّخزين المنطقيّة من أكثر المميّزات المتوفّرة في LVM. إذ يُمكنك بسهولة تعديل عدد أو حجم وحدات التّخزين المنطقيّة دون إيقاف النّظام. لزيادة حجم وحدة تخزين منطقيّة أنشئت مُسبقا، استعمل الأمر lvresize. مرّر قيمة إلى الخيار -L لتحديد حجم جديد. يُمكنك كذلك استخدام أحجام نسبيّة عبر الرّمز +. في هذه الحالة سيقوم LVM بإضافة الكميّة المُحدّدة إلى الحجم الكلي لوحدة التّخزين. لتعديل حجم نظام الملفّات المُستخدم على وحدة التّخزين بشكل آلي، استعمل الخيار --resizefs. لتحديد اسم صحيح لوحدة التّخزين التي ترغب بتوسيعها، سيتوجّب عليك تحديد مجموعة التّخزين، ثمّ رمز / متبوعا باسم وحدة التّخزين المنطقيّة: sudo lvresize -L +5G --resizefs LVMVolGroup/test في هذا المثال، سيتمّ زيادة 5G لكل من وحدة التّخزين test ونظام الملفّات المُستخدم. إن أردت توسيع نظام الملفّات يدويًّا، يُمكنك حذف الخيار --resizefs واستعمال أداة توسيع نظام الملفّات الخاصّة. مثلا، لو كان نظام الملفّات هو Ext4 فيُمكن أن تكتب: sudo lvresize -L +5G LVMVolGroup/test sudo resize2fs /dev/LVMVolGroup/test سيكون لهذه العمليّة نفس تأثير ما سبق. ترجمة – بتصرّف - للمقال How To Use LVM To Manage Storage Devices on Ubuntu 16.04 لكاتبه Justin Ellingwood.
  6. مُقدّمة LVM اختصار لعبارة Logical Volume Management، عبارة عن تقنيّة لإدارة أجهزة التّخزين تُمكّن المُستخدمين من توحيد وتجريد التّخطيط الماديّ لمكونات أجهزة التّخزين، لإدارتها بسهولة ومرونة. يُمكن استعمال النّسخة الحاليّة LVM2 بالاعتماد على إطار العمل الخاصّ بربط الأجهزة في نواة لينكس لجمع أجهزة التّخزين المُتوفّرة في مجموعات وتخصيص وحدات منطقيّة Logical units من المساحة المُركّبة حسب الطّلب. سنتعرّف في هذا الدليل على كيفية استخدام LVM لإدارة أجهزة التّخزين الخاصّة بك. سنرى كيفيّة عرض معلومات حول وحدات التّخزين وأهداف مُحتملة، كيفيّة إنشاء ومحو مُختلف أنواع وحدات التّخزين، وكيفيّة تعديل وحدات تخزينيّة متواجدة عبر إعادة تخصيص حجم لها وتحويلها. سنعتمد على Ubuntu 16.04 لأمثلة على تنفيذ هذه العمليّات. المُتطلّبات لمُتابعة الدّرس، يجب أن تكون قادرا على الوصول إلى خادوم Ubuntu 16.04. ستحتاج إلى مُستخدم ذي صلاحيّات sudo مع ضبط مُسبق لتمكينه من تنفيذ مهام إداريّة، غير المستخدم الجذر root. لأخذ فكرة حول مكوّنات LVM ومبادئه ولاختبار إعداد بسيط باستخدام LVM، اتّبع درس مدخل إلى LVM قبل الشّروع في هذا الدّرس. إن كنت جاهزا، ادخل إلى خادومك باستخدام حساب المُستخدم ذي صلاحيّات sudo. ملحوظة: يُفضَّل إن كانت هذه أول مرة تتعامل فيها مع LVM أو إن لم تكن متأكّدا من ما تريد فعله أن تختبر الخطوات المعروضة في هذا الدليل في آلة افتراضية أو على خادوم خاصّ بأغراض التجربة والاختبار. قد يؤدّي تنفيذ أوامر بطريقة غير صحيحة إلى ضياع البيانات. عرض معلومات حول وحدات التّخزين الماديّة، مجموعات التّخزين، ووحدات التّخزين المنطقيّة من المُهمّ أن تكون قادرا على الحصول على معلومات حول مُختلف مُكوّنات LVM في نظامك بسهولة. لحسن الحظّ، توفّر حزمة أدوات LVM كميّة وفيرة من الأدوات لعرض معلومات حول كلّ طبقة من طبقات كومة LVM. عرض معلومات حول جميع أجهزة التّخزين الكتليّة المتوافقة مع LVM لعرض جميع أجهزة التّخزين الكتليّة التي يُمكن لـLVM إدارتها، استخدم الأمر lvmdiskscan: sudo lvmdiskscan المُخرَج: /dev/ram0 [ 64.00 MiB] /dev/sda [ 200.00 GiB] /dev/ram1 [ 64.00 MiB] . . . /dev/ram15 [ 64.00 MiB] /dev/sdb [ 100.00 GiB] 2 disks 17 partitions 0 LVM physical volume whole disks 0 LVM physical volumes بتجاهل أجهزة /dev/ram* (التي تُعتبر جزءا من قرص الذاكرة العشوائيّة في لينكس)، يُمكننا مُلاحظة الأجهزة التي يُمكن استخدامها لتشكيل وحدات تخزين ماديّة لـLVM. ستكون هذه الخطوة في الغالب أول خطوة لتحديد أجهزة تخزين لاستعمالها مع LVM. عرض معلومات عن وحدات التّخزين الماديّة تُكتَب ترويسة Header على أجهزة التّخزين لتعليمها على أنها مكوّنات يُمكن لـLVM استخدامها. الأجهزة التي تحمل ترويسة تُسمّى بوحدات التّخزين الماديّة Physical Volumes. يُمكنك عرض جميع أجهزة التّخزين الماديّة على جهازك عبر استخدام الأمر lvmdiskscan مع خيار -l، ما سيُرجع وحدات التّخزين الماديّة فقط في نتيجة تنفيذ الأمر: sudo lvmdiskscan -l المُخرج: WARNING: only considering LVM devices /dev/sda [ 200.00 GiB] LVM physical volume /dev/sdb [ 100.00 GiB] LVM physical volume 2 LVM physical volume whole disks 0 LVM physical volumes الأمر pvscan مُشابه لما سبق، إذ يبحث عن جميع وحدات التّخزين الماديّة الخاصّة بـLVM. إلّا أنّ تنسيق المُخرج مُختلف نوعا ما، إذ يعرض معلومات إضافيّة: sudo pvscan المُخرج: PV /dev/sda VG LVMVolGroup lvm2 [200.00 GiB / 0 free] PV /dev/sdb VG LVMVolGroup lvm2 [100.00 GiB / 10.00 GiB free] Total: 2 [299.99 GiB] / in use: 2 [299.99 GiB] / in no VG: 0 [0 ] إن كنت ترغب بالحصول على المزيد من المعلومات، فاستعمال الأمرين pvs وpvdisplay خيار أفضل. يتميّز الأمر pvs بقابليّة تخصيصه وإمكانيّة استخدامه لعرض المعلومات في عدّة أشكال وتنسيقات مُختلفة. ولأنّ مُخرجات الأمر قابلة للتّخصيص، فاستخدامه شائع في السكريبتات أو عند الحاجة إلى أتمتة الأمور Automation. تُوفّر المُخرجات الأساسيّة للأمر خلاصة مُفيدة مُشابهة لما سبق: sudo pvs المُخرج: PV VG Fmt Attr PSize PFree /dev/sda LVMVolGroup lvm2 a-- 200.00g 0 /dev/sdb LVMVolGroup lvm2 a-- 100.00g 10.00g لمعلومات أكثر إسهابا وقابليّة للقراءة، فالأمر pvdisplay عادة ما يكون خيارا أفضل: sudo pvdisplay المُخرج: --- Physical volume --- PV Name /dev/sda VG Name LVMVolGroup PV Size 200.00 GiB / not usable 4.00 MiB Allocatable yes (but full) PE Size 4.00 MiB Total PE 51199 Free PE 0 Allocated PE 51199 PV UUID kRUOyU-0ib4-ujPh-kAJP-eeQv-ztRL-4EkaDQ --- Physical volume --- PV Name /dev/sdb VG Name LVMVolGroup PV Size 100.00 GiB / not usable 4.00 MiB Allocatable yes PE Size 4.00 MiB Total PE 25599 Free PE 2560 Allocated PE 23039 PV UUID udcuRJ-jCDC-26nD-ro9u-QQNd-D6VL-GEIlD7 كما ترى، فالأمر pvdisplay أسهل أمر للحصول على معلومات مُفصّلة عن وحدات التّخزين الماديّة. لاستكشاف المدااءات المنطقيّة المُرتبطة بكلّ وحدة تخزين، مرّر الخيار -m إلى الأمر pvdisplay: sudo pvdisplay -m المُخرج: --- Physical volume --- PV Name /dev/sda VG Name LVMVolGroup PV Size 200.00 GiB / not usable 4.00 MiB Allocatable yes PE Size 4.00 MiB Total PE 51199 Free PE 38395 Allocated PE 12804 PV UUID kRUOyU-0ib4-ujPh-kAJP-eeQv-ztRL-4EkaDQ --- Physical Segments --- Physical extent 0 to 0: Logical volume /dev/LVMVolGroup/db_rmeta_0 Logical extents 0 to 0 Physical extent 1 to 5120: Logical volume /dev/LVMVolGroup/db_rimage_0 Logical extents 0 to 5119 . . . يُمكن لهذا الأمر أن يكون مُفيدا لك عند الرّغبة في تحديد أي بيانات تتواجد في أي من الأقراص الماديّة لأغراض إداريّة. عرض معلومات عن مجموعات التّخزين Volume Groups يحتوي LVM على العديد من الأدوات التّي يُمكن بها عرض معلومات حول مجموعات التّخزين. يُستعملُ الأمر vgscan لفحص النّظام عن مجموعات التّخزين المتوفّرة. بالإضافة إلى إعادة بناء ملفّ التّخبئة Cache عند الحاجة. ويُعدّ أمرا جيّدا للاستخدام عند استيراد مجموعة تخزين إلى نظام جديد: sudo vgscan المُخرج: Reading all physical volumes. This may take a while... Found volume group "LVMVolGroup" using metadata type lvm2 المُخرج لا يُعطي الكثير من المعلومات، لكن يجب عليه أن يكون قادرا على إيجاد جميع مجموعات التّخزين على النّظام. لعرض المزيد من المعلومات، فالأمران vgs و vgdisplay مُتوفّران لذلك. تماما مثل مثيله المُخصّص لوحدات التّخزين الماديّة، فالأمر vgs مُتعدّد الاستعمالات ويُمكن له أن يعرض كميّة ضخمة من المعلومات في أشكال مُتعدّدة. ولأنّ إمكانيّة تخصيص مُخرجات الأمر عاليّة المرونة، فاستخدامه في برمجة السكريبتات والأتمتة أمر شائع. على سبيل المثال، من الأمور المُفيدة التي يُمكنك فعلها هي تخصيص المُخرج ليعرض فقط الأجهزة الماديّة ومسار وحدات التّخزين المنطقيّة: sudo vgs -o +devices,lv_path المُخرج: VG #PV #LV #SN Attr VSize VFree Devices Path LVMVolGroup 2 4 0 wz--n- 299.99g 10.00g /dev/sda(0) /dev/LVMVolGroup/projects LVMVolGroup 2 4 0 wz--n- 299.99g 10.00g /dev/sda(2560) /dev/LVMVolGroup/www LVMVolGroup 2 4 0 wz--n- 299.99g 10.00g /dev/sda(3840) /dev/LVMVolGroup/db LVMVolGroup 2 4 0 wz--n- 299.99g 10.00g /dev/sda(8960) /dev/LVMVolGroup/workspace LVMVolGroup 2 4 0 wz--n- 299.99g 10.00g /dev/sdb(0) /dev/LVMVolGroup/workspace لمُخرج أكثر إسهابا وقابليّة للقراءة، فالأمر vgdisplay عادة ما يكون خيارا أفضل. إضافة الخيار -v يُوفّر كذلك معلومات حول وحدات التّخزين الماديّة التّي تُشكّل مجموعة التّخزين، بالإضافة إلى وحدات التّخزين المنطقيّة التي تمّ إنشاءها باستخدام مجموعة التّخزين: sudo vgdisplay -v المُخرج: Using volume group(s) on command line. --- Volume group --- VG Name LVMVolGroup . . . --- Logical volume --- LV Path /dev/LVMVolGroup/projects . . . --- Logical volume --- LV Path /dev/LVMVolGroup/www . . . --- Logical volume --- LV Path /dev/LVMVolGroup/db . . . --- Logical volume --- LV Path /dev/LVMVolGroup/workspace . . . --- Physical volumes --- PV Name /dev/sda . . . PV Name /dev/sdb . . . الأمر vgdisplay مُفيد لقُدرته على الرّبط بين المعلومات حول مُختلف عناصر كومة LVM. عرض معلومات حول وحدات التّخزين المنطقيّة يمتلك LVM مجموعة من الأدوات لعرض معلومات عن وحدات التّخزين المنطقيّة كذلك. كما الحال مع مُكوّنات LVM الأخرى، يُمكن استعمال الأمر lvscan لفحص النّظام وعرض معلومات وجيزة حول وحدات التّخزين المنطقيّة التي يجدها: sudo lvscan المُخرج: ACTIVE '/dev/LVMVolGroup/projects' [10.00 GiB] inherit ACTIVE '/dev/LVMVolGroup/www' [5.00 GiB] inherit ACTIVE '/dev/LVMVolGroup/db' [20.00 GiB] inherit ACTIVE '/dev/LVMVolGroup/workspace' [254.99 GiB] inherit لمعلومات أكثر كمالا، يُمكنك استخدام الأمر lvs الذي يتمتّع بمرونة وقوّة بالإضافة إلى سهولة استخدامه في السكربتات: sudo lvs المُخرج: LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert db LVMVolGroup -wi-ao---- 20.00g projects LVMVolGroup -wi-ao---- 10.00g workspace LVMVolGroup -wi-ao---- 254.99g www LVMVolGroup -wi-ao---- 5.00g لإيجاد عدد شرائط Stripes وحدة التّخزين المنطقيّة ونوعها، استعمل الخيار --segments: sudo lvs --segments المُخرج: LV VG Attr #Str Type SSize db LVMVolGroup rwi-a-r--- 2 raid1 20.00g mirrored_vol LVMVolGroup rwi-a-r--- 3 raid1 10.00g test LVMVolGroup rwi-a-r--- 3 raid5 10.00g test2 LVMVolGroup -wi-a----- 2 striped 10.00g test3 LVMVolGroup rwi-a-r--- 2 raid1 10.00g يُمكن الحصول على أكثر مُخرج قابل للقراءة عبر الأمر lvdisplay. عند إضافة الخيار -m، فستعرض الأداة معلومات حول مكوّنات وحدة التّخزين المنطقيّة وكيفيّة توزيعها: sudo lvdisplay -m المُخرج: --- Logical volume --- LV Path /dev/LVMVolGroup/projects LV Name projects VG Name LVMVolGroup LV UUID IN4GZm-ePJU-zAAn-DRO3-1f2w-qSN8-ahisNK LV Write Access read/write LV Creation host, time lvmtest, 2016-09-09 21:00:03 +0000 LV Status available # open 1 LV Size 10.00 GiB Current LE 2560 Segments 1 Allocation inherit Read ahead sectors auto - currently set to 256 Block device 252:0 --- Segments --- Logical extents 0 to 2559: Type linear Physical volume /dev/sda Physical extents 0 to 2559 . . . كما تُلاحظ من الجزء السّفلي للمُخرج أعلاه، فوحدة التّخزين المنطقيّة /dev/LVMVolGroup/projects متواجدة بالكامل على وحدة التّخزين الماديّة /dev/sda في هذا المثال. هذه المعلومة مُفيدة إن كنت ترغب بإزالة جهاز التّخزين المُعتمد عليه وتريد نقل البيانات إلى مكان مُحدّد. رأينا في هذا الجزء من الدليل كيفية عرض معلومات عن مختلف المكوّنات في LVM. سنكمل في الأجزاء التالية بقيّة المهام الإدارية. ترجمة – بتصرّف - للمقال How To Use LVM To Manage Storage Devices on Ubuntu 16.04 لكاتبه Justin Ellingwood.
  7. مُقدّمة بعد إنشاء جدولي المقالات والمُستخدمين في قاعدة البيانات في الدّرس السّابق، حان الوقت للتّعامل مع البيانات وإدارتها باستعمال لغة بايثون عوضا عن لغة SQL؛ في هذا الدّرس سنتعرّف على كيفيّة الوصول إلى مُفسّر بايثون في مجلّد مشروعنا وكذلك كيفيّة إضافة البيانات وجلبها من قاعدة البيانات. الوصول إلى مُفسّر بايثون داخل مُجلّد المشروع سنقوم في هذا الدّرس بالتّعامل مع قاعدة البيانات باستعمال Flask-SQLAlchemy مُباشرة من مُفسّر بايثون، إن كنت على نظام Windows فهو في الغالب متواجد في قائمة ابدأ، أمّا إن كنت على لينكس أو OSX فادخل إلى الطّرفيّة Terminal واكتب python تأكّد فقط من أنّك في مُجلّد kalima قبل أن تقوم بالوصول إلى مُفسّر بايثون، وتأكّد كذلك بأنّك قد فعّلت البيئة الوهميّة. إن لم تستطع الوصول إلى مُفسّر بايثون في مُجلّد kalima فيُمكنك تنفيذ الأمر chdir من وحدة os لتغيير المُجلّد الحالي إلى مسار مُجلّد kalima: >>> import os >>> os.chdir('path/to/kalima') مع تغيير path/to/kalima إلى مسار مُجلّد kalima في حاسوبك. مُلاحظة: الشّيفرة التّي سأقوم بتنفيذها ستُسبق بعلامة >>> أمّا المُخرج/النّتيجة فلن يُسبق بشيء. مثال: >>> site = 'Hsoub Academy' >>> site 'Hsoub Academy' بما أنّنا في مُفسّر بايثون نستطيع الوصول إلى قيمة المُتغيّر دون طباعتها، فقط اكتب اسم المُتغيّر ثم اضغط مفتاح ENTER. سنستعمل كلّا من db والصّنفين اللذ]ن يُشكلان كلّا من جدول المقالات والمُستخدمين للتّعامل مع البيانات فيها، وللوصول إلى كلّ من الكائن db والصّنفين Post و User يجب عليك أن تقوم باستيرادها: >>> from project import db >>> from project.models import Post, User إن حصلت على خطأ كما يلي: Traceback (most recent call last): File "<stdin>", line 1, in <module> ImportError: No module named project فهذا يعني بأنّك لست في مجلّد kalima لذا تأكّد من أنّك اتّبعت الإرشادات السّابقة على نحو صحيح. إدخال البيانات إلى قاعدة البيانات الآن، بعد استيراد كلّ من الكائن db والصّنفين Post وUser نستطيع القيام بأول عمليّة، ألا وهي عمليّة الإضافة، وسنُضيف أولا مُستخدما باسم khalid وبعدها سنُضيف مقالا له بعنوان Post 1. إضافة المُستخدم لإضافة مُستخدم سنُنشئ أولا كائنا من الصّنف User مع المعاملات التّي حدّدناها سابقا في دالّة init الخاصّة بالصّنف، ما يعني بأنّنا سنُنشئ مُستخدما كما يلي: user = User('khalid', 'email@example.com', 'password') وهنا يكون التّرتيب مُهمّا ومُطابقا لترتيب المُعاملات في الدّالة __init__، ويُمكنك كذلك تسميّة كلّ معامل وتغيير التّرتيب إن لم تتذكّر سوى أسماء المعاملات كما يلي: user = User(password='password', email='email@example.com',name='khalid') كما تُلاحظ عندما تذكر اسم كلّ معامل، فالتّرتيب غير مهم خلافا للمثال الأول. بعد إنشاء الكائن من صنف المُستخدم بالبيانات التّي نُريدها، يُمكننا الوصول إلى كل قيمة أو تغييرها ببساطة: >>> user = User('khalid', 'email@example.com', 'password') >>> user.name 'khalid' >>> user.email 'email@example.com' >>> user.password 'password' >>> user.email = 'khalid@example.com' >>> user.email 'khalid@example.com' >>> user <username: khalid | email: khalid@example.com > كما تُلاحظ، يُمكننا الوصول إلى كل قيمة من القيم التّي حدّدناها سابقا وكذا تعديلها بإسناد قيمة جديدة بكل بساطة. لاحظ كذلك مُخرجات آخر أمر، يُمكنك أن ترى بأنّ طريقة التّنسيق هذه هي نفسها ما قد حدّدناه سابقا في الدّالة __repr__ بالصّنف User، وإليك تذكيرا سريعا: def __repr__(self): return '<username: {} | email: {} >'.format(self.name, self.email) وأنبّه إلى أنّنا لم نغيّر أي شيء بعد في قاعدة البيانات، وإنشاء الكائن ليس سوى تمهيد لإضافته إلى جلسة تتواجد في الكائن db ثمّ تأكيد التّغييرات لإضافة المُستخدم إلى قاعدة البيانات حقيقة، ولإضافة الكائن user الذي أنشأناها قبل قليل إلى الجلسة نقوم بما يلي: >>> db.session.add(user) والجلسة هنا عبارة عن مكان لإضافة البيانات لوضعها بعد ذلك في قاعدة البيانات، وعند إضافة كل كائن إلى الجلسة لا يُضاف إلى قاعدة البيانات حقيقة إلى أن تنفّذ الأمر db.session.commit. لذا لإضافة المُستخدم خالد إلى قاعدة البيانات PostgreSQL فسننفّذ الأمر التّالي: >>> db.session.commit() بعد تنفيذ الأمر والعودة إلى أداة psql لعرض جميع البيانات في جدول المُستخدمين فستلاحظ بأنّ المُستخدم الذي أضفناه موجود بالفعل: kalima=# select * from users; id | name | email | password | created_date ----+--------+--------------------+----------+---------------------------- 1 | khalid | khalid@example.com | password | 2017-04-08 18:48:05.316805 (1 row) لاحظ بأنّ العمود id أخذ القيمة 1 رغم أنّنا لم نُحدّدها، ولو أضفنا مُستخدما آخر الآن لكان رقم مُعرّفه 2 ولو حذفت مثلا المُستخدم الذي يحمل رقم المعرّف 1 وأضفت مُستخدما آخر، فرقم مُعرّف هذا المُستخدم لن يأخذ مكان المُستخدم الأول بل سيأخذ رقما جديدا مُخالفا للأرقام السّابقة؛ ولاحظ كذلك بأنّ تاريخ الإضافة قد أضيف دون أن نقوم بتحديده يدويا. إضافة المقال بعد أن قُمنا بإضافة المُستخدم، حان الوقت لإضافة مقال خاص به، بالطّبع في التّطبيق المُستخدِم هو من سيُضيف مقالاته، وبعد تسجيله الدّخول، سنستطيع إضافة المقال ووضع قيمة مفتاح مُعرّفه كقيمة للمُعامل author_id، وسنقوم في هذا الجزء بإعطاء مثال لكيفيّة إضافة مقال وإسناد كاتب له، وفي الجزء التّالي سوف نضيف المزيد من المقالات والمُستخدمين لشرح كيفيّة الوصول إلى بيانات كل مُستخدم بما فيها مقالاته، وكذا كاتب كلّ مقال. لإضافة المقال ذي العنوان Post 1 نقوم بنفس الشّيء، أولا نُنشئ كائنا من الصّنف Post ثمّ نُسند له القيم التّي حدّدناها سابقا في الدّالة __init__ الخّاصّة بالصّنف، لكن هذه المرّة نقوم بإضافة مُعامل آخر، وهو عبارة عن قيمة رقم مُعرّف المُستخدم الذي أضاف المقال، وبما أنّنا نمتلك مُستخدما رقم مُعرّفه 1 فهذا يعني بأنّنا نستطيع أن نُسند له هذا المقال بصفته كاتبا له. >>> post = Post('Post 1', 'post 1 content', author_id=1) >>> post <title Post 1 | post 1 content > >>> db.session.add(post) >>> db.session.commit() >>> post.author <username: khalid | email: khalid@example.com > >>> post.author.name u'khalid' >>> post.author.email u'khalid@example.com' >>> post.author.password u'password' كما تُلاحظ، يُمكنك تحديد أسماء معاملات إن أردت، وترك أسماء أخرى على شرط أن تكون القيم مُرتّبة، فقد ذكرت اسم المُعامل author_id فقط لكي تفهم القيمة الثّالثة، وسيتوجّب عليك ذكر الاسم إن كان لك العديد من المفاتيح الأجنبيّة (أرقام مُعرّف تُشير إلى جداول كثيرة) لأنّها ستكون كلّها عبارة عن أرقام لا معنى لها لمن يقرأ الشّيفرة، ويجب عليك أن تتذكّر ترتيبها باستمرار، لذا من المُفضّل تجنّب هذا بذكر اسم كلّ معامل. لاحظ كذلك عندما نستدعي الكائن، فإنّ الدّالة __repr__ تستبدل كلّا من المنطقتين بالعنوان والمحتوى كما هو مُتوقّع. بعد إضافة المقال إلى الجلسة ثمّ تأكيد التّغييرات وكتابتها إلى قاعدة البيانات، أصبح بإمكاننا الآن الوصول إلى كاتب المقال عن طريق post.author والمُخرج عبارة عن كائن يحمل بيانات المُستخدم الذي أدخل البيانات كما لو أنّك حصلت عليه من قاعدة البيانات، إذ تستطيع الوصول إلى قيمة أي عمود في جدول المُستخدمين والنّتيجة تكون عبارة عن سلسلة نصيّة مسبوقة بحرف u للإشارة إلى أنّها عبارة عن Unicode وبالطّبع يُمكنك التّعامل معها كما تتعامل مع سلسلة نصيّة عاديّة. بعد الوصول إلى المُستخدم كاتب المقال، يُمكنك الحصول على مقالاته الأخرى كذلك: >>> post.author.posts.all() [<title Post 1 | post 1 content >] المُخرج عبارة عن قائمة من المقالات يُمكنك الدّوران حولها باستخدام حلقة for ولأنّنا لا نمتلك سوى مقال واحد حاليّا، سنقوم بالوصول إليه مُباشرة برقم العنصر في القائمة: >>> post.author.posts[0] <title Post 1 | post 1 content > >>> post.author.posts[0].title u'Post 1' >>> post.author.posts[0].content u'post 1 content' >>> post.author.posts[0].author <username: khalid | email: khalid@example.com > لاحظ أنّنا استطعنا الوصول إلى كاتب المقال مُجدّدا في آخر سطر، يُمكنك الاستمرار إلى ما لانهاية، المقال –> كاتبُه –> مقال كَتَبه –> كاتبُه –> مقال كَتَبه –> كاتبُه وهكذا… ما يعني بأنّ السّطر التّالي مُمكن: >>> post.author.posts[0].author.posts[0].author.posts[0].author.posts[0].author.posts[0].author.name u'khalid' بالطّبع، لا فائدة من استعمال SQLAlchemy بهذه الطّريقة، إنّما يجب عليك أن تفهم كيف يعمل، وبأنّ مثل هذه الأمور مُمكنة فربّما تُفيدك في تفادي بعض الأخطاء البرمجيّة. خاتمة تعرّفنا في هذا الدّرس على كيفيّة استعمال مُفسّر لغة بايثون للتّعامل مع قاعدة البيانات الخاصّة بنا، وتمكّنّا من إضافة سجلّ إلى جدول المُستخدمين وسجلّ آخر إلى جدول المقالات. في الدّرس القادم، سنطّلع على كيفيّة إنشاء العديد من السّجلات لنتمكّن من العمل معها في بقيّة الدّروس الخاصّة بالتّعامل مع قاعدة البيانات باستعمال مكتبة SQLAlchemy لتتمكّن من تطوير تطبيقات ويب أفضل وأكثر تعقيدا باستعمال إطار العمل Flask.
  8. تمهيد LVM اختصار لعبارة Logical Volume Management، عبارة عن تقنيّة لإدارة أجهزة التّخزين تُمكّن المُستخدمين من توحيد وتجريد التّخطيط الماديّ (الفعلي) لمكونات أجهزة التّخزين، لإدارتها بسهولة ومرونة. يُمكن استعمال النّسخة الحاليّة LVM2 بالاعتماد على إطار العمل الخاصّ بربط الأجهزة Device mapper في نواة لينكس لجمع أجهزة التّخزين المُتوفّرة في مجموعات وتخصيص وحدات منطقيّة Logical units من المساحة المُركّبة حسب الطّلب. التّجريد، المرونة والتّحكم من الميزات الرّئيسيّة لاستخدام LVM. يُمكن لوحدات التّخزين Volumes أن تحمل أسماء ذات معنى واضح مثل “database” أو “root-backup”. يُمكن إعادة ضبط حجم وحدات التّخزين ديناميكيًّا حسب تغيّر متطلّبات المساحة، يُمكن كذلك تهجيرها بين الأجهزة الماديّة داخل مجمع التخزين Storage pool أو على نظام قيد التّنفيذ. يُوفّر LVM كذلك ميزات مُتقدّمة مثل أخذ اللقطات Snapshotting، الدّمج Striping والمُطابقة Mirroring. في هذا الدّرس، سنتحدّث باختصار عن آليّة عمل LVM وبعدها سنستعرض بعضا من الأوامر التي ستحتاج إليها في البداية. مبادئ ومُصطلحات LVM قبل أن أن ننتقل إلى أوامر LVM الإداريّة، من المهمّ أن تفهم مبدئيًّا كيف ينظّم LVM أجهزة التّخزين وبعضا من المُصطلحات المُستعملة. بنيات مُدير التّخزين في LVM يعمل LVM عبر إنشاء طبقات مُجرّدة فوق أجهزة التّخزين الماديّة. الطّبقات التي يستعملها LVM بدءا بأكثرها بدائيّة هي كالتّالي: وحدات التّخزين الماديّة Physical Volumes: السابقة في أداة LVM: المقطع pv... الوصف: الأجهزة الماديّة أو الأجهزة التي تشبه الأقراص (مثلا، أجهزة تخزين أنشِئت باستعمال رابط الأجهزة Device Mapper مثل مصفوفات RAID)، وتعدّ المّادة الأساسيّة التي يستعملها LVM للطبقات العليا من التّجريد. وحدات التّخزين الماديّة عبارة عن أجهزة تخزين عاديّة. يكتب LVM ترويسة Header على الجهاز لتخصيصه ليكون قابلا للإدارة. مجموعات وحدات التّخزين Volume Groups: السابقة في أداة LVM: المقطع vg... الوصف: يجمع LVM وحدات التّخزين الماديّة في مساحات تخزينيّة موحّدة تُسمى “مجموعات وحدات التّخزين”. تجرّد مجموعات التّخزين هذه أجهزة التّخزين التّابعة لها من مواصفاتها لتعمل على شكل جهاز تخزين منطقيّ واحد مع جمع المساحات التّخزينيّة الخاصّة بوحدات التّخزين التي تُكوّن المجموعة. وحدات التّخزين المنطقيّة Logical Volumes: السابقة في أداة LVM: المقطع lv... (أدوات LVM العامّة تبدأ عادة بالمقطع lvm...) الوصف: يُمكن تقسيم مجموعة تخزين إلى أي عدد من وحدات التّخزين المنطقيّة. وحدات التّخزين المنطقيّة تُعادل وظيفيّا التجزئات Partitions في قرص ماديّ، لكنّها تتمتّع بمرونة أكثر. تعدّ وحدات التّخزين المنطقيّة المكوّن الأساسيّ الذي يتعامل معه المستخدمون والتّطبيقات. باختصار، يُمكن استعمال LVM لجمع وحدات التّخزين الماديّة في مجموعات تخزين لتوحيد مساحة التّخزين المتواجدة على نظام ما. بعدها، يُمكن للمدراء تقسيم مجموعة التّخزين إلى وحدات تخزين منطقيّة تعمل على شكل تجزئات مرنة. ما هي المداءات Extents؟ تُقسَّمُ كلّ وحدة تخزين ضمن مجموعة تخزين إلى قطع صغيرة ذات حجم ثابت تُسمّى المداءات (جمع مدى). تحدّد مجموعة التّخزين حجم المدى (جميع وحدات التّخزين بداخل المجموعة تحمل نفس حجم المدى). المداءات في وحدة تخزين ماديّة تُسمّى بالمداءات الماديّة، أمّا المداءات في وحدات التّخزين المنطقيّة فتُسمّى مداءات منطقيّة. وحدة تخزين منطقيّة عبارة ببساطة عن رابط يصونه LVM بين المداءات المنطقيّة و الماديّة. بسبب هذه العلاقة، يُمثّل حجم المداءات أصغر مقدار من المساحة التي يُمكن لـLVM تخصيصها. المداءات سبب رئيسيّ للمرونة والقوّة اللتان يتمتّع بهما LVM. إذ ليس من الضّروريّ على المداءات الماديّة الارتباط بالمداءات المنطقيّة المُمثّلة على شكل جهاز تخزين موحّد بواسطة LVM. يُمكن لـLVM نسخ وإعادة ترتيب المداءات الماديّة التي تُكوّن وحدة تخزين منطقيّة دون إعاقة سير الأمور بالنّسبة للمُستخدم. يُمكن كذلك توسيع أو تقليص وحدات التّخزين المنطقيّة ببساطة عبر إضافة أو إزالة المداءات من وحدة التّخزين. حالة الاستخدام البسيطة والآن بعد أن تعرّفنا على بعض من المُصطلحات والبنيات التي يستعملها LVM، يُمكننا استكشاف بعض من أكثر استخدامات LVM شيوعا. سنبدأ التّعرّف على إجراء بسيط يُمكّننا من استعمال قرصين ماديّين لإنشاء أربع وحدات تخزين منطقيّة. تنبيه: تأكّد من أنّ الأجهزة التّي ترغب باستعمالها مع LVM لا تحتوي على أيّة بيانات مُهمّة. استخدام هذه الأجهزة مع LVM سيؤدّي إلى الكتابة فوق المحتويات الحاليّة. إذا كانت لديك بيانات مهمّة على خادومك فأنشئ نسخا احتياطية قبل الاستمرار في تطبيق الدّرس. إن كنت تريد استكشاف آلية العمل بأمان فالأفضل أن تستخدم آلة افتراضية للتطبيق عليها. تحديد الأجهزة الماديّة Physical Devices لتُشكّل وحدات تخزين ماديّة Physical Volumes أول خطوة هي فحص النّظام للوصول إلى الأجهزة التّي يُمكن لـLVM رؤيتها وإدارتها. يُمكنك القيام بهذه الخطوة عبر كتابة ما يلي في الطّرفيّة: sudo lvmdiskscan المُخرَج سيكون عبارة عن قائمة بجميع الأجهزة التّي يُمكن لـLVM التّعامل معها: /dev/ram0 [ 64.00 MiB] /dev/sda [ 200.00 GiB] /dev/ram1 [ 64.00 MiB] . . . /dev/ram15 [ 64.00 MiB] /dev/sdb [ 100.00 GiB] 2 disks 17 partitions 0 LVM physical volume whole disks 0 LVM physical volumes من المُخرج أعلاه، يُمكننا أن نرى بأنّنا نتوفّر على قرصين و17 تجزئة. مُعظم التجزئات عبارة عن قرص ذاكرة عشوائيّة /dev/ram* لزيادة الأداء. الأقراص في هذا المثال هي /dev/sda الذي يحتوي على 200G من المساحة، و /dev/sdb ذي المساحة 100G. بعد تحديد الأجهزة الماديّة التي نريد استخدامها، يُمكننا الآن تخصيصها لتكون وحدات تخزين ماديّة داخل LVM باستخدام الأمر pvcreate: sudo pvcreate /dev/sda /dev/sdb المُخرَج: Physical volume "/dev/sda" successfully created Physical volume "/dev/sdb" successfully created ستقوم هذه العمليّة بكتابة ترويسة LVM على الأجهزة للإشارة إلى أنّها جاهزة للإضافة إلى مجموعة تخزين. يُمكنك التّحقّق من أنّ وحدات التّخزين الماديّة قد سُجّلت بنجاح من طرف LVM عبر كتابة الأمر التّالي: sudo pvs المُخرج: PV VG Fmt Attr PSize PFree /dev/sda lvm2 --- 200.00g 200.00g /dev/sdb lvm2 --- 100.00g 100.00g كما ترى، كلا الجهازان مُتواجدان تحت عمودPV (اختصار لـPhysical Volume). إضافة وحدات التّخزين الماديّة إلى مجموعة تخزين بعد أن أنشأنا وحدات تخزين ماديّة من أجهزتنا، يُمكننا الآن إنشاء مجموعة تخزين. سيتوجّب علينا اختيار اسم لمجموعة التّخزين، سنختار اسما عامّا لتبسيط الأمور. في معظم الأوقات، ستجد مجموعة واحدة فقط لكل نظام من أجل الحصول على أقصى درجة من المرونة في التّخصيص. سنُسمّي مجموعة التّخزين الخاصّة بنا بالاسم LVMVolGroup لتبسيط الأمور. لإنشاء مجموعة تخزين وإضافة وحدتي التّخزين الماديّتين إليها في أمر واحد، اكتب ما يلي: sudo vgcreate LVMVolGroup /dev/sda /dev/sdb المُخرج: Volume group "LVMVolGroup" successfully created إن اطّلعنا على مُخرج الأمر pvs مُجدّدا، يُمكننا أن نرى بأنّ وحدتي التّخزين الماديّتين أصبحتا مرتبطتين بمجموعة التّخزين الجديدة: sudo pvs المُخرَج: PV VG Fmt Attr PSize PFree /dev/sda LVMVolGroup lvm2 a-- 200.00g 200.00g /dev/sdb LVMVolGroup lvm2 a-- 100.00g 100.00g يُمكننا الحصول على خلاصة موجزة لمجموعة التّخزين عبر الأمر التّالي: sudo vgs المُخرَج: VG #PV #LV #SN Attr VSize VFree LVMVolGroup 2 0 0 wz--n- 299.99g 299.99g كما ترى، مجموعة التّخزين الخاصّة بنا تمتلك وحدتي تخزين ماديّتين، دون أية وحدات تخزين منطقيّة، كما لها مساحة تخزين تُساوي مجموعة مساحتي الجهازيْن المُستخدمين. إنشاء وحدات تخزين منطقيّة من مجموعة التّخزين المُوحّدة الآن وقد أصبحت لدينا مجموعة تخزين، يُمكننا استخدامها لتخصيص وحدات تخزين منطقيّة منها. على عكس تقسيم القرص التّقليديّ، فعند العمل مع وحدات التّخزين المنطقيّة، لا تحتاج إلى معرفة تخطيط وحدة التّخزين لأنّ LVM يتكلّف بالأمر من أجلنا. ستحتاج فقط إلى تمرير حجم وحدة التّخزين واسم لها. سننشئ أربعة وحدات تخزين منطقيّة من مجموعة التّخزين الخاصّة بنا: وحدة تخزين باسم projects بمساحة 10G، وحدة تخزين www بمساحة 5G لمحتوى الوِب، وحدة تخزين db بمساحة 20G لقاعدة البيانات، وحدة تخزين workspace للاستفادة من باقي المساحة. لإنشاء وحدات تخزين منطقيّة، نستعمل الأمر lvcreate. سيتوجّب علينا تمرير اسم مجموعة التّخزين، ويُمكننا تسميّة وحدة التّخزين المنطقيّة باستخدام الخيار n-. ولتحديد حجم المساحة مُباشرة، يُمكنك استخدام الخيار L-. إن أردت تحديد الحجم حسب عدد النّطاقات، يُمكنك استعمال الخيار l-. يُمكننا إنشاء أول ثلاثة وحدات تخزين منطقيّة باستخدام الخيار L- كالتّالي: sudo lvcreate -L 10G -n projects LVMVolGroup sudo lvcreate -L 5G -n www LVMVolGroup sudo lvcreate -L 20G -n db LVMVolGroup المُخرَج: Logical volume "projects" created. Logical volume "www" created. Logical volume "db" created. يُمكننا الاطّلاع على وحدات التّخزين المنطقيّة وعلاقتها بمجموعة التّخزين عبر تحديد مُخرج مُخصّص من الأمر vgs كما يلي: sudo vgs -o +lv_size,lv_name المُخرج: VG #PV #LV #SN Attr VSize VFree LSize LV LVMVolGroup 2 3 0 wz--n- 299.99g 264.99g 10.00g projects LVMVolGroup 2 3 0 wz--n- 299.99g 264.99g 5.00g www LVMVolGroup 2 3 0 wz--n- 299.99g 264.99g 20.00g db أضفنا آخر عمودين لنرى الحجم المُخصّص لكلّ وحدة تخزين منطقيّة. الآن، يُمكننا إسناد بقيّة المساحة في مجموعة التّخزين إلى وحدة التّخزين workspace باستعمال الخيار -l، التي تعمل حسب مبدأ المداءات. يُمكننا أيضا توفير نسبة مئويّة ووحدة قياس لإيصال الفكرة على نحو أوضح. في حالتنا، نريد أن نُخصّص باقي المساحة الفارغة، لذا سنُمرّر القيمة 100%FREE: sudo lvcreate -l 100%FREE -n workspace LVMVolGroup المُخرج: Logical volume "workspace" created. إذا أعدنا التّحقّق من معلومات مجموعة التّخزين، سنُلاحظ بأنّنا قد استعملنا كافّة المساحة المُتوفّرة: sudo vgs -o +lv_size,lv_name المُخرَج: VG #PV #LV #SN Attr VSize VFree LSize LV LVMVolGroup 2 4 0 wz--n- 299.99g 0 10.00g projects LVMVolGroup 2 4 0 wz--n- 299.99g 0 5.00g www LVMVolGroup 2 4 0 wz--n- 299.99g 0 20.00g db LVMVolGroup 2 4 0 wz--n- 299.99g 0 264.99g workspace كما ترى، فقد أُنشِئت وحدة التّخزين workspace ومجموعة التّخزين LVMVolGroup وقد خُصِّصتا بالكامل. تهيئة وتركيب وحدات التّخزين المنطقيّة أصبحت لدينا الآن وحدات تخزين منطقيّة جاهزة للاستعمال، يُمكننا استخدامها كما تُستَخدَم أجهزة التخزين العاديّة. الأجهزة المنطقيّة متواجدة داخل المُجلّد /dev/ كأي جهاز تخزين آخر. يُمكنك الوصول إليها بطريقتين: /dev/volume_group_name/logical_volume_name /dev/mapper/volume_group_name-logical_volume_name مع تغيير قيمة volume_group_name باسم مجموعة التّخزين، وتغيير قيمة logical_volume_name باسم وحدة التّخزين المنطقيّة. لذا لتهيئة وحدات التّخزين المنطقيّة الخاصّة بنا باستخدام نظام الملفّات Ext4، يُمكن أن نكتب ما يلي: sudo mkfs.ext4 /dev/LVMVolGroup/projects sudo mkfs.ext4 /dev/LVMVolGroup/www sudo mkfs.ext4 /dev/LVMVolGroup/db sudo mkfs.ext4 /dev/LVMVolGroup/workspace أو كما يلي: sudo mkfs.ext4 /dev/mapper/LVMVolGroup-projects sudo mkfs.ext4 /dev/mapper/LVMVolGroup-www sudo mkfs.ext4 /dev/mapper/LVMVolGroup-db sudo mkfs.ext4 /dev/mapper/LVMVolGroup-workspace بعد التّهيئة، يُمكننا إنشاء نقاط تركيب Mount points كما يلي: sudo mkdir -p /mnt/{projects,www,db,workspace} بعدها يُمكننا تركيب وحدات التّخزين المنطقيّة على المسار المُناسب: sudo mount /dev/LVMVolGroup/projects /mnt/projects sudo mount /dev/LVMVolGroup/www /mnt/www sudo mount /dev/LVMVolGroup/db /mnt/db sudo mount /dev/LVMVolGroup/workspace /mnt/workspace لتركيب النّقاط بطريقة دائمة، أضفها إلى /etc/fstab كما لو كانت أجهزة تخزين عاديّة: sudo nano /etc/fstab نضيف ما يلي إلى نهاية الملفّ: /dev/LVMVolGroup/projects /mnt/projects ext4 defaults,nofail 0 0 /dev/LVMVolGroup/www /mnt/www ext4 defaults,nofail 0 0 /dev/LVMVolGroup/db /mnt/db ext4 defaults,nofail 0 0 /dev/LVMVolGroup/workspace /mnt/workspace ext4 defaults,nofail 0 0 يجب على نظام التّشغيل الآن أن يركّب وحدات التّخزين المنطقيّة الخاصّة بـLVM آليًّا عند التشغيل. ختاما نأمل أن يكون لديك الآن فهم جيّد لمُختلف المُكوّنات التّي يُديرها LVM لإنشاء نظام تخزين مرن. يجب كذلك أن تكون لديك قدرة على إنشاء أجهزة واستعمالها مع LVM. هذا الدّرس مجرّد مُقدّمة وجيزة لما يُمكن لـLVM توفيره لمُدراء أنظمة Linux. للاستزادة حول كيفيّة العمل مع LVM، ألق نظرة على درس كيفيّة استخدام LVM مع Ubuntu 16.04. ترجمة - بتصرّف - للمقال An Introduction to LVM Concepts, Terminology, and Operations لكاتبه Justin Ellingwood.
  9. مقدّمة بعد أن تعرّفنا على كيفيّة تجهيز جدولي المقالات والمُستخدمين اللّذين سيُشكّلان أساس بيانات تطبيقنا، وبعد أن تعرّفنا في الدّرس السّابق على كيفيّة الرّبط بين الجدولين ليكون لكلّ كاتب مقالاته ولكل مقال كاتب خاص به، حان الوقت لإنشاء الجدولين في قاعدة البيانات والتّعرّف على كيفيّة استغلال كلّ من مكتبة SQLAlchemy وإضافة Flask-SQLAlchemy للتّعامل مع البيانات. لماذا الدّروس القادمة أساسيّة؟ هذا الدّرس والدّروس القادمة مُهمّة لك إن كنت ترغب بالاعتماد على قواعد بيانات SQL ومكتبة SQLAlchemy في تطوير تطبيقاتك، وما ستتعلّمه من هذه الدروس سيُفيدك في حالات كثيرة، وستتمكّن من القيام بأكثر العمليّات شيوعا باستخدام SQLAlchemy عوضا عن لغة SQL ما سيبقي شفرتك نظيفة وسيُمكّنك من العمل مع بياناتك بأريحيّة أكثر. ورغم أنّنا لن نستعمل الكثير من الأشياء التّي سأذكرها في تطبيق “كلمة”، إلّا أنّ الإلمام بها مهم جدّا، وعندما أقول بأنّ هذه المفاهيم مهمّة فأنا لا أعني أن تحفظها عن ظهر قلب، إذ يُمكنك أن تعود إلى درس مُعيّن في كلّ مرّة تُريد أن تتذكّر كيفيّة القيام بعمليّة معيّنة باستخدام مكتبة SQLAlchemy وإضافة Flask-SQLAlchemy، وأشجّعك على توظيف كل ما ستتعلّمه لإضافة ميّزات جديدة إلى التّطبيق الذي سنبنيه سويّا (مثلا إضافة جزء لعرض مقال عشوائي في كلّ مرّة يُعاد فيها تحميل الصّفحة)، وإن أردت أن تعرف كيفيّة القيام بعمليّة أخرى من عمليّات SQL التّقليديّة باستخدام SQLAlchemy فعد أولا إلى التّوثيق الرّسمي للمكتبة أو ضع سؤالا مصاغا بوضوح ولغة سليمة في قسم الأسئلة والأجوبة للحصول على إجابة كافيّة. إنشاء جدولي المقالات والمُستخدمين في قاعدة البيانات بعد أن أنشأنا الصّنفين المسؤولين عن إنشاء الجدولين في ملفّ models.py الذي يقع تحت مُجلّد project أصبح بإمكاننا إنشاء الجدولين عن طريق مكتبة SQLAlchemy اعتمادا على محتويات ملفّ models.py والكائن db الذي سبق لنا وأنشأناها بمُساعدة إضافة Flask-SQLAlchemy. بعد إنشاء الجدولين، ستُطبّق التّغييرات مُباشرة إلى خادوم PostgreSQL الذي نُصّب بجهازك المحلي، ما يعني بأنّك ستتمكّن من التّعامل مع قاعدة البيانات باستخدام لغة SQL والميّزات التي تُوفّرها قاعدة البيانات PostgreSQL. لإنشاء الجدولين في قاعدة بياناتنا، سنقوم أولا بإنشاء ملف باسم create_db.py في المجلّد الرّئيسي kalima وسنضع به ثلاثة أسطر فقط: # create_db.py from project import db from project.models import Post, User db.create_all() تأكّد فقط من أنّ إعداد SQLALCHEMY_DATABASE_URI يُشير إلى قاعدة بيانات PostgreSQL التّي أنشأناها سابقا باسم kalima وأنّ اسم المُستخدم وكلمة المرور صحيحان في ذات الإعداد، وللمزيد أعد إلقاء نظرة على درس تجهيز قاعدة البيانات PostgreSQL ودرس إنشاء جداول البيانات. كما تُلاحظ، الشّفرة بسيطة، أولا نستدعي الكائن db من حزمة المشروع، ثمّ نستدعي كلّا من الصّنف Post وUser لإعلام SQLAlchemy بأنّنا قد حدّدنا هذه الجداول والأعمدة الأساسيّة فيها، وبعد ذلك نقوم بإنشاء الجداول في قاعدة البيانات باستعمال الدّالة db.create_all. وبنفس الطّريقة لو أردت حذف الجداول يُمكنك استدعاء الدّالة db.drop_all عوضا عن db.create_all. وإن أمكنك الوصول إلى سطر أوامر PostgreSQL عن طريق أداة psql فيُمكنك الاتّصال بقاعدة البيانات kalima وعرض الجداول فيه، بالأمرين التّاليين: postgres=# \c kalima You are now connected to database "kalima" as user "postgres". kalima=# \d No relations found. الأمر الأول للاتّصال بقاعدة البيانات الخاصّة بتطبيقنا، وذلك لأنّ الخادوم يحتوي على أكثر من قاعدة بيانات واحدة، أمّا الأمر الثّاني فهو لعرض الجداول المُتواجدة بقاعدة البيانات، وبما أنّنا لم ننفّذ ملفّ create_db.py بعد فالنّتيجة هي بطبيعة الحال أنّنا لا نمتلك أية جداول أو علاقات كما تُلاحظ من الرّسالة No relations found. الآن، نفّذ الملفّ عبر الأمر: python create_db.py إن لم تلاحظ أية رسالة، فهذا يعني بأنّ كل شيء بخير، ولو عدت إلى سطر أوامر psql وأعدت تنفيذ الأمر \d للاحظت ما يلي: List of relations Schema | Name | Type | Owner --------+--------------+----------+---------- public | posts | table | postgres public | posts_id_seq | sequence | postgres public | users | table | postgres public | users_id_seq | sequence | postgres ربّما تختلف قيمة العمود Owner لديك لأنّها تُشير إلى اسم المُستخدم الذي أنشأ الجداول أو مالكها، وهذا الاسم هو نفسه الذي تُحدّده في إعداد قاعدة البيانات الذي سبق وأن أشرنا إليه، لاحظ معي فقط كلّا من الجدول posts و الجدول users: public | posts | table | postgres public | users | table | postgres بما أنّ نوع العلاقة هو table (جدول)، فهذا يعني بأنّنا استطعنا إنشاء الجدولين بنجاح، ونستطيع الآن التّعامل معه بلغة SQL مباشرة من أداة psql. في ما يلي استعلامان يُمكنك تنفيذهما للحصول على جميع السّجلات في كلّ جدول: select * from posts; select * from users; بالطّبع، لأنّنا لم نُضف أية سجلّات بعد، فالمُخرج سيكون كما يلي في كلتا الحالتين: Posts Table: id | title | content | created_date | author_id ----+-------+---------+--------------+----------- (0 rows) Users Table: id | name | email | password | created_date ----+------+-------+----------+-------------- (0 rows) وكما تُلاحظ، جميع الأعمدة التّي سبق وأن حدّدناها في ملفّ models.py باستعمال db.Column متواجدة في هذين الجدولين. يُمكنك بالطّبع إضافة بعض السّجلّات بلغة SQL إن أردت ذلك بالاعتماد على جملة INSERT INTO وكذا التّعامل مع البيانات بمُختلف الطّرق التّي تُتيحها PostgreSQL، ولكنّنا لن نستعمل لغة SQL لأنّنا نمتلك أداة SQLAlchemy وإضافة فلاسك الخاصّة به. ختاما تعرّفنا في هذا الدّرس على كيفيّة إنشاء جدولي المقالات والمُستخدمين في قاعدة البيانات مُباشرة باستعمال الدّالة db.create_all() التّي توفّرها لنا إضافة Flask-SQLAlchemy. في الدّرس القادم، سنتعرّف على كيفيّة استعمال مُفسّر لغة بايثون للتعامل مع قاعدة البيانات بمُساعدة من مكتبة SQLAlchemy، وبذلك سنتمكّن من التواصل مع قاعدة البيانات وإدارة سجلات الجداول فيها.
  10. مُقدّمة: بعد أن عرّفنا كلّا من جدول المقالات وجدول المُستخدمين، بقيت لنا خطوة الرّبط بينهما ليُشير كلّ مقال إلى كاتبه ونستطيع الوصول إلى مقالات كلّ مُستخدم، وسنقوم بهذا الأمر باستغلال خصائص قواعد بيانات SQL العلائقيّة لإنشاء علاقة واحد للعديد، بحيث يُمكن لكلّ مُستخدم أن يمتلك العديد من المقالات ويكون لكلّ مقال كاتب واحد فقط. المُشكلة عند تصفّحك لتطبيقات الوب في وقتنا الحالي، ستُلاحظ بأنّ المُستخدمين يمتلكون بياناتهم الخاصّة، مثلا يُمكن للمُستخدم الواحد أن يمتلك عدّة مقالات وتدرج تحت اسمه، ويُمكن أن يمتلك كذلك عدّة تعليقات باسمه، إذن كيف يُمكن أن نُدرج مقالات من جدول المقالات الخاصّ بنا إلى المُستخدم الذي يقوم بإضافتها؟ بمعنى آخر، كيف يُمكن للمُستخدم الواحد أن يمتلك عدّة مقالات مُندرجة باسمه؟ الحل الخاطئ الطّريقة التّي يُمكن أن تُفكّر فيها هي بإدراج رقم مُعرّف كل مقال يُضيفه المُستخدم إلى عمود خاص به مع الفصل بينها بمسافة أو شيء من هذا القبيل، بعدها تحصل عليها وتقوم باستخراج مقالات كل مُستخدم. الجدول سيكون كما يلي: id | name | posts 1 | Ali | 1, 3, 5 ... أهملت هنا عمودي البريد الإلكتروني وكلمة المرور لأنّها غير مُهمّة في مثالنا. الآن إن نظرت إلى العمود posts فستجد بأنّ المقالات ذات المُعرّفات 1 و 3 و 5 قد أضافها المُستخدم Ali. هذه الطّريقة فكرة سيّئة جدّا، فلو أردنا حذف مقال فحذف علاقته بالمُستخدم أمر مُعقّد، وهناك العديد من المشاكل مع استخدام هذه الطّريقة، وقد أشرت إليها لأنّها شائعة جدّا ويجب أن تحذفها من ذهنك قبل أن تحدث أمور أسوأ، فإن أردت أن تكون مُطوّر وب جيّدا فعليك تعلّم الطّرق الصّحيحة وعليك كذلك الإلمام بالطّرق الخاطئة في التّطوير. الطّريقة الصّحيحة لربط سجلّ واحد بالعديد من السّجلّات في جدولين مُختلفين علاقة الواحد-للعديد أو One-to-Many Relationship هي طريقة لربط جدولين أو أكثر بحيث يمتلك كلّ فرد في جدول العديد من السّجلّات في جدول آخر. في مثالنا، سيمتلك المُستخدم الواحد عدّة مقالات باسمه. للقيام بهذا الأمر، نقوم بوضع عمود آخر في الجدول الذي سيمتلك العديد من السّجلّات الخاصّة بفرد واحد من جدول آخر ليحمل قيمة رقم مُعرّف هذا الفرد، أي أننا سنُضيف عمودا آخر في جدول المقالات ليحمل رقم مُعرّف المُستخدم الذي أضاف المقال ليكون جدول المقالات كما يلي: | id | title | user_id | 1 | Post 1 | 1 | 2 | Post 2 | 2 | 3 | Post 3 | 1 | 4 | Post 4 | 3 لنفترض بأنّ لدينا ثلاثة مُستخدمين في جدول المُستخدمين كما يلي: | id | name | 1 | Ali | 2 | Abdelhadi | 3 | Hassan مُجدّدا، قمت بإهمال الأعمدة الأخرى لأنّها لا تهمّنا في هذا المثال، وكما تُلاحظ لكل مقال قيمة في عمود user_id لتُشير إلى المُستخدم الذي أنشأ هذا المقال، وعند النّظر إلى جدولي المقالات والمُستخدمين والسّجلّات المتواجدة فيها حاليّا، يتبيّن لنا بأنّ المستخدم Ali قد أضاف مقالين، المقال ذو رقم المُعرّف 1 و المقال ذو رقم المُعرّف 3، أمّا Abdelhadi فهو صاحب المقال ذي المُعرّف 2 أمّا المقال الأخير فمن الواضح بأنّ المُستخدم Hassan هو من أضافه. هذه هي علاقة الواحد للعديد ببساطة، عندما يمتلك سجلّ واحد من جدول العديد من السّجلّات في جدول آخر، فإنّنا نُضيف عمودا إلى الجدول الذي يحمل العديد من السّجلات ليحمل كل سجلّ منه قيمة المفتاح الأولي Primary Key للسّجل الواحد في الجدول الآخر والذي يُسمّى فيه مفتاحا أجنبيا Foreign Key، أي أنّ العمود user_id عبارة عن مفتاح أجنبي لأنّه يحمل قيمة المفتاح الأولي الخاصّ بالمُستخدم، وعندما نقوم بتحديد عمود على أنّه مفتاح أجنبي، فالعلاقة بين الجدولين تكون مفهومة بالنّسبة لقاعدة البيانات لدينا، كما يُساعدنا ذلك على تخطّي بعض المشاكل في حالة ما أضيفت قيمة خاطئة إلى العمود عوضا عن قيمة صحيحة. في SQLAlchemy يُمكننا إنشاء علاقة واحد للعديد بسهولة وبساطة بإضافة سطرين فقط إلى الشفرة التّي حدّدناها سابقا، سطر في صنف (جدول) المقالات لإضافة عمود باسم author_id والذي سيكون عبارة عن مفتاح أجنبي يُشير إلى قيمة المفتاح الأولي للمُستخدم، وسطر في صنف المُستخدم لنتمكّن من الوصول إلى مقالات كل مُستخدم ببساطة. أضف هذا السّطر في آخر الصّنف Post: # project/models.py author_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) لتعريف مفتاح أجنبي، نستعمل الدّالة db.ForeignKey مع تمرير اسم الجدول واسم المفتاح الأولي مفصولين بنُقطة (في هذا المثال users.id) ونقوم كذلك بإضافة المُعامل nullable وإسناد القيمة المنطقيّة False له للتأكّد من أنّ الحقل الذي يربط بين المُستخدم والمقال الذي أضافه لا يحمل قيمة فارغة. بعد إضافة المفتاح الأجنبي، حان الوقت لإخبار SQLAlchemy بأنّنا نريد الحصول على مقالات كلّ مُستخدم، والمُستخدم الذي أضاف كلّ مقال انطلاقا من العلاقة بين الجدولين، لذا أضف السّطر التّالي في آخر الصّنف User: # project/models.py posts = db.relationship("Post", backref="author", lazy="dynamic") في هذا السّطر، نقوم بتعريف العلاقة بين المُستخدم والمقال عن طريق الدّالّة db.relationship مع تمرير ثلاثة مُعاملات، الأول عبارة عن اسم الصّنف الذي يشير لجدول المقالات، والمُعامل backref يُمكّننا من الوصول إلى المُستخدم الذي كتب المقال عن طريق الاسم author أمّا المعامل الأخير فهو لتحديد نوعيّة النّتيجة التّي سنحصل عليها عند طلب جميع المقالات التّي كتبها أحد المُستخدمين، وقد وضعنا القيمة dynamic لتكون النّتيجة عبارة عن استعلام ديناميكي لجميع المقالات التّي كتبها المُستخدم، وهكذا سنحصل على مرونة أكثر عند طلب المقالات وسنتمكّن من التّعامل معها على أنّها كائنات يُمكننا استغلالها بجميع ما توفّره مكتبة SQLAlchemy من طرق ودوال مُساعدة، وسنرى تطبيقا لهذا الكلام فيما بعد. سنُضيف كذلك عمود author_id إلى التّابع __init__ الخاصّة بالصّنف Post وبالتّالي سيُصبح التّابع كما يلي: def __init__(self, title, content, author_id, created_date = None): self.title = title self.content = content self.author_id = author_id self.created_date = datetime.utcnow() بعد تحديد العلاقة بين الجدولين، سنستطيع إضافة مقال وإسناد رقم مُعرّف المُستخدم أو المُستخدم ككاتب للمقال الذي أضافه لنتمكّن فيما بعد من الحصول على كاتب كل مقال بجميع معلوماته (بريده الإلكتروني، اسمه …)، وكذا المقالات التّي كتبها كلّ مُستخدم مع مرونة في التّعامل معها عبر ما تُوفّره لنا مكتبة SQLAlchemy من مُساعدة. خاتمة بعد أن تعرّفنا على طريقة ربط المُستخدم ومقالاته والمقالات وكاتبها، أصبحنا نستطيع تطبيق التّغييرات إلى قاعدة بياناتنا والتّعامل مع السّجلّات بعمليّات قواعد البيانات المعروفة اختصارا بـCRUD أو Create, Read, Update, Delete، أي الإضافة/الإنشاء، القراءة/العرض، التّعديل والحذف.
  11. مُقدّمة بعد تجهيز قاعدة بيانات PostgreSQL التي سنستعملها في تطبيقنا، وبعد أن تعرّفنا في الدرس السابق على كيفيّة تنصيب وتجهيز كل من مكتبة SQLAlchemy و إضافة Flask-SQLAlchemy، حان الوقت لتجهيز جدولين من أهم جداول تطبيقات إدارة المحتوى، ألا وهي جدول المقالات الذي يمثل المحتوى بعينه وجدول المُستخدمين الذين سيُدِيرُون المحتوى، وبالتالي فسنقوم في هذا الدرس بوضع أساس لتطبيق “إدارة المحتوى” عبر تجهيز الطرف الذي سيقوم بالإدارة والطرف الذي يُمثل المحتوى. تذكير نعلم بأنّ قاعدة البيانات تحتوي على عدة جداول، كل جدول يمثل نوعا من المحتوى، وكل جدول يشمل عدة أعمدة، فمثلا جدول المُستخدمين سيحتوي على عمود لأسماء المستخدمين، وعمود لكلمات المرور، بحيث تكون قيمة كل عمود مربطة بمستخدم واحد فقط. تعرّفنا في الدرس السابق على الشكل العام لجداول قاعدة البيانات عند استعمال إضافة Flask-SQLAlchemy وقلنا بأنّ جدولا باسم table_name سيكون كالتّالي: from project import db class TableName(db.Model): __tablename__ = 'table_name' column_name = db.Column(db.Type, args) الجدول به عمود واحد باسم column_name ونقوم بإخبار SQLAlchemy بأنّ هذا عمود في الجدول عبر استخدام التّابع db.Column الذي يقبل عدّة معاملات لتحديد نوعية العمود وما يمكن له أن يقبل من قيم. نقوم بتحديد نوع البيانات التي يجب أن يقبلها العمود عبر استخدام المعامل db.Type مع استبدال Type بأحد الأنواع التي يدعمها SQLAlchemy. ما يلي أكثر أنواع القيم استخداما والتي يدعمها SQLAlchemy: Integer الأعداد الصّحيحة String(size) سلسلة نصيّة، size مُعامل يشير إلى أقصى قيمة لعدد المحارف في السّلسلة النّصيّة، مثلاString(25) يشير إلى أنّ العمود يُمكن أن يحمل سلسلة نصيّة بحدود خمسة وعشرين محرفا فقط ولا يُمكن تجاوز هذا الحد. Text نوع للإشارة إلى أنّ هذا العمود يُمكن أن يحتوي على النّصوص الطّويلة. DateTime نوع يُشير إلى أنّ العمود سيحمل تواريخ مُعبّر عنها بالكائن datetime في لغة بايثون، وسنستخدم هذا النّوع لوضع تاريخ نشر للمقال وتاريخ لانضمام مُستخدم معيّن. يُساعد تحديد نوع القيم في العمود على تجنّب الكثير من الأخطاء، ومن المُفضّل أن تختار دائما النّوع الذي يُناسبك أكثر لتحفظ المساحة وتتجنّب بعض الهجمات المُمكنة، فمثلا لو كان نوع القيمة Text في حقل اسم المُستخدم لتمكّن أحدهم من وضع نص كاسم مُستخدم له، وإن عرضناه في صفحة لكانت تلك الصّفحة بشعة لوجود نص عوضا عن كلمة أو كلمتين، هذا بالإضافة إلى أنّ المساحة في الخادوم ستُستهلك بشكل غير معقول. أعلم بأنّ الكلام النّظري مُمل بعض الشّيء، لكنّه مهم جدّا لتفهم ما سنقوم به تاليّا، وسنرى تطبيقا لهذا الكلام عند إنشاء جدولي المقالات والمُستخدمين في ما بعد وأتمنّى أن تُساعد هذه المُقدّمة على تقليص الصّداع الذي يحدث عندما تتعلّم شيئا لأول مرّة. تجهيز جدول المقالات بعد أن وضّحنا المفاهيم الأساسيّة، حان الوقت لتطبيق هذه المفاهيم وإنشاء الجداول التّي سنتعامل معها في تطبيقنا، تذكر بأنّ هذه الجداول عبارة عن أصناف بايثون عادية، ويجب عليك وضعها داخل ملفّ models.py المتواجد بمُجلّد project. سنبدأ بجدول المقالات الذي سيكون كالآتي: # project/models.py from datetime import datetime from project import db class Post(db.Model): __tablename__ = "posts" id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(255), nullable=False) content = db.Column(db.Text, nullable=False) created_date = db.Column(db.DateTime) def __init__(self, title, content, created_date = None): self.title = title self.content = content self.created_date = datetime.utcnow() def __repr__(self): return '<title {} | {} >'.format(self.title, self.content) مُلاحظة: تذكّر بأنّ جميع الجداول التّي نعرّفها عبارة عن أصناف في ملفّ project/models.py. أولا، نقوم باستيراد الكائن datetime لنستعمله في تأريخ إضافة المقال، بعدها نستدعي الكائن db الذي عرّفناه سابقا، بعد ذلك نقوم بتطبيق مفهوم الصّنف كجدول لإنشاء جدول المقالات باسم Post الذي يرث من db.Model، بعدها نقوم بتحديد اسم الجدول posts ثمّ نعرّف عمود رقم المُعرّف كمفتاح أولي، ثمّ العنوان كسلسلة نصيّة بحدود 255 حرفا، محتوى المقال كنصّ، تاريخ إضافة المقال من نوع db.DateTime. بعد تعريف جميع الأعمدة، نعرّف الأعمدة التّي يُمكننا إدارتها عبر الدّالة __init__، وتشمل هذه الأعمدة حاليا العنوان والمحتوى وتاريخ الإضافة فقط، وإدارة تاريخ الإضافة اختياري لأنّنا سبق وأن وضعنا له قيمة افتراضيّة عبارة عن تاريخ إضافة السّجل إلى قاعدة البيانات والذي نصل إليه من الدّالة datetime.utcnow، الآن إن أردنا إضافة قيم إلى الجدول فسنحتاج إلى توفير عنوان المقال ومحتواه فقط، أمّا العمودان الآخران فيهتمّ بهما SQLAlchemy. بعد الانتهاء من التّابع الخاص __init__ نعرّف التّابع الخاص __repr__ ليُساعدنا على عرض عنوان المقال ومحتواه بعد استعلامه بـSQLAlchemy ككائن، وقد سبق وأن شرحت وظيفة هذه الدّالة الخاصّة في درس البرمجة كائنيّة التوجّه ضمن سلسلة تعلّم بايثون، وسنرى في ما بعد نتيجة هذه الدّالة وبماذا ستُفيدنا. تجهيز جدول المُستخدمين سنقوم بإنشاء جدول المُستخدمين بنفس الطريقة، صنف باسم User، جدول باسم users، رقم مُعرّف كقيمة أوليّة، اسم مُستخدم، بريد إلكتروني، كلمة المرور، وتاريخ الانضمام. class User(db.Model): __tablename__ = "users" id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(25), nullable=False) email = db.Column(db.String(25), nullable=False) password = db.Column(db.String(25), nullable=False) created_date = db.Column(db.DateTime) def __init__(self, name, email, password, created_date = None): self.name = name self.email = email self.password = password self.created_date = datetime.utcnow() def __repr__(self): return '<username: {} | email: {} >'.format(self.name, self.email) هذه المرّة سيتوجّب علينا تحديد كلّ من اسم المُستخدم، كلمة المرور وبريده الإلكتروني إذا أردنا إضافة سجل مُستخدم إلى قاعدة البيانات، أمّا الدّالّة __repr__ فستعرض كلّا من اسم المُستخدم وبريده الإلكتروني. مُلاحظة: حاليّا نقوم بتخزين كلمات المرور على شكل نصوص واضحة، ويُمكن لأي شخص أن يحصل عليها ببساطة إن استطاع الوصول إلى قاعدة بيانات التّطبيق، وهذا أمر خطير جدّا، إذ أنّ حماية بيانات مُستخدمي التّطبيق أمر مهم جدّا، ولابد من تشفير كلمات المرور قبل إدخالها إلى قاعدة البيانات، وسنقوم بذلك فيما بعد، فالهدف الرّئيسي من هذا الدّرس هو إعطاؤك مقدّمة لكيفيّة تجهيز الجداول في قاعدة البيانات أمّا الحماية فسنتطرّق إليها لاحقا، لذا لا تحاول القيام بتخزين كلمات المرور بهذه الطّريقة، وتابع السّلسلة إلى نهايتها لتتعلّم كيفيّة تطوير تطبيقاتك بإطار العمل Flask بشكل آمن. خاتمة بعد أن تعرّفنا على كيفيّة تجهيز جداول قاعدة البيانات التّي سنتعامل معها في تطبيقنا باستعمال مكتبة SQLAlchemy وإضافة Flask-SQLAlchemy وتعرّفنا على بعض المفاهيم المُهمّة في التّعامل مع قواعد البيانات بهذه المكتبة، بقيت نقطة مهمّة يجب الإشارة إليها قبل إنشاء الجداول في قاعدة البيانات وجلب، إضافة وتعديل وحذف السّجلات منها، ألا وهي علاقة الواحد للعديد One-to-Many Relationship، وهي العلاقة التّي سنستكشفها وسنستعملها لربط كلّ مقال بالمُستخدم الذي أضافه وربط المقالات بكاتبها في الدّرس القادم.
  12. flask_cms

    مُقدّمة لا شك بأنّ معظم تطبيقات اليوم تعتمد على نوع من أنواع البيانات، لذا لا بدّ من حفظها في مكان مُناسب، وتعتبر قواعد البيانات التّي يُمكن التّعامل معها بلغة SQL أحد أفضل الطّرق لحفظ بيانات تطبيقات الويب، وبما أنّنا أنشأنا قاعدة بيانات خاصّة بنا، فقد حان الوقت للتعرف على كيفية استعمال مكتبة SQLAlchemy لإدارة قاعدة بيانات تطبيقنا. في هذا الدرس، سنتعرف على كيفية تجهيز إضافة Flask-SQLAlchemy لتسهيل العمل مع مكتبة SQLAlchemy. تنصيب إضافة Flask-SQLAlchemy تأكّد أولا من أنّ البيئة الوهميّة مفعّلة عندك وأنّك في مُجلّد kalima، وبعدها نفّذ الأمر التّالي لتنصيب الإضافة مع مكتبة psycopg2: pip install Flask-SQLAlchemy psycopg2 لكي نتمكّن من التّعامل مع قاعدة بيانات PostgreSQL باستعمال SQLAlchemy يجب تنصيب مكتبة psycopg2 التّي تُمكنّنا من ذلك. مُلاحظة: تعتمد الإضافة أساسا على مكتبة SQLAlchemy لذا ستُنصّب كذلك، هذا يعني بأنّك تستطيع الوصول إلى جميع الدّوال والكائنات المُتواجدة في المكتبة إن أردت ذلك. لاختبار نجاح التّنصيب، افتح مفسّر بايثون وقم باستيراد الحزم التّاليّة: import flask_sqlalchemy import sqlalchemy إن لم يحدث أي خطأ فالحزمة مُنصّبة بنجاح، وإن واجهت أي خطأ فحاول التّأكد من أنّك اتّبعت ما ذكرته في الدّروس الأولى من السّلسلة بشكل صحيح. تذكير قواعد البيانات من نوع SQL تعتمد بشكل رئيسيّ على الجداول Tables، كلّ جدول يحمل أعمدة مُختلفة، وكلّ عمود يحمل قيما معيّنة، وهناك عمود خاصّ يسمى برقم المُعرّف وعادة ما يُشار إليه بـid ولأنّ كل مُدخل من المُدخلات يحمل رقم مُعرّف فريدا، فسيُمكّننا من تمييز المُستخدمين والمقالات وبقيّة الأجزاء المُهمّة في تطبيقاتنا، وقد سبق وأن شرحت هذا في درس ربط تطبيق Flask مع قاعدة بيانات SQLite، لذا عد إلى ذلك الدّرس لتفهم أكثر. ربط التّطبيق بإضافة Flask-SQLAlchemy لربط التّطبيق مع إضافة Flask-SQLAlchemy نقوم أولا باستيراد SQLAlchemy من حزمة الإضافة بالسّطر التّالي: # project/__init__.py from flask_sqalchemy import SQLAlchemy وبعدها نقوم بتمرير كائن التّطبيق app إلى ما قمنا باستيراده وإسناد النّاتج إلى مُتغيّر باسم db كما يلي: # project/__init__.py db = SQLAlchemy(app) سنقوم كذلك بإضافة سطرين لإعدادات التّطبيق app.config، السّطر الأول سيكون عبارة عن رابط قاعد البيانات، كما يلي: app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://username:password@127.0.0.1/kalima' استبدل username بالمُستخدم الذي أنشأت به قاعدة البيانات وكلمة المرور بكلمة المرور التّي سبق أن حدّدتها، إن كنت على أحد أنظمة لينكس فالمُستخدم المبدئي هو postgres والعنوان 127.0.0.1 يُشير إلى أنّنا سنعتمد على قاعدة بياناتنا المحليّة، أمّا kalima فهو اسم قاعدة البيانات التّي أنشأناها سابقا. السّطر الثّاني غير مُهم وقد لا تحتاج إليه في قادم النّسخ من إضافة Flask-SQLAlchemy، وقد قمت بإضافته لإخفاء تنبيه يظهر بعد تشغيل الخادوم. app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True مُلاحظة: من المعروف بأنّ التّطبيق يمر عبر عدّة مراحل، مرحلة التّطوير، الاختبار ثمّ مرحلة الإنتاج (النّشر إلى العالم الخارجي)، وعادة ما تُعزل كلّ مرحلة عن الأخرى فيما يُسمّى بالبيئة، ولذا تسمع مُصطلحات كبيئة التّطوير Development Environment، بيئة الاختبار Testing Environment، بيئة الإنتاج Production Environment، وإعدادات كلّ بيئة تكون مختلفة عن البيئات الأخرى، ولهذا يعدّ تخزين الإعدادات في الملفّ الرّئيسي للمشروع أمر سيئا لأنّك ستضطر إلى تغيير الإعدادات في كل مرّة تنتقل فيها من بيئة إلى أخرى، وقد يُشكل إعداد خاطئ واحد خطرا كبيرا على تطبيقك، خاصّة إن كان التّطبيق ذا وظائف مُتعدّدة، والحل الأمثل هو تخزين هذه الإعدادات في ملفّ مُستقل وهذا الأمر مهم جدا وقد سبق وأن شرحت طريقة بسيطة للقيام بذلك، وسنتطرّق إليه بالتّفصيل في ما بعد، المهم الآن أن تُدرك بأنّ كل ما يقع تحت قاموس app.config عبارة عن إعداد من إعدادات التّطبيق. بعد ربط كائن التّطبيق بإضافة Flask-SQLAlchemy سنتمكّن من استغلال الكائن db للاستفادة من كل ما تمنحه لنا الإضافة من تسهيلات للعمل مع مكتبة SQLAlchemy. ومع التّغييرات التّي قُمنا بها سيُصبح الجزء العلوي من ملفّ project/__init__.py كالآتي: # project/__init__.py from flask import Flask, render_template app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://username:password@127.0.0.1/kalima' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy(app) from project.posts.views import posts from project.users.views import users app.register_blueprint(posts) app.register_blueprint(users) تأكّد فقط من أنّ الإعدادين يقعان مُباشرة بعد تعريف الكائن app وأنّ استيراد الطّبعات الزّرقاء Blueprints وتسجيلها يقع بعد تعريف المُتغيّر db لتجنّب بعض المشاكل التّي قد تحدث عندما تعتمد كل حزمة على الأخرى أثناء الاستيراد أو ما يُسمى بـ circular imports أو الاستيرادات الدّائريّة. الجداول في Flask-SQLAlchemy لتجهيز الجداول في التّطبيق، سنفتح أولا ملفّا جديدا باسم models.py في مجلّد project، وسنضع فيه كل ما يتعلّق بالجداول وطريقة تسجيلها والبيانات المبدئية. وطريقة إنشاء الجداول في Flask-SQLAlchemy هي كالآتي: from project import db class TableName(db.Model): __tablename__ = 'table_name' column_name = db.Column(db.Type, args) كما ترى، نقوم أولا باستيراد الكائن db من حزمة المشروع ثمّ نقوم بإنشاء صنف باسم الجدول، ولابد لهذا الصّنف أن يرث من الصّنف db.Model الذي تُوفّره لنا إضافة Flask-SQLAlchemy، بعدها نقوم بتسميّة الجدول عبر وضع اسمه على شكل سلسلة نصيّة وإسنادها للمُتغيّر الخاصّ __tablename__، ثمّ بعد ذلك نقوم بتعريف مُختلف أسماء الأعمدة وإسنادها قيمة عن طريق التّابع db.Column، ثمّ نُخصّص نوع قيم العمود وبعض المُعاملات الإضافيّة، وأحد أشهر المُعاملات هو مُعامل primary_key الذي يجعل من العمود مفتاحا أوليا إن أسندت له القيمة المنطقيّة True وعادة ما يُستخدم مع عمود رقم المُعرّف id، والمُعامل الآخر الذي ستصادفه كثيرا هو مُعامل nullable ويقبل كذلك القيمتين المنطقيّتين True و False فإن كانت قيمة المُعامل False فهذا يعني بأنّ العمود لا يمكن أن يحمل قيمة فارغة، ويُستعمل في الأعمدة المُهمّة كالبريد الإلكتروني وكلمة المرور، ويُمكن أن تسمح بأن تكون قيم العمود فارغة إن لم يكن توفير المعلومة ضروريا مثل نبذة عن المُستخدم وصورته الشّخصيّة أو اسمه الحقيقي وهذه الأمور تختلف حسب نوعيّة التّطبيق بكل تأكيد. المفتاح الأولي Primary Key عبارة عن عمود يحمل قيما فريدة في الجدول بأكمله، ولا يُمكن أن تكون قيمته فارغة ولكنّنا لا نحتاج إلى إضافة المُعامل nullable عند تعريف العمود، ويُستعمل لتعريف كل سجلّ من سجلّات الجدول على حدة، ومن المُفضّل أن يكون نوعه عددا صحيحا لحفظ بعض المساحة، ولإبقاء القيم فريدة ومُختلفة عن بعضها البعض، يضاف واحد إلى القيمة السّابقة عند كل إضافة إلى قاعدة البيانات، وكما قُلت سابقا، الطّريقة الشّائعة لاستخدامه هو بوضع عمود باسم id في الجدول ليحمل القيم الفريدة على شكل أعداد صحيحة، فمثلا لو كان لدينا قاعدة بيانات بأسماء ما كما يلي: id | name 1 | أحمد 2 | مُحمّد 3 | أحمد للوهلة الأولى يبدو بأنّ سجلّ أحمد قد كرّر مرّتين، لكن لاحظ رقم المُعرّف الخاص بكل سجلّ، أحمد الأول رقم مُعرّفه 1 والآخر 3، هذا يعني بأنّهما شخصان مُختلفان بنفس الاسم، لا بد أنّك أدركت الآن مدى أهميّة المفتاح الأولي، فلو كان تطبيقنا يسمح بتسجيل المُستخدمين بأسمائهم لوجدنا مُشكلة مع أحمد هذا، لكنّ رقم المُعرّف يقوم بحل المُشكلة. أمّا عن db.Type فهو لتحديد نوع القيم التي سيحملها العمود، وهي مُشابهة لأنواع القيم المُتاحة في لغة SQL، مثل العدد الصّحيح Integer والسّلسلة النّصيّة String (VARCHAR في لغة SQL) والتّاريخ وغير ذلك من أنواع القيم. خاتمة قمنا في هذا الدرس بتجهيز أداة SQLAlchemy وتعرّفنا على كيفيّة استخدام إضافة Flask-SQLAlchemy للتعامل معها وألقينا نظرة بسيطة إلى الصورة العامة لجدول SQL مكتوب بلغة بايثون. في الدرس القادم، سنقوم بإنشاء جدولي المقالات والمُستخدمين في قاعدة بياناتنا.
  13. تجهيز قاعدة البيانات بما أنّنا سنستخدم قاعدة البيانات PostgreSQL لهذا التّطبيق، عليك أولا أن تقوم بتنصيبها، إن كنت تستخدم Ubuntu فيُمكنك مُراجعة درس كيفيّة تنصيب PostgreSQL على Ubuntu، أمّا إن كنت على نظام Windows أو نظام Mac OS فتستطيع تحميلها من الموقع الرّسمي وتنصيبها، يُمكنك كذلك استخدام برنامج pgAdmin لإدارة قواعد بياناتك عن طريق برنامج رسومي. يُمكنك استعمال البرنامج الرّسومي لإنشاء قاعدة البيانات إن أردت ذلك، لكنّنا سنستعمل أداة psql لتنفيذ أوامر PostgreSQL واستعمال لغة SQL مباشرة من سطر الأوامر، لذا تأكّد من أنّك تستطيع الوصول إلى أداة psql. إنشاء قاعدة البيانات لإنشاء قاعدة البيانات، يكفي أن ننفّذ الأمر التّالي على سطر أوامر psql : CREATE DATABASE kalima; بعد تنفيذ الأمر، سيكون المُخرج كما يلي: CREATE DATABASE كلمة المرور للاتّصال بقاعدة البيانات، سنحتاج إلى كلمة مرور، إن سبق لك وأن خصّصت كلمة مرور أثناء التّنصيب، تستطيع تخطي هذه الخطوة، أمّا إن لم تمتلك كلمة مرور أو أنّك نسيتها، فيُمكنك ببساطة تغييرها في سطر أوامر psql بالأمر التّالي: \password بعد تنفيذ الأمر سيُطلب منك كتابة كلمة مرور جديدة وإعادة كتابتها للتّأكد من أنّك لم تُخطئ. كلمة المرور هذه مُهمّة جدا للاتّصال بقاعدة البيانات من تطبيق فلاسك باستخدام أداة SQLAlchemy لذا تأكّد من أنّها مُتاحة. ما معنى ORM؟ ORM اختصار Object-Relational Mapper أي رابط الكائنات بالعلاقات (الجداول)، الهدف منه ببساطة التّعامل مع قواعد بيانات SQL باستعمال الكائنات Objects والأصناف Classes عوضا عن تعليمات SQL، فعوضا عن إنشاء البيانات وجلبها وتعديلها وحذفها عن طريق تعليمات SQL التّي ستتكرّر كثيرا في الشّفرة يُمكن ببساطة استخدام لغة بايثون للوصول إلى نفس الهدف، كما أنّ استعمالها مع لغة برمجة يخلق شيئا من عدم التّناسق. لو تابعت سلسلة دروس Flask للمُبتدئين، للاحظت بأنّنا استعملنا ملفّا باسم manage_db للتّعامل مع قواعد البيانات عن طريق الدّوال عوضا عن كتابة تعليمات SQL عند الرّغبة في إجراء كل عمليّة، ولو استعملنا SQL الخام لكان تكرار الشّفرة كثيرا. بالإضافة إلى ميّزة تعريف الأصناف التّي تُمثّل الجداول والتّعامل معها عن طريق التّوابع (الدوال) لتجنّب تكرار الشّفرة، يقوم الـORM بالكثير من الأشياء المُملّة خلف الكواليس، فعوضا عن الاهتمام بالتّفاصيل الصّغيرة بأنفسنا، يقوم الـORM بالاهتمام بها من أجلنا لنهتمّ بالأمور الأكثر أهميّة، وفي النّهاية، مُعظمنا لا يتقن لغة SQL كثيرا لذا من المُفضّل استعمال مكتبة للتّعامل مع قواعد البيانات باللغة التّي نُجيدها. وبما أنّ استعمال الـORM يُتيح لنا عزل الشّفرة المسؤولة عن التّعامل مع قاعدة البيانات في معزل عن التّطبيق فهذا يُتيح لنا تعديل الخصائص وإضافة الميّزات للتّطبيقات دون التّأثير على تطبيق معيّن بحد ذاته، ومن الشّائع جمع الشّفرة المسؤولة عن التّعامل مع قاعدة البيانات في ملف باسم models.py في مجلّد المشروع. يُساعدنا الـORM كذلك على تحقيق مبدأ MVC أو Model-View-Controller أي “نموذج، عرض، مُتحكّم ” وهو مبدأ يقوم على عزل جزء العرض، المُتحكّمات، والنّماذج في تطوير التّطبيقات لمرونة أكثر في التّطوير، أي أنّ كلّا من العرض (الذي يُمثّل القوالب في تطبيقنا) والمُتحكّم (الذي يُمثّل دوال الموجّهات في ملفّات views.py) والنّموذج (أي الجزء الذي يتعامل مع البيانات) أجزاء منفصلة حسب الوظيفة متّصلة في التّطبيق ويُمكن أن تنتقل البيانات بينها ببساطة، ورغم أنّ إطار Flask يمنحك الحريّة الكاملة في طريقة تسميّة أجزاء تطبيقك، إلّا أن أطر عمل أخرى مثل Django و Rails تفرض عليك تسميّات محدّدة. ما هو SQLAlchemy؟ بعد أن عرّفنا معنى الـORM بقي تعريف مكتبة SQLAlchemy، وهي مكتبة تُسهّل علينا التّعامل مع قواعد البيانات المُختلفة، كما أنّها تحتوي على ORM قوي ومُعتمد عليه من طرف المشاريع الكبيرة. تحتوي SQLAlchemy على الكثير من الدّوال المُساعدة والكائنات التّي تُسهّل عليك كتابة SQL بلغة بايثون، والORM المبني فيه يُستعمل لربط الكائنات بالبيانات في قاعدة البيانات. سنستعمل في غالب هذا المشروع الـORM الخاص بـSQLAlchemy ولن نتطرّق إلى بقيّة ميّزات المكتبة، لذا إن أردت معرفة المزيد عن المكتبة فعليك بالتّوثيق الرّسمي. إضافات Flask Flask Extensions أو إضافات فلاسك عبارة عن مكتبات وأطر عمل Frameworks مبنيّة لتُسهّل على المُطوّر القيام بالأشياء المُفصّلة وتجنّب التّعامل مع كل شيء بأنفسنا، فمثلا، يُمكننا أن نستعمل مكتبة SQLAlchemy مع تطبيق Flask مُباشرة، إلّا أن في هذه الطّريقة الكثير من الأمور غير المُهمّة والكثير من الإعدادات، وبما أنّنا في مسيرة تعلّم إطار فلاسك لا كيفيّة استعمال SQLAlchemy، ولتسهيل استعمال SQLAlchemy مع المشاريع المبنيّة بإطار العمل فلاسك، قام كاتب الإطار Armin Ronacher بتطوير إضافة للإطار باسم Flask-SQLAlchemy التّي أصبحت أحد أشهر الإضافات على موقع Github. بالإضافة إلى Flask-SQLAlchemy هناك الكثير من الإضافات التّي يُمكننا استعمالها لتطوير تطبيقات فلاسك، وإليك بعضا من هذه الإضافات: Flask-Login إضافة لتسهيل التّعامل مع جلسات المُستخدمين Sessions، تُتيح إمكانيّة تسجيل دخول مُستخدم وتسجيل خروجه، لكنّها لا تقوم بالتّحقق من صحّة البيانات (اسم المُستخدم وكلمة المرور مثالا) وتترك المُطوّر للاهتمام بالأمر. Flask-Bcrypt إضافة لتشفير كلمات المرور باستعمال خوارزميّة Bcrypt قبل حفظها بقاعدة البيانات لحماية أكثر، هذا لمنع المُخترق من سرقة كلمات المرور حتى بعد وصوله إلى قاعدة البيانات، وكلّما كانت خوارزميّة التّشفير أعقد كلّما كان من الصّعب فك التّشفير عن كلمة المرور ما يُوفّر حماية أكثر. Flask-WTF بما أنّ مُعظم التّطبيقات تعتمد على نماذج HTML (حقول النّصوص، كلمات المرور و مساحة النّص TextArea) وبما أنّها تُسبّب الكثير من الثّغرات الأمنيّة في التّطبيقات والتّي يُمكن أن يُساء استغلالها، فلا بد من توفير طريقة آمنة وبسيطة للتأكّد من أنّ ما يُدخله المُستخدم في هذه الحقول آمن. يُمكنك بالطّبع التّأكد من مُدخلات المُستخدم بنفسك، لكنّ الأمر يأخذ وقتا، كما أنّه ممل في الحقيقة، إذ عليك تكرار نفس الشّيء كلّ مرّة. عوضا عن الاعتماد على مهاراتك البرمجيّة للتأكّد من سلامة المُدخلات، يُمكنك أن تترك إضافة Flask-WTF للاهتمام بالأمر في تطبيقاتك المبنيّة بإطار فلاسك، الإضافة مبنيّة على مكتبة WTForms التّي يُمكنك استعمالها مع أطر عمل أخرى، وأنصحك بالاطلاع على توثيق المكتبة الرّسمي للمزيد من المعلومات عن كيفيّة مُعالجة النّماذج. Flask-Migrate ببساطة، Flask-Migrate إضافة تُتيح لك تهجير البيانات Migrations عند تطوير تطبيقك. هذه الإضافة مهمّة جدّا إن كنت تطوّر تطبيقا سبق وأن نشرته على نحو مُتواصل، إذ تُتيح لك تهجير البيانات في كل مرّة تُضيف جدولا جديدا أو علاقة بين الجداول إلى قاعدة البيانات. تخيّل أنّك تمتلك تطبيقا يحتوي جدول المُستخدمين فيه على أكثر من ألف مُدخل، إن أردت إتاحة إمكانيّة إضافة تاريخ ازدياد للمُستخدمين فستضطر إلى حذف كلّ شيء في هذا الجدول وإضافة العمود الجديد ثمّ إعادة البيانات من جديد، الأمر مُعقّد بعض الشّيء وفقدان البيانات أمر غير مرغوب فيه بتاتا. مُجدّدا عوضا عن القيام بكل شيء بنفسك، دع إضافة Flask-Migrate لتهتمّ بكل هذا التّعقيد من أجلك، فبسطر أو سطرين تستطيع تطبيق الميّزة الجديدة لتطبيقك وتهجير البيانات القديمة بكل بساطة. Flask-Testing إضافة لتسهيل إجراء اختبارات وحدة Unit tests لتطبيقك، إذ أنّ اختبار التّطبيق مُهمّ جدّا للتأكّد من أنّ كلّ شيء بخير وأنّ المُستخدمين لا يواجهون أية مشاكل، خاصّة إذا كان مشروعك كبيرا، فاختبار كل وظيفة يدويّا قد يأخذ منك وقتا طويلا، وإن لم تختبره آليا فنسبة الخطأ كبيرة جدّا. إذا أضفت خاصيّة جديدة لتطبيقك فأنت بذلك تُعرّض أجزاء أخرى من التّطبيق إلى الخطأ، وستُسهّل عليك الاختبارات معرفة ما إذا كان التّطبيق لا يزال يعمل كما ينبغي له أو لا، وبما أنّ اختبارات الوحدة تختبر كلّ وظيفة على نحو مُستقل، فسيسهل عليك معرفة أي جزء يجب عليك الاهتمام به لإصلاح التّطبيق. Flask-Gravatar هذه الإضافة تُسهّل لنا استعمال خدمة Gravatar لتمكين المُستخدمين من إضافة صورة شخصيّة لهم، إذ تكون الصّورة الشّخصيّة مُرتبطة بالبريد الإلكتروني للمُستخدم وتُستعمل هذه الخدمة في الكثير من التّطبيقات في وقتنا الحالي. هناك مئات الإضافات الخاصّة بإطار Flask لذا لا يُمكنني ذكرها كلّها، إن أردت الاطّلاع على إضافات أخرى، تستطيع البحث عنها عن طريق أداة pip كما يلي: pip search flask سنتعرّف على معظم هذه الإضافات في قادم الدّروس مع التّقدم في المشروع، وهذا الهدف الأساسي من هذه الإضافات، كلّما تقدّمت أكثر وكبر المشروع كلّما احتجت إلى التّعامل مع الكثير من المهام المُملّة كالتّأكد من أنّ ما يرسله المُستخدم إلى قاعدة بياناتك آمن، وكالتّعامل مع الجلسات لتوفير نظام تسجيل دخول آمن دون الاضطرار للقيام بكل التّفاصيل؛ كما أنّ الإضافات تُقلّل من حجم الخطأ في التّطوير، فالاعتماد على إضافة لتسجيل دخول المُستخدمين بشكل آمن أفضل من مُحاولة إنشاء طريقة خاصّة بك قد تكون مليئة بالثّغرات الأمنيّة. وبما أنّ فلاسك إطار عمل مُصغّر، فبإضافاته يُمكنك أن تستخدمه كما تُستخدم أطر العمل الضّخمة كـDjango و Rails و Laravel وغيرها من الأطر المشهورة. خاتمة بعد أن أنشأنا قاعدة البيانات وتعرّفنا على بعض المفاهيم المُهمّة في تطوير تطبيقات الويب مع إطار فلاسك، حان الوقت لإنشاء الجداول الأساسيّة في قاعدة البيانات لنتمكّن من التّعامل معها عن طريق إضافة Flask-SQLAlchemy وهذا بالضّبط ما سنقوم به في الدّرس القادم.
  14. flask_cms

    مُقدّمة تعرّفنا سابقا على حلقة التّكرار for للدّوران على عناصر قائمة أو مفاتيح وقيم قاموس بايثون، سنتعرّف الآن على تقنيات مُتقدّمة ستُساعدك على التّعامل مع حلقة for على نحو أفضل لتتمكّن من تصميم قوالب HTML مع مُحرّك القوالب Jinja بطريقة أسرع وأكثر إتقانا. استعمال حلقة for مع قائمة بايثون تُستعمل حلقة for مع قوائم بايثون عاديّة كما يلي: {% set names = ['Ahmed', 'Khalid', 'Ali', 'Abdelhadi', 'Yasser'] %} <ul> {% for name in names %} <li>{{ name }}</li> {% endfor %} </ul> النّتيجة: Ahmed Khalid Ali Abdelhadi Yasser بهذه الطّريقة، يُمكنك عرض كل عنصر من القائمة داخل وسم HTML مُعيّن وتطبيق تنسيق باستخدام CSS كما تشاء. استعمال حلقة for مع قواميس بايثون بالإضافة إلى استعمالها مع قائمة عاديّة، يُمكنك كذلك استعمال حلقة for مع قاموس ما للوصول إلى مفاتيحه وقيّم كلّ مفتاح بحيث تدور حول القاموس لتحصل على كل مفتاح وقيمته، يُمكنك القيام بذلك ببساطة كالتّالي: {% set website = { 'url': 'academy.hsoub.com', 'company': 'Hsoub', 'description': 'Learn new skills for free' } %} <ul> {% for key, value in website.iteritems() %} <li> {{ key }} : {{ value }} </li> {% endfor %} </ul> لاحظ بأنّنا نقوم بالوصول إلى محتويات القاموس عبر تطبيق التّابع iteritems() على المُتغيّر website، بحيث نحصل على المفتاح والقيمة عبر المُتغيّرين key و value على التّوالي، داخل حلقة for نقوم بعرض كلّ من المفتاح وقيمته داخل وسم <li>. النّتيجة: url : academy.hsoub.com company : Hsoub description : Learn new skills for free يُمكنك كذلك استخدام تنسيقات إطار العمل Bootstrap لعرض مُحتويات قاموس ما بشكل جميل. بالإضافة إلى ما سبق، يُمكنك كذلك إسناد قائمة بايثون على هيئة قيمة لمفتاح مُعيّن من القاموس، على سبيل المثال، يُمكن لمقال ما أن يحمل عنوانا واحدا وأكثر من تعليق واحد، بحيث تكون التّعليقات عناصر من قائمة بايثون كما يلي: {% set post = {'title': 'Introduction to Flask', 'comments': ['Great!', 'Thank you!'] } مثال تطبيقي لنجمع الآن الأفكار السّابقة ولنُصمّم صفحة HTML بسيطة لعرض معلومات شخص ما: {% set person = { 'first_name': 'Ali', 'last_name': 'Sayed', 'age': '27', 'languages': ['Arabic', 'English'], 'Children': ['Hassan', 'Fatima'], } %} <div class="col-sm-4"> <ul class="list-group"> <li class="list-group-item active"> {{ person['first_name']}} {{ person['last_name']}} </li> {% for key, value in person.iteritems() %} {% if value is string %} <li class="list-group-item">{{ key }} : {{ value }}</li> {% else %} <li class="list-group-item"> {{ key }} : {% for item in value %} {{ item }}, {% endfor %} {% endif %} </li> {% endfor %} </ul> </div> رغم أنّ الشّفرة مُعقّدة بعض الشّيء، إلّا أنّها في الحقيقة بسيطة، وستبدو لك كذلك إن تابعت الفقرتين السّابقتين من هذا الدّرس. إليك ما يحدث في الشّفرة أعلاه، أولا نُعرّف قاموسا باسم person ليحمل المفاتيح التّالية: الاسم الأول للشّخص، الاسم العائلي (النّسب)، العمر، اللغات التي يُتقنها الشّخص، أطفاله. لاحظ بأنّ قيم اللغات والأطفال عبارة عن قوائم بايثون. في شفرة HTML نقوم بالدّوران حول مُحتويات القاموس person، بعدها نستعمل الاختبار string المُعرّف قياسيا في Jinja للتّحقق ممّا إذا كانت القيمة عبارة عن سلسلة نصيّة أو لا، إن كانت كذلك فإنّنا نعرضها مع مفتاحها بشكل طبيعي، إن لم تكن القيمة سلسلة نصيّة فهذا يعني بأنّها قائمة من عناصر مُتعدّدة وأنّ الجزء الذي يلي الكلمة المفتاحيّة else هو الذي سيُعرَض. في الجزء else نقوم بعرض المفتاح ثمّ نستعمل الحلقة for مُجدّدا للدّوران على عناصر القائمة المُتواجدة في قيمة المفتاح المذكور آنفا. هذا هو الجزء الخاص بعرض عناصر قائمة بعد قيمة المفتاح: {% else %} <li class="list-group-item"> {{ key }} : {% for item in value %} {{ item }}, {% endfor %} إن استخدمت إطار العمل Bootstrap فستكون النّتيجة مُشابهة لما في الصّورة التّاليّة: لاحظ بأنّ القاموس غير مُرتّب على نحو صحيح، ذلك لأنّ قواميس بايثون ليست مُرتّبة مبدئيًّا، لكن يُمكنك ترتيب مُحتويات القاموس أبجديا عبر استعمال المُرشّح dictsort كما يلي: {% for key, value in person | dictsort %} مع مُلاحظة أنّك لن تحتاج إلى التّابع iteritems() عند استعمال المُرشّح. المُتغيّر loop عند استخدام الحلقة for حلقة for تعمل بطريقة بسيطة، إذ تأخذ عدّة قيم وتمنحك كل قيمة داخل مُتغيّر مؤقّت، وفي كلّ دورة تتغيّر قيمة المُتغيّر. يوفّر مُحرّك القوالب Jinja عند التّعامل مع حلقة for مُتغيّرا خاصّا داخل الحلقة باسم loop؛ باستعمال هذا المُتغيّر الخاص، يُمكنك الوصول إلى معلومات خاصّة بالدورة الحاليّة وتُمكّننا كذلك من استعمال تقنيّات الاستدعاء الذّاتي (Recursion) لتضمين عناصر قائمة داخل عناصر قائمة أخرى خاصّة بالدّورة الحاليّة (ستتضّح الصّورة أكثر عندما ننتقل إلى الأمثلة التّطبيقية). الوصول إلى رقم الدّورة الحاليّة يُمكنك استخدام الخاصيّة index مع المُتغيّر loop في الحلقة for كما يلي: {% for item in items %} {{ loop.index }} {% endfor %} مثال: {% set list = ['Python', 'Flask', 'Jinja', 'Programming', 'Web developement'] %} {% for item in list %} <p>{{ loop.index }} - {{ item }}</p> {% endfor %} النّتيجة: 1 - Python 2 - Flask 3 - Jinja 4 - Programming 5 - Web development لاحظ بأنّ العدّ يبدأ من واحد، إن أردت أن يبدأ العدّ من الصّفر، سيتوجّب عليك استخدام التّابع index0 عوضا عن التّابع index كما يلي: {{ loop.index0 }} بهذا التّعديل ستكون النّتيجة كما يلي: 0 - Python 1 - Flask 2 - Jinja 3 - Programming 4 - Web developement الوصول إلى رقم الدّورة الحاليّة عكسيّا يُمكنك كذلك عكس تأثير التّابع index عبر استعمال التّابع revindex كالتّالي: {{ loop.revindex }} النّتيجة: 5 - Python 4 - Flask 3 - Jinja 2 - Programming 1 - Web developement وللانتهاء بالرّقم 0 يُمكنك استعمال التّابع revindex0. التحقّق مما إذا كانت الدّورة الحاليّة هي الأولى أو الأخيرة يُمكنك استعمال التّابع first الذي يُرجع القيمة True إذا كانت الدّورة الحاليّة هي أول دورة في حلقة for. وبنفس الطّريقة فإنّ التّابع last يُساعد على التّحقّق ممّا إذا كانت الدّورة الحاليّة هي الأخيرة أو لا. مثال: {% for item in list %} {% if loop.first %} <p>{{ loop.index }} (First) - {{ item }}</p> {% elif loop.last %} <p>{{ loop.index }} (Last) - {{ item }}</p> {% else %} <p>{{ loop.index }} - {{ item }}</p> {% endif %} {% endfor %} النّتيجة: 1 (First) - Python 2 - Flask 3 - Jinja 4 - Programming 5 (Last) - Web developement الوصول إلى عدد عناصر القيم الإجمالي بالإضافة إلى إمكانيّة الحصول على عدد عناصر قائمة ما باستعمال المُرشّح count أو المُرشّح length، يُمكنك كذلك استخدام loop.length للحصول على عدد عناصر القيم التي اِستُخْدِمَتْ مع حلقة for الحاليّة. يُمكنك التّحقق من الأمر عبر المثال التّالي (على افتراض أنّك تستعمل القائمة list السّابقة): <p>{{ list | length }}</p> <p>{{ list | count }}</p> {% for item in list %} <p>{{ loop.length }} - {{ item }}</p> {% endfor %} ستُلاحظ بأنّ النّتيجة هي نفسها سواء استخدمنا المرشّحات أو التّابع loop.length، بهذه الطّريقة يُمكنك الحصول على عدد عناصر قائمة من داخل حلقة for. إعادة استعمال نفس الحلقة مع قائمة داخل قائمة أخرى لنفترض بأنّنا نُريد عرض أسماء أشخاص مع أبنائهم بحيث تكون قائمة HTML الخاصّة بالأبناء تابعة للأب. إليك البيانات التّي سنستعملها في مثالنا هذا: {% set people = [ dict(name='Ali', children=[dict(name='Mohammed'), dict(name='Hassan')]), dict(name='Khaled', children=None), dict(name='Fahd', children=[dict(name='Kamal'), dict(name='Omar')]) ] %} في القائمة أعلاه، لدينا ثلاثة قواميس، القاموس الأول خاصّ بعلي، وأطفاله (الذين يعتبرون قائمة من القواميس كذلك) محمد وحسن، أما القاموس الثّاني فخاص بخالد الذي ليس لديه أبناء، والقاموس الأخير خاص بفهد، وأبناؤه كمال وعمر. لعرض أسماء الآباء الذين يتواجدون في القواميس الثّلاثة، يُمكن أن نستخدم حلقة for بسيطة، لكن سيتوجّب علينا تكرار الشّفرة إذا ما أردنا الدوران حول أبناء كل شخص. عوضا عن استخدام أكثر من حلقة for واحدة، يُمكننا استخدام حلقة for خاصّة تُكرّر نفسها، ويُمكننا فعل هذا الأمر عبر إضافة الكلمة recursive عند استخدام حلقة for كالتّالي: {% for person in people recursive %} هكذا سنتمكّن من استعمال المُتغيّر loop لإعادة الدّوران حول قائمة أطفال كل شخص، لكن سنفعل ذلك فقط إن كان للشّخص أبناء بالفعل، لذا سنتحقّق من أنّ القيمة لا تُساوي القيمة None عبر الاختبار كما يلي: {% if person.children is not none %} <ul class="list-group"> {{ loop(person.children) }} </ul> {% endif %} المثال النّهائيّ سيكون كالتّالي: {% set people = [ dict(name='Ali', children=[dict(name='Mohammed'), dict(name='Hassan')]), dict(name='Khaled', children=None), dict(name='Fahd', children=[dict(name='Kamal'), dict(name='Omar')]) ] %} <div class="col-sm-4"> <ul class="list-group"> {% for person in people recursive %} <li class="list-group-item"> {{ person.name }} {% if person.children is not none %} <ul class="list-group"> {{ loop(person.children) }} </ul> {% endif %} </li> {% endfor %} </ul> </div> النّتيجة ستكون كما في الصّورة التّاليّة: يُمكنك كذلك معرفة عمق الحلقة باستخدام التّابع loop.depth داخل حلقة for من النّوع recursive كالتّالي: <span class="badge">{{ loop.depth }} </span> تأكّد فقط من أن تضع السّطر السّابق مُباشرة بعد السّطر: {% for person in people recursive %} النّتيجة ستكون كالتّالي: لاحظ بأنّ رقم العمق يبدأ من واحد، إن أردت أن يبدأ من الصّفر فاستعمل loop.depth0. يُمكنك كذلك استخدام هذه الميّزة مع تعليقات في مُدوّنة أو منصّة تتطلّب تفاعل الأعضاء بين بعضهم البعض. الصّورة التّاليّة توضيح بسيط للنّتيجة النّهائيّة التي يُمكنك أن تصل إليها إن كنت تمتلك تعليقات متداخلة في قاعدة بياناتك: خاتمة تعرّفنا في هذا الدّرس على كيفيّة استخدام حلقة for في مُحرّك القوالب Jinja للحصول على قوالب HTML أحسن وتطويرها بسرعة. وبهذا نكون قد أكملنا أساسيّات دروس مُحرّك القوالب Jinja وسننتقل في الدّروس القادمة للتّعرف على جزء آخر مُهمّ في تطوير الويب، ألا وهو كيفيّة التّعامل مع قواعد البيانات في التّطبيقات المكتوبة بإطار العمل Flask.
  15. flask_cms

    مُقدّمة تعرّفنا في الدّروس السّابقة على العديد من ميّزات مُحرّك القوالب Jinja وكيفيّة استخدامها لتصميم صفحات HTML بسرعة، إذ بدأنا بمبدأ المرشّحات ثمّ مررنا على الاختبارات لنُنهي مشوارنا عبر التّعرف على طرق لتفادي تكرار شفرة HTML وإعادة استخدام القوالب أو أجزاء منها في مشروع مُعيّن، بل يُمكنك كذلك كتابة شيفرة واحدة واستعمالها مع أكثر من مشروع لما يُوفّره لنا مُحرّك القوالب Jinja من مرونة في التّطوير وقابليّة إعادة استعمال الشّفرة في مُختلف الظّروف. في هذا الدّرس، سنتعرّف على السّياق (Context) في Jinja عند التّضمين والاستيراد لتتفادى الوقوع في أخطاء يصعب تصحيحها. لتطبيق أمثلة هذا الدّرس، يُمكنك إنشاء تطبيق Flask صغير ليقوم بتقديم قوالب HTML أو يُمكنك استعمال إطار عمل آخر مثل Pyramid أو Django أو Bottle لكن سيتوجّب عليك البحث عن كيفيّة استعمال Jinja مع إطار العمل الذي تختاره. السّياق في مُحرّك القوالب Jinja عند تضمين قالب في قالب آخر أو عند الاستيراد يعني مُختلف المُتغيّرات والدّوال المُعرّفة في جزء مُعيّن من الشّفرة. فمثلا، إن عرّفت مُتغيّرا في الجزء العلوي من قالب HTML كما يلي: {% set website = 'academy.hsoub.com' %} بعد تعريف المُتغيّر website في القالب، فسيُصبح هذا المُتغيّر مُتاحا في هذا القالب فقط، ما يعني بأنّك لن تستطيع الوصول إلى المُتغيّر website في قالب HTML آخر دون استيراده. السّياق عند تضمين قالب داخل قالب آخر فلنضع السّيناريو التّاليَ لتتوضّح الفكرة أكثر، لنقل بأنّ لدينا ملفّات HTML التّالية: base.html index.html url.html مُحتويات ملفّ base.html ستكون كالتّالي: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"> <title>{% block title %}{% endblock %}</title> </head> <body> {% block body %} {% endblock %} </body> </html> وملفّ index.html يرث من الملفّ base.html مع تعريف المُتغيّر website وتضمين الملفّ url.html باستعمال الجملة include: {% extends 'base.html' %} {% block title %} My Application {% endblock %} {% block body %} {% set website = 'academy.hsoub.com' %} {% include 'url.html' %} {% endblock %} لنقل بأنّك تُريد الوصول إلى قيمة المُتغيّر website داخل الملفّ url.html لعرض الرّابط داخل وسم <a> كما يلي: {# 'url.html' #} <a href='http://{{ website }}'>{{ website }}</a> ستكون النّتيجة كما يلي: <a href='http://academy.hsoub.com'>academy.hsoub.com</a> نجح الأمر لأنّ السّياق قد مُرِّر من الملفّ index.html إلى الملفّ url.html عند استخدام التعليمة include، ما يعني بأنّ المُتغيّرات المُعرّفة في الملفّ الأصلي ستكون مُتاحة في الملفّ الفرعي الّذي ضُمِّن بالجملة include. لكنّك لن تستطيع الوصول إلّا إلى المُتغيّرات التي عُرّفت قبل استعمال الجملة include. ما يعني بأنّك لو عرّفت المُتغيّر website بعد تضمين القالب url.html كما يلي: {% include 'url.html' %} {% set website = 'academy.hsoub.com' %} فالقالب url.html لن يتعرّف على المُتغيّر website وسيعتبره سلسلة نصيّة فارغة لأنّه لا يستطيع الوصول إلى قيمته. ستحصل بالتّالي على النّتيجة التّالية: <a href='http://'></a> لاحظ بأنّ مُحرّك القوالب Jinja لم يُصدر أية أخطاء، لذا كن حذرا من هذا النّوع من المشاكل الصّامتة، وتأكّد دائما من الاطّلاع على مصدر الصّفحة لتفهم ما يحدث وتستطيع حلّ المُشكلة. تضمين قالب داخل قالب آخر باستعمال التعليمة include يُمرّر السّياق مبدئيا من قالب لآخر ، لكن إن أردت منع حدوث هذا الأمر، يُمكنك استعمال التّعبير without context لمنع القالب الفرعي من الوصول إلى سيّاق القالب الأصلي، وبالتّالي لن يتمكّن من الوصول إلى المُتغيّرات التّي سبق تعريفها. لتجربة هذا، أضف التّعبير without context إلى صفحة index.html ليُصبح مُحتوى الملفّ كما يلي: {% extends 'base.html' %} {% block title %} My Application {% endblock %} {% block body %} {% set website = 'academy.hsoub.com' %} {% include 'url.html' without context %} {% endblock %} إن ألقيت نظرة على النّتيجة، ستجدها مُشابهة لما كانت عليه عندما عرّفنا المُتغيّر website بعد التعليمة include ما يعني أنّ القالب url.html لم يعد مُؤهّلا للوصول إلى قيمة المُتغيّر، ولهذا نفس التّأثير مع جميع المُتغيّرات والدّوال المُعرّفة في السّياق. السّياق عند الاستيراد يُمرّر السّياق مبدئيا بين القوالب عند استعمال الجملة include، لكنّ العكس يحدث عند استيراد قالب داخل قالب آخر. لتوضيح هذه الفكرة، لنُغيّر مُحتويات الملفّ url.html إلى الماكرو التّالي : {% macro url() %} <a href='http://{{ website }}'>{{ website }}</a> {% endmacro %} لاحظ بأنّ الماكرو لا يقبل أية مُعاملات، لكنّنا نحاول أن نصل إلى المُتغيّر website داخل الماكرو. لنُغيّر الآن التعليمة include إلى جملة import لاستيراد هذا الماكرو من القالب url.hmtl: {% set website = 'academy.hsoub.com' %} {% from 'url.html' import url %} {{ url() }} لاحظ بأنّنا نقوم بالاستيراد بعد تعريف المُتغيّر. بما أنّ السّياق لا يُمرّر مبدئيا عند الاستيراد، فالنّتيجة ستكون كما لو أنّ قيمة المُتغيّر website فارغة. استعمل التّعبير with context عند الاستيراد كما يلي، للتصريح برغبتك في تمرير السّياق : {% from 'url.html' import url with context %} التّعبير with context له تأثير مُعاكس لتأثير التّعبير without context، لذا فالمُتغيّر website سيُمرَّرُ إلى القالب url.html وبالتّالي فالماكرو url() يستطيع الوصول إلى قيمة المُتغيّر ومن ثمّ عرضه بصيغة رابط. ملحوظة: المثال أعلاه مُجرّد توضيح لفكرة السّياق عند الاستيراد، ومن الأفضل استعمال المُعاملات لنقل قيمة مُتغيّر إلى ماكرو مُعيّن عوضا عن تفعيل السّياق. الخلاصة أنّك تستطيع التّحكم في كيفيّة تعامل القوالب مع السّياق عند التضمين أو الاستيراد عبر استخدام كلّ من التّعبيرين with context و without context. الحالة المبدئية للتعليمة include هي كالتّالي: {% include 'url.html' with context %} والحالة االمبدئية للتعليمة import: {% import 'url.html' as url without context %} تجاهل تضمين القوالب إن لم تكن متواجدة أساسا سأعرض لك في هذه الفقرة بشكل وجيز تقنيتين من التّقنيات التّي ستُساعدك على تصميم قوالب HTML أفضل عند استخدام التعليمة include. أولا، يُمكنك منح مُحرّك القوالب Jinja إمكانيّة تضمين قالب فقط إن كان موجودا عبر تمرير قائمة من أسماء القوالب إلى التعليمة include كالتّالي: {% include ['page1.html', 'page2.html'] %} إذا كان القالب page1.html موجودا فسيكون للمثال أعلاه نفس تأثير ما يلي: {% include 'page1.html' %} سواء أكان القالب page2.html موجودا أم لا فهذا غير مُهمّ ما دام القالب page1.html أول قالب في القائمة وكان موجودا بالفعل داخل مُجلّد templates. أمّا في حالة لم يكن القالب page1.html موجودا داخل مُجلّد القوالب وكان القالب page2.html موجودا عوضا عنه، فهذا الأخير هو القالب الذي سيُضمّن. وفي حالة لم يكن أي قالب من القوالب المُوفَّرة موجودا، فستحصل على خطأ كالتّالي: jinja2.exceptions.TemplatesNotFound TemplatesNotFound: none of the templates given were found: page1.html, page2.html ولتجاهل هذا الخطأ وعرض مُحتويات القالب index.html رغم عدم وجود هذه القوالب المُضمَّنة، يُمكنك إضافة التّعبير ignore missing عند استعمال التعليمة include كالآتي: {% include ['page1.html', 'page2.html'] ignore missing %} التّعبير ignore missing ليس محصورا بقائمة قوالب فقط، بل تستطيع كذلك استخدامه عند تضمين قالب واحد فقط كما يلي: {% include "sidebar.html" ignore missing %} عند استعمال التّعبير، سيتحقّق مُحرّك القوالب Jinja من أنّ القالب sidebar.html موجود في مُجلّد القوالب، إن كان كذلك فعمليّة التّضمين ستكون ناجحة دون أخطاء، وإن لم يكن القالب موجودا فلن تحدث أية أخطاء وسيبقى مكان التعليمة include فارغا عند عرض النّتيجة. تنبيه: عند استخدام التّعبير ignore missing مع كلّ من التّعبيرين with context و without context في آن واحد، تأكّد من أنّ التّعبير ignore missing يأتي أولا. ما يعني بأنّ الأمثلة أسفله صحيحة: {% include ['page1.html', 'page2.html'] ignore missing with context %} {% include "sidebar.html" ignore missing with context %} {% include "sidebar.html" ignore missing without context %} والأمثلة التّاليّة خاطئة: {% include ['page1.html', 'page2.html'] with context ignore missing %} {% include "sidebar.html" with context ignore missing %} {% include "sidebar.html" without context ignore missing %} خاتمة تعرّفنا في هذا الدّرس على كيفيّة عمل السّياق في Jinja عند التّضمين والاستيراد، بعد قراءتك لهذا الدّرس، يجب أن تكون قادرا على إدارة السّياق باحترافيّة وتتجنّب المشاكل التي تقع في حالة إساءة فهم كيفيّة عمل السّياق، في الدّرس التّالي، سنتعرّف على تقنيات متقدّمة للتّعامل مع حلقات for من أجل تحسين تطبيقاتك وتكتب شفرة أكثر فعاليّة عند تطوير تطبيقات الويب باستعمال إطار العمل Flask أو أي إطار عمل يعتمد على Jinja.