Как создать современное веб-приложение для управления информацией о клиентах с помощью Django и React в Ubuntu 18.04

Автор выбралOpen Sourcing Mental Illness Ltd для получения пожертвования в рамках программыWrite for DOnations.

Вступление

Люди используют разные типы устройств для подключения к Интернету и просмотра веб-страниц. Из-за этого приложения должны быть доступны из разных мест. Для традиционных веб-сайтов обычно достаточно адаптивного пользовательского интерфейса, но более сложные приложения часто требуют использования других методов и архитектур. К ним относятся наличие отдельных внутренних и внешних приложений REST, которые могут быть реализованы в виде клиентских веб-приложений, Progressive Web Apps (PWA) или собственных мобильных приложений.

Некоторые инструменты, которые вы можете использовать при создании более сложных приложений:

  • React, фреймворк JavaScript, который позволяет разработчикам создавать веб-интерфейсы и собственные интерфейсы для своих серверных программ REST API.

  • Django, бесплатная веб-платформа Python с открытым исходным кодом, которая следует архитектурному шаблону программного обеспеченияmodel view controller (MVC).

  • Django REST framework, мощный и гибкий набор инструментов для создания REST API в Django.

В этом руководстве вы создадите современное веб-приложение с отдельным интерфейсом и интерфейсом REST API с использованием React, Django и Django REST Framework. Используя React с Django, вы сможете воспользоваться последними достижениями в области JavaScript и внешней разработки. Вместо создания приложения Django, использующего встроенный шаблонизатор, вы будете использовать React в качестве библиотеки пользовательского интерфейса, используя преимущества своей виртуальной объектной модели документов (DOM), декларативного подхода и компонентов, которые быстро отображают изменения в данных.

Веб-приложение, которое вы создадите, хранит записи о клиентах в базе данных, и вы можете использовать его в качестве отправной точки для приложения CRM. Когда вы закончите, вы сможете создавать, читать, обновлять и удалять записи, используя интерфейс React со стилемBootstrap 4.

Предпосылки

Для завершения этого урока вам понадобится:

[[step-1 -—- create-a-python-virtual-environment-and-install-dependencies]] == Шаг 1. Создание виртуальной среды Python и установка зависимостей

На этом этапе мы создадим виртуальную среду и установим необходимые зависимости для нашего приложения, включая Django, структуру Django REST иdjango-cors-headers.

Наше приложение будет использовать два разных сервера разработки для Django и React. Они будут работать на разных портах и ​​будут функционировать как два отдельных домена. Из-за этого нам нужно включитьcross-origin resource sharing (CORS) для отправки HTTP-запросов из React в Django без блокировки браузером.

Перейдите в свой домашний каталог и создайте виртуальную среду, используя модуль Python 3venv:

cd ~
python3 -m venv ./env

Активируйте созданную виртуальную среду с помощьюsource:

source env/bin/activate

Затем установите зависимости проекта с помощьюpip. Они будут включать в себя:

  • Django: Веб-фреймворк для проекта.

  • Django REST framework: стороннее приложение, которое создает REST API с помощью Django.

  • django-cors-headers: пакет, который включает CORS.

Установите фреймворк Django:

pip install django djangorestframework django-cors-headers

Установив зависимости проекта, вы можете создать проект Django и интерфейс React.

[[step-2 -—- created-the-django-project]] == Шаг 2 - Создание проекта Django

На этом этапе мы сгенерируем проект Django, используя следующие команды и утилиты:

  • django-admin startproject project-name:django-admin - это утилита командной строки, используемая для выполнения задач с Django. Командаstartproject создает новый проект Django.

  • python manage.py startapp myapp:manage.py - служебный сценарий, автоматически добавляемый в каждый проект Django, который выполняет ряд административных задач: создание новых приложений, перенос базы данных и локальное обслуживание проекта Django. Его командаstartapp создает приложение Django внутри проекта Django. В Django терминapplication описывает пакет Python, который предоставляет некоторый набор функций в проекте.

Для начала создайте проект Django сdjango-admin startproject. Назовем наш проектdjangoreactproject:

django-admin startproject djangoreactproject

