ساخت یک CRM مقدماتی با Django و React روی اوبونتو ۱۸.۰۴ — از صفر تا صد

۵۰۸ بازدید
آخرین به‌روزرسانی: ۲۰ شهریور ۱۴۰۲
زمان مطالعه: ۲۵ دقیقه
ساخت یک CRM مقدماتی با Django و React روی اوبونتو ۱۸.۰۴ — از صفر تا صد

افراد مختلف از انواع متفاوتی از دستگاه‌ها برای اتصال به اینترنت و مرور وب استفاده می‌کنند. به همین دلیل وب اپلیکیشن‌ها باید از مکان‌های مختلف قابل دسترسی باشند. در مورد وب‌سایت‌های سنتی، داشتن یک رابط کاربری واکنش‌گرا معمولاً کافی است؛ اما اپلیکیشن‌های پیچیده‌تر غالباً نیازمند استفاده از تکنیک‌ها و معماری‌های دیگری هستند. این تکنیک‌ها شامل داشتن بک‌اند REST و همچنین اپلیکیشن فرانت‌اندی است که می‌تواند در سمت کلاینت به صورت وب اپلیکیشن، وب اپلیکیشن پیش‌رونده یعنی «Progressive Web App»به اختصار (PWA)، یا اپلیکیشن native موبایل پیاده‌سازی شود.

برخی ابزارهایی که می‌توان برای ساخت چنین اپلیکیشن‌های پیچیده‌ای مورد استفاده قرار داد، به صورت زیر هستند:

  • React یک فریمورک جاوا اسکریپت است که به توسعه دهنگان امکان ساخت فرانت‌اندهای وب و بومی برای بک‌اندهای دارای API به صورت REST را می‌دهد.
  • Django یک فریمورک وب پایتون اوپن سورس است که از الگوی معماری نرم‌افزار Model View Controller یا به اختصار MVC پیروی می‌کند.
  • فریمورک REST جنگو یک کیت ابزار قدرتمند و انعطاف‌پذیر برای ساخت REST API ها در جنگو محسوب می‌شود.

در این راهنما یک وب اپلیکیشن مدرن با بک‌اند REST API مستقل و فرانت‌اندی با استفاده از React، جنگو و فریمورک Django REST می‌سازیم. شما با استفاده از جنگو و ری‌اکت می‌توانید از مزیت آخرین پیشرفت‌ها در زمینه جاوا اسکریپت و توسعه فرانت‌اند بهره‌مند شوید. ما به جای ساخت یک اپلیکیشن جنگو که از موتور قالب درونی خودش استفاده می‌کند، از ری‌اکت به عنوان کتابخانه UI استفاده می‌کنیم و از مزیت «مدل شیء سند» (DOM) مجازی، رویکرد اعلانی و کامپوننت‌هایی که به سرعت تغییرات داده‌ها را رندر می‌کنند بهره می‌گیریم.

این وب اپلیکیشن که می‌خواهیم بسازیم به ذخیره‌سازی رکوردهایی در مورد مشتریان در یک پایگاه داده می‌پردازد و می‌توان از آن به عنوان یک نقطه شروع برای ساخت یک اپلیکیشن CRM استفاده کرد. زمانی که کار ساخت این اپلیکیشن به پایان برسد، قادر خواهیم بود رکوردهایی را با استفاده از یک رابط ری‌اکت که به وسیله Bootstrap 4 سبک‌بندی شده است، ایجاد کرده، خوانده، و به‌روزرسانی یا حذف کنیم.

پیش‌نیازها

برای تکمیل این راهنما به موارد زیر نیاز داریم:

  • یک ماشین توسعه با سیستم عامل لینوکس اوبونتو 18.04
  • پایتون 3، pip و venv که روی این ماشین اوبونتو نصب شده باشند.
  • Node.js نسخه 6 به بالا و npm 5.2 یا بالاتر باید روی ماشین مورد نظر نصب شده باشند.

مرحله اول – ایجاد یک محیط توسعه مجازی و نصب وابستگی‌ها

در این مرحله یک محیط مجازی ایجاد کرده و وابستگی‌های مورد نیاز برای اپلیکیشن خود را که شامل جنگو، فریمورک REST جنگو و django-cors-headers است نصب می‌کنیم.

