人们使用不同类型的设备连接到互联网并浏览网页。因此,需要从各种位置访问应用程序。对于传统网站,具有响应式UI通常就足够了,但更复杂的应用程序通常需要使用其他技术和体系结构。其中包括具有单独的REST后端和前端应用程序,可以实现为客户端Web应用程序,Progressive Web Apps(PWA)或本机移动应用程序。
构建更复杂的应用程序时可以使用的一些工具包括:
在本教程中,您将使用React,Django和Django REST Framework构建一个带有单独REST API后端和前端的现代Web应用程序。通过将React与Django一起使用,您将能够从JavaScript和前端开发的最新进展中受益。您将使用React作为UI库,而不是构建使用内置模板引擎的Django应用程序,利用其虚拟文档对象模型(DOM),声明性方法和快速呈现数据更改的组件。
您将构建的Web应用程序在数据库中存储有关客户的记录,您可以将其用作CRM应用程序的起点。完成后,您将能够使用使用Bootstrap 4设置样式的React接口创建,读取,更新和删除记录。
要完成本教程,您需要:
pip
和venv
安装在您的机器上。npm
您的计算机上安装了Node.js 6+和5.2或更高版本。您可以按照如何在安装PPA时在Ubuntu 18.04上安装Node.js中的说明安装它们。在这一步中,我们将创建一个虚拟环境并为我们的应用程序安装所需的依赖项,包括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框架:
pip install django djangorestframework django-cors-headers
安装项目依赖项后,您可以创建Django项目和React前端。
在这一步中,我们将使用以下命令和实用程序生成Django项目:
django-admin
是一个命令行实用程序,用于完成Django的任务。该startproject
命令将创建一个新的Django项目。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
您将看到以下输出:
├── djangoreactproject
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── manage.py
该~/djangoreactproject
文件夹是项目的根目录。在此文件夹中,有几个对您的工作很重要的文件:
INSTALLED_APPS
,指定项目的已启用应用程序的字符串列表。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'
]
接下来,将先前安装的CORS包中的中间件corsheaders.middleware.CorsMiddleware
添加到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
您将看到类似于以下内容的输出:
Performing system checks...
System check identified no issues (0 silenced).
October 22, 2018 - 15:14:50
Django version 2.1.2, using settings 'djangoreactproject.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
您的Web应用程序将从http://127.0.0.1:8000
中运行。如果您在Web浏览器中导航到此地址,您应该看到以下页面:
此时,让应用程序继续运行并打开一个新终端以继续开发项目。
在本节中,我们将使用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
你会看到这样的结构:
├── 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接口的样式,因此我们将其包含在管理CSS设置的frontend/src/App.css
文件中。打开文件:
nano ~/djangoreactproject/frontend/src/App.css
将以下导入添加到文件的开头。您可以删除文件的现有内容,但这不是必需的:
@import 'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css';
这@import
是一个CSS指令,用于从其他样式表导入样式规则。
现在我们已经创建了后端和前端应用程序,让我们创建Customer模型和一些演示数据。
在创建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
模型将具有以下数据库字段:
我们还将添加该__str__()
函数,该函数定义了模型的显示方式。在我们的例子中,它将以客户的名字命名。
将以下代码添加到文件中:
from django.db import models
class Customer(models.Model):
first_name = models.CharField("First name", max_length=255)
last_name = models.CharField("Last name", max_length=255)
email = models.EmailField()
phone = models.CharField(max_length=20)
address = models.TextField(blank=True, null=True)
description = models.TextField(blank=True, null=True)
createdAt = models.DateTimeField("Created At", auto_now_add=True)
def __str__(self):
return self.first_name
接下来,迁移数据库以创建数据库表。该makemigrations
命令将创建将添加模型更改的迁移文件,并将迁移文件中的更改的migrate
应用于数据库。
导航回项目的根文件夹:
cd ~/djangoreactproject
运行以下命令以创建迁移文件:
python manage.py makemigrations
您将获得如下所示的输出:
customers/migrations/0001_initial.py
- Create model Customer
将这些更改应用于数据库:
python manage.py migrate
您将看到指示成功迁移的输出:
Operations to perform:
Apply all migrations: admin, auth, contenttypes, customers, sessions
Running migrations:
Applying customers.0001_initial... OK
接下来,您将使用数据迁移文件来创建初始客户数据。一个数据迁移文件是指添加或在数据库中的改变的数据的迁移。为customers
应用程序创建一个空数据迁移文件:
python manage.py makemigrations --empty --name customers customers
您将看到以下有关迁移文件名称的确认:
Migrations for 'customers':
customers/migrations/0002_customers.py
请注意,迁移文件的名称是0002_customers.py
。
接下来,在customers
应用程序的迁移文件夹中导航:
cd ~/djangoreactproject/customers/migrations
打开创建的迁移文件:
nano 0002_customers.py
这是文件的初始内容:
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('customers', '0001_initial'),
]
operations = [
]
import语句migrations
从django.db
包含用于处理数据库的类的内置包中导入API(用于创建迁移的Django API)。
本Migration
类是一种Python类,介绍了迁移数据库时执行的操作。这个类扩展了migrations.Migration
并有两个列表:
接下来,添加一个方法来创建演示客户数据。在定义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
能够创建新客户的类,我们使用apps
对象的get_model()
方法。该apps
对象表示已安装应用程序及其数据库模型的注册表。
当我们使用RunPython()
方法运行create_data()
时,该apps
对象将从该RunPython()
方法传递。将方法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
您将看到确认迁移的输出:
Operations to perform:
Apply all migrations: admin, auth, contenttypes, customers, sessions
Running migrations:
Applying customers.0002_customers... OK
通过创建Customer模型和演示数据,我们可以继续构建REST API。
在这一步中,我们将使用Django REST Framework创建REST API。我们将创建几个不同的API视图。API视图是处理API请求或调用的函数,而API端点是表示REST系统的接触点的唯一URL。例如,当用户向API端点发送GET请求时,Django会调用相应的函数或API视图来处理请求并返回任何可能的结果。
我们还将使用序列化器。在API消耗方面,在Django的REST框架中的一个串行器允许将复杂的模型实例和查询集转换成JSON格式。序列化程序类也可以在另一个方向上工作,提供将数据解析和反序列化为Django模型和QuerySets的机制。
我们的API端点包括:
api/customers
:此端点用于创建客户并返回分页的客户组。api/customers/<pk>
:此端点用于按主键或ID获取,更新和删除单个客户。我们还将在项目的urls.py
文件中为相应的端点(即api/customers
和api/customers/<pk>
)创建URL 。
让我们从为Customer
模型创建序列化程序类开始。
为我们的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视图,当用户访问对应于视图函数的端点时,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'])
decorator 创建一个可以接受GET和POST请求的API视图。 decorator 是一个函数,它的另一功能和动态延伸它。
在方法体中,我们使用request.method
变量来检查当前的HTTP方法,并根据请求类型执行相应的逻辑:
save()
序列化程序对象的方法。然后它返回一个Response对象,一个HttpResponse实例,带有201状态代码。您创建的每个视图都负责撤消HttpResponse
对象。该save()
方法将序列化数据保存在数据库中。现在添加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
字段中的检查验证请求方法,并根据其值调用正确的逻辑:
save()
创建的序列化程序对象的方法。最后,它发送一个带有更新客户的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
获取,更新或删除单个客户的API端点。
开放~/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端点后,让我们看看如何使用它们。
在此步骤中,我们将安装Axios,即我们将用于进行API调用的HTTP客户端。我们还将创建一个类来使用我们创建的API端点。
首先,停用您的虚拟环境:
deactivate
接下来,导航到您的frontend
文件夹:
cd ~/djangoreactproject/frontend
从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中的数据。
在这一步中,我们将创建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();
接下来,创建一个可以扩展Component
来调用REST API 的CustomersList
组件。React组件应该扩展或子类化Component
。
添加以下代码以创建扩展react.Component
的React组件:
...
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
对象。这使用空customers
数组来保存组件的状态变量。此阵列将保留客户和可以保存从后端API检索的下一页的URL的nextPageURL
。我们还为此this
结合了nextPage()
和handleDelete()
方法,以使他们将会从HTML代码访问。
接下来,在结束大括号之前,在CustomersList
类中添加componentDidMount()
方法和调用getCustomers()
。
该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
(主键)删除客户。如果操作成功,则会针对已删除的客户筛选出customers
阵列。
接下来,添加一个nextPage()
方法来获取下一页的数据并更新下一页链接:
...
nextPage(){
var self = this;
customersService.getCustomersByURL(this.state.nextPageURL).then((result) => {
self.setState({ customers: result.data, nextPageURL: result.nextlink})
});
}
该nextPage()
方法调用一个getCustomersByURL()
方法,该方法从状态对象this.state.nextPageURL
获取下一页URL ,并使用返回的数据更新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
类,该类提供与REST API后端交互的方法:
...
import CustomersService from './CustomersService';
const customersService = new CustomersService();
接下来,创建一个扩展Component
为创建和更新客户的CustomerCreateUpdate
组件:
...
class CustomerCreateUpdate extends Component {
constructor(props) {
super(props);
}
}
export default CustomerCreateUpdate;
在类定义中,添加组件的render()
方法,该方法呈现一个HTML表单,其中包含有关客户的信息:
...
render() {
return (
<form onSubmit={this.handleSubmit}>
<div className="form-group">
<label>
First Name:</label>
<input className="form-control" type="text" ref='firstName' />
<label>
Last Name:</label>
<input className="form-control" type="text" ref='lastName'/>
<label>
Phone:</label>
<input className="form-control" type="text" ref='phone' />
<label>
Email:</label>
<input className="form-control" type="text" ref='email' />
<label>
Address:</label>
<input className="form-control" type="text" ref='address' />
<label>
Description:</label>
<textarea className="form-control" ref='description' ></textarea>
<input className="btn btn-primary" type="submit" value="Submit" />
</div>
</form>
);
}
对于每个表单输入元素,该方法添加一个ref
属性以访问和设置表单元素的值。
接下来,在render()
方法上方,定义一个handleSubmit(event)
方法,以便在用户单击提交按钮时具有正确的功能:
...
handleSubmit(event) {
const { match: { params } } = this.props;
if(params && params.pk){
this.handleUpdate(params.pk);
}
else
{
this.handleCreate();
}
event.preventDefault();
}
...
该handleSubmit(event)
方法处理表单提交,并根据路由调用handleUpdate(pk)
方法以使用传递更新客户pk
,或调用handleCreate()
创建新客户的方法。我们将很快定义这些方法。
回到组件构造函数,绑定新添加的handleSubmit()
方法至this
,以便您可以在表单中访问它:
...
class CustomerCreateUpdate extends Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
...
接下来,定义从表单数据创建客户的handleCreate()
方法。在handleSubmit(event)
方法上方,添加以下代码:
...
handleCreate(){
customersService.createCustomer(
{
"first_name": this.refs.firstName.value,
"last_name": this.refs.lastName.value,
"email": this.refs.email.value,
"phone": this.refs.phone.value,
"address": this.refs.address.value,
"description": this.refs.description.value
}).then((result)=>{
alert("Customer created!");
}).catch(()=>{
alert('There was an error! Please re-check your form.');
});
}
...
该handleCreate()
方法将用于根据输入的数据创建客户。它调用相应的CustomersService.createCustomer()
方法,该方法对后端进行实际API调用以创建客户。
接下来,在handleCreate()
方法下面,定义实现更新的handleUpdate(pk)
方法:
...
handleUpdate(pk){
customersService.updateCustomer(
{
"pk": pk,
"first_name": this.refs.firstName.value,
"last_name": this.refs.lastName.value,
"email": this.refs.email.value,
"phone": this.refs.phone.value,
"address": this.refs.address.value,
"description": this.refs.description.value
}
).then((result)=>{
alert("Customer updated!");
}).catch(()=>{
alert('There was an error! Please re-check your form.');
});
}
该updateCustomer()
方法将通过pk
,使用客户信息表中的新信息,从而来更新客户。它调用customersService.updateCustomer()
方法。
接下来,添加一个componentDidMount()
方法。如果用户访问customer/:pk
路线,我们希望使用URL中的主键为表单填写与客户相关的信息。为此,我们可以在组件在生命周期的componentDidMount()
事件中挂载之后添加该getCustomer(pk)
方法。在组件构造函数下面添加以下代码以添加此方法:
...
componentDidMount(){
const { match: { params } } = this.props;
if(params && params.pk)
{
customersService.getCustomer(params.pk).then((c)=>{
this.refs.firstName.value = c.first_name;
this.refs.lastName.value = c.last_name;
this.refs.email.value = c.email;
this.refs.phone.value = c.phone;
this.refs.address.value = c.address;
this.refs.description.value = c.description;
})
}
}
这是文件的完整内容:
import React, { Component } from 'react';
import CustomersService from './CustomersService';
const customersService = new CustomersService();
class CustomerCreateUpdate extends Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidMount(){
const { match: { params } } = this.props;
if(params && params.pk)
{
customersService.getCustomer(params.pk).then((c)=>{
this.refs.firstName.value = c.first_name;
this.refs.lastName.value = c.last_name;
this.refs.email.value = c.email;
this.refs.phone.value = c.phone;
this.refs.address.value = c.address;
this.refs.description.value = c.description;
})
}
}
handleCreate(){
customersService.createCustomer(
{
"first_name": this.refs.firstName.value,
"last_name": this.refs.lastName.value,
"email": this.refs.email.value,
"phone": this.refs.phone.value,
"address": this.refs.address.value,
"description": this.refs.description.value
}
).then((result)=>{
alert("Customer created!");
}).catch(()=>{
alert('There was an error! Please re-check your form.');
});
}
handleUpdate(pk){
customersService.updateCustomer(
{
"pk": pk,
"first_name": this.refs.firstName.value,
"last_name": this.refs.lastName.value,
"email": this.refs.email.value,
"phone": this.refs.phone.value,
"address": this.refs.address.value,
"description": this.refs.description.value
}
).then((result)=>{
console.log(result);
alert("Customer updated!");
}).catch(()=>{
alert('There was an error! Please re-check your form.');
});
}
handleSubmit(event) {
const { match: { params } } = this.props;
if(params && params.pk){
this.handleUpdate(params.pk);
}
else
{
this.handleCreate();
}
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<div className="form-group">
<label>
First Name:</label>
<input className="form-control" type="text" ref='firstName' />
<label>
Last Name:</label>
<input className="form-control" type="text" ref='lastName'/>
<label>
Phone:</label>
<input className="form-control" type="text" ref='phone' />
<label>
Email:</label>
<input className="form-control" type="text" ref='email' />
<label>
Address:</label>
<input className="form-control" type="text" ref='address' />
<label>
Description:</label>
<textarea className="form-control" ref='description' ></textarea>
<input className="btn btn-primary" type="submit" value="Submit" />
</div>
</form>
);
}
}
export default CustomerCreateUpdate;
随着CustomerCreateUpdate
组件创建,我们可以更新主要的App
成分添加链接到我们创建的不同组件。
在本节中,我们将更新App
应用程序的组件,以创建指向我们在前面步骤中创建的组件的链接。
从该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
,和创建了路由组件的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
使用HTML5历史记录API使UI与URL保持同步。
接下来,创建一个基本布局,该布局提供要由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
属性告诉路由器匹配确切的路径。
最后,创建React应用程序的App
组件,根或顶级组件:
...
class App extends Component {
render() {
return (
<BrowserRouter>
<BaseLayout/>
</BrowserRouter>
);
}
}
export default App;
我们已经使用BrowserRouter
组件包装了BaseLayout
组件,因为我们的应用程序是在浏览器中运行的。
完成的文件如下所示:
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存储库中找到此项目的源代码。
更多Ubuntu教程请前往腾讯云+社区学习更多知识。
参考文献:《How To Build a Modern Web Application to Manage Customer Information with Django and React on Ubuntu 18.04》
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。