Прежде чем двигаться дальше, давайте посмотрим на структуру каталогов нашего проекта Django с помощью командыtree.

[.Примечание]##

Tip:tree - полезная команда для просмотра структур файлов и каталогов из командной строки. Вы можете установить его с помощью следующей команды:

sudo apt-get install tree

Чтобы использовать его, введитеcd в нужный каталог и введитеtree или укажите путь к начальной точке с помощьюtree /home/sammy/sammys-project.

Перейдите в папкуdjangoreactproject в корне вашего проекта и выполните командуtree:

cd ~/djangoreactproject
tree

Вы увидите следующий вывод:

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

Папка~/djangoreactproject является корнем проекта. В этой папке есть несколько файлов, которые будут важны для вашей работы:

  • manage.py: служебный сценарий, выполняющий ряд административных задач.

  • settings.py: основной файл конфигурации для проекта Django, в котором вы можете изменять настройки проекта. Эти параметры включают такие переменные, какINSTALLED_APPS, alist строк, обозначающих включенные приложения для вашего проекта. В документации Django есть дополнительная информация оavailable settings.

  • urls.py: этот файл содержит список шаблонов URL и связанных представлений. Каждый шаблон отображает связь между URL-адресом и функцией, которая должна вызываться для этого URL-адреса. Дополнительные сведения об URL-адресах и представлениях см. В нашем руководстве поHow To Create Django Views.

Нашим первым шагом в работе с проектом будет настройка пакетов, которые мы установили на предыдущем шаге, включая структуру Django REST и пакет Django CORS, путем добавления их вsettings.py. Откройте файл с помощьюnano или вашего любимого редактора:

nano ~/djangoreactproject/djangoreactproject/settings.py

Перейдите к настройкеINSTALLED_APPS и добавьте приложенияrest_framework иcorsheaders в конец списка:

~/djangoreactproject/djangoreactproject/settings.py

...
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, класса Python, который содержит код, обрабатываемый каждый раз, когда ваше веб-приложение обрабатывает запрос или ответ:

~/djangoreactproject/djangoreactproject/settings.py

...

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

Далее вы можете включить CORS. ПараметрCORS_ORIGIN_ALLOW_ALL указывает, хотите ли вы разрешить CORS для всех доменов, аCORS_ORIGIN_WHITELIST - это кортеж Python, содержащий разрешенные URL-адреса. В нашем случае, поскольку сервер разработки React будет работать наhttp://localhost:3000, мы добавим новые настройкиCORS_ORIGIN_ALLOW_ALL = False иCORS_ORIGIN_WHITELIST('localhost:3000',) в наш файлsettings.py. Добавьте эти параметры в любое место файла:

~/djangoreactproject/djangoreactproject/settings.py

...
CORS_ORIGIN_ALLOW_ALL = False

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

Вы можете найти больше параметров конфигурации вdjango-cors-headers docs.

Сохраните файл и выйдите из редактора, когда закончите.

По-прежнему в каталоге~/djangoreactproject создайте новое приложение Django с именемcustomers:

python manage.py startapp customers

Он будет содержатьmodels иviews для управления клиентами. Модели определяют поля и поведение данных нашего приложения, а представления позволяют нашему приложению правильно обрабатывать веб-запросы и возвращать требуемые ответы.

Затем добавьте это приложение в список установленных приложений в файлеsettings.py вашего проекта, чтобы Django распознал его как часть проекта. Снова откройтеsettings.py:

nano ~/djangoreactproject/djangoreactproject/settings.py

Добавьте приложениеcustomers:

~/djangoreactproject/djangoreactproject/settings.py

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

Затемmigrate база данных и запуск локального сервера разработки. Migrations - это способ Django распространять изменения, которые вы вносите в свои модели, в схему базы данных. Эти изменения могут включать, например, добавление поля или удаление модели. Подробнее о моделях и миграции см.How To Create Django Models.

Перенос базы данных:

python manage.py migrate

Запустите локальный сервер разработки:

python manage.py runserver

Вы увидите вывод, похожий на следующий:

OutputPerforming 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. Если вы перейдете по этому адресу в вашем веб-браузере, вы должны увидеть следующую страницу:

