作者选择了Open Sourcing Mental Illness Ltd作为Write for DOnations计划的一部分进行捐赠。
介绍
人们使用不同类型的设备连接到互联网并浏览网页。 因此,需要从各种位置访问应用程序。 对于传统网站,具有响应式UI通常就足够了,但更复杂的应用程序通常需要使用其他技术和体系结构。 其中包括具有单独的REST后端和前端应用程序,可以实现为客户端Web应用程序,Progressive Web Apps(PWA)或本机移动应用程序。
构建更复杂的应用程序时可以使用的一些工具包括:
- React ,一个JavaScript框架,允许开发人员为他们的REST API后端构建Web和本地前端。
- Django ,一个免费的开源Python Web框架,遵循模型视图控制器(MVC)软件架构模式。
- Django REST框架 ,一个功能强大且灵活的工具包,用于在Django中构建REST API。
在本教程中,您将使用React,Django和Django REST Framework构建一个带有单独REST API后端和前端的现代Web应用程序。 通过将React与Django一起使用,您将能够从JavaScript和前端开发的最新进展中受益。 您将使用React作为UI库,而不是构建使用内置模板引擎的Django应用程序,利用其虚拟文档对象模型(DOM),声明性方法和快速呈现数据更改的组件。
您将构建的Web应用程序在数据库中存储有关客户的记录,您可以将其用作CRM应用程序的起点。 完成后,您将能够使用使用Bootstrap 4设置样式的React接口创建,读取,更新和删除记录。
先决条件
要完成本教程,您需要:
- 使用Ubuntu 18.04的开发机器。
- 按照如何在Ubuntu 18.04上安装Python 3和设置本地编程环境的第1步和2,在您的机器上安装Python 3,
pip
和venv
。 - 您的计算机上安装了Node.js 6+和
npm
5.2或更高版本。 您可以按照如何在安装PPA时在Ubuntu 18.04上安装Node.js中的说明安装它们。
第1步 - 创建Python虚拟环境并安装依赖项
在这一步中,我们将创建一个虚拟环境并为我们的应用程序安装所需的依赖项,包括Django,Django REST框架和django-cors-headers
。
我们的应用程序将为Django和React使用两个不同的开发服务器。 它们将在不同的端口上运行,并将作为两个独立的域运行。 因此,我们需要启用跨源资源共享(CORS),以便将来自React的HTTP请求发送到Django,而不会被浏览器阻止。
导航到您的主目录并使用venv
Python 3模块创建虚拟环境:
cd ~
python3 -m venv ./env
使用source
激活创建的虚拟环境:
source env/bin/activate
接下来,使用pip
安装项目的依赖项。 这些将包括:
- Django :项目的Web框架。
- Django REST框架 :使用Django构建REST API的第三方应用程序。
-
django-cors-headers
:启用CORS的软件包。
安装Django框架:
pip install django djangorestframework django-cors-headers
安装项目依赖项后,您可以创建Django项目和React前端。
第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中,术语应用程序描述了一个Python包,它提供了项目中的一些功能集。
首先,使用django-admin startproject
创建Django项目。 我们将调用我们的项目djangoreactproject
:
django-admin startproject djangoreactproject
在继续之前,让我们使用tree
命令查看Django项目的目录结构。
提示: 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
项目的主要配置文件,您可以在其中修改项目的设置。 这些设置包括诸如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类,包含每次Web应用程序处理请求或响应时处理的代码:
...
MIDDLEWARE = [
...
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'corsheaders.middleware.CorsMiddleware'
]
接下来,您可以启用CORS。 CORS_ORIGIN_ALLOW_ALL
设置指定是否要为所有域允许CORS, CORS_ORIGIN_WHITELIST
是包含允许的URL的Python元组。 在我们的例子中,因为React开发服务器将在http://localhost:3000
,我们将在我们的settings.py
文件中添加新的CORS_ORIGIN_ALLOW_ALL = False
和CORS_ORIGIN_WHITELIST('localhost:3000',)
设置。 在文件中的任何位置添加这些设置:
...
CORS_ORIGIN_ALLOW_ALL = False
CORS_ORIGIN_WHITELIST = (
'localhost:3000',
)
...
您可以在django-cors-headers
文档中找到更多配置选项。
完成后保存文件并退出编辑器。
仍然在~/djangoreactproject
目录中,创建一个名为customers
的新Django应用程序:
python manage.py startapp customers
这将包含管理客户的和视图 。 模型定义应用程序数据的字段和行为,而视图使我们的应用程序能够正确处理Web请求并返回所需的响应。
接下来,将此应用程序添加到项目的settings.py
文件中已安装的应用程序列表中,以便Django将其识别为项目的一部分。 再次打开settings.py
:
nano ~/djangoreactproject/djangoreactproject/settings.py
添加customers
应用程序:
...
INSTALLED_APPS = [
...
'rest_framework',
'corsheaders',
'customers'
]
...
接下来, 迁移数据库并启动本地开发服务器。 迁移是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.
您的Web应用程序将从http://127.0.0.1:8000
运行。 如果您在Web浏览器中导航到此地址,您应该看到以下页面:
此时,让应用程序继续运行并打开一个新终端以继续开发项目。
第3步 - 创建React前端
在本节中,我们将使用React创建项目的前端应用程序。
React有一个官方实用程序,允许您快速生成React项目,而无需直接配置Webpack 。 Webpack是一个模块捆绑器,用于捆绑Web资产,如JavaScript代码,CSS和图像。 通常,在使用Webpack之前,您需要设置各种配置选项,但是由于create-react-app
实用程序,在您决定需要更多控制之前,您不必直接处理Webpack。 要运行create-react-app
您可以使用npx ,这是一个执行npm
包二进制文件的工具。
在第二个终端中,确保您在项目目录中:
cd ~/djangoreactproject
使用create-react-app
和npx
创建一个名为frontend
的React项目:
npx create-react-app frontend
接下来,在React应用程序中导航并启动开发服务器:
cd ~/djangoreactproject/frontend
npm start
您的应用程序将从http://localhost:3000/
:
让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 'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css';
这里,@ import是一个CSS指令,用于从其他样式表导入样式规则。
现在我们已经创建了后端和前端应用程序,让我们创建Customer模型和一些演示数据。
第4步 - 创建客户模型和初始数据
在创建Django应用程序和React前端之后,我们的下一步将是创建Customer模型,该模型表示将保存有关客户的信息的数据库表。 您不需要任何SQL,因为Django 对象关系映射器(ORM)将通过将Python类和变量映射到SQL表和列来处理数据库操作。 通过这种方式,Django ORM通过Python接口抽象出与数据库的SQL交互。
再次激活您的虚拟环境:
cd ~
source env/bin/activate
移至customers
目录,打开models.py
,这是一个包含应用程序模型的Python文件:
cd ~/djangoreactproject/customers/
nano models.py
该文件将包含以下内容:
from django.db import models
# Create your models here.
由于from django.db import models
import语句,Customer模型的API已经导入到文件中。 您现在将添加Customer
类,它扩展了models.Model
。 Django中的每个模型都是一个扩展django.db.models.Model
的Python类。
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
接下来,迁移数据库以创建数据库表。 命令创建将添加模型更改的迁移文件, 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
接下来,您将使用数据迁移文件来创建初始客户数据。 数据迁移文件是一种迁移,用于添加或更改数据库中的数据。 为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
这是文件的初始内容:
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('customers', '0001_initial'),
]
operations = [
]
import语句从django.db
导入migrations
API(用于创建迁移的Django API), 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()
...
在这种方法中,我们抓住customers
应用程序的Customer
类并创建一个插入数据库的演示客户。
要获得可以创建新客户的Customer
类,我们使用apps
对象的get_model()
方法。 apps
对象表示已安装应用程序及其数据库模型的注册表 。
当我们使用它来运行create_data()
时,将从RunPython()
方法传递apps
对象。 将migrations.RunPython()
方法添加到空operations
列表:
...
operations = [
migrations.RunPython(create_data),
]
RunPython()
是Migrations API的一部分,允许您在迁移中运行自定义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中有关数据迁移的文档
要迁移数据库,首先导航回项目的根文件夹:
cd ~/djangoreactproject
迁移数据库以创建演示数据:
python manage.py migrate
您将看到确认迁移的输出:
OutputOperations to perform:
Apply all migrations: admin, auth, contenttypes, customers, sessions
Running migrations:
Applying customers.0002_customers... OK
有关此过程的更多详细信息,请参阅 。
通过创建Customer模型和演示数据,我们可以继续构建REST API。
第5步 - 创建REST API
在这一步中,我们将使用Django REST Framework创建REST API。 我们将创建几个不同的API视图 。 API视图是处理API请求或调用的函数,而API端点是表示REST系统的接触点的唯一URL。 例如,当用户向API端点发送GET请求时,Django会调用相应的函数或API视图来处理请求并返回任何可能的结果。
我们还将使用序列化器 。 Django REST Framework中的序列化程序允许将复杂的模型实例和QuerySets转换为JSON格式以供API使用。 序列化程序类也可以在另一个方向上工作,提供将数据解析和反序列化为Django模型和QuerySets的机制。
我们的API端点包括:
-
api/customers
:此端点用于创建客户并返回分页的客户组。 -
api/customers/<pk>
:此端点用于按主键或ID获取,更新和删除单个客户。
我们还将在项目的urls.py
文件中为相应的端点创建URL(即api/customers
和api/customers/<pk>
)。
让我们从为Customer
模型创建序列化器类开始。
添加Serializer类
为我们的Customer
模型创建序列化程序类是将客户实例和QuerySet转换为JSON和从JSON转换的必要条件。 要创建序列化程序类,首先在customers
应用程序中创建一个serializers.py
文件:
cd ~/djangoreactproject/customers/
nano serializers.py
添加以下代码以导入序列化程序API和Customer
模型:
from rest_framework import serializers
from .models import Customer
接下来,创建一个扩展serializers.ModelSerializer
的序列化程序类,并指定将被序列化的字段:
...
class CustomerSerializer(serializers.ModelSerializer):
class Meta:
model = Customer
fields = ('pk','first_name', 'last_name', 'email', 'phone','address','description')
Meta
类指定要序列化的模型和字段: pk
, first_name
, last_name
, email
, phone
, address
, description
。
这是文件的完整内容:
from rest_framework import serializers
from .models import Customer
class CustomerSerializer(serializers.ModelSerializer):
class Meta:
model = Customer
fields = ('pk','first_name', 'last_name', 'email', 'phone','address','description')
现在我们已经创建了序列化器类,我们可以添加API视图。
添加API视图
在本节中,我们将为我们的应用程序创建API视图,当用户访问对应于视图函数的端点时,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
模型和Django和Django REST Framework API。
接下来,添加用于处理POST和GET HTTP请求的视图:
...
@api_view(['GET', 'POST'])
def customers_list(request):
"""
List customers, or create a new customer.
"""
if request.method == 'GET':
data = []
nextPage = 1
previousPage = 1
customers = Customer.objects.all()
page = request.GET.get('page', 1)
paginator = Paginator(customers, 10)
try:
data = paginator.page(page)
except PageNotAnInteger:
data = paginator.page(1)
except EmptyPage:
data = paginator.page(paginator.num_pages)
serializer = CustomerSerializer(data,context={'request': request} ,many=True)
if data.has_next():
nextPage = data.next_page_number()
if data.has_previous():
previousPage = data.previous_page_number()
return Response({'data': serializer.data , 'count': paginator.count, 'numpages' : paginator.num_pages, 'nextlink': '/api/customers/?page=' + str(nextPage), 'prevlink': '/api/customers/?page=' + str(previousPage)})
elif request.method == 'POST':
serializer = CustomerSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
首先,我们使用@api_view(['GET', 'POST'])
装饰器来创建一个可以接受GET和POST请求的API视图。 装饰器是一个函数,它接受另一个函数并动态扩展它。
在方法体中,我们使用request.method
变量来检查当前的HTTP方法,并根据请求类型执行相应的逻辑:
- 如果是GET请求,则该方法使用Django Paginator对数据进行分页,并返回序列化后的第一页数据,可用客户的数量,可用页面的数量以及前一页和下一页的链接。 Paginator是一个内置的Django类,它将数据列表分页到页面中,并提供访问每个页面的项目的方法。
- 如果是POST请求,则该方法序列化收到的客户数据,然后调用序列化程序对象的
save()
方法。 然后它返回一个Response对象,一个HttpResponse实例,带有201状态代码。 您创建的每个视图都负责撤销HttpResponse
对象。save()
方法将序列化数据保存在数据库中。
有关HttpResponse
和视图的更多信息,请参阅有关创建视图函数的讨论。
现在添加API视图,该视图将负责处理通过pk
(主键)获取,更新和删除客户的GET,PUT和DELETE请求:
...
@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'])
修饰,表示它是一个可以接受GET,PUT和DELETE请求的API视图。
request.method
字段中的检查验证请求方法,并根据其值调用正确的逻辑:
- 如果是GET请求,则客户数据将被序列化并使用Response对象发送。
- 如果是PUT请求,则该方法为新客户数据创建序列化程序。 接下来,它调用创建的序列化程序对象的
save()
方法。 最后,它发送一个带有更新客户的Response对象。 - 如果它是DELETE请求,则该方法调用customer对象的
delete()
方法将其删除,然后返回一个没有数据的Response对象。
完成的文件如下所示:
from rest_framework.response import Response
from rest_framework.decorators import api_view
from rest_framework import status
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from .models import Customer
from .serializers import *
@api_view(['GET', 'POST'])
def customers_list(request):
"""
List customers, or create a new customer.
"""
if request.method == 'GET':
data = []
nextPage = 1
previousPage = 1
customers = Customer.objects.all()
page = request.GET.get('page', 1)
paginator = Paginator(customers, 5)
try:
data = paginator.page(page)
except PageNotAnInteger:
data = paginator.page(1)
except EmptyPage:
data = paginator.page(paginator.num_pages)
serializer = CustomerSerializer(data,context={'request': request} ,many=True)
if data.has_next():
nextPage = data.next_page_number()
if data.has_previous():
previousPage = data.previous_page_number()
return Response({'data': serializer.data , 'count': paginator.count, 'numpages' : paginator.num_pages, 'nextlink': '/api/customers/?page=' + str(nextPage), 'prevlink': '/api/customers/?page=' + str(previousPage)})
elif request.method == 'POST':
serializer = CustomerSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@api_view(['GET', 'PUT', 'DELETE'])
def customers_detail(request, pk):
"""
Retrieve, update or delete a customer by id/pk.
"""
try:
customer = Customer.objects.get(pk=pk)
except Customer.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
if request.method == 'GET':
serializer = CustomerSerializer(customer,context={'request': request})
return Response(serializer.data)
elif request.method == 'PUT':
serializer = CustomerSerializer(customer, data=request.data,context={'request': request})
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == 'DELETE':
customer.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
我们现在可以继续创建我们的端点。
添加API端点
我们现在将创建API端点: api/customers/
,用于查询和创建客户,以及api/customers/<pk>
,用于通过pk
获取,更新或删除单个客户。
打开~/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
接下来,将api/customers/
和api/customers/<pk>
URL添加到包含应用程序URL的urlpatterns
列表中:
...
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端点后,让我们看看如何使用它们。
第6步 - 使用Axios使用REST API
在此步骤中,我们将安装Axios ,即我们将用于进行API调用的HTTP客户端。 我们还将创建一个类来使用我们创建的API端点。
首先,停用您的虚拟环境:
deactivate
接下来,导航到您的frontend
文件夹:
cd ~/djangoreactproject/frontend
使用以下axios
从npm
安装axios
:
npm install axios --save
--save
选项将axios
依赖项添加到应用程序的package.json
文件中。
接下来,创建一个名为CustomersService.js
的JavaScript文件,该文件将包含调用REST API的代码。 我们将在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()
:删除客户。
我们现在可以通过创建CustomersList
组件在我们的React UI界面中显示API中的数据。
第7步 - 在React应用程序中显示API中的数据
在此步骤中,我们将创建CustomersList
React 组件 。 React组件代表UI的一部分; 它还允许您将UI拆分为独立的,可重用的部分。
首先在frontend/src
创建CustomersList.js
:
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();
Next, create a CustomersList
component that extends Component
to call the REST API. A React component should extend or subclass the Component
class . For more about E6 classes and inheritence, please see our tutorial on Understanding Classes in JavaScript .
Add the following code to create a React component that extends 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;
Inside the constructor , we are initializing the state
object. This holds the state variables of our component using an empty customers
array . This array will hold customers and a nextPageURL
that will hold the URL of the next page to retrieve from the back-end API. We are also binding the nextPage()
and handleDelete()
methods to this
so they will be accessible from the HTML code.
Next, add the componentDidMount()
method and a call to getCustomers()
within the CustomersList
class, before the closing curly brace.
The componentDidMount()
method is a lifecycle method of the component that is called when the component is created and inserted into the DOM. getCustomers()
calls the Customers Service object to get the first page of data and the link of the next page from the Django backend:
...
componentDidMount() {
var self = this;
customersService.getCustomers().then(function (result) {
self.setState({ customers: result.data, nextPageURL: result.nextlink})
});
}
Now add the handleDelete()
method, which handles deleting a customer, below 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})
});
}
The handleDelete()
method calls the deleteCustomer()
method to delete a customer using its pk
(primary key). If the operation is successful, the customers
array is filtered out for the removed customer.
Next, add a nextPage()
method to get the data for the next page and update the next page link:
...
nextPage(){
var self = this;
customersService.getCustomersByURL(this.state.nextPageURL).then((result) => {
self.setState({ customers: result.data, nextPageURL: result.nextlink})
});
}
The nextPage()
method calls a getCustomersByURL()
method, which takes the next page URL from the state object, this.state.nextPageURL
, and updates the customers
array with the returned data.
Finally, add the component render()
method , which renders a table of customers from the component state:
...
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>
);
}
This is the full content of the file:
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;
Now that we've created the CustomersList
component for displaying the list of customers, we can add the component that handles customer creation and updates.
Step 8 — Adding the Customer Create and Update React Component
In this step, we'll create the CustomerCreateUpdate
component, which will handle creating and updating customers. It will do this by providing a form that users can use to either enter data about a new customer or update an existing entry.
In frontend/src
, create a CustomerCreateUpdate.js
file:
nano ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js
Add the following code to create a React component, importing React
and Component
:
import React, { Component } from 'react';
We can also import and instantiate the CustomersService
class we created in the previous step, which provides methods that interface with the REST API backend:
...
import CustomersService from './CustomersService';
const customersService = new CustomersService();
Next, create a CustomerCreateUpdate
component that extends Component
to create and update customers:
...
class CustomerCreateUpdate extends Component {
constructor(props) {
super(props);
}
}
export default CustomerCreateUpdate;
Within the class definition, add the render()
method of the component, which renders an HTML form that takes information about the customer:
...
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>
);
}
For each form input element, the method adds a ref
property to access and set the value of the form element.
Next, above the render()
method, define a handleSubmit(event)
method so that you have the proper functionality when a user clicks on the submit button:
...
handleSubmit(event) {
const { match: { params } } = this.props;
if(params && params.pk){
this.handleUpdate(params.pk);
}
else
{
this.handleCreate();
}
event.preventDefault();
}
...
The handleSubmit(event)
method handles the form submission and, depending on the route, calls either the handleUpdate(pk)
method to update the customer with the passed pk
, or the handleCreate()
method to create a new customer. We will define these methods shortly.
Back on the component constructor, bind the newly added handleSubmit()
method to this
so you can access it in your form:
...
class CustomerCreateUpdate extends Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
...
Next, define the handleCreate()
method to create a customer from the form data. Above the handleSubmit(event)
method, add the following code:
...
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.');
});
}
...
The handleCreate()
method will be used to create a customer from inputted data. It calls the corresponding CustomersService.createCustomer()
method that makes the actual API call to the backend to create a customer.
Next, below the handleCreate()
method, define the handleUpdate(pk)
method to implement updates:
...
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.');
});
}
The updateCustomer()
method will update a customer by pk
using the new information from the customer information form. It calls the customersService.updateCustomer()
method.
Next, add a componentDidMount()
method. If the the user visits a customer/:pk
route, we want to fill the form with information related to the customer using the primary key from the URL. To do that, we can add the getCustomer(pk)
method after the component gets mounted in the lifecycle event of componentDidMount()
. Add the following code below the component constructor to add this method:
...
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;
})
}
}
This is the full content of the file:
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;
With the CustomerCreateUpdate
component created, we can update the main App
component to add links to the different components we've created.
Step 9 — Updating the Main App Component
In this section, we'll update the App
component of our application to create links to the components we've created in the previous steps.
From the frontend
folder, run the following command to install the React Router , which allows you to add routing and navigation between various React components:
cd ~/djangoreactproject/frontend
npm install --save react-router-dom
Next, open ~/djangoreactproject/frontend/src/App.js
:
nano ~/djangoreactproject/frontend/src/App.js
Delete everything that's there and add the following code to import the necessary classes for adding routing. These include BrowserRouter
, which creates a Router component, and Route
, which creates a route component:
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
keeps the UI in sync with the URL using the HTML5 history API .
Next, create a base layout that provides the base component to be wrapped by the BrowserRouter
component:
...
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>
)
We use the Route
component to define the routes of our application; the component the router should load once a match is found. Each route needs a path
to specify the path to be matched and a component
to specify the component to load. The exact
property tells the router to match the exact path.
Finally, create the App
component, the root or top-level component of our React application:
...
class App extends Component {
render() {
return (
<BrowserRouter>
<BaseLayout/>
</BrowserRouter>
);
}
}
export default App;
We have wrapped the BaseLayout
component with the BrowserRouter
component since our app is meant to run in the browser.
The completed file looks like this:
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;
After adding routing to our application, we are now ready to test the application. Navigate to http://localhost:3000
. You should see the first page of the application:
With this application in place, you now have the base for a CRM application.
结论
In this tutorial, you created a demo application using Django and React. You used the Django REST framework to build the REST API, Axios to consume the API, and Bootstrap 4 to style your CSS. You can find the source code of this project in this GitHub repository .
This tutorial setup used separate front-end and back-end apps. For a different approach to integrating React with Django, check this tutorial and this tutorial .
For more information about building an application with Django, you can follow the Django development series . You can also look at the official Django docs .