Автор выбрал организацию Open Sourcing Mental Illness Ltd для получения пожертвований в рамках программы Write for DOnations.
Для подключения к Интернету и просмотра сайтов люди используют разные устройства. В связи с этим, приложения должны быть доступными из разных мест. Для традиционных сайтов часто достаточно иметь быстрый пользовательский интерфейс, однако для более сложных приложений обычно требуются другие методики и архитектуры. В ним относятся отдельные серверные и клиентские приложения REST, которые можно реализовать как клиентские веб-приложения, прогрессивные веб-приложения (PWA) или мобильные приложения.
Помимо прочего, для создания более сложных веб-приложений можно использовать следующие инструменты:
В этом обучающем руководстве вы создадите современное веб-приложение с отдельными серверным и клиентским REST API, используя React, Django и систему Django REST. Используя React вместе с Django, вы сможете воспользоваться последними достижениями JavaScript и разработки клиентской части. Вместо создания приложения Django, использующего встроенный механизм шаблонов, вы используете React как библиотеку пользовательского интерфейса. Это позволяет воспользоваться преимуществами виртуальной объектной модели документов (DOM), декларативного подхода и компонентов, быстро обрабатывающих изменения данных.
Создаваемое веб-приложение будет хранить записи о клиентах в базе данных, и вы сможете использовать его как основу для приложения CRM. После завершения обучения вы научитесь создавать, читать, обновлять и удалять записи, используя интерфейс в стиле React с Bootstrap 4.
Для данного курса обучения вам потребуется следующее:
pip
и venv
в соответствии с шагами 1 и 2 руководства «Установка Python 3 и настройка локальной среды программирования в Ubuntu 18.04».npm
версии 5.2 или выше. Для их установки необходимо следовать указаниям документа «Установка Node.js в Ubuntu 18.04» по установке с PPA.На этом шаге мы создадим виртуальную среду и установим требуемые зависимости для нашего приложения, в том числе Django, систему Django REST и заголовки django-cors-headers
.
Наше приложение будет использовать два разных сервера разработки для Django и React. Они будут работать на разных портах и функционировать как два отдельных домена. В связи с этим нам потребуется совместное использование ресурсов между разными источниками (CORS) для отправки запросов HTTP из React в Django без блокировки браузером.
Перейдите в каталог home и создайте виртуальную среду, используя модуль venv
Python 3:
- cd ~
- python3 -m venv ./env
Активируйте созданную виртуальную среду с помощью source
:
- source env/bin/activate
Установите зависимости проекта с помощью pip
. К ним будут относиться:
django-cors-headers
: пакет для активации CORS.Установите систему Django:
- pip install django djangorestframework django-cors-headers
После установки зависимостей проекта вы сможете создать проект Django и клиентскую часть React.
На этом шаге мы создадим проект Django, используя следующие команды и утилиты:
django-admin startproject project-name
: django-admin
— утилита командной строки, используемая для выполнения задач с помощью Django. Команда startproject
создает новый проект Django.
python manage.py startapp myapp
: manage.py
— утилитарный сценарий, автоматически добавляемый в каждый проект Django. Он выполняет ряд административных задач, включая создание новых приложений, миграцию базы данных и локальное обслуживание проекта Django. Его команда startapp
создает приложение Django внутри проекта Django. В Django термин приложение **описывает пакет Python, предоставляющий определенный набор функций проекта.
Для начала создайте проект Django с помощью команды django-admin startproject
. Назовем наш проект djangoreactproject
:
- django-admin startproject djangoreactproject
Прежде чем продолжить, посмотрим структуру каталогов нашего проекта Django с помощью команды tree
.
Подсказка: 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
, список строк с обозначением приложений, активированных для вашего проекта. Более подробную информацию по доступным настройкам можно найти в документации по Django.urls.py
: этот файл содержит список шаблонов URL и связанных просмотров. В каждом шаблоне сопоставляются URL и функция, вызываемая для этого URL. Более подробную информацию об URL и представлениях можно найти в обучающем руководстве «Создание представления Django».Первым шагом в работе с проектом будет настройка пакетов, установленных на предыдущем шаге, включая систему Django REST и пакет 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',
'rest_framework',
'corsheaders'
]
Затем добавьте промежуточный уровень corsheaders.middleware.CorsMiddleware
из ранее установленного пакета CORS в параметр MIDDLEWARE
. Этот параметр представляет собой перечень промежуточных уровней. Промежуточный уровень — это класс Python, который содержит код, выполняемый каждый раз, когда ваше веб-приложение обрабатывает запрос или отклик:
...
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 будет работать по 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
, создайте новое приложение Django под названием customers
:
- python manage.py startapp customers
В нем будут содержаться модели и представления для управления клиентами. Модели определяют поля и схемы поведения данных нашего приложения, а представления позволяют приложению правильно обрабатывать веб-запросы и возвращать требуемые ответы.
Затем добавьте это приложение в список установленных приложений в файле проекта settings.py
, чтобы Django распознавал его как часть проекта. Откройте файл settings.py
еще раз:
- nano ~/djangoreactproject/djangoreactproject/settings.py
Добавьте приложение customers
:
...
INSTALLED_APPS = [
...
'rest_framework',
'corsheaders',
'customers'
]
...
Затем выполните миграцию базы данных и запустите локальный сервер разработки. Миграция используется Django для записи изменений модели в схему базы данных. Изменения могут включать добавление поля, удаление модели и т. д. Более подробную информацию о моделях и миграции можно найти в документе «Создание моделей Django».
Выполните миграцию базы данных:
- 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
. Если вы откроете этот адрес в браузере, вы должны увидеть следующую страницу:
Теперь оставьте приложение работать и откройте новый терминал, чтобы продолжить разработку проекта.
В этом разделе мы создадим клиентское приложение нашего проекта с помощью 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 работать и откройте другое окно терминала, чтобы продолжить.
Чтобы увидеть полную структуру каталогов проекта на этом этапе, перейдите в корневую папку root и снова запустите команду 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 в начало файла. Вы можете удалить содержание файла, хотя это не обязательно:
@import 'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css';
Здесь @import
— это инструкция CSS, используемая для импорта правил стиля из других таблиц стилей.
Мы создали серверную и клиентскую часть приложения. Теперь мы создадим модель клиентов и добавим демонстрационные данные.
После создания приложения Django и клиентской части React мы создадим модель клиентов, представляющую таблицу базы данных, где будет храниться информация о клиентах. Вам не потребуется SQL, поскольку Django Object Relational Mapper (ORM) будет обрабатывать операции базы данных посредством сопоставления классов и переменных Python с таблицами и столбцами SQL. Так Django ORM абстрагирует взаимодействия SQL с базой данных через интерфейс Python.
Запустите свою виртуальную среду еще раз:
- cd ~
- source env/bin/activate
Перейдите в каталог customers
и откройте models.py
, файл Python, где хранятся модели вашего приложения:
- cd ~/djangoreactproject/customers/
- nano models.py
Файл будет иметь следующее содержание:
from django.db import models
# Create your models here.
API клиентской модели уже импортирован в файл из django.db с помощью выражения import models
. Теперь вы добавите класс Customer
как расширение models.Model
. Каждая модель в Django представляет собой класс Python, являющийся расширением django.db.models.Model
.
Customer
модель будет иметь следующие поля базы данных:
first_name
— имя клиента.last_name
— фамилия клиента.email
— адрес электронной почты клиента.phone
— номер телефона клиента.address
— адрес клиента.description
— описание клиента.createdAt
— дата добавления клиента.Также мы добавим функцию __str__()
, определяющую способ отображения модели. В нашем случае это будет имя клиента. Более подробную информацию по построению классов и определению объектов можно найти в документе «Построение классов и определение объектов в Python 3».
Добавьте в файл следующий код:
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
применяет все изменения в файлах миграции к базе данных.
Перейдите обратно в корневую папку проекта root:
- 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
Затем вы используете файл миграции данных для создания начального набора данных клиента. Файл миграции данных используется для добавления или изменения данных в базе данных при миграции. Создайте пустой файл миграции данных дл приложения customers
:
- python manage.py makemigrations --empty --name customers customers
Вы увидите следующее подтверждение с именем файла миграции:
OutputMigrations for 'customers':
customers/migrations/0002_customers.py
Обратите внимание, что имя файла миграции 0002_customers.py
.
Затем перейдите в папку migrations приложения 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 migrations
. Это API Django для создания миграции из django.db
, встроенного пакета, содержащего классы для работы с базами данных.
Класс Migration
— это класс Python, описывающий операции, выполняемые во время миграции баз данных. Этот класс является расширением 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()
...
В этом методе мы берем класс Customer
нашего приложения customers
и создаем демо-клиента для добавления в базу данных.
Чтобы получить класс Customer
, который позволит нам создавать новых клиентов, мы используем метод get_model()
объекта apps
. Объект apps
представляет реестр установленных приложений и их моделей баз данных.
Объект apps
передается из метода RunPython()
, когда мы используем его для запуска create_data()
. Добавьте метод migrations.RunPython()
в пустой список operations
:
...
operations = [
migrations.RunPython(create_data),
]
RunPython()
— часть API Migrations, позволяющая запускать заданный код Python при миграции. Список 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),
]
Дополнительную информацию по миграции данных можно найти в документации по миграции данных в Django
Чтобы выполнить миграцию базы данных, вернитесь в папку root вашего проекта:
- cd ~/djangoreactproject
Выполните миграцию базы данных для создания демонстрационных данных:
- python manage.py migrate
Вы увидите результат, подтверждающий миграцию:
OutputOperations to perform:
Apply all migrations: admin, auth, contenttypes, customers, sessions
Running migrations:
Applying customers.0002_customers... OK
Более подробную информацию об этом процессе можно найти в документе «Создание моделей Django».
После создания модели Customer и демо-данных, мы можем продолжить работу по созданию API REST.
На этом шаге мы создадим API REST с помощью системы Django REST. Мы создадим несколько разных представлений API. Представление API представляет собой функцию, выполняющую запрос или вызов API, а конечная точка API — уникальный URL, представляющий собой элемент связи с системой REST. Например, когда пользователь направляет запрос GET в конечную точку API, Django вызывает соответствующую функцию или представление API для обработки запроса и возвращения любых возможных результатов.
Также мы используем сериализаторы. Сериализатор в системе Django REST позволяет преобразовывать экземпляры сложных моделей и наборы QuerySets в формат JSON для использования API. Класс serializer может работать и в другом направлении, предоставляя механизмы для синтаксического анализа и десериализации данных в моделях Django и QuerySets.
Мы получим следующие конечные точки API:
api/customers
: эта конечная точка используется для создания клиентов и возврата разбитых на страницы наборов клиентов.api/customers/
: эта конечная точка испольузется для получения, обновления и удаления отдельных клиентов по первичным ключам или идентификаторам.Мы также создадим URL в файле проекта urls.py
для соответствующих конечных точек (т.е. api/customers
и api/customers/
).
Начнем с создания класса serializer для нашей модели Customer
.
Создание класса serializer для нашей модели Customer
необходимо для преобразования экземпляров клиентов и наборов QuerySets в JSON и обратно. Чтобы создать класс serializer, сначала создайте файл serializers.py
в приложении customers
:
- cd ~/djangoreactproject/customers/
- nano serializers.py
Добавьте следующий код для импорта API serializers и модели Customer
:
from rest_framework import serializers
from .models import Customer
Затем создайте класс serializer, являющийся расширением 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')
Создав класс serializer, мы можем добавить представления API.
В этом разделе мы создадим представления API для нашего приложения, которое будет вызываться Django, когда пользователь будет посещать конечную точку, соответствующую функции представления.
Откройте ~/djangoreactproject/customers/views.py
:
- 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 Django и системы Django REST.
Затем добавьте представление для обработки запросов POST и GET HTTP:
...
@api_view(['GET', 'POST'])
def customers_list(request):
"""
List customers, or create a new customer.
"""
if request.method == 'GET':
data = []
nextPage = 1
previousPage = 1
customers = Customer.objects.all()
page = request.GET.get('page', 1)
paginator = Paginator(customers, 10)
try:
data = paginator.page(page)
except PageNotAnInteger:
data = paginator.page(1)
except EmptyPage:
data = paginator.page(paginator.num_pages)
serializer = CustomerSerializer(data,context={'request': request} ,many=True)
if data.has_next():
nextPage = data.next_page_number()
if data.has_previous():
previousPage = data.previous_page_number()
return Response({'data': serializer.data , 'count': paginator.count, 'numpages' : paginator.num_pages, 'nextlink': '/api/customers/?page=' + str(nextPage), 'prevlink': '/api/customers/?page=' + str(previousPage)})
elif request.method == 'POST':
serializer = CustomerSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Вначале мы используем декоратор @api_view(['GET', 'POST'])
для создания представления API, способного принимать запросы GET и POST. Декоратор — это функция, которая берет другую функцию и динамически расширяет ее.
В теле метода мы используем переменную request.method
для проверки текущего метода HTTP и выполнения соответствующей логики в зависимости от типа запроса:
save()
с помощью объекта serializer. Затем он возвращает объект Response, являющийся экземпляром HttpResponse, с кодом состояния 201. Каждое создаваемое представление отвечает за возврат объекта HttpResponse
. Метод save()
сохраняет сериализированные данные в базе данных.Дополнительную информацию об HttpResponse
и представлениях можно найти в этой дискуссии о создании функций представления.
Теперь добавьте представление API, которое будет отвечать за обработку запросов GET, PUT и DELETE для получения, обновления и удаления клиентов по pk
(первичный ключ):
...
@api_view(['GET', 'PUT', 'DELETE'])
def customers_detail(request, pk):
"""
Retrieve, update or delete a customer by id/pk.
"""
try:
customer = Customer.objects.get(pk=pk)
except Customer.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
if request.method == 'GET':
serializer = CustomerSerializer(customer,context={'request': request})
return Response(serializer.data)
elif request.method == 'PUT':
serializer = CustomerSerializer(customer, data=request.data,context={'request': request})
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == 'DELETE':
customer.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
Метод оформлен с помощью декораторов @api_view(['GET', 'PUT', 'DELETE'])
. Это означает, что он является представлением API, который может принимать запросы GET, PUT и DELETE.
Проверка поля request.method
проверяет метод запроса и вызывает нужную логику в зависимости от значения:
save()
созданного объекта serializer. Наконец, он отправляет объект Response с обновленными данными клиента.delete()
объекта customer для его удаления, после чего возвращает объект 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/customers/
для запроса и создания клиентов и api/customers/<pk>
для получения, обновления и удаления отдельных клиентов по pk
.
Откройте ~/djangoreactproject/djangoreactproject/urls.py
:
- 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/
в список 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, теперь рассмотрим их употребление.
На этом шаге мы установим Axios, клиент HTTP, служащий для вызова API. Также мы создадим класс для потребления созданных нами конечных точек API.
Вначале отключите свою виртуальную среду:
- deactivate
Затем перейдите в папку frontend
:
- cd ~/djangoreactproject/frontend
Установите axios
из npm
с помощью следующей команды:
- npm install axios --save
Опция --save
добавляет зависимость axios
в файл package.json
вашего приложения.
Затем создайте файл JavaScript под названием CustomersService.js
, который будет содержать код для вызова API REST. Это будет реализовано в папке src
, где будет располагаться код приложения для нашего проекта:
- cd src
- nano CustomersService.js
Добавьте следующий код, содержащий методы подключения к Django 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()
: получает клиента по первичному ключу.createCustomer()
: создает клиента.updateCustomer()
: обновляет клиента.deleteCustomer()
: удаляет клиента.Теперь мы можем выводить данные нашего API в пользовательском интерфейсе React, создавая компонент CustomersList
.
На этом шаге мы создадим компонент CustomersList
React. Компонент React представляет часть пользовательского интерфейса; также он позволяет разделять пользовательский интерфейс на независимые элементы многоразового использования.
Для начала создайте CustomersList.js
в папке frontend/src
:
- nano ~/djangoreactproject/frontend/src/CustomersList.js
Вначале импортируйте React
и Component
для создания компонента React:
import React, { Component } from 'react';
Затем импортируйте и создайте экземпляр модуля CustomersService
, который вы создали на предыдущем шаге и который предоставляет методы взаимодействия с серверной частью REST API:
...
import CustomersService from './CustomersService';
const customersService = new CustomersService();
Затем создайте компонент CustomersList
, расширяющий Component
для вызова REST API. Компонент React должен расширять класс Component
или создавать его подкласс. Дополнительную информацию о классах E6 и наследовании можно найти в обучающем руководстве «Понимание классов в JavaScript».
Добавьте следующий код для создания компонента React, являющегося расширением react.Component
:
...
class CustomersList extends Component {
constructor(props) {
super(props);
this.state = {
customers: [],
nextPageURL: ''
};
this.nextPage = this.nextPage.bind(this);
this.handleDelete = this.handleDelete.bind(this);
}
}
export default CustomersList;
Внутри конструктора мы выполняем инициализацию объекта state
. В нем хранятся переменные state нашего компонента, для чего используется пустой массив customers
. В этом массиве будут храниться клиенты и nextPageURL
, где будет храниться URL следующей страницы для получения от серверного API. Также мы выполняем привязку методов nextPage()
и handleDelete()
, this
чтобы они были доступны из кода HTML.
Затем добавьте метод componentDidMount()
и вызов getCustomers()
из класса CustomersList
, после чего закройте фигурную скобку.
Метод componentDidMount()
— это метод жизненного цикла компонента, вызываемый, когда компонент создается и вставляется в DOM. getCustomers()
вызывает объект Customers Service для получения первой страницы данных и ссылки на следующую страницу из серверной части Django:
...
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
(первичный ключ). Если операция выполняется успешно, удаленный customer
отфильтровывается из массива
Затем добавьте метод 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
, импортировав классы React и Component
:
import React, { Component } from 'react';
Также мы можем импортировать класс CustomersService
, созданный на предыдущем шаге и создать экземпляр этого класса, который предоставляет методы взаимодействия с серверной частью API REST:
...
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)
, чтобы при нажатии на кнопку Submit (Отправить) функция работала надлежащим образом:
...
handleSubmit(event) {
const { match: { params } } = this.props;
if(params && params.pk){
this.handleUpdate(params.pk);
}
else
{
this.handleCreate();
}
event.preventDefault();
}
...
Метод handleSubmit(event)
обрабатывает отправку форм и (в зависимости от маршрута) вызывет метод handleUpdate(pk)
для обновления клиента с переданным pk
или метод handleCreate()
для создания нового клиента. Мы дадим краткое определение этих методов.
Выполните в конструкторе компонентов привязку добавленного метода handleSubmit()
к этому
методу, чтобы вы могли использовать его в своей форме:
...
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
, нужно заполнить форму информацией о клиенте, используя первичный ключ из 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
мы можем обновить компонент main 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
обеспечивает синхронизацию пользовательского интерфейса с URL, используя API истории HTML5.
Далее создайте базовую схему, обеспечивающую базовый компонент для включения в компонент BrowserRouter
:
...
const BaseLayout = () => (
<div className="container-fluid">
<nav className="navbar navbar-expand-lg navbar-light bg-light">
<a className="navbar-brand" href="#">Django React Demo</a>
<button className="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
<span className="navbar-toggler-icon"></span>
</button>
<div className="collapse navbar-collapse" id="navbarNavAltMarkup">
<div className="navbar-nav">
<a className="nav-item nav-link" href="/">CUSTOMERS</a>
<a className="nav-item nav-link" href="/customer">CREATE CUSTOMER</a>
</div>
</div>
</nav>
<div className="content">
<Route path="/" exact component={CustomersList} />
<Route path="/customer/:pk" component={CustomerCreateUpdate} />
<Route path="/customer/" exact component={CustomerCreateUpdate} />
</div>
</div>
)
Мы используем компонент Route
для определения маршрутов нашего приложения; компонент, который должен загружать маршрутизатор после обнаружения совпадения. Для каждого маршрута требуются параметры path
для указания пути сопоставления и component
для указания загружаемого компонента. Свойство exact
указывает маршрутизатору на необходимость точного соответствия пути.
Наконец, создайте компонент App
. Это корневой компонент или компонент самого верхнего уровня нашего приложения 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. Вы использовали систему Django REST для создания REST API, Axios для потребления API, и Bootstrap 4 для стилизации CSS. Исходный код этого проекта можно найти в хранилище GitHub.
В этом обучающем руководстве были использованы отдельные приложения для клиентской части и серверной части. Другой подход к интеграции React с Django можно найти в этом руководстве и этом руководстве.
Дополнительную информацию о создании приложений с помощью Django можно найти в серии публикаций «Разработка в Django». Также вы можете ознакомиться с официальной документацией по Django.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
мало того вместо :
CORS_ORIGIN_ALLOW_ALL = False
CORS_ORIGIN_WHITELIST = ( ‘localhost:3000’, )
замените на: CORS_ORIGIN_WHITELIST = [ ‘https://localhost:3000’ ]
не нужно добавлять ‘corsheaders.middleware.CorsMiddleware’ в MIDDLEWARE. Это не сработает.Вместо этого добавьте после MIDDLEWARE следующий код:
MIDDLEWARE_CLASSES = ( ‘corsheaders.middleware.CorsMiddleware’, ‘django.middleware.common.CommonMiddleware’, )
поправьте:
CORS_ORIGIN_WHITELIST = ( ‘localhost:3000’ )
на:
CORS_ORIGIN_WHITELIST = [ ‘https://localhost:3000’ ]
иначе миграции не пройдут