Django demo page

На этом этапе оставьте приложение запущенным и откройте новый терминал, чтобы продолжить разработку проекта.

[[step-3 -—- Creating-the-react-frontend]] == Шаг 3 - Создание React Frontend

В этом разделе мы собираемся создать интерфейсное приложение нашего проекта с использованием React.

В React есть официальная утилита, которая позволяет быстро создавать проекты React без необходимости напрямую настраиватьWebpack. Webpack - это пакет модулей, используемый для объединения веб-ресурсов, таких как код JavaScript, CSS и изображения. Обычно, прежде чем вы сможете использовать Webpack, вам необходимо установить различные параметры конфигурации, но благодаря утилитеcreate-react-app вам не придется иметь дело с Webpack напрямую, пока вы не решите, что вам нужно больше контроля. Для запускаcreate-react-app вы можете использоватьnpx, инструмент, который выполняет двоичные файлы пакетаnpm.

Во втором терминале убедитесь, что вы находитесь в каталоге вашего проекта:

cd ~/djangoreactproject

Создайте проект React с именемfrontend, используяcreate-react-app иnpx:

npx create-react-app frontend

Затем перейдите в приложение React и запустите сервер разработки:

cd ~/djangoreactproject/frontend
npm start

Ваше приложение будет запущено сhttp://localhost:3000/:

React demo page

Оставьте сервер разработки React работающим и откройте другое окно терминала, чтобы продолжить.

Чтобы увидеть структуру каталогов всего проекта на этом этапе, перейдите в корневую папку и снова запуститеtree:

cd ~/djangoreactproject
tree

Вы увидите такую ​​структуру:

Output├── 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 в начало файла. Вы можете удалить существующее содержимое файла, хотя это не обязательно:

~/djangoreactproject/frontend/src/App.css

@import  'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css';

Здесь@import - это инструкция CSS, которая используется для импорта правил стилей из других таблиц стилей.

Теперь, когда мы создали как внутренние, так и внешние приложения, давайте создадим модель Customer и некоторые демонстрационные данные.

[[step-4 -—- Creating-the-customer-model-and-initial-data]] == Шаг 4 - Создание модели клиента и исходных данных

После создания приложения Django и внешнего интерфейса React наш следующий шаг - создание модели Customer, представляющей таблицу базы данных, в которой будет храниться информация о клиентах. Вам не нужен SQL, поскольку DjangoObject Relational Mapper (ORM) будет обрабатывать операции с базой данных, отображая классы и переменные Python в таблицы и столбцы SQL. Таким образом, Django ORM абстрагирует взаимодействия SQL с базой данных через интерфейс Python.

Активируйте вашу виртуальную среду снова:

cd ~
source env/bin/activate

Перейдите в каталогcustomers и откройтеmodels.py, файл Python, содержащий модели вашего приложения:

cd ~/djangoreactproject/customers/
nano models.py

Файл будет содержать следующее содержимое:

~/djangoreactproject/customers/models.py

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

API модели клиента уже импортирован в файл благодаря оператору импортаfrom django.db import models. Теперь вы добавите классCustomer, который расширяетmodels.Model. Каждая модель в Django - это класс Python, расширяющийdjango.db.models.Model.

МодельCustomer будет иметь следующие поля базы данных:

  • first_name - Имя покупателя.

  • last_name - Фамилия покупателя.

  • email - адрес электронной почты покупателя.

  • phone - Номер телефона покупателя.

  • address - Адрес покупателя.

  • description - Описание заказчика.

  • createdAt - Дата добавления покупателя.

Мы также добавим функцию__str__(), которая определяет, как будет отображаться модель. В нашем случае это будет имя клиента. Подробнее о создании классов и определении объектов см.How To Construct Classes and Define Objects in Python 3.

Добавьте следующий код в файл:

~/djangoreactproject/customers/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 применяет изменения в файлах миграции к базе данных.

Вернитесь в корневую папку проекта:

cd ~/djangoreactproject

Выполните следующее для создания файлов миграции:

python manage.py makemigrations

Вы получите вывод, который выглядит следующим образом:

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

