يستخدم الناس أنواعًا مختلفة من الأجهزة للاتصال بالإنترنت وتصفح الويب، ولهذا تحتاج إلى تمكين المستخدمين من الوصول إلى التطبيقات مهما كان موقعهم؛ ففي حالة المواقع التقليدية، يكفي غالبًا امتلاك واجهة مستخدم متجاوبة؛ بينما تحتاج في حالة التطبيقات الأعقد إلى استخدام تقنيات ومعماريات أخرى، منها امتلاك تطبيقات ريست REST مستقلة للواجهة الأمامية والواجهة الخلفية والتي يمكن تنفيذها مثل تطبيقات ويب من جهة العميل أو تطبيقات ويب تقدّميّة Progressive Web Apps -اختصارًا PWA- أو تطبيقات جوال أصلية.
نذكر فيما يلي بعض الأدوات الممكن استخدامها لبناء تطبيقات أعقد:
- ريآكت، وهو إطار عمل مبني بلغة جافا سكريبت يتيح للمطورين بناء تطبيقات ويب بواجهات أمامية وأصلية للواجهات الخلفية لواجهة برمجة التطبيقات API ريست.
- جانغو، وهو إطار عمل ويب مبني بلغة بايثون مفتوح المصدر ومجاني يتبع نموذج معمارية البرمجيات نموذج عرض متحكم Model View Controller -أو اختصارًا MVC-.
- إطار عمل جانغو ريست، الذي يمثّل أدوات قوية ومرنة لبناء واجهة برمجة تطبيقات ريست APIs بجانغو.
ستبني في هذه المقالة تطبيق ويب حديث مع واجهة REST API خلفية منفصلة وواجهة أمامية مستخدمًا لهذا الغرض ريآكت وجانغو وإطار عمل جانغو ريست، إذ يمكنك باستخدام ريآكت وجانغو معًا الاستفادة من أحدث الترقيات في جافا سكريبت وتطوير الواجهات الأمامية، وبدلًا من بناء تطبيق جانغو يستخدم محرك قوالب داخلي، ستستخدم ريآكت مثل مكتبة واجهة مستخدم UI library مستفيدًا من نموذج كائن الوثيقة Document Object Model الافتراضي -أو اختصارًا DOM-، والطريقة التصريحية declarative والمكونات التي تصيّر render سريعًا التغيرات على البيانات.
يخزن تطبيق الويب الذي ستبنيه سجلات العملاء في قاعدة بيانات، ويمكنك استخدامها بمثابة نقطة بداية لتطبيق إدارة علاقات العملاء CRM، وعند انتهائك ستكون قادرًا على إنشاء وقراءة وتحديث وحذف السجلات باستخدام واجهة ريآكت المُنسقة مستخدمًا بوتستراب 4.
المتطلبات الأساسية
ستحتاج لإتمام هذه المقالة وتطبيق ما فيها إلى ما يلي:
- جهاز تطوير مع نظام تشغيل أوبنتو Ubuntu 18.04.
- تثبيت بايثون 3 و pip و venv على جهازك باتباع الخطوتين الأولى والثانية من مقالة كيف تثبت بايثون 3 وتعد بيئة برمجية محلية على نظام أبونتو 18.04.
- تثبيت الإصدار 6 أو ما بعده من نود جي إس Node.js و npm 5.2 أو أعلى منه على جهازك، ويمكنك تثبيت كليهما باتباع التعليمات الواردة في مقالة كيف تثبت نود جي إس على نظام أوبنتو 18.04 حول التثبيت من مستودعات البرمجيات التي يطلق عليها اسم أرشيف الحزم الشخصية personal package archive -أو اختصارًا PPA.
الخطوة الأولى - إنشاء بيئة بايثون افتراضية وتثبيت الاعتماديات
ستنشئ في هذه الخطوة بيئةً افتراضيةً وتثبت الاعتمادات اللازمة لتطبيقك بما فيها جانغو وإطار عمل جانغو ريست وحزمة django-cors-headers.
سيستخدم تطبيقنا خادمي تطوير مختلفين لجانغو وريآكت، وسيعملان على منفذين مختلفين وبمثابة نطاقين منفصلين، ولهذا نحتاج تفعيل سياسة تعدد الموارد cross-origin resource sharing -أو اختصارًا CORS- لإرسال طلبات من نوع HTTP من ريآكت إلى جانغو دون التعرض للحظر على يدي المتصفح.
انتقل إلى المجلد الأساسي وأنشئ بيئة افتراضية باستخدام وحدة venv
في بايثون 3:
$ cd ~ $ python3 -m venv ./env
فعّل البيئة الافتراضية التي أنشأتها مستخدمًا الأمر source
:
$ source env/bin/activate
ثم ثبّت اعتماديات المشروع مستخدمًا pip
، وهذه الاعتمادات هي:
- جانغو: وهو إطار عمل ويب المشروع.
- إطار عمل جانغو ريست: تطبيق جانبي (منشأ من طرف ثالث) يبني واجهات برمجة التطبيقات لريست بواسطة جانغو.
-
حزمة
django-cors-headers
: وهي حزمة تفعّل سياسة تعدد الموارد CORS.
ثبّت إطار عمل جانغو بالأمر التالي:
(env) ali@ubuntu:$ pip install django djangorestframework django-cors-headers
والآن أصبح بإمكانك بعد أن ثبتّت اعتمادات المشروع أن تنشئ مشروع جانغو وواجهة ريآكت الأمامية.
الخطوة الثانية - إنشاء مشروع جانغو
سنولّد في هذه الخطوة مشروع جانغو باستخدام التعليمات والأدوات التالية:
-
أداة
django-admin startproject project-name
، إذ تُعدdjango-admin
أداةً تعمل في بيئة سطر الأوامر وتُستخدم لإتمام المهام بواسطة جانغو. ينشئ الأمرstartproject
مشروع جانغو جديد. -
أداة سكريبت
python manage.py startapp myapp
، إذ يُعد manage.py سكريبت برمجي يضاف تلقائيًا لكل مشروع جانغو وهو يؤدي عددًا من المهام الإدارية، مثل إنشاء تطبيقات جديدة وتهجير ملفات قاعدة البيانات وخدمة مشروع جانغو محلي. ينشئ الأمرstartapp
التابع لهذا السكريبت تطبيق جانغو داخل مشروع جانغو. يُقصد بالمصطلح "تطبيق application" في جانغو حزمة بايثون توفّر بعض المزايا في المشروع.
ولكي تبدأ، أنشئ مشروع جانغو باستخدام الأمر django-admin startproject
، وسنطلق على التطبيق اسم "djangoreactproject":
(env) ali@ubuntu:$ django-admin startproject djangoreactproject
سننظر إلى بنية المجلد لمشروع جانغو الذي نعمل عليه باستخدام الأمر tree
قبل المتابعة.
نصيحة: الأمر tree
هو أمرٌ مفيد لعرض بُنى الملفات والمجلدات من مستوى سطر الأوامر، يمكنك تثبيته مستخدمًا الأمر التالي:
$ sudo apt-get install tree
ولكي تستخدمه انتقل إلى المجلد الذي تريد رؤية بنيته واكتب tree
أو اكتب المسار كاملًا وصولًا إلى نقطة البداية "tree /home/ali/ali-project".
انتقل إلى المجلد djangoreactproject ضمن جذر مشروعك ونفذ الأمر tree
:
(env) ali@ubuntu:$ cd ~/djangoreactproject (env) ali@ubuntu:$ tree
سيظهر لك الخرج التالي:
├── djangoreactproject │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── manage.py
يمثّل المجلد "djangoreactproject/~" جذر المشروع، وتوجد داخله عدة ملفات مهمة لمشروعك، هي:
- "manage.py": وهو سكريبت خدمي يؤدي عددًا من المهام الإدارية.
-
"settings.py": هو ملف التهيئة الرئيسي لمشروع جانغو، حيث يمكنك تعديل إعدادات المشروع، إذ تتضمن هذه الإعدادات متحولاتٍ، مثل
INSTALLED_APPS
الذي يحدد قائمة التطبيقات النشطة في مشروعك. ويوجد في توثيق جانغو معلومات أعمق عن الإعدادات المتاحة. - "urls.py": يحتوي هذا الملف على قائمة بنماذج محددات الموارد الموحدة URL والعروض المرتبطة بها، إذ يُنظِّم كل نموذج اتصالًا بين عنوان محدد موارد موحد URL والدالة التي يجب أن تُستدعى لذلك العنوان URL.
أولى خطواتنا للعمل في المشروع هي ضبط الحزم التي ثبتناها في الخطوة السابقة ومن بينها إطار عمل جانغو ريست وحزمة جانغو CORS، وذلك بإضافتهما إلى الملف "settings.py". افتح الملف مستخدمًا الأمر nano
أو محرر النصوص المفضل لديك:
(env) ali@ubuntu:$ nano ~/djangoreactproject/djangoreactproject/settings.py
انتقل إلى الإعداد INSTALLED_APPS
وأضف التطبيقين "rest_framework" و "corsheaders" أسفل القائمة:
... INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', 'corsheaders' ]
ثم أضف البرمجية الوسيطة corsheaders.middleware.CorsMiddleware
من حزمة سياسة تعدد الموارد CORS
التي ثبتناها سابقًا إلى الإعداد MIDDLEWARE
، الذي يمثّل قائمةً بالبرمجيات الوسيطة middlewares؛ وهو صنف بايثون يحتوي على شيفرة تُعالج في كل مرة يتعامل تطبيق الويب مع طلب أو استجابة:
... MIDDLEWARE = [ ... 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'corsheaders.middleware.CorsMiddleware' ]
يمكنك بعد ذلك تفعيل CORS، إذ يحدّد الإعداد CORS_ORIGIN_ALLOW_ALL
ما إذا كنت ستسمح بسياسة تعدد الموارد لكل النطاقات أم لا، والإعداد CORS_ORIGIN_WHITELIST
هو صف بايثون يحتوي على عناوين URL المسموحة.
سيعمل خادم تطوير ريآكت على العنوان "http://localhost:3000"، لذلك سنضيف الإعدادين التاليين إلى ملف الإعدادات "settings.py":
CORS_ORIGIN_ALLOW_ALL = False (,'CORS_ORIGIN_WHITELIST('localhost:3000
حيث يمكنك إضافة هذين الإعدادين في أي مكان تشاء من هذا الملف:
... CORS_ORIGIN_ALLOW_ALL = False CORS_ORIGIN_WHITELIST = ( 'localhost:3000', ) …
ويمكنك العثور على المزيد من خيارات التهيئة في وثائق django-cors-headers.
احفظ الملف واخرج من محرر النصوص عندما تنتهي.
نحتاج إلى إنشاء تطبيق جانغو جديد داخل المجلد "djangoreactproject/~" اسمه "customers":
(env) ali@ubuntu:$ python manage.py startapp customers
سيحتوي هذا التطبيق على النماذج models والعروض views لإدارة العملاء؛ إذ تعرّف النماذجُ الحقولَ والسلوكات لبيانات التطبيق؛ بينما تمكّن العروض تطبيقنا من التعامل مع الطلبات على النحو المناسب وإعادة الاستجابات المطلوبة على النحو المناسب أيضًا.
بعد ذلك، أضف التطبيق إلى قائمة التطبيقات المثبتة في ملف المشروع "settings.py" لكي يتعرف جانغو عليه على أنه جزءٌ من المشروع. افتح الملف "settings.py" مرةً أخرى:
(env) ali@ubuntu:$ nano ~/djangoreactproject/djangoreactproject/settings.py
أضف التطبيق "customers":
... INSTALLED_APPS = [ ... 'rest_framework', 'corsheaders', 'customers' ] …
بعد ذلك، هجّر قاعدة البيانات وشغّل خادم التطوير المحلي، إذ تُعد التهجيرات طريقة جانغو في نشر التغييرات التي تجريها على نماذجك إلى مخطط قاعدة بياناتك، ويمكن أن تتضمن هذه التغييرات أشياء مختلفة، مثل إضافة حقل، أو حذف نموذج.
هجّر قاعدة البيانات كما يلي:
(env) ali@ubuntu:$ python manage.py migrate
وشغّل خادم التطوير المحلي:
(env) ali@ubuntu:$ python manage.py runserver
سترى خرجًا يشابه التالي:
Performing system checks... System check identified no issues (0 silenced). October 22, 2018 - 15:14:50 Django version 2.1.2, using settings 'djangoreactproject.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C.
سيعمل تطبيق الويب من خلال العنوان "http://127.0.0.1:8000"، وإذا انتقلت إلى هذا العنوان في متصفحك فيفترض أن ترى الصفحة التالية:
دع التطبيق يعمل وافتح نافذة طرفية جديدة لمتابعة تطوير المشروع.
الخطوة الثالثة - إنشاء الواجهة الأمامية لريآكت
سننشئ في هذا القسم تطبيق الواجهة الأمامية لمشروعنا مستخدمين ريآكت؛ إذ يوجد لدى ريآكت برنامج خدمي رسمي يسمح لك بتوليد مشاريع ريآكت بسرعة دون الحاجة إلى ضبط مجمع الوحدات Webpack، التي تمثّل مجمع وحدات module bundler يُستخدم لتجميع أصول الويب، مثل شيفرة جافا سكريبت و CSS والصور. قبل أن تتمكن من استخدام Webpack، ستحتاج عادةً إلى تحديد خيارات ضبط متعددة، لكن بفضل الأداة create-react-app
لن تحتاج للتعامل مع Webpack مباشرةً حتى تقرر أو عندما تكون بحاجةٍ لمزيدٍ من التحكم. يمكنك استخدام أداة npx (وهي أداة تنفّذ الملفات الثنائية لحزم npm
) لتشغيل create-react-app
.
تأكد في نافذة الطرفية الثانية أنك موجود داخل مجلد المشروع:
$ cd ~/djangoreactproject
أنشئ مشروع ريآكت اسمه "frontend" باستخدام create-react-app
و npx
:
$ npx create-react-app frontend
ثم انتقل إلى داخل تطبيق ريآكت وشغّل خادم التطوير:
$ cd ~/djangoreactproject/frontend $ npm start
سيعمل تطبيقك باستخدام العنوان "/http://localhost:3000":
دع خادم تطوير ريآكت يعمل وافتح نافذة طرفية جديدة للمتابعة.
ولرؤية بنية مجلدات كامل المشروع عند هذه النقطة، انتقل إلى المجلد الجذر ونفّذ الأمر tree
مرةً أخرى:
$ cd ~/djangoreactproject $ tree
سترى بنيةً تشبه التالية:
├── customers │ ├── admin.py │ ├── apps.py │ ├── __init__.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py ├── djangoreactproject │ ├── __init__.py │ ├── __pycache__ │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── frontend │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ ├── README.md │ ├── src │ │ ├── App.css │ │ ├── App.js │ │ ├── App.test.js │ │ ├── index.css │ │ ├── index.js │ │ ├── logo.svg │ │ └── registerServiceWorker.js │ └── yarn.lock └── manage.py
سيستخدم تطبيقنا بوتستراب 4 لتحيدد تنسيق style واجهة ريآكت، لذلك سنضمّنه في الملف "frontend/src/App.css" الذي يدير إعدادات CSS. افتح الملف:
$ nano ~/djangoreactproject/frontend/src/App.css
أضف تعليمة الاستيراد import إلى بداية الملف. يمكن حذف المحتوى الحالي للملف رغم أن هذا غير مطلوب:
@import 'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css';
تعليمة import@
هي تعليمة CSS تُستخدم لاستيراد قواعد التنسيق style rules من صفحات تنسيق أخرى.
الآن بعد أن انتهينا من إنشاء تطبيق الواجهة الأمامية والواجهة الخلفية، دعنا ننشئ نموذج العميل Customer وبعض البيانات التجريبية.
الخطوة الرابعة - إنشاء نموذج العميل وبيانات التهيئة
بعد أن انتهينا من إنشاء تطبيق جانغو والواجهة الأمامية لريآكت، ستكون خطوتنا التالية هي إنشاء نموذج العميل Customer، الذي يمثّل جدول قاعدة البيانات الذي سيخزن معلومات العملاء. لن تحتاج إلى أي أوامر SQL، إذ سيتولى الرابط العلائقي للكائنات في جانغو Object Relational Mapper -أو اختصارًا ORM- أمر العمليات التي تجري على قاعدة البيانات من خلال إسقاط أصناف بايثون ومتحولاته على جداول SQL وأعمدته، وبهذه الطريقة يكون الرابط العلائقي للكائنات في جانغو قد جرّد تفاعلات SQL مع قاعدة البيانات من خلال واجهة بايثون.
فعّل البيئة الافتراضية من جديد:
$ cd ~ $ source env/bin/activate
انتقل إلى المجلد "customers" وافتح الملف "models.py"، وهو ملف بايثون يخزن كافة نماذج تطبيقك:
(env) ali@ubuntu:$ cd ~/djangoreactproject/customers/ (env) ali@ubuntu:$ nano models.py
سيحتوي الملف على المحتوى التالي:
from django.db import models # Create your models here.
نشير إلى أنه قد استوردت واجهة برمجة التطبيقات لنموذج العملاء سلفًا ضمن الملف بفضل تعليمة الاستيرادfrom django.db import models
. ستضيف الآن الصنف Customer
المشتق من الصنف models.Model
. كل نموذج في جانغو هو صنف بايثون مشتق من django.db.models.Model.
سيكون في نموذج العميل "Customer" حقول قاعدة البيانات التالية:
- first_name: الاسم الأول للعميل.
- last_name: اسم العائلة للعميل.
- email: البريد الإلكتروني للعميل.
- phone: رقم هاتف العميل.
- address: عنوان العميل.
- description: وصف العميل.
- createdAt: تاريخ إضافة العميل.
سنضيف أيضًا الدالة ()__str__
التي تعرّف طريقة عرض النموذج، والذي سيكون في حالتنا مع الاسم الأول للعميل. ولمزيدٍ من التفاصيل عن إنشاء الأصناف وتعريف الكائنات يرجى الاطلاع على المقالة كيف تبني الأصناف وتعرف الكائنات في بايثون 3.
أضف الشيفرة التالية إلى الملف "models.py":
from django.db import models class Customer(models.Model): first_name = models.CharField("First name", max_length=255) last_name = models.CharField("Last name", max_length=255) email = models.EmailField() phone = models.CharField(max_length=20) address = models.TextField(blank=True, null=True) description = models.TextField(blank=True, null=True) createdAt = models.DateTimeField("Created At", auto_now_add=True) def __str__(self): return self.first_name
ثم هجّر قاعدة البيانات لإنشاء جداول قاعدة البيانات. ينشئ الأمر makemigrations
ملفات التهجير التي تضاف فيها التغييرات على النموذج، في حين يطبّق migrate
التغييرات في ملفات التهجير إلى قاعدة البيانات.
عُد الآن إلى المجلد الجذر للمشروع:
(env) ali@ubuntu:$ cd ~/djangoreactproject
ونفذ ما يلي من أجل إنشاء ملفات التهجير:
(env) ali@ubuntu:$ python manage.py makemigrations
ستحصل على خرج يشبه التالي:
customers/migrations/0001_initial.py - Create model Customer
طبّق هذه التغييرات على قاعدة البيانات:
(env) ali@ubuntu:$ python manage.py migrate
وسترى خرجًا يشير إلى نجاح عملية التهجير:
Operations to perform: Apply all migrations: admin, auth, contenttypes, customers, sessions Running migrations: Applying customers.0001_initial... OK
ستستخدم الآن ملف تهجير البيانات لإنشاء بيانات العميل المبدئية،إن أن ملف تهجير البيانات هو تهجير يضيف البيانات أو يعدّلها في قاعدة البيانات. أنشئ ملف تهجير بيانات فارغ للتطبيق customers:
(env) ali@ubuntu:$ python manage.py makemigrations --empty --name customers customers
سترى التأكيد التالي مع اسم ملف التهجير:
Migrations for 'customers': customers/migrations/0002_customers.py
لاحظ أن اسم ملف التهجير هو "customers.py_0002".
انتقل بعد ذلك إلى داخل مجلد التهجيرات للتطبيق customers.
(env) ali@ubuntu:$ cd ~/djangoreactproject/customers/migrations
افتح ملف التهجير المنشأ للتو:
(env) ali@ubuntu:$ nano 0002_customers.py
وهذا هو محتوى التهيئة للملف:
from django.db import migrations class Migration(migrations.Migration): dependencies = [ ('customers', '0001_initial'), ] operations = [ ]
تستورد تعليمة الاستيراد واجهة برمجة التطبيقات للملف "migrations" وواجهة برمجة التطبيقات لجانغو من أجل إنشاء التهجيرات من الملف "django.db" والتي هي حزمة مبنية مسبقًا built-in تحتوي على الأصناف اللازمة للعمل مع قواعد البيانات.
الصنف Migration
هو صنف بايثون يصف العمليات التي تُنفذ عند تهجير قواعد البيانات، وهو امتداد للصنف migrations.Migration
، ويملك قائمتين:
- dependencies: تحتوي على التهجيرات المعتمدة.
- operations: تحتوي على العمليات التي ستُنفذ عندما نطبّق التهجير.
الآن، أضف تابعًا لإنشاء بيانات تجريبية للعملاء قبل تعريف الصنف Migration
:
... def create_data(apps, schema_editor): Customer = apps.get_model('customers', 'Customer') Customer(first_name="Customer 001", last_name="Customer 001", email="customer001@email.com", phone="00000000", address="Customer 000 Address", description= "Customer 001 description").save() ...
أمسكنا grab في هذا التابع الصنف Customer
التابع للتطبيق customers
وأنشأنا عميلًا تجريبيًّا لإدخاله إلى قاعدة البيانات.
للحصول على الصنف Customer
، الذي سيمكّن من إنشاء عملاء جدد، نستخدم التابع ()get_model
للكائن apps
، الذي يمثّل مسجلًا registry للتطبيقات المثبتة ونماذج قواعد البيانات الخاصة بها.
سيمرّر الكائن apps من التابع ()RunPython
عندما نستخدمه لتشغيل ()create_data
. أضف التابع ()migrations.RunPython
إلى القائمة الفارغة operations
:
... operations = [ migrations.RunPython(create_data), ]
يُعد التابع ()RunPython
جزءًا من واجهة برمجة التطبيقات للتهجيرات التي تسمح لك بتشغيل شيفرة بايثون معينة في تهجير ما. تحدد القائمة operations
أن هذا التابع سيُنفّذ عندما نطبق التهجير.
وفيما يلي الملف كاملًا:
from django.db import migrations def create_data(apps, schema_editor): Customer = apps.get_model('customers', 'Customer') Customer(first_name="Customer 001", last_name="Customer 001", email="customer001@email.com", phone="00000000", address="Customer 000 Address", description= "Customer 001 description").save() class Migration(migrations.Migration): dependencies = [ ('customers', '0001_initial'), ] operations = [ migrations.RunPython(create_data), ]
ولمزيدٍ من المعلومات عن تهجيرات البيانات، راجع التوثيق تهجير البيانات في جانغو.
الآن لتهجير قاعدة بياناتك، عُد أولًا إلى مجلد الجذر لمشروعك:
(env) ali@ubuntu:$ cd ~/djangoreactproject
هجّر قاعدة بياناتك لتنشئ البيانات التجريبية:
(env) ali@ubuntu:$ python manage.py migrate
سنرى خرجًا يؤكد على نجاح عملية التهجير:
Operations to perform: Apply all migrations: admin, auth, contenttypes, customers, sessions Running migrations: Applying customers.0002_customers... OK
ولمزيدٍ من المعلومات عن العملية، راجع مقالة كيف تنشئ نماذج جانغو.
وهكذا بعد إنشاء نموذج العميل والبيانات التوضيحية، يمكننا المتابعة إلى بناء واجهة برمجة التطبيقات لريست REST API.
الخطوة الخامسة - إنشاء واجهة برمجة التطبيقات لريست REST API
سننشئ في هذه الخطوة واجهة برمجة التطبيقات لريست باستخدام إطار عمل جانغو ريست، إذ سننشئ عدة عروض مختلفة لواجهة برمجة التطبيقات؛ إذ يُقصد بعرض واجهة برمجة التطبيقات API view دالة تعالج طلب واجهة برمجة تطبيقات أو استدعاء واجهة برمجة التطبيقات؛ في حين أن نقطة نهاية واجهة برمجة التطبيقات هي عنوان URL متفرد يمثّل نقطة تماس مع نظام ريست. على سبيل المثال، عندما يرسل المستخدم طلب من نوع GET إلى نقطة نهاية واجهة برمجة التطبيقات، يستدعي جانغو الدالة المقابلة، أو عرض واجهة برمجة التطبيقات لمعالجة الطلب وإعادة أي نتائج محتملة.
سنستفيد أيضًا من المسلسِلات Serializers؛ إذ يسمح المسلسِل في إطار عمل جانغو بأن تُحوّل نسخ النماذج المعقدة ومجموعات الاستعلام QuerySets إلى صيغة محتوى JSON من أجل استهلاك واجهة برمجة التطبيقات، ويمكن أن يعمل صنف المسلسِل في الاتجاه الآخر أيضًا، إذ يزوِّد بآليات لتحليل وإلغاء سَلسَلة البيانات إلى نماذج جانغو ومجموعات استعلامه.
ستتضمن نقاط نهاية واجهة برمجة التطبيقات ما يلي:
- "api/customers": تُستخدم نقطة النهاية هذه لإنشاء عملاء وإعادة مجموعات العملاء المرقمة.
- "<api/customers/<pk": تُستخدم نقطة النهاية هذه للحصول على عملاء منفردين وتحديثهم وحذفهم بالاستعانة بمفتاح أساسي أو معرّف id.
سننشئ أيضًا عناوين URL في الملف "urls.py" التابع للمشروع من أجل نقاط النهاية المقابلة، أي "api/customers" و "<api/customers/<pk".
دعنا نبدأ بإنشاء الصنف serializer
من أجل نموذجنا Customer
.
إضافة صنف المسسلسل Serializer
يُعد إنشاء صنف مسلسِل لنموذج "Customer" ضروري لتحويل نسخ العملاء ومجموعات الاستعلام QuerySets من وإلى JSON، ولإنشاء الصنف المسلسِل، أنشئ أولًا الملف "serializers.py" داخل التطبيق "customers":
(env) ali@ubuntu:$ cd ~/djangoreactproject/customers/ (env) ali@ubuntu:$ nano serializers.py
أضف الشيفرة التالية لاستيراد واجهة برمجة التطيبقات للمسلسِلات ونموذج العميل Customer
:
from rest_framework import serializers from .models import Customer
ثم أنشئ صنف مسلسِل مُمتد عن الصنف serializers.ModelSerializer
ويحدد الحقول التي ستُسلسَل:
... class CustomerSerializer(serializers.ModelSerializer): class Meta: model = Customer fields = ('pk','first_name', 'last_name', 'email', 'phone','address','description')
يحدد الصنف Meta
النموذج والحقول التي ستُسلسَل، وهي:
-
pk
-
first_name
-
last_name
-
email
-
phone
-
address
-
description
وهذا هو المحتوى الكامل لملف serializers.py:
from rest_framework import serializers from .models import Customer class CustomerSerializer(serializers.ModelSerializer): class Meta: model = Customer fields = ('pk','first_name', 'last_name', 'email', 'phone','address','description')
الآن وبعد أن انتهينا من إنشاء صنف المسلسِل، يمكننا إضافة عروض واجهة برمجة التطبيقات.
إضافة عروض واجهة برمجة التطبيقات
سننشئ في هذا القسم عروض API لتطبيقنا الذي سيستدعيه جانغو عندما يزور المستخدمُ نقطةَ النهاية المقابلة لدالة العرض.
افتح الملف التالي:
(env) ali@ubuntu:$ nano ~/djangoreactproject/customers/views.py
احذف محتوى الملف وأضف أوامر الاستيراد التالية:
from rest_framework.response import Response from rest_framework.decorators import api_view from rest_framework import status from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from .models import Customer from .serializers import *
نحن نستورد المسلسِل الذي أنشأناه إلى جانب نموذج العميل Customer
وجانغو وواجهات برمجة التطبيقات لجانغو ريست.
بعد ذلك، أضف العرض الذي سيتولى معالجة طلبات من نوع POST و GET HTTP:
... @api_view(['GET', 'POST']) def customers_list(request): """ List customers, or create a new customer. """ if request.method == 'GET': data = [] nextPage = 1 previousPage = 1 customers = Customer.objects.all() page = request.GET.get('page', 1) paginator = Paginator(customers, 10) try: data = paginator.page(page) except PageNotAnInteger: data = paginator.page(1) except EmptyPage: data = paginator.page(paginator.num_pages) serializer = CustomerSerializer(data,context={'request': request} ,many=True) if data.has_next(): nextPage = data.next_page_number() if data.has_previous(): previousPage = data.previous_page_number() return Response({'data': serializer.data , 'count': paginator.count, 'numpages' : paginator.num_pages, 'nextlink': '/api/customers/?page=' + str(nextPage), 'prevlink': '/api/customers/?page=' + str(previousPage)}) elif request.method == 'POST': serializer = CustomerSerializer(data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
نستخدم أولًا المزخرِف (['api_view(['GET', 'POST@
لإنشاء عرض واجهة برمجة تطبيقات يمكنه قبول طلبات GET
و POST
، إذ يُعد المزخرِف decorator دالة تأخذ دالةً أخرى وتمدّدها extend تلقائيًا.
نستخدم المتغيّر request.method
داخل متن التابع لفحص تابع HTTP الحالي وتنفيذ المنطق المقابل اعتمادًا على نوع الطلب:
- إذا كان الطلب من النوع GET، يرقّم التابع البيانات باستخدام مرقّم جانغو Django Paginator ويعيد الصفحة الأولى من البيانات بعد السَّلسَلَة، وعدد العملاء المتوفرين وعدد الصفحات المتوفرة والروابط إلى الصفحات السابقة واللاحقة. المرقّم paginator هو صنف جانغو داخلي يقسّم قوائم البيانات في عدة صفحات ويزوّد بتوابع للوصول إلى عناصر كل صفحة.
-
إذا كان الطلب من النوع POST، يسلسِل التابع بيانات العميل المستلمة ثم يستدعي التابع
()save
لكائن المسلسِل، ثم يعيد كائن استجابة Response وهو نسخة عن استجابة http أو HttpResponse مع رمز الحالة 201. كل عرض تنشؤه مسؤول عن إعادة كائنHttpResponse
. يحفظ التابع()save
البيانات المسلسَلة في قاعدة البيانات.
أضف الآن عرض واجهة برمجة التطبيقات الذي سيكون مسؤولًا عن معالجة طلبات GET و PUT و DELETE من أجل جلب وتحديث وحذف العملاء اعتمادًا على المفتاح الأساسي pk
:
... @api_view(['GET', 'PUT', 'DELETE']) def customers_detail(request, pk): """ Retrieve, update or delete a customer by id/pk. """ try: customer = Customer.objects.get(pk=pk) except Customer.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) if request.method == 'GET': serializer = CustomerSerializer(customer,context={'request': request}) return Response(serializer.data) elif request.method == 'PUT': serializer = CustomerSerializer(customer, data=request.data,context={'request': request}) if serializer.is_valid(): serializer.save() return Response(serializer.data) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) elif request.method == 'DELETE': customer.delete() return Response(status=status.HTTP_204_NO_CONTENT)
زُخرِف التابعُ باستخدام (['api_view(['GET', 'PUT', 'DELETE@
للإشارة إلى أنه عرض واجهة برمجة تطبيقات API view يمكنه قبول طلبات من النوع GET و PUT و DELETE.
التحقق الموجود في الحقل request.method
يتحقق من تابع الطلب واعتمادًا على قيمته يستدعي المنطق المناسب:
-
إذا كان الطلب من النوع GET، تُسَلسَلُ بياناتُ العميل وترسلُ باستخدام كائن استجابة
Response
. -
إذا كان الطلب من النوع PUT، ينشئ التابع مسلسِلًا لبيانات العميل الجديد، ثم يستدعي التابع
()save
لكائن المسلسَل المُنشأ، وأخيرًا يرسل كائن الاستجابةResponse
مع العميل المحدّث. -
إذا كان الطلب من النوع DELETE، يستدعي التابعُ التابعَ
()delete
لكائن العميل لحذفه، ثم يعيد كائن استجابة دون بيانات.
يبدو ملف "views.py" المكتمل كما يلي:
from rest_framework.response import Response from rest_framework.decorators import api_view from rest_framework import status from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from .models import Customer from .serializers import * @api_view(['GET', 'POST']) def customers_list(request): """ List customers, or create a new customer. """ if request.method == 'GET': data = [] nextPage = 1 previousPage = 1 customers = Customer.objects.all() page = request.GET.get('page', 1) paginator = Paginator(customers, 5) try: data = paginator.page(page) except PageNotAnInteger: data = paginator.page(1) except EmptyPage: data = paginator.page(paginator.num_pages) serializer = CustomerSerializer(data,context={'request': request} ,many=True) if data.has_next(): nextPage = data.next_page_number() if data.has_previous(): previousPage = data.previous_page_number() return Response({'data': serializer.data , 'count': paginator.count, 'numpages' : paginator.num_pages, 'nextlink': '/api/customers/?page=' + str(nextPage), 'prevlink': '/api/customers/?page=' + str(previousPage)}) elif request.method == 'POST': serializer = CustomerSerializer(data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @api_view(['GET', 'PUT', 'DELETE']) def customers_detail(request, pk): """ Retrieve, update or delete a customer by id/pk. """ try: customer = Customer.objects.get(pk=pk) except Customer.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) if request.method == 'GET': serializer = CustomerSerializer(customer,context={'request': request}) return Response(serializer.data) elif request.method == 'PUT': serializer = CustomerSerializer(customer, data=request.data,context={'request': request}) if serializer.is_valid(): serializer.save() return Response(serializer.data) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) elif request.method == 'DELETE': customer.delete() return Response(status=status.HTTP_204_NO_CONTENT)
يمكننا الآن المتابعة إلى إنشاء نقاط النهاية.
إضافة نقاط النهاية لواجهة برمجة التطبيقات
سننشئ الآن نقطتي النهاية لواجهة برمجة التطبيقات، الأولى /api/customers
من أجل الاستعلام عن العملاء وإنشائهم، والثانية <api/customers/<pk
من أجل جلب وتحديث وحذف العملاء المنفردين اعتمادًا على المفاتيح الأساسية لكل منهم.
افتح الملف التالي:
(env) ali@ubuntu:$ nano ~/djangoreactproject/djangoreactproject/urls.py
دع المحتويات كما هي ولكن أضف الاستيراد إلى عروض customers
في أعلى الملف:
from django.contrib import admin from django.urls import path from customers import views from django.conf.urls import url
ثم أضف عنواني URL لكلٍ من /api/customers
و <api/customers/<pk
إلى قائمة نماذج عناوين URL urlpatterns التي تحتوي على عناوين URLs للتطبيق:
... urlpatterns = [ path('admin/', admin.site.urls), url(r'^api/customers/$', views.customers_list), url(r'^api/customers/(?P<pk>[0-9]+)$', views.customers_detail), ]
الآن بعد أن انتهينا من إنشاء نقاط النهاية لريست، لنرى كيف نستهلكها.
الخطوة السادسة - استهلاك واجهة برمجة التطبيقات لريست باستخدام اكسيوس Axios
سنثبت في هذه الخطوة Axios، عميل HTTP الذي سنستخدمه لإنشاء استدعاءات واجهة برمجة التطبيقات. سننشئ أيضًا صنفًا لاستهلاك نقاط النهاية لواجهة برمجة التطبيقات التي أنشأناها.
نبدأ بإيقاف تنشيط البيئة الافتراضية:
(env) ali@ubuntu:$ deactivate
ثم ننتقل إلى مجلد الواجهة الأمامية "frontend":
(env) ali@ubuntu:$ cd ~/djangoreactproject/frontend
ثبت اكسيوس axios
من npm
باستخدام الأمر التالي:
(env) ali@ubuntu:$ npm install axios --save
يضيف الخيار save–
اعتمادية axios
إلى ملف التطبيق "package.json".
ثم أنشئ ملف جافا سكريبت اسمه "CustomersService.js" الذي سيحتوي على الشيفرة التي ستستدعي واجهات برمجة التطبيقات لريست. سنفعّل هذا داخل مجلد "src"، حيث توجد شيفرة التطبيق لمشروعنا:
$ cd src $ nano CustomersService.js
أضف الشيفرة التالية التي تحتوي على توابع ستتصل بواجهات برمجة التطبيقات لجانغو ريست:
import axios from 'axios'; const API_URL = 'http://localhost:8000'; export default class CustomersService{ constructor(){} getCustomers() { const url = `${API_URL}/api/customers/`; return axios.get(url).then(response => response.data); } getCustomersByURL(link){ const url = `${API_URL}${link}`; return axios.get(url).then(response => response.data); } getCustomer(pk) { const url = `${API_URL}/api/customers/${pk}`; return axios.get(url).then(response => response.data); } deleteCustomer(customer){ const url = `${API_URL}/api/customers/${customer.pk}`; return axios.delete(url); } createCustomer(customer){ const url = `${API_URL}/api/customers/`; return axios.post(url,customer); } updateCustomer(customer){ const url = `${API_URL}/api/customers/${customer.pk}`; return axios.put(url,customer); } }
سيستدعي الصنف CustomersService
توابع اكسيوس Axios التالية:
-
()getCustomers
: يحصل على الصفحة الأولى من العملاء. -
()getCustomersByURL
: يحصل على العملاء بواسطة عنوان URL، ويتيح هذا بدوره الحصول على الصفحات التالية من العملاء بتمرير روابط، مثل "api/customers/?page=2/". -
()getCustomer
: يحصل على عميل بواسطة مفتاحه الأساسي. -
()createCustomer
: ينشئ عميلًا. -
()updateCustomer
: يحدّث عميلًا. -
()deleteCustomer
: يحذف عميلًا.
نستطيع الآن عرض البيانات من واجهة برمجة التطبيقات الخاصة بنا في واجهة المستخدم لريآكت بإنشاء مكوّن CustomersList
.
الخطوة السابعة - عرض البيانات من واجهة برمجة التطبيقات في تطبيق ريآكت
سننشئ في هذه الخطوة مكون ريآكت اسمه: CustomersList
، الذي يمثّل جزءًا من واجهة المستخدم، ويمكنك أيضًا من تقسيم واجهة المستخدم إلى أقسام منفصلة وقابلة للاستخدام مجددًا.
أنشئ أولًا الملف "CustomersList.js" في المجلد "frontend/src":
$ nano ~/djangoreactproject/frontend/src/CustomersList.js
ثم ابدأ في استيراد React
و Component
لإنشاء مكون ريآكت:
import React, { Component } from 'react';
ثم استورد واستنسخ الوحدة CustomersService
التي أنشأتها في الخطوة السابقة، والتي توفر توابع تُربط مع الواجهة الخلفية لواجهة برمجة تطبيقات ريست REST API:
... import CustomersService from './CustomersService'; const customersService = new CustomersService();
ثم أنشئ المكون CustomersList
المُمتد عن الصنف Component
، الذي سيستدعي واجهة برمجة التطبيقات لريست، إذ يجب أن يمتد مكون ريآكت عن الصنف Component
أو أن يكون صنفًا فرعيًا عنه.
أضف الشيفرة التالية إلى الملف "CustomersList.js" لإنشاء مكون ريآكت الذي يمتدّ عن المكوّن react.Component
:
... class CustomersList extends Component { constructor(props) { super(props); this.state = { customers: [], nextPageURL: '' }; this.nextPage = this.nextPage.bind(this); this.handleDelete = this.handleDelete.bind(this); } } export default CustomersList;
هيأنا في الشيفرة كائن الحالة state داخل الباني constructor؛ إذ يحتفظ هذا الكائن بحالة متغيرات مكوّننا باستخدام مصفوفة عملاء فارغة. ستحتفظ هذه المصفوفة بالعملاء والمتغير nextPageURL
الذي يحتفظ بعنوان URL للصفحة التالية التي ستستعيدها من واجهة برمجة تطبيقات الواجهة الخلفية.
ربطنا أيضًا التابعين ()nextPage
و ()handleDelete
بالمؤشر this
بحيث يستطاع الوصول إليهما من شيفرة HTML، ثم أضفنا التابع ()componentDidMount
واستدعاءً للتابع ()getCustomers
ضمن الصنف CustomersList
قبل قوس الإغلاق.
يُعد التابع ()componentDidMount
تابع دورة حياة المكوّن ويُستدعى عند إنشاء المكوّن وإدخاله إلى نموذج كائن الوثيقة DOM. يستدعي التابع ()getCustomers
كائن خدمة العملاء Customers Service للحصول على صفحة البيانات الأولى ورابط الصفحة التالية من الواجهة الخلفية لجانغو:
... componentDidMount() { var self = this; customersService.getCustomers().then(function (result) { self.setState({ customers: result.data, nextPageURL: result.nextlink}) }); }
أضف تحت التابع ()componentDidMount
بالضبط التابع ()handleDelete
، الذي يتولى مهمة حذف عميل:
... handleDelete(e,pk){ var self = this; customersService.deleteCustomer({pk : pk}).then(()=>{ var newArr = self.state.customers.filter(function(obj) { return obj.pk !== pk; }); self.setState({customers: newArr}) }); }
يستدعي التابعُ ()handleDelete
التابعَ ()deleteCustomer
لحذف عميل باستخدام مفتاحه الأساسي، وإذا كانت العملية ناجحة، ستُرشّح filtered المصفوفة لتعكس آخر حالة بعد حذف ذلك العميل.
بعد ذلك، أضف التابع ()nextPage
لتحصل على بيانات الصفحة التالية وحدّث رابط الصفحة التالية:
... nextPage(){ var self = this; customersService.getCustomersByURL(this.state.nextPageURL).then((result) => { self.setState({ customers: result.data, nextPageURL: result.nextlink}) }); }
يستدعي التابعُ ()nextPage
التابعَ ()getCustomersByURL
، الذي يأخذ عنوان URL للصفحة التالية من كائن الحالة this.state.nextPageURL
ويحدّث مصفوفة العملاء customers
بالبيانات المعادة.
وأخيرًا، أضف التابع ()render
، الذي يصيّر جدولًا للعملاء من حالة المكوّن:
... render() { return ( <div className="customers--list"> <table className="table"> <thead key="thead"> <tr> <th>#</th> <th>First Name</th> <th>Last Name</th> <th>Phone</th> <th>Email</th> <th>Address</th> <th>Description</th> <th>Actions</th> </tr> </thead> <tbody> {this.state.customers.map( c => <tr key={c.pk}> <td>{c.pk} </td> <td>{c.first_name}</td> <td>{c.last_name}</td> <td>{c.phone}</td> <td>{c.email}</td> <td>{c.address}</td> <td>{c.description}</td> <td> <button onClick={(e)=> this.handleDelete(e,c.pk) }> Delete</button> <a href={"/customer/" + c.pk}> Update</a> </td> </tr>)} </tbody> </table> <button className="btn btn-primary" onClick= { this.nextPage }>Next</button> </div> ); }
وفيما يلي يظهر المحتوى الكامل للملف "CustomersList.js":
import React, { Component } from 'react'; import CustomersService from './CustomersService'; const customersService = new CustomersService(); class CustomersList extends Component { constructor(props) { super(props); this.state = { customers: [], nextPageURL: '' }; this.nextPage = this.nextPage.bind(this); this.handleDelete = this.handleDelete.bind(this); } componentDidMount() { var self = this; customersService.getCustomers().then(function (result) { console.log(result); self.setState({ customers: result.data, nextPageURL: result.nextlink}) }); } handleDelete(e,pk){ var self = this; customersService.deleteCustomer({pk : pk}).then(()=>{ var newArr = self.state.customers.filter(function(obj) { return obj.pk !== pk; }); self.setState({customers: newArr}) }); } nextPage(){ var self = this; console.log(this.state.nextPageURL); customersService.getCustomersByURL(this.state.nextPageURL).then((result) => { self.setState({ customers: result.data, nextPageURL: result.nextlink}) }); } render() { return ( <div className="customers--list"> <table className="table"> <thead key="thead"> <tr> <th>#</th> <th>First Name</th> <th>Last Name</th> <th>Phone</th> <th>Email</th> <th>Address</th> <th>Description</th> <th>Actions</th> </tr> </thead> <tbody> {this.state.customers.map( c => <tr key={c.pk}> <td>{c.pk} </td> <td>{c.first_name}</td> <td>{c.last_name}</td> <td>{c.phone}</td> <td>{c.email}</td> <td>{c.address}</td> <td>{c.description}</td> <td> <button onClick={(e)=> this.handleDelete(e,c.pk) }> Delete</button> <a href={"/customer/" + c.pk}> Update</a> </td> </tr>)} </tbody> </table> <button className="btn btn-primary" onClick= { this.nextPage }>Next</button> </div> ); } } export default CustomersList;
الآن وبعد أنشأنا المكون CustomersList
المسؤول عن عرض قائمة العملاء يمكننا الآن إضافة المكون الذي يتولى مهمة إنشاء العملاء وتحديثهم.
الخطوة الثامنة - إضافة مكون ريآكت لإنشاء العملاء وتحديثهم
سننشئ في هذه الخطوة المكون CustomerCreateUpdate
الذي سيتولى مهمة إنشاء وتحديث العملاء، وذلك بإتاحة نموذج تعبئة form للمستخدمين لملئها ببيانات عميل جديد أو تحديث بيانات عميل موجود.
أنشئ الملف "CustomerCreateUpdate.js" داخل المجلد "frontend/src":
$ nano ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js
أضف الشيفرة التالية لإنشاء مكون ريآكت الذي يستورد React
و Component
:
import React, { Component } from 'react';
يمكنك أيضًا استيراد واستنساخ الصنف CustomersService
الذي أنشأناه في الخطوة السابقة والذي يزود بتوابع توصل مع الواجهة الخلفية لواجهة برمجة تطبيقات ريست:
... import CustomersService from './CustomersService'; const customersService = new CustomersService();
ثم أنشئ المكون CustomerCreateUpdate
المُمتدّ عن Component
لإنشاء العملاء وتحديثهم:
... class CustomerCreateUpdate extends Component { constructor(props) { super(props); } } export default CustomerCreateUpdate;
أضف ضمن تعريف الصنف تابعَ المكوّن ()render
الذي يصيّر نموذج HTML الذي يجمع معلومات عن العميل.
... render() { return ( <form onSubmit={this.handleSubmit}> <div className="form-group"> <label> First Name:</label> <input className="form-control" type="text" ref='firstName' /> <label> Last Name:</label> <input className="form-control" type="text" ref='lastName'/> <label> Phone:</label> <input className="form-control" type="text" ref='phone' /> <label> Email:</label> <input className="form-control" type="text" ref='email' /> <label> Address:</label> <input className="form-control" type="text" ref='address' /> <label> Description:</label> <textarea className="form-control" ref='description' ></textarea> <input className="btn btn-primary" type="submit" value="Submit" /> </div> </form> ); }
يضيف هذا التابع الخاصية ref
للوصول إلى عنصر النموذج التعبئة ووضع قيمة له وذلك من أجل كل عنصر إدخال ضمن نموذج التعبئة.
الآن، عرّف التابعَ (handleSubmit(event
فوق التابع ()render
لكي تمتلك الوظائف المناسبة عندما ينقر عميل على زر الإرسال submit:
... handleSubmit(event) { const { match: { params } } = this.props; if(params && params.pk){ this.handleUpdate(params.pk); } else { this.handleCreate(); } event.preventDefault(); } …
يعالج التابع (handleSubmit(event
إرسال النموذج ويستدعي -اعتمادًا على المسار- إما التابع (handleUpdate(pk
لتحديث العميل بالمفتاح الأساسي الممرر، أو التابع ()handleCreate
لإنشاء عميل جديد. سنعرّف هذين التابعين باختصار.
عد إلى باني المكون، واربط التابع ()handleSubmit
بالمؤشر this
لكي تتمكن من الوصول إليه في نموذجك:
... class CustomerCreateUpdate extends Component { constructor(props) { super(props); this.handleSubmit = this.handleSubmit.bind(this); } ...
ثم عرّف التابع ()handleCreate
لإنشاء عميل من بيانات نموذج التعبئة، وأضف الشيفرة التالية فوق التابع (handleSubmit(event
:
... handleCreate(){ customersService.createCustomer( { "first_name": this.refs.firstName.value, "last_name": this.refs.lastName.value, "email": this.refs.email.value, "phone": this.refs.phone.value, "address": this.refs.address.value, "description": this.refs.description.value }).then((result)=>{ alert("Customer created!"); }).catch(()=>{ alert('There was an error! Please re-check your form.'); }); } …
سوف يُستخدَم التابع ()handleCreate
من أجل إنشاء عميل من البيانات المُدخلة، إذ يستدعي التابع المقابل ()CustomersService.createCustomer
الذي يجري استدعاءً لواجهة برمجة التطبيقات الفعلية للواجهة الخلفية لينشئ عميلًا جديدًا.
بعد ذلك عرّف التابع (handleUpdate(pk
الذي يجري التحديثات أسفل التابع ()handleCreate
:
... handleUpdate(pk){ customersService.updateCustomer( { "pk": pk, "first_name": this.refs.firstName.value, "last_name": this.refs.lastName.value, "email": this.refs.email.value, "phone": this.refs.phone.value, "address": this.refs.address.value, "description": this.refs.description.value } ).then((result)=>{ alert("Customer updated!"); }).catch(()=>{ alert('There was an error! Please re-check your form.'); }); }
سيحدّث التابع ()updateCustomer
العميل ذا المفتاح الأساسي pk
بالمعلومات الجديدة التي جلبها من نموذج معلومات العميل، ويستدعي التابع ()customersService.updateCustomer
.
الآن، أضف التابع ()componentDidMount
، وبالتالي إذا زار المستخدم الوجهة "customer/:pk"، فإننا نريد تعبئة النموذج بمعلومات مرتبطة بالعميل باستخدام المفتاح الأساسي من عنوان URL، ولفعل ذلك يمكننا إضافة التابع (getCustomer(pk
بعد أن يُثبّت المكون في حدث دورة الحياة للتابع ()componentDidMount
. أضف الشيفرة التالية أسفل باني المكون لإضافة هذا التابع:
... componentDidMount(){ const { match: { params } } = this.props; if(params && params.pk) { customersService.getCustomer(params.pk).then((c)=>{ this.refs.firstName.value = c.first_name; this.refs.lastName.value = c.last_name; this.refs.email.value = c.email; this.refs.phone.value = c.phone; this.refs.address.value = c.address; this.refs.description.value = c.description; }) } }
وفيما يلي المحتوى الكامل للملف "CustomerCreateUpdate.js":
import React, { Component } from 'react'; import CustomersService from './CustomersService'; const customersService = new CustomersService(); class CustomerCreateUpdate extends Component { constructor(props) { super(props); this.handleSubmit = this.handleSubmit.bind(this); } componentDidMount(){ const { match: { params } } = this.props; if(params && params.pk) { customersService.getCustomer(params.pk).then((c)=>{ this.refs.firstName.value = c.first_name; this.refs.lastName.value = c.last_name; this.refs.email.value = c.email; this.refs.phone.value = c.phone; this.refs.address.value = c.address; this.refs.description.value = c.description; }) } } handleCreate(){ customersService.createCustomer( { "first_name": this.refs.firstName.value, "last_name": this.refs.lastName.value, "email": this.refs.email.value, "phone": this.refs.phone.value, "address": this.refs.address.value, "description": this.refs.description.value } ).then((result)=>{ alert("Customer created!"); }).catch(()=>{ alert('There was an error! Please re-check your form.'); }); } handleUpdate(pk){ customersService.updateCustomer( { "pk": pk, "first_name": this.refs.firstName.value, "last_name": this.refs.lastName.value, "email": this.refs.email.value, "phone": this.refs.phone.value, "address": this.refs.address.value, "description": this.refs.description.value } ).then((result)=>{ console.log(result); alert("Customer updated!"); }).catch(()=>{ alert('There was an error! Please re-check your form.'); }); } handleSubmit(event) { const { match: { params } } = this.props; if(params && params.pk){ this.handleUpdate(params.pk); } else { this.handleCreate(); } event.preventDefault(); } render() { return ( <form onSubmit={this.handleSubmit}> <div className="form-group"> <label> First Name:</label> <input className="form-control" type="text" ref='firstName' /> <label> Last Name:</label> <input className="form-control" type="text" ref='lastName'/> <label> Phone:</label> <input className="form-control" type="text" ref='phone' /> <label> Email:</label> <input className="form-control" type="text" ref='email' /> <label> Address:</label> <input className="form-control" type="text" ref='address' /> <label> Description:</label> <textarea className="form-control" ref='description' ></textarea> <input className="btn btn-primary" type="submit" value="Submit" /> </div> </form> ); } } export default CustomerCreateUpdate;
وهكذا بعد إنشاء المكوّن CustomerCreateUpdate
، أصبح بإمكاننا تحديث المكوّن App
الرئيسي لإضافة روابط للمكونات المختلفة التي أنشأناها.
الخطوة التاسعة - تحديث مكون التطبيق الرئيسي
سنحدّث في هذا القسم المكوّن App
من تطبيقنا لإنشاء روابط إلى المكونات التي أنشأناها في الخطوات السابقة.
نفذ الأمر التالي من المجلد "frontend"، لتثبيت موجه ريآكت الذي يمكّنك من إضافة المسارات والتنقلات بين مكونات ريآكت المتعددة.
$ cd ~/djangoreactproject/frontend $ npm install --save react-router-dom
والآن افتح الملف "djangoreactproject/frontend/src/App.js/~":
$ nano ~/djangoreactproject/frontend/src/App.js
احذف كل شيء هناك وأضف الشيفرة التالية لاستيراد الأصناف اللازمة لإضافة التوجيه، إذ تتضمن هذه الأصناف الصنفَ BrowserRouter
الذي ينشئ مكوّن الموجّه والصنفَ Route
الذي ينشئ مكوّن الوُجهة route:
import React, { Component } from 'react'; import { BrowserRouter } from 'react-router-dom' import { Route, Link } from 'react-router-dom' import CustomersList from './CustomersList' import CustomerCreateUpdate from './CustomerCreateUpdate' import './App.css';
ملاحظة: يحافظ BrowserRouter
على التزامن بين واجهة المستخدم وعنوان URL باستخدام واجهة برمجة تطبيقات تاريخ HTML5.
أنشئ نمطًا أساسيًّا ليزودنا بالمكون الأساسي الذي سيُغلِّفُه المكون BrowserRouter
.
... const BaseLayout = () => ( <div className="container-fluid"> <nav className="navbar navbar-expand-lg navbar-light bg-light"> <a className="navbar-brand" href="#">Django React Demo</a> <button className="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation"> <span className="navbar-toggler-icon"></span> </button> <div className="collapse navbar-collapse" id="navbarNavAltMarkup"> <div className="navbar-nav"> <a className="nav-item nav-link" href="/">CUSTOMERS</a> <a className="nav-item nav-link" href="/customer">CREATE CUSTOMER</a> </div> </div> </nav> <div className="content"> <Route path="/" exact component={CustomersList} /> <Route path="/customer/:pk" component={CustomerCreateUpdate} /> <Route path="/customer/" exact component={CustomerCreateUpdate} /> </div> </div> )
استخدمنا بالشيفرة أعلاه المكون Route
لتعريف وجهات تطبيقنا، وهو المكون الذي يتعيّن على الموجّه تحميله عندما يجد تطابقًا. تحتاج كل وجهة إلى مسار path
لتحديد المسار الذي سيجري مطابَقته، وإلى مكوّن component
لتحديد المكون الذي سيحمّله. تخبر الخاصّيّة exact
الموجّه أن يطابق المسار الصحيح.
وأخيرًا أنشئ المكون App
المكوّن الجذر أو مكوّن مستوى القمة في تطبيق ريآكت:
... class App extends Component { render() { return ( <BrowserRouter> <BaseLayout/> </BrowserRouter> ); } } export default App;
وبما أننا نريد أن يعمل التطبيق في المتصفح، فقد غلّفنا المكوّن BaseLayout
بالمكوّن BrowserRouter
.
الآن يبدو ملف "App.js" المكتمل شبيهًا بما يلي:
import React, { Component } from 'react'; import { BrowserRouter } from 'react-router-dom' import { Route, Link } from 'react-router-dom' import CustomersList from './CustomersList' import CustomerCreateUpdate from './CustomerCreateUpdate' import './App.css'; const BaseLayout = () => ( <div className="container-fluid"> <nav className="navbar navbar-expand-lg navbar-light bg-light"> <a className="navbar-brand" href="#">Django React Demo</a> <button className="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation"> <span className="navbar-toggler-icon"></span> </button> <div className="collapse navbar-collapse" id="navbarNavAltMarkup"> <div className="navbar-nav"> <a className="nav-item nav-link" href="/">CUSTOMERS</a> <a className="nav-item nav-link" href="/customer">CREATE CUSTOMER</a> </div> </div> </nav> <div className="content"> <Route path="/" exact component={CustomersList} /> <Route path="/customer/:pk" component={CustomerCreateUpdate} /> <Route path="/customer/" exact component={CustomerCreateUpdate} /> </div> </div> ) class App extends Component { render() { return ( <BrowserRouter> <BaseLayout/> </BrowserRouter> ); } } export default App;
بعد إضافة التوجيه إلى تطبيقنا، أصبحنا الآن جاهزين لاختبار التطبيق. انتقل إلى العنوان "http://localhost:3000"، ويفترض أن ترى الصفحة الأولى من التطبيق:
وبهذا يكتمل التطبيق، وأصبح لديك الآن الأساس لتطبيق إدارة علاقات العملاء CRM.
الخاتمة
أنشأت في هذا المقال تطبيقًا تجريبيًّا باستخدام جانغو وريآكت، واستخدمت إطار عمل جانغو ريست لبناء واجهة برمجة التطبيقات لريست REST واكسيوس Axios لاستهلاك واجهة برمجة التطبيقات وبوتستراب 4 لتنسيق CSS.
يمكنك العثور على الشيفرة المصدرية لهذا المشروع في مستودع غيت هب GitHub.
ترجمة - وبتصرف - للمقالة How To Build a Modern Web Application to Manage Customer Information with Django and React on Ubuntu 18.04 لصاحبها Ahmed Bouchefra.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.