اپلیکیشن ما از دو سرور توسعه متفاوت برای جنگو و ری‌اکت بهره می‌گیرد. این دو سرور روی پورت‌های مختلف کار می‌کنند و کارکردی به صورت دو دامنه مستقل از هم دارند. به همین دلیل باید Cross-Origin Resource Sharing یا به اختصار (CORS) را برای ارسال درخواست‌های HTTP از ری‌اکت به جنگو بدون مسدود شدن از سوی مرورگر فعال کنیم.

به دایرکتوری home رفته و یک محیط مجازی با استفاده از ماژول venv در پایتون 3 بسازید:

cd ~
python3 -m venv./env

محیط مجازی ایجاد شده را با استفاده از source فعال کنید:

source env/bin/activate

سپس وابستگی‌های پروژه را با pip نصب کند. این موارد شامل فهرست زیر هستند:

  • جنگو: وب فریمورک پروژه است.
  • فریمورک REST جنگو: یک اپلیکیشن شخص ثالث برای ساخت REST API ها با جنگو است.
  • django-cors-headers: بسته‌ای است که امکان CORS را فراهم می‌کند.

فریمورک جنگو را نصب کنید:

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 می‌گذاریم:

django-admin startproject djangoreactproject

پیش از آن که به کار خود ادامه بدهیم، با استفاده از دستور tree نگاهی به ساختار دایرکتوری پروژه خود می‌اندازیم.

دقت کنید که tree یک دستور مفید برای دیدن ساختارهای فایل و دایرکتوری در خط فرمان است. آن را می‌توانید با استفاده از دستور زیر نصب کنید:

sudo apt-get install tree

برای استفاده از آن به دایرکتوری که می‌خواهید ببینید cd کرده و عبارت tree را در مسیر آغازین به صورت زیر وارد کنید:

 tree /home/sammy/sammys-project

در ادامه به پوشه djangoreactproject درون ریشه پروژه رفته و دستور tree را اجرا کنید:

cd ~/djangoreactproject
tree

خروجی زیر را شاهد خواهید بود:

├── djangoreactproject
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── manage.py

پوشه djangoreactproject/~ ریشه پروژه است. درون این پوشه چند فایل قرار دارند که برای کار ما حائز اهمیت هستند:

manage.py – اسکریپت کاربردی است که چند وظیفه مدیریتی انجام می‌دهد.

settings.py – فایل اصلی پیکربندی برای پروژه جنگو است که می‌توان تنظیمات پروژه را در آن تغییر داد. این تنظیمات شامل متغیرهایی مانند INSTALLED_APPS، فهرستی از رشته‌های اختصاص یافته به اپلیکیشن‌های فعال برای پروژه است. مستندات جنگو اطلاعات بیشتری در مورد تنظیمات موجود (+) ارائه کرده است.

urls.py – این فایل شامل فهرستی از الگوهای URL و view های مربوط به آن‌ها است. هر الگو یک اتصال بین یک URL و تابعی که باید برای آن URL فراخوانی شود برقرار می‌سازد.

نخستین گام در مسیر ساخت پروژه، پیکربندی بسته‌هایی است که در مرحله قبلی نصب کردیم و شامل Django REST framework و Django CORS است. ابتدا باید این بسته‌ها را به فایل settings.py اضافه کنیم. به این منظور فایل را با ویرایشگر nano یا ویرایشگر محبوب خودتان باز کنید:

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',
   <span style="color: #ff0000;" data-mce-style="color: #ff0000;">'rest_framework',</span>
<span style="color: #ff0000;" data-mce-style="color: #ff0000;">   'corsheaders'</span>
]

سپس میان‌افزار (middleware) به نام corsheaders.middleware.CorsMiddleware که از بسته قبلاً نصب شده CORS است را به تنظیمات MIDDLEWARE اضافه کنید. این تنظیمات فهرستی از میان‌افزارها را شامل می‌شود که یک کلاس پایتون است و هر بار یک درخواست یا پاسخ از سوی وب اپلیکیشن مدیریت شود، به پردازش کد آن خواهد پرداخت.

...

MIDDLEWARE = [
...
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'corsheaders.middleware.CorsMiddleware'
]