Примените эти изменения к базе данных:

python manage.py migrate

Вы увидите вывод, указывающий на успешную миграцию:

OutputOperations 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

Вы увидите следующее подтверждение с именем вашего файла миграции:

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

Обратите внимание, что имя вашего файла миграции0002_customers.py.

Затем перейдите в папку миграции приложенияcustomers:

cd ~/djangoreactproject/customers/migrations

Откройте созданный файл миграции:

nano 0002_customers.py

Это начальное содержимое файла:

~/djangoreactproject/customers/migrations/0002_customers.py

from django.db import migrations

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

Оператор импорта импортирует APImigrations, API Django для создания миграций, изdjango.db, встроенного пакета, который содержит классы для работы с базами данных.

КлассMigration - это класс Python, описывающий операции, выполняемые при миграции баз данных. Этот класс расширяетmigrations.Migration и имеет два списка:

  • dependencies: содержит зависимые миграции.

  • operations: содержит операции, которые будут выполнены при применении миграции.

Затем добавьтеmethod, чтобы создать демонстрационные данные о клиентах. Добавьте следующий метод перед определением классаMigration:

~/djangoreactproject/customers/migrations/0002_customers.py

...
def create_data(apps, schema_editor):
    Customer = apps.get_model('customers', 'Customer')
    Customer(first_name="Customer 001", last_name="Customer 001", email="[email protected]", phone="00000000", address="Customer 000 Address", description= "Customer 001 description").save()

...

В этом методе мы берем классCustomer нашего приложенияcustomers и создаем демонстрационного клиента для вставки в базу данных.

Чтобы получить классCustomer, который позволит создавать новых клиентов, мы используем методget_model() объектаapps. Объектapps представляетregistry установленных приложений и их моделей баз данных.

Объектapps будет передан из методаRunPython(), когда мы будем использовать его для запускаcreate_data(). Добавьте методmigrations.RunPython() в пустой списокoperations:

~/djangoreactproject/customers/migrations/0002_customers.py

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

RunPython() - это часть Migrations API, позволяющая запускать собственный код Python при миграции. В нашем спискеoperations указано, что этот метод будет выполнен, когда мы применим миграцию.

Это полный файл:

~/djangoreactproject/customers/migrations/0002_customers.py

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="[email protected]", phone="00000000", address="Customer 000 Address", description= "Customer 001 description").save()

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

Для получения дополнительной информации о миграции данных см. Документацию поdata migrations in Django

Чтобы перенести базу данных, сначала вернитесь в корневую папку вашего проекта:

cd ~/djangoreactproject

Перенесите базу данных, чтобы создать демонстрационные данные:

python manage.py migrate

Вы увидите вывод, который подтверждает миграцию:

OutputOperations to perform:
  Apply all migrations: admin, auth, contenttypes, customers, sessions
Running migrations:
  Applying customers.0002_customers... OK

Для получения дополнительных сведений об этом процессе обратитесь кHow To Create Django Models.

Создав модель Customer и демонстрационные данные, мы можем перейти к созданию REST API.

[[step-5 -—- Creating-the-rest-api]] == Шаг 5. Создание REST API

На этом шаге мы создадим REST API с использованием Django REST Framework. Мы создадим несколько разныхAPI views. Представление API - это функция, которая обрабатывает запрос или вызов API, аAPI endpoint - это уникальный URL-адрес, который представляет точку взаимодействия с системой REST. Например, когда пользователь отправляет запрос GET на конечную точку API, Django вызывает соответствующую функцию или представление API для обработки запроса и возврата любых возможных результатов.

Мы также будем использоватьserializers. serializer в Django REST Framework позволяет преобразовывать сложные экземпляры модели и QuerySets в формат JSON для использования API. Класс сериализатора также может работать в другом направлении, предоставляя механизмы для синтаксического анализа и десериализации данных в модели Django и QuerySets.

Наши конечные точки API будут включать:

  • api/customers: эта конечная точка используется для создания клиентов и возвращает разбитые на страницы наборы клиентов.

  • api/customers/<pk>: эта конечная точка используется для получения, обновления и удаления отдельных клиентов по первичному ключу или идентификатору.

Мы также создадим URL-адреса в файле проектаurls.py для соответствующих конечных точек (например,api/customers иapi/customers/<pk>).

Начнем с созданияserializer class для нашей моделиCustomer.

Добавление класса сериализатора

Создание класса сериализатора для нашей моделиCustomer необходимо для преобразования экземпляров клиентов и QuerySet в JSON и обратно. Чтобы создать класс сериализатора, сначала создайте файлserializers.py внутри приложенияcustomers:

cd ~/djangoreactproject/customers/
nano serializers.py

Добавьте следующий код для импорта API сериализаторов и моделиCustomer:

~/djangoreactproject/customers/serializers.py

from rest_framework import serializers
from .models import Customer

Затем создайте класс сериализатора, который расширяетserializers.ModelSerializer и определяет поля, которые будут сериализованы:

~/djangoreactproject/customers/serializers.py

...
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.

Это полное содержимое файла:

~/djangoreactproject/customers/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.

Добавление представлений API

В этом разделе мы создадим представления API для нашего приложения, которые будут вызываться Django, когда пользователь посещает конечную точку, соответствующую функции представления.

Открыть~/djangoreactproject/customers/views.py:

nano ~/djangoreactproject/customers/views.py

Удалите то, что там, и добавьте следующий импорт:

~/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-интерфейсами Django и Django REST Framework.

Затем добавьте представление для обработки запросов POST и GET HTTP:

~/djangoreactproject/customers/views.py

...

@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. decorator - это функция, которая берет другую функцию и динамически ее расширяет.

В теле метода мы используем переменнуюrequest.method для проверки текущего HTTP-метода и выполнения соответствующей логики в зависимости от типа запроса:

  • Если это запрос GET, метод разбивает данные на страницы с помощью DjangoPaginator и возвращает первую страницу данных после сериализации, количество доступных клиентов, количество доступных страниц и ссылки на предыдущую и следующую страницы. . Paginator - это встроенный класс Django, который разбивает список данных на страницы и предоставляет методы для доступа к элементам для каждой страницы.

  • Если это запрос POST, метод сериализует полученные данные клиента, а затем вызывает методsave() объекта сериализатора. Затем он возвращает объект Response, экземплярHttpResponse, с кодом состояния 201. Каждое созданное вами представление отвечает за сохранение объектаHttpResponse. Методsave() сохраняет сериализованные данные в базе данных.

Дополнительные сведения оHttpResponse и представлениях см. В этом обсужденииcreating view functions.

Теперь добавьте представление API, которое будет отвечать за обработку запросов GET, PUT и DELETE для получения, обновления и удаления клиентов поpk (первичный ключ):

~/djangoreactproject/customers/views.py

...
@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 без данных.

Готовый файл выглядит так:

~/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 *


@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.

Открыть~/djangoreactproject/djangoreactproject/urls.py:

nano ~/djangoreactproject/djangoreactproject/urls.py

Оставьте то, что есть, но добавьте импорт в представленияcustomers вверху файла:

~/djangoreactproject/djangoreactproject/urls.py

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 list, содержащему URL-адреса приложения:

~/djangoreactproject/djangoreactproject/urls.py

...

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

Создав наши конечные точки REST, давайте посмотрим, как мы можем их использовать.

[[step-6 -—- consuming-the-rest-api-with-axios]] == Шаг 6 - Использование REST API с Axios

На этом этапе мы установимAxios, HTTP-клиент, который мы будем использовать для вызовов API. Мы также создадим класс для использования конечных точек API, которые мы создали.

Во-первых, деактивируйте вашу виртуальную среду:

deactivate

Затем перейдите в папкуfrontend:

cd ~/djangoreactproject/frontend

Установитеaxios изnpm, используя:

npm install axios --save

Параметр--save добавляет зависимостьaxios к файлуpackage.json вашего приложения.

Затем создайте файл JavaScript с именемCustomersService.js, который будет содержать код для вызова REST API. Мы сделаем это внутри папкиsrc, где будет жить код приложения для нашего проекта:

cd src
nano CustomersService.js

Добавьте следующий код, который содержит методы для подключения к API REST Django:

~/djangoreactproject/frontend/src/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(): удаляет клиента.

Теперь мы можем отображать данные из нашего API в нашем интерфейсе React UI, создав компонентCustomersList.

[[step-7 -—- displaying-data-from-the-api-in-the-react-application]] == Шаг 7. Отображение данных из API в приложении React

На этом этапе мы создадимCustomersList Reactcomponent. Компонент React представляет собой часть пользовательского интерфейса; это также позволяет вам разделить пользовательский интерфейс на независимые, многократно используемые части.

Начнем с созданияCustomersList.js вfrontend/src:

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

Начните с импортаReact иComponent, чтобы создать компонент React:

~/djangoreactproject/frontend/src/CustomersList.js

import  React, { Component } from  'react';

Затем импортируйте и создайте экземпляр модуляCustomersService, который вы создали на предыдущем шаге, который предоставляет методы, которые взаимодействуют с серверной частью REST API:

~/djangoreactproject/frontend/src/CustomersList.js

...
import  CustomersService  from  './CustomersService';

const  customersService  =  new  CustomersService();

Затем создайте компонентCustomersList, который расширяетComponent для вызова REST API. Компонент React долженextend or subclass the Component class. Дополнительные сведения о классах E6 и наследовании см. В нашем руководстве поUnderstanding Classes in JavaScript.

Добавьте следующий код, чтобы создать компонент React, расширяющийreact.Component:

~/djangoreactproject/frontend/src/CustomersList.js

...
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. Он содержит переменные состояния нашего компонента, используя пустойcustomersarray. Этот массив будет содержать клиентов иnextPageURL, которые будут содержать URL-адрес следующей страницы, которую нужно получить из внутреннего API. Мы такжеbindingnextPage() иhandleDelete()methods доthis, поэтому они будут доступны из HTML-кода.

Затем добавьте методcomponentDidMount() и вызовgetCustomers() в классеCustomersList перед закрывающей фигурной скобкой.

МетодcomponentDidMount() - это метод жизненного цикла компонента, который вызывается, когда компонент создается и вставляется в DOM. getCustomers() вызывает объект обслуживания клиентов, чтобы получить первую страницу данных и ссылку на следующую страницу из бэкэнда Django:

~/djangoreactproject/frontend/src/CustomersList.js

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

Теперь добавьте методhandleDelete(), который обрабатывает удаление клиента, нижеcomponentDidMount():

~/djangoreactproject/frontend/src/CustomersList.js

...
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 (первичный ключ). Если операция прошла успешно, массивcustomers отфильтровывается для удаленного клиента.

Затем добавьте методnextPage(), чтобы получить данные для следующей страницы и обновить ссылку на следующую страницу:

~/djangoreactproject/frontend/src/CustomersList.js

...
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() method, который отображает таблицу клиентов из состояния компонента:

~/djangoreactproject/frontend/src/CustomersList.js

...
render() {

    return (
    
{this.state.customers.map( c => )}
# First Name Last Name Phone Email Address Description Actions
{c.pk} {c.first_name} {c.last_name} {c.phone} {c.email} {c.address} {c.description} Update
); }

Это полное содержимое файла:

~/djangoreactproject/frontend/src/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 (
        
{this.state.customers.map( c => )}
# First Name Last Name Phone Email Address Description Actions
{c.pk} {c.first_name} {c.last_name} {c.phone} {c.email} {c.address} {c.description} Update
); } } export default CustomersList;

Теперь, когда мы создали компонентCustomersList для отображения списка клиентов, мы можем добавить компонент, который обрабатывает создание и обновление клиентов.

[[step-8 -—- add-the-customer-create-and-update-react-component]] == Шаг 8 - Добавление компонента Customer Create и Update React

На этом этапе мы создадим компонентCustomerCreateUpdate, который будет обрабатывать создание и обновление клиентов. Это будет сделано путем предоставления формы, которую пользователи могут использовать для ввода данных о новом клиенте или обновления существующей записи.

Вfrontend/src создайте файлCustomerCreateUpdate.js:

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

Добавьте следующий код для создания компонента React, импортирующегоReact иComponent:

~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

import  React, { Component } from  'react';

Мы также можем импортировать и создать экземпляр классаCustomersService, который мы создали на предыдущем шаге, который предоставляет методы, взаимодействующие с серверной частью REST API:

~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

...
import  CustomersService  from  './CustomersService';

const  customersService  =  new  CustomersService();

Затем создайте компонентCustomerCreateUpdate, который расширяетComponent для создания и обновления клиентов:

~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

...
class  CustomerCreateUpdate  extends  Component {

    constructor(props) {
        super(props);
    }

}
export default CustomerCreateUpdate;

В определение класса добавьте методrender() компонента, который отображает HTML-форму, которая принимает информацию о клиенте:

~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

...
render() {
        return (
          
); }

Для каждого элемента ввода формы метод добавляет свойствоref для доступа и установки значения элемента формы.

Затем, над методомrender(), определите методhandleSubmit(event), чтобы у вас была правильная функциональность, когда пользователь нажимает кнопку отправки:

~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

...
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, чтобы вы могли получить к нему доступ в своей форме:

~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

...
class CustomerCreateUpdate extends Component {

constructor(props) {
    super(props);
    this.handleSubmit = this.handleSubmit.bind(this);
}
...

Затем определите методhandleCreate() для создания клиента из данных формы. Над методомhandleSubmit(event) добавьте следующий код:

~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

...
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) для реализации обновлений:

~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

...
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(). Добавьте следующий код ниже конструктора компонента, чтобы добавить этот метод:

~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

...
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;
        })
    }
}

Это полное содержимое файла:

~/djangoreactproject/frontend/src/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 (
          
); } } export default CustomerCreateUpdate;

Создав компонентCustomerCreateUpdate, мы можем обновить основной компонентApp, чтобы добавить ссылки на различные компоненты, которые мы создали.

[[step-9 -—- update-the-main-app-component]] == Шаг 9 - Обновление основного компонента приложения

В этом разделе мы обновим компонент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, который создает компонент маршрута:

~/djangoreactproject/frontend/src/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';

BrowserRouter поддерживает синхронизацию пользовательского интерфейса с URL-адресом с помощьюHTML5 history API.

Затем создайте базовый макет, который предоставляет базовый компонент для обертывания компонентомBrowserRouter:

~/djangoreactproject/frontend/src/App.js

...

const  BaseLayout  = () => (

)

Мы используем компонентRoute для определения маршрутов нашего приложения; компонент, который маршрутизатор должен загрузить после обнаружения совпадения. Для каждого маршрута требуетсяpath, чтобы указать путь для сопоставления, иcomponent, чтобы указать компонент для загрузки. Свойствоexact указывает маршрутизатору соответствовать точному пути.

Наконец, создайте компонентApp, корневой или компонент верхнего уровня нашего приложения React:

~/djangoreactproject/frontend/src/App.js

...

class  App  extends  Component {

render() {
    return (
    
        
    
    );
}
}
export  default  App;

Мы обернули компонентBaseLayout компонентомBrowserRouter, поскольку наше приложение предназначено для работы в браузере.

Готовый файл выглядит так:

~/djangoreactproject/frontend/src/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 = () => (
  
)

class App extends Component {
  render() {
    return (
      
        
      
    );
  }
}

export default App;

После добавления маршрутизации в наше приложение мы готовы протестировать приложение. Перейдите кhttp://localhost:3000. Вы должны увидеть первую страницу приложения:

Application Home Page

С этим приложением у вас теперь есть база для приложения CRM.

Заключение

В этом руководстве вы создали демонстрационное приложение с использованием Django и React. Вы использовали среду REST Django для создания API REST, Axios для использования API и Bootstrap 4 для стилизации вашего CSS. Вы можете найти исходный код этого проекта в этомGitHub repository.

В этой настройке учебника использовались отдельные интерфейсные и фоновые приложения. Чтобы узнать другой подход к интеграции React с Django, проверьте этоtutorial и этоtutorial.

Для получения дополнительной информации о создании приложения с помощью Django вы можете следить заDjango development series. Вы также можете посмотретьofficial Django docs.

Related