اینک می‌توانیم CORS را فعال کنیم. تنظیمات CORS_ORIGIN_ALLOW_ALL تعیین می‌کند که می‌خواهیم CORS برای همه دامنه‌ها فعال شود یا نه و CORS_ORIGIN_WHITELIST نیز یک چندتایی پایتون است که شامل URL های مجاز است. در مورد اپلیکیشنمان از آنجا که سرور توسعه ری‌اکت در آدرس http://localhost:3000 اجرا خواهد شد ، باید یک تنظیمات جدید CORS_ORIGIN_ALLOW_ALL = False و (,'CORS_ORIGIN_WHITELIST('localhost:3000 به فایل settings.py اضافه کنیم. همه این تنظیمات را به فایل مربوطه اضافه می‌کنیم:

...
CORS_ORIGIN_ALLOW_ALL = False

CORS_ORIGIN_WHITELIST = (
'localhost:3000',
)
...

گزینه‌های پیکربندی بیشتر را در مستندات django-cors-headers (+) می‌توانید ملاحظه کنید. هنگامی که ویرایش فایل به پایان رسید، آن را ذخیره کرده و خارج شوید. همچنان که در دایرکتوری djangoreactproject/~ هستند یک اپلیکیشن جدید جنگو به نام customers ایجاد کنید:

python manage.py startapp customers

این اپلیکیشن شامل مدل‌ها و ویوهایی برای مدیریت مشتریان خواهد بود. مدل‌ها فیلدها و رفتارهای داده‌های اپلیکیشن را تعریف می‌کنند؛ در حالی که ویو ها امکان مدیریت مناسب درخواست‌های وب و بازگشت پاسخ‌های ضروری را فراهم می‌سازند.

سپس این اپلیکیشن را به فهرست اپلیکیشن‌های نصب شده در فایل settings.py پروژه اضافه کنید تا جنگو آن را به عنوان بخشی از پروژه تشخیص دهد. فایل settings.py را مجدداً باز کنید.

nano ~/djangoreactproject/djangoreactproject/settings.py

اپلیکیشن customers را اضافه کنید.

...
INSTALLED_APPS = [
   ...
   'rest_framework',
   'corsheaders',
   'customers'
]
...

سپس پایگاه داده را migrate کنید و یک سرور توسعه محلی را آغاز نمایید. مهاجرت (migration) ها روشی هستند که جنگو برای انتشار تغییرات صورت گرفته روی مدل‌ها در پایگاه داده استفاده می‌کند. این تغییرات می‌توانند شامل چیزهایی مانند افزودن یک فیلد یا حذف کردن یک مدل باشند.

پایگاه داده را migrate کنید:

python manage.py migrate

سرور توسعه محلی را آغاز کنید:

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 اجرا شده است. اگر به این آدرس در مرورگر وب مراجعه کنید، با صفحه زیر مواجه می‌شوید:

 web application

در این زمان اجازه می‌دهیم اپلیکیشن ما همچنان در حال اجرا بماند و یک ترمینال جدید برای پیگیری ادامه مراحل توسعه پروژه باز می‌کنیم.

مرحله سوم - ایجاد فرانت‌اند ری‌اکت

در این بخش قصد داریم با استفاده از react یک اپلیکیشن فرانت‌اند برای پروژه خود ایجاد کنیم.

React یک ابزار رسمی دارد که امکان تولید سریع پروژه‌های ری‌اکت بدون نیاز به پیکربندی مستقیم Webpack را فراهم می‌کند. Webpack یک module bundler است که برای بسته‌بندی فایل‌های وب مانند کد جاوا اسکریپت، CSS و تصاویر استفاده می‌شود. به طور معمول پیش از آن که از Webpack استفاده کنیم، باید گزینه‌های پیکربندی مختلفی را تنظیم کرده باشیم؛ اما به لطف ابزار create-react-app دیگر نیاز نداریم به طور مستقیم با Webpack سر و کله بزنیم؛ مگر این که خواستار کنترل بیشتری باشیم. برای اجرای create-react-app می‌توان از npx استفاده کرد. npx ابزاری برای اجرای فایل‌های باینری بسته‌های npm است.

در پنجره دوم ترمینال، ابتدا اطمینان حاصل کنید که در دایرکتوری پروژه هستید:

cd ~/djangoreactproject

با استفاده از دستور create-react-app و npx یک پروژه React به نام frontend ایجاد کنید:

npx create-react-app frontend

سپس به اپلیکیشن React مراجعه کرده و یک سرور توسعه را آغاز کنید:

cd ~/djangoreactproject/frontend
npm start

اپلیکیشن شما در مسیر /http://localhost:3000 اجرا خواهد شد:

 React development server

اجازه بدهید که سرور توسعه React در حالت اجرا بماند و پنجره ترمینال دیگری برای ادامه کار باز کنید.

در این زمان برای مشاهده ساختار دایرکتوری کل پروژه به پوشه ریشه رفته و دستور 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

اپلیکیشن ما از Bootstrap 4 برای تعیین سبک یا استایل‌شیت‌های ظاهری رابط کاربری React استفاده می‌کند و از این رو آن را در فایل 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 است که برای ایمپورت کردن قواعد استایل‌شیت‌ها از استایل‌شیت‌های دیگر مورد استفاده قرار می‌گیرد. اکنون که هر دو اپلیکیشن بک‌اند و فرانت‌اند را ساخته‌ایم، می‌توانیم اقدام به ساخت مدل مشتری (Customer) و برخی داده‌های دمو بکنیم.

مرحله چهارم – ایجاد مدل Customer و داده‌های اولیه

پس از ایجاد اپلیکیشن جنگو و فرانت‌اند React مرحله بعدی این است که یک مدل مشتری ایجاد کنیم که نماینده جدول پایگاه داده‌ای باشد که اطلاعات در مورد مشتری را نگه‌داری می‌کند. البته نیازی به استفاده از هیچ گونه SQL نداریم، زیرا ORM خاص جنگو عملیات پایگاه داده را با نگاشت کلاس‌های پایتون و متغیرها به جداول و ستون‌های SQL اجرا می‌کند. ORM جنگو تعامل‌های SQL با پایگاه داده را از طریق رابط پایتون انتزاع می‌کند.

دوباره محیط مجازی را فعال کنید:

cd ~
source env/bin/activate

به دایرکتوری customers رفته و فایل models.py را باز کنید. این یک فایل پایتون است که مدل‌های اپلیکیشن در آن ذخیره می‌شوند:

cd ~/djangoreactproject/customers/
nano models.py

این فایل شامل محتوای زیر است:

from django.db import models
# Create your models here.

API مدل Customer قبلاً به لطف دستور ایمپورت from django.db import models در فایل ایمپورت شده است. اینک باید کلاس Customer که models.Model را بسط داده است اضافه کنیم. هر مدل در Django یک کلاس پایتون است که django.db.models.Model را بسط می‌دهد.

مدل customer این فیلدهای پایگاه داده را دارد:

  • first_name – نام مشتری
  • last_name – نام خانوادگی مشتری
  • Email – آدرس ایمیل مشتری
  • Phone – شماره تلفن مشتری
  • Address – نشانی مشتری
  • description – توصیف مشتری
  • createdAt – زمان اضافه شدن مشتری

همچنین یک تابع ()__str__ را نیز اضافه می‌کنیم که شیوه نمایش مدل را تعریف می‌کند. در مثال مورد بررسی این مقدار بر اساس نام مشتری خواهد بود.

کد زیر را به فایل اضافه کنید:

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

سپس پایگاه داده را migrate کنید تا جداول پایگاه داده ایجاد شوند. دستور makemigrations فایل‌های مهاجرت را ایجاد می‌کند که در آن تغییرات مدل اضافه می‌شوند و دستور migrate تغییرات موجود در فایل migration را در پایگاه داده اعمال می‌کند.

دوباره به پوشه ریشه پروژه بازگردید:

cd ~/djangoreactproject

دستور زیر را برای ایجاد فایل‌های مهاجرت اجرا کنید:

python manage.py makemigrations

خروجی آن چیزی مانند زیر است:

customers/migrations/0001_initial.py
- Create model Customer

این تغییرات را روی پایگاه داده اعمال کنید:

python manage.py migrate

در ادامه خروجی زیر را مشاهده می‌کنید که نشان دهنده موفقیت فرایند مهاجرت است:

Operations to perform:
   Apply all migrations: admin, auth, contenttypes, customers, sessions
Running migrations:
   Applying customers.0001_initial... OK

سپس از یک فایل data migration file برای ایجاد داده‌های اولیه مشتری استفاده می‌کنیم. یک data migration file فایل مهاجرتی است که داده‌هایی را به پایگاه داده اضافه کرده یا داده‌های موجود را تغییر می‌دهد. یک فایل مهاجرت داده خالی برای اپلیکیشن customers بسازید:

python manage.py makemigrations --empty --name customers customers

تأیید زیر را در مورد نام فایل مهاجرت مشاهده خواهید کرد:

Migrations for 'customers':
customers/migrations/0002_customers.py

دقت کنید که نام فایل مهاجرت به صورت زیر است:

0002_customers.py

سپس به داخل پوشه مهاجرت اپلیکیشن customers بروید:

cd ~/djangoreactproject/customers/migrations

فایل مهاجرت ایجاد شده را باز کنید:

nano 0002_customers.py

محتوای اولیه این فایل به صورت زیر است:

from django.db import migrations

class Migration(migrations.Migration):
    dependencies = [
        ('customers', '0001_initial'),
    ]
    operations = [
    ]

دستور import باعث ایمپورت شدن API مربوط به migration می‌شود. این API جنگو مهاجرت‌ها را از طریق django.db ایجاد می‌کند. django.db یک بسته جنگو است که شامل کلاس‌هایی برای کار با پایگاه‌های داده است.

کلاس 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()

...

در این متد از کلاس Customers اپلیکیشن customers خود استفاده کرد و یک مشتری دمو برای درج درون پایگاه داده ایجاد می‌کنیم.

برای دریافت کلاس Customer که امکان ایجاد مشتریان جدید را فراهم می‌کند، باید از متد ()get_model شیء apps استفاده کنیم. شیء apps نشان دهنده یک رجیستری از اپلیکیشن‌های نصب شده و مدل‌های پایگاه‌های داده آن‌ها است.

شیء apps از متد ()RunPython هنگام استفاده برای اجرای ()create_data ارسال خواهد شد. متد ()migrations.RunPython را برای خالی کردن فهرست operations اجرا کنید:

...
    operations = [
        migrations.RunPython(create_data),
    ]

()RunPython بخشی از API مهاجرت است که امکان اجرای کد پایتون خاص مهاجرت را فراهم می‌کند. فهرست 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),
    ]

برای مهاجرت پایگاه داده، ابتدا باید به پوشه ریشه پروژه بازگردیم:

cd ~/djangoreactproject

پایگاه داده خود را برای ایجاد داده‌های دمو migrate کنید:

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

در این مرحله با استفاده از فریمورک REST جنگو اقدام به ساخت REST API می‌کنیم. در واقع چندین ویو مختلف برای API می‌سازیم. منظور از ویوی API تابعی است که به مدیریت درخواست یا فراخوانی API می‌پردازد، در حالی که نقطه انتهایی (endpoint) برای API یک URL منحصر به فرد است که نشان دهنده نقطه تماس با سیستم REST است. برای نمونه هنگامی که یک کاربر درخواست GET را به یک نقطه انتهایی API ارسال می‌کند، جنگو تابع مربوطه یا همان ویوی API را برای مدیریت درخواست و بازگشت نتایج ممکن فراخوانی می‌کند.

همچنین باید از serializer ها استفاده کنیم. یک سریالایزر در فریمورک REST جنگو امکان تبدیل وهله‌های مدل پیچیده و QuerySet ها به فرمت JSON برای مصرف API را فراهم می‌کند. کلاس سریالایزر می‌تواند در جهت دیگر نیز کار کند و سازوکارهایی برای تجزیه و سریال‌زدایی داده‌ها به صورت مدل‌ها و QuerySet های جنگو ارائه کند.

نقاط انتهایی API ما شامل موارد زیر است:

  • api/customers – این نقطه انتهایی برای ایجاد مشتری‌ها و بازگشت مجموعه‌های صفحه‌بندی شده‌ای از مشتریان استفاده می‌شود.
  • <api/customers/<pk – این نقطه انتهایی برای دریافت، به‌روزرسانی و حذف مشتریان منفرد به وسیله کلید ابتدایی یا id استفاده می‌شود.

همچنین URL-هایی در فایل urls.py پروژه خود برای نقاط انتهایی متناظر (یعنی api/customers و <api/customers/<pk) ایجاد می‌کنیم.

کار خود را با ایجاد کلاس serializer برای مدل Customer خود آغاز می‌کنیم:

افزودن کلاس Serializer

ایجاد یک کلاس Serializer برای مدل Customer جهت تبدیل وهله‌های مشتری و QuerySet ها به و از قالب جیسون ضروری است. برای ایجاد کلاس Serializer ابتدا یک فایل serializers.py درون اپلیکیشن customers ایجاد می‌کنیم:

cd ~/djangoreactproject/customers/
nano serializers.py

کد زیر را برای ایمپورت کردن API سریالایزر ها و مدل 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.

محتوای کامل فایل به صورت زیر است:

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 را اضافه کنیم.

افزودن ویوهای API

در این بخش ویوهای API را برای اپلیکیشن خود می‌سازیم که هنگامی که کاربران از نقطه انتهایی متناظر با تابع ویو بازدید می‌کنند از سوی جنگو فراخوانی می‌شوند:

فایل زیر را باز کنید:

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 و API های جنگو و فریمورک REST جنگو ایمپورت می‌کنیم.

سپس ویو را برای پردازش درخواست‌های HTTP به صورتGET و POST اضافه می‌کنیم:

...

@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@ برای ایجاد ویوی API استفاده می‌کنیم که می‌تواند درخواست‌های GET و POST را بپذیرد. یک دکوراتور تابعی است که تابع دیگری می‌گیرد و به طور دینامیک آن را بسط می‌دهد.

در بدنه متد از متغیر request.method استفاده شده است که به بررسی متد HTTP جاری پرداخته و منطق متناظر وابسته به نوع درخواست را اجرا می‌کند:

اگر یک درخواست GET باشد، متد به صفحه‌بندی داده‌ها با استفاده از Paginator جنگو می‌پردازد و نخستین صفحه از داده‌ها را پس از سریال‌سازی، تعداد مشتریان موجود، تعداد صفحه‌های موجود و پیوندهایی به صفحه‌های قبل و بعد را بازمی‌گرداند. Paginator یک کلاس داخلی جنگو است که فهرستی از داده‌ها را صفحه‌بندی کرده و متدهای برای دسترسی به آیتم‌های هر صفحه ارائه می‌کند.

اگر درخواست HTTP از نوع POST باشد، متد اقدام به سریال‌سازی داده‌های مشتری دریافتی کرده و سپس متد ()save را برای شیء سریالایزر فراخوانی می‌کند. سپس یک شیء پاسخ (Response) که وهله‌ای از HttpResponse است به همراه کد وضعیت 201 بازمی‌گرداند. متد ()save داده‌های سریال شده را در پایگاه داده ذخیره می‌کند.

اکنون ویوی API مسئول پردازش درخواست‌های GET، PUT و DELETE برای دریافت، به‌روزرسانی، و حذف مشتریان به وسیله pk یعنی  (primary key) است:

...
@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 است که می‌تواند درخواست‌های GET، PUT و DELETE را بپذیرد.

فیلد request.method به بررسی متد درخواست پرداخته و بسته به فراخوانی‌های مقدار آن منطق مناسب را مورد استفاده قرار می‌دهد:

  • اگر یک درخواست GET باشد، داده‌های مشتری سریال‌سازی شده و با استفاده از شیء Response ارسال می‌شود.
  • اگر یک درخواست PUT باشد، متد یک سریالایزر برای داده‌های مشتری جدید ایجاد می‌کند. سپس متد ()save برای شیء سریالایزر ایجاد شده فراخوانی می‌کند. در نهایت شیء Response را با مشتری به‌روزشده ارسال می‌کند.
  • اگر درخواست از نوع DELETE باشد، این متد یک متد دیگر به نام ()delete از شیء مشتری را فراخوانی می‌کند که آن را حذف کند و سپس شیء Response بدون هیچ داده‌ای بازگشت می‌یابد.

فایل کامل به صورت زیر است:

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

اینک نقاط انتهایی API را ایجاد می‌کنیم: api/customers/ برای کوئری زدن و ایجاد مشتری‌ها و <api/customers/<pk برای دریافت، به‌روزرسانی یا حذف کردن مشتریان منفرد از طریق pk شان.

فایل زیر را باز کنید:

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 را به فهرست urlpatterns اضافه کنید که شامل URL-های اپلیکیشن است:

...

urlpatterns = [
    path('admin/', admin.site.urls),
    url(r'^api/customers/$', views.customers_list),
    url(r'^api/customers/(?P<pk>[0-9]+)$', views.customers_detail),
]

اینک که نقاط انتهایی REST ما آماده شده است و می‌توانیم به بررسی چگونگی استفاده از آن‌ها بپردازیم.

مرحله ششم – مصرف کردن REST API با استفاده از Axios

در این مرحله Axios را نصب می‌کنیم. Axios یک کلاینت HTTP است که برای ایجاد فراخوان‌های API از آن استفاده خواهیم کرد. همچنین یک کلاس برای مصرف نقاط انتهایی API ایجاد شده می‌سازیم.

ابتدا محیط مجازی خود را غیر فعال کنید:

Deactivate

سپس به پوشه frontend بروید:

cd ~/djangoreactproject/frontend

axios را با استفاده از دستور زیر از npm نصب کنید:

npm install axios –save

گزینه save-- وابستگی axios را به فایل package.json اپلیکیشن شما اضافه می‌کند.

سپس یک فایل جاوا اسکریپت به نام CustomersService.js ایجاد کنید که شامل کدی برای فراخوانی REST API ها باشد. این کار درون پوشه scr صورت می‌گیرد که کد اپلیکیشن برای پروژه در آن قرار دارد:

cd src
nano CustomersService.js

کد زیر را که شامل متدهایی برای اتصال به REST API جنگو است، به فایل فوق اضافه کنید:

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: یک مشتری را به وسیله primary key دریافت می‌کند.
  • ()createCustomer: یک مشتری را ایجاد می‌کند.
  • ()updateCustomer: یک مشتری را به‌روزرسانی می‌کند.
  • ()deleteCustomer: یک مشتری را حذف می‌کند.

همچنین می‌توانیم با ایجاد یک کامپوننت به نام CustomersList، داده‌هایی را از API خودمان در رابط کاربری (UI) ری‌اکت نمایش دهیم.

مرحله هفتم - نمایش داده‌های API در اپلیکیشن React

در این مرحله کامپوننت ری‌اکت به نام CustomersList را ایجاد می‌کنیم. این کامپوننت ری‌اکت نشان دهنده بخشی از UI است و همچنین امکان افراز رابط کاربری به تکه‌های مستقل از هم و با قابلیت استفاده مجدد را فراهم می‌سازد.

کار خود را با ایجاد 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 برای فراخوانی REST API می‌پردازد. هر کامپوننت ری‌اکت باید کلاس Component را بسط داده یا زیرکلاسی از آن باشد.

کد زیر را برای ایجاد کامپوننت ری‌اکت که 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;

درون constructor اقدام به وهله‌سازی از شیء state کرده‌ایم. این سازنده متغیرهای حالت کامپوننت ما را با استفاده از یک آرایه خالی customers نگه‌داری می‌کند. این آرایه مشتریان را ذخیره خواهد کرد و یک متغیر به نام nextPageURL نیز دارد که URL صفحه بعدی که API بک‌اند بازیابی خواهد کرد را در خود نگهداری می‌کند. همچنین متدهای ()nextPage و ()handleDelete را طوری ایجاد می‌کنیم که از سمت کد HTML قابل دسترسی باشند.

سپس متد ()componentDidMount را اضافه کرده و پیش از بستن پرانتزها یک فراخوانی به ()getCustomers انجام می‌دهیم.

...
componentDidMount() {
    var  self  =  this;
    customersService.getCustomers().then(function (result) {
        self.setState({ customers:  result.data, nextPageURL:  result.nextlink})
    });
}

سپس متد ()handleDelete را زیر ()componentDidMount اضافه می‌کنیم که فرایند حذف مشتری را مدیریت می‌کند:

...
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 را فراخوانی می‌کند که با استفاده از pk یا همان primary key اقدام به حذف مشتری می‌کند. اگر این عملیات موفق باشد، آرایه cusromers مشتری حذف شده را دیگر نشان نمی‌دهد.

سپس یک متد ()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>
    );
}

محتوای کامل فایل به صورت زیر خواهد بود:

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 را ایجاد خواهیم کرد که فرایند ایجاد و به‌روزرسانی مشتریان را مدیریت کند. این کار از طریق ارائه یک فرم صورت می‌گیرد که کاربران می‌توانند از آن برای ورود داده‌ها در مورد یک مشتری جدید یا به‌روزرسانی مدخل موجود استفاده کنند.

در پوشه frontend/src یک فایل به نام CustomerCreateUpdate.js استفاده کنید:

nano ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

کد زیر را برای ایجاد یک کامپوننت ری‌اکت اضافه کرده و React و Component را ایمپورت کنید:

import React, { Component } from 'react';

همچنین می‌توانیم کلاس CustomersService را که در مرحله قبلی ساخته‌ایم، ایمپورت و وهله‌سازی کرده و متدهایی ارائه کنیم که رابطی برای بک‌اند REST API باشند:

...
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 برای دسترسی و تعیین مقدار عنصر فرم اضافه می‌کند.

سپس در بخش بالای متد ()render، یک متد (handleSubmit(event تعریف می‌کنیم به طوری که هنگام کلیک کاربر روی دکمه ارسال فرم، کارکرد صحیحی داشته باشیم:

...
handleSubmit(event) {
    const { match: { params } } =  this.props;
    if(params  &&  params.pk){
        this.handleUpdate(params.pk);
    }
    else
    {
        this.handleCreate();
    }
    event.preventDefault();
}

...

متد (handleSubmit(event فرایند ارسال فرم را مدیریت کرده و بسته به نوع مسیر، متد (handleUpdate(pk را برای به‌روزرسانی مشتری با 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 متناظر را فراخوانی می‌کند که API واقعی را از بک‌اند فراخوانی می‌کند.

سپس زیر متد ()handleCreate به تعریف متد (handleUpdate(pk برای پیاده‌سازی به‌روزرسانی‌ها می‌پردازیم.

...
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 بازدید کند باید فرم را با اطلاعات مرتبط با مشتری پر کنیم و این کار از طریق دریافت primary key از 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;
        })
    }
}

محتوای کامل فایل به صورت زیر است:

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 اصلی

در این بخش کامپوننت App را به‌روزرسانی می‌کنیم تا اپلیکیشنمان لینک‌هایی به کامپوننت‌هایی ایجاد کنند که در مراحل قبلی ایجاد کردیم.

در پوشه frontend دستور زیر را برای نصب React Router اجرا کنید که امکان افزودن مسیریابی و ناوبری بین کامپوننت‌های مختلف React را فراهم می‌سازد.

cd ~/djangoreactproject/frontend
npm install --save react-router-dom

سپس فایل djangoreactproject/frontend/src/App.js/~ را باز کنید:

nano ~/djangoreactproject/frontend/src/App.js

هر چیزی که درون آن است را حذف کنید و کد زیر را برای ایمپورت کردن کلاس‌های ضروری برای افزودن مسیریابی اضافه نمایید. این کدها شامل BrowserRouter هستند که یک کامپوننت Router ایجاد می‌کند و 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 باعث می‌شود که UI با URL-ی که از API تاریخچه HTML استفاده می‌کند همگام بماند. سپس یک لایه مبنا طراحی می‌کنیم که کامپوننت پایه‌ای ایجاد می‌کند و از سوی کامپوننت 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 را ایجاد می‌کنیم که کامپوننت ریشه یا سطح بالای اپلیکیشن React ما محسوب می‌شود:

...

class  App  extends  Component {

render() {
    return (
    <BrowserRouter>
        <BaseLayout/>
    </BrowserRouter>
    );
}
}
export  default  App;

ما کامپوننت BaseLayout را با کامپوننت BrowserRouter پوشش داده‌ایم، چون اپلیکیشن ما قرار است در مرورگر اجرا شود. فایل کامل به صورت زیر خواهد بود:

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 استفاده کرد.

سخن پایانی

در این راهنما یک اپلیکیشن دمو با استفاده از Django و React ساختیم. از فریمورک REST جنگو نیز برای ساخت REST API استفاده شده و همچنین Axios برای مصرف API و Bootstrap 4 برای سبک‌بندی CSS مورد استفاده قرار گرفته است. کد منبع این پروژه را می‌توانید در این ریپازیتوری گیت‌هاب (+) ملاحظه کنید.

ما در این راهنما اپلیکیشن‌های فرانت‌اند و بک‌اند را از هم جدا کرده‌ایم. البته رویکردهای دیگری نیز برای یکپارچه‌سازی React یا Django وجود دارند.

اگر این مطلب برایتان مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۳ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
digitalocean
نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *