如何在Ubuntu18.04上使用Django和React构建现代Web应用程序来管理客户信息
作者选择 Open Sourcing Mental Illness Ltd 作为 Write for DOnations 计划的一部分接受捐赠。
介绍
人们使用不同类型的设备连接到互联网并浏览网络。 因此,应用程序需要可从多个位置访问。 对于传统网站,拥有响应式 UI 通常就足够了,但更复杂的应用程序通常需要使用其他技术和架构。 其中包括拥有独立的 REST 后端和前端应用程序,这些应用程序可以实现为客户端 Web 应用程序、渐进式 Web 应用程序 (PWA) 或本机移动应用程序。
在构建更复杂的应用程序时可以使用的一些工具包括:
- React,一个 JavaScript 框架,允许开发人员为其 REST API 后端构建 Web 和本机前端。
- Django,一个免费的开源 Python Web 框架,遵循 模型视图控制器 (MVC) 软件架构模式。
- Django REST 框架,一个强大而灵活的工具包,用于在 Django 中构建 REST API。
在本教程中,您将使用 React、Django 和 Django REST 框架构建一个具有独立 REST API 后端和前端的现代 Web 应用程序。 通过将 React 与 Django 结合使用,您将能够受益于 JavaScript 和前端开发的最新进展。 无需构建使用内置模板引擎的 Django 应用程序,您将使用 React 作为 UI 库,利用其虚拟文档对象模型 (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 或更高版本。 您可以按照 How To Install Node.js on Ubuntu 18.04 中关于从 PPA 安装的说明安装它们。
第 1 步 - 创建 Python 虚拟环境并安装依赖项
在这一步中,我们将创建一个虚拟环境并为我们的应用程序安装所需的依赖项,包括 Django、Django REST 框架和 django-cors-headers
。
我们的应用程序将为 Django 和 React 使用两个不同的开发服务器。 它们将在不同的端口上运行,并将作为两个独立的域运行。 因此,我们需要启用 跨域资源共享 (CORS) 以将 HTTP 请求从 React 发送到 Django,而不会被浏览器阻止。
导航到您的主目录并使用 venv
Python 3 模块创建一个虚拟环境:
cd ~ python3 -m venv ./env
使用 source
激活创建的虚拟环境:
source env/bin/activate
接下来,使用 pip
安装项目的依赖项。 这些将包括:
- Django:项目的网络框架。
- 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 中,术语 application 描述了一个 Python 包,它在项目中提供了一些功能集。
首先,使用 django-admin startproject
创建 Django 项目。 我们将调用我们的项目 djangoreactproject
:
django-admin startproject djangoreactproject
在继续之前,让我们看看使用 tree
命令的 Django 项目的目录结构。
提示: tree
is a useful command for viewing file and directory structures from the command line. You can install it with the following command:
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
、list 字符串,用于指定项目启用的应用程序。 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
应用程序添加到列表底部:
~/djangoreactproject/djangoreactproject/settings.py
... INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', 'corsheaders' ]
接下来,将之前安装的 CORS 包中的 corsheaders.middleware.CorsMiddleware
中间件添加到 MIDDLEWARE
设置中。 此设置是 middlewares 的列表,这是一个 Python 类,包含每次 Web 应用程序处理请求或响应时处理的代码:
~/djangoreactproject/djangoreactproject/settings.py
... MIDDLEWARE = [ ... 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'corsheaders.middleware.CorsMiddleware' ]
接下来,您可以启用 CORS。 CORS_ORIGIN_ALLOW_ALL
设置指定是否要允许所有域的 CORS,并且 CORS_ORIGIN_WHITELIST
是一个包含允许 URL 的 Python 元组。 在我们的例子中,因为 React 开发服务器将在 http://localhost:3000
上运行,我们将在 settings.py
文件中添加新的 CORS_ORIGIN_ALLOW_ALL = False
和 CORS_ORIGIN_WHITELIST('localhost:3000',)
设置。 在文件中的任意位置添加这些设置:
~/djangoreactproject/djangoreactproject/settings.py
... CORS_ORIGIN_ALLOW_ALL = False CORS_ORIGIN_WHITELIST = ( 'localhost:3000', ) ...
您可以在 django-cors-headers 文档 中找到更多配置选项。
完成后保存文件并退出编辑器。
仍然在 ~/djangoreactproject
目录中,新建一个名为 customers
的 Django 应用程序:
python manage.py startapp customers
这将包含用于管理客户的 models 和 views。 模型定义应用程序数据的字段和行为,而视图使我们的应用程序能够正确处理 Web 请求并返回所需的响应。
接下来,将此应用程序添加到项目的 settings.py
文件中的已安装应用程序列表中,以便 Django 将其识别为项目的一部分。 再次打开settings.py
:
nano ~/djangoreactproject/djangoreactproject/settings.py
添加 customers
应用程序:
~/djangoreactproject/djangoreactproject/settings.py
... INSTALLED_APPS = [ ... 'rest_framework', 'corsheaders', 'customers' ] ...
接下来,迁移数据库并启动本地开发服务器。 Migrations 是 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.
您的 Web 应用程序将从 http://127.0.0.1:8000
运行。 如果您在 Web 浏览器中导航到该地址,您应该会看到以下页面:
此时,让应用程序保持运行并打开一个新终端以继续开发项目。
第三步——创建 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 添加到文件的开头。 您可以删除文件的现有内容,但这不是必需的:
~/djangoreactproject/frontend/src/App.css
@import 'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css';
这里,@import
是一个 CSS 指令,用于从其他样式表中导入样式规则。
现在我们已经创建了后端和前端应用程序,让我们创建客户模型和一些演示数据。
第 4 步 — 创建客户模型和初始数据
在创建 Django 应用程序和 React 前端之后,我们的下一步将是创建 Customer 模型,它表示将保存有关客户信息的数据库表。 您不需要任何 SQL,因为 Django Object Relational Mapper (ORM) 将通过将 Python 类和变量映射到 SQL 表和列来处理数据库操作。 这样,Django ORM 通过 Python 接口抽象出与数据库的 SQL 交互。
再次激活您的虚拟环境:
cd ~ source env/bin/activate
移动到 customers
目录,然后打开 models.py
,这是一个包含应用程序模型的 Python 文件:
cd ~/djangoreactproject/customers/ nano models.py
该文件将包含以下内容:
~/djangoreactproject/customers/models.py
from django.db import models # Create your models here.
由于 from django.db import models
导入语句,客户模型的 API 已经导入到文件中。 您现在将添加 Customer
类,它扩展了 models.Model
。 Django 中的每个模型都是一个扩展 django.db.models.Model 的 Python 类。
Customer
模型将具有以下数据库字段:
- first_name — 客户的名字。
- last_name — 客户的姓氏。
- email — 客户的电子邮件地址。
- phone — 客户的电话号码。
- address — 客户的地址。
- description — 客户的描述。
- createdAt — 添加客户的日期。
我们还将添加 __str__()
函数,它定义了模型的显示方式。 在我们的例子中,它将是客户的名字。 有关构造类和定义对象的更多信息,请参阅如何在 Python 3 中构造类和定义对象。
将以下代码添加到文件中:
~/djangoreactproject/customers/models.py
from django.db import models class Customer(models.Model): first_name = models.CharField("First name", max_length=255) last_name = models.CharField("Last name", max_length=255) email = models.EmailField() phone = models.CharField(max_length=20) address = models.TextField(blank=True, null=True) description = models.TextField(blank=True, null=True) createdAt = models.DateTimeField("Created At", auto_now_add=True) def __str__(self): return self.first_name
接下来,迁移数据库以创建数据库表。 makemigrations 命令创建将添加模型更改的迁移文件,migrate
将迁移文件中的更改应用到数据库。
导航回项目的根文件夹:
cd ~/djangoreactproject
运行以下命令来创建迁移文件:
python manage.py makemigrations
您将获得如下所示的输出:
Outputcustomers/migrations/0001_initial.py - Create model Customer
将这些更改应用于数据库:
python manage.py migrate
您将看到指示迁移成功的输出:
OutputOperations to perform: Apply all migrations: admin, auth, contenttypes, customers, sessions Running migrations: Applying customers.0001_initial... OK
接下来,您将使用 数据迁移文件 创建初始客户数据。 数据迁移文件 是在数据库中添加或更改数据的迁移。 为 customers
应用程序创建一个空的数据迁移文件:
python manage.py makemigrations --empty --name customers customers
您将看到以下带有迁移文件名称的确认信息:
OutputMigrations for 'customers': customers/migrations/0002_customers.py
请注意,您的迁移文件的名称是 0002_customers.py
。
接下来,在 customers
应用程序的迁移文件夹中导航:
cd ~/djangoreactproject/customers/migrations
打开创建的迁移文件:
nano 0002_customers.py
这是文件的初始内容:
~/djangoreactproject/customers/migrations/0002_customers.py
from django.db import migrations class Migration(migrations.Migration): dependencies = [ ('customers', '0001_initial'), ] operations = [ ]
import 语句从 django.db
导入 migrations
API,这是一个用于创建迁移的 Django API,这是一个包含用于处理数据库的类的内置包。
Migration
类是一个 Python 类,描述了迁移数据库时执行的操作。 这个类扩展了 migrations.Migration
并且有两个列表:
- dependencies:包含依赖的迁移。
- operations:包含应用迁移时将执行的操作。
接下来,添加一个 方法 来创建演示客户数据。 在 Migration
类的定义前添加如下方法:
~/djangoreactproject/customers/migrations/0002_customers.py
... def create_data(apps, schema_editor): Customer = apps.get_model('customers', 'Customer') Customer(first_name="Customer 001", last_name="Customer 001", email="customer001@email.com", phone="00000000", address="Customer 000 Address", description= "Customer 001 description").save() ...
在这种方法中,我们将获取 customers
应用程序的 Customer
类并创建一个演示客户以插入数据库。
为了获得能够创建新客户的 Customer
类,我们使用 apps
对象的 get_model()
方法。 apps
对象代表已安装应用程序及其数据库模型的 registry。
当我们使用 apps
对象将从 RunPython()
方法传递。 将 migrations.RunPython()
方法添加到空的 operations
列表中:
~/djangoreactproject/customers/migrations/0002_customers.py
... operations = [ migrations.RunPython(create_data), ]
RunPython()
是 Migrations API 的一部分,允许您在迁移中运行自定义 Python 代码。 我们的 operations
列表指定当我们应用迁移时将执行此方法。
这是完整的文件:
~/djangoreactproject/customers/migrations/0002_customers.py
from django.db import migrations def create_data(apps, schema_editor): Customer = apps.get_model('customers', 'Customer') Customer(first_name="Customer 001", last_name="Customer 001", email="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
有关此过程的更多详细信息,请参阅 如何创建 Django 模型。
创建客户模型和演示数据后,我们可以继续构建 REST API。
第 5 步 — 创建 REST API
在这一步中,我们将使用 Django REST 框架创建 REST API。 我们将创建几个不同的 API 视图。 API 视图是处理 API 请求或调用的函数,而 API 端点 是表示与 REST 系统的接触点的唯一 URL。 例如,当用户向 API 端点发送 GET 请求时,Django 会调用相应的函数或 API 视图来处理请求并返回任何可能的结果。
我们还将使用 序列化器 。 Django REST Framework 中的 serializer 允许将复杂的模型实例和查询集转换为 JSON 格式以供 API 使用。 序列化器类也可以在另一个方向工作,提供将数据解析和反序列化为 Django 模型和查询集的机制。
我们的 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
模型:
~/djangoreactproject/customers/serializers.py
from rest_framework import serializers from .models import Customer
接下来,创建一个扩展 serializers.ModelSerializer
并指定将被序列化的字段的序列化程序类:
~/djangoreactproject/customers/serializers.py
... class CustomerSerializer(serializers.ModelSerializer): class Meta: model = Customer fields = ('pk','first_name', 'last_name', 'email', 'phone','address','description')
Meta
类指定要序列化的模型和字段:pk
,first_name
, last_name
, email
, phone
, address
,description
。
这是文件的完整内容:
~/djangoreactproject/customers/serializers.py
from rest_framework import serializers from .models import Customer class CustomerSerializer(serializers.ModelSerializer): class Meta: model = Customer fields = ('pk','first_name', 'last_name', 'email', 'phone','address','description')
现在我们已经创建了序列化程序类,我们可以添加 API 视图。
添加 API 视图
在本节中,我们将为我们的应用程序创建 API 视图,当用户访问与视图函数对应的端点时,Django 将调用这些视图。
打开~/djangoreactproject/customers/views.py
:
nano ~/djangoreactproject/customers/views.py
删除那里的内容并添加以下导入:
~/djangoreactproject/customers/views.py
from rest_framework.response import Response from rest_framework.decorators import api_view from rest_framework import status from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from .models import Customer from .serializers import *
我们正在导入我们创建的序列化程序,以及 Customer
模型以及 Django 和 Django REST Framework API。
接下来,添加用于处理 POST 和 GET HTTP 请求的视图:
~/djangoreactproject/customers/views.py
... @api_view(['GET', 'POST']) def customers_list(request): """ List customers, or create a new customer. """ if request.method == 'GET': data = [] nextPage = 1 previousPage = 1 customers = Customer.objects.all() page = request.GET.get('page', 1) paginator = Paginator(customers, 10) try: data = paginator.page(page) except PageNotAnInteger: data = paginator.page(1) except EmptyPage: data = paginator.page(paginator.num_pages) serializer = CustomerSerializer(data,context={'request': request} ,many=True) if data.has_next(): nextPage = data.next_page_number() if data.has_previous(): previousPage = data.previous_page_number() return Response({'data': serializer.data , 'count': paginator.count, 'numpages' : paginator.num_pages, 'nextlink': '/api/customers/?page=' + str(nextPage), 'prevlink': '/api/customers/?page=' + str(previousPage)}) elif request.method == 'POST': serializer = CustomerSerializer(data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
首先我们使用 @api_view(['GET', 'POST'])
装饰器创建一个可以接受 GET 和 POST 请求的 API 视图。 decorator 是一个接受另一个函数并动态扩展它的函数。
在方法体中,我们使用 request.method
变量来检查当前的 HTTP 方法,并根据请求类型执行相应的逻辑:
- 如果是GET请求,该方法使用Django Paginator对数据进行分页,并返回序列化后的第一页数据,可用客户数,可用页数,以及上一页和一页的链接下一页。 Paginator 是一个内置的 Django 类,它将数据列表分页到页面中,并提供访问每个页面项目的方法。
- 如果是POST请求,该方法将接收到的客户数据序列化,然后调用序列化器对象的
save()
方法。 然后它返回一个 Response 对象,一个 HttpResponse 的实例,带有 201 状态码。 您创建的每个视图都负责返回一个HttpResponse
对象。save()
方法将序列化的数据保存在数据库中。
有关 HttpResponse
和视图的更多信息,请参阅 创建视图函数 的讨论。
现在添加 API 视图,该视图将负责处理通过 pk
(主键)获取、更新和删除客户的 GET、PUT 和 DELETE 请求:
~/djangoreactproject/customers/views.py
... @api_view(['GET', 'PUT', 'DELETE']) def customers_detail(request, pk): """ Retrieve, update or delete a customer by id/pk. """ try: customer = Customer.objects.get(pk=pk) except Customer.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) if request.method == 'GET': serializer = CustomerSerializer(customer,context={'request': request}) return Response(serializer.data) elif request.method == 'PUT': serializer = CustomerSerializer(customer, data=request.data,context={'request': request}) if serializer.is_valid(): serializer.save() return Response(serializer.data) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) elif request.method == 'DELETE': customer.delete() return Response(status=status.HTTP_204_NO_CONTENT)
该方法用 @api_view(['GET', 'PUT', 'DELETE'])
修饰,表示它是一个可以接受 GET、PUT 和 DELETE 请求的 API 视图。
request.method
字段中的检查验证请求方法,并根据其值调用正确的逻辑:
- 如果是 GET 请求,客户数据将被序列化并使用 Response 对象发送。
- 如果是 PUT 请求,该方法会为新的客户数据创建一个序列化程序。 接下来,它调用创建的序列化程序对象的
save()
方法。 最后,它发送一个带有更新后的客户的 Response 对象。 - 如果是DELETE请求,该方法调用客户对象的
delete()
方法将其删除,然后返回一个没有数据的Response对象。
完成的文件如下所示:
~/djangoreactproject/customers/views.py
from rest_framework.response import Response from rest_framework.decorators import api_view from rest_framework import status from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from .models import Customer from .serializers import * @api_view(['GET', 'POST']) def customers_list(request): """ List customers, or create a new customer. """ if request.method == 'GET': data = [] nextPage = 1 previousPage = 1 customers = Customer.objects.all() page = request.GET.get('page', 1) paginator = Paginator(customers, 5) try: data = paginator.page(page) except PageNotAnInteger: data = paginator.page(1) except EmptyPage: data = paginator.page(paginator.num_pages) serializer = CustomerSerializer(data,context={'request': request} ,many=True) if data.has_next(): nextPage = data.next_page_number() if data.has_previous(): previousPage = data.previous_page_number() return Response({'data': serializer.data , 'count': paginator.count, 'numpages' : paginator.num_pages, 'nextlink': '/api/customers/?page=' + str(nextPage), 'prevlink': '/api/customers/?page=' + str(previousPage)}) elif request.method == 'POST': serializer = CustomerSerializer(data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @api_view(['GET', 'PUT', 'DELETE']) def customers_detail(request, pk): """ Retrieve, update or delete a customer by id/pk. """ try: customer = Customer.objects.get(pk=pk) except Customer.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) if request.method == 'GET': serializer = CustomerSerializer(customer,context={'request': request}) return Response(serializer.data) elif request.method == 'PUT': serializer = CustomerSerializer(customer, data=request.data,context={'request': request}) if serializer.is_valid(): serializer.save() return Response(serializer.data) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) elif request.method == 'DELETE': customer.delete() return Response(status=status.HTTP_204_NO_CONTENT)
我们现在可以继续创建我们的端点。
添加 API 端点
我们现在将创建 API 端点:api/customers/
,用于查询和创建客户,api/customers/<pk>
,用于通过 pk
获取、更新或删除单个客户。
打开~/djangoreactproject/djangoreactproject/urls.py
:
nano ~/djangoreactproject/djangoreactproject/urls.py
保留那里的内容,但将导入添加到文件顶部的 customers
视图中:
~/djangoreactproject/djangoreactproject/urls.py
from django.contrib import admin from django.urls import path from customers import views from django.conf.urls import url
接下来,将 api/customers/
和 api/customers/<pk>
URL 添加到包含应用程序 URL 的 urlpatterns 列表:
~/djangoreactproject/djangoreactproject/urls.py
... 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
使用以下命令从 npm
安装 axios
:
npm install axios --save
--save
选项将 axios
依赖项添加到应用程序的 package.json
文件中。
接下来,创建一个名为 CustomersService.js
的 JavaScript 文件,其中将包含调用 REST API 的代码。 我们将把它放在 src
文件夹中,我们项目的应用程序代码将存放在该文件夹中:
cd src nano CustomersService.js
添加以下代码,其中包含连接到 Django REST API 的方法:
~/djangoreactproject/frontend/src/CustomersService.js
import axios from 'axios'; const API_URL = 'http://localhost:8000'; export default class CustomersService{ constructor(){} getCustomers() { const url = `${API_URL}/api/customers/`; return axios.get(url).then(response => response.data); } getCustomersByURL(link){ const url = `${API_URL}${link}`; return axios.get(url).then(response => response.data); } getCustomer(pk) { const url = `${API_URL}/api/customers/${pk}`; return axios.get(url).then(response => response.data); } deleteCustomer(customer){ const url = `${API_URL}/api/customers/${customer.pk}`; return axios.delete(url); } createCustomer(customer){ const url = `${API_URL}/api/customers/`; return axios.post(url,customer); } updateCustomer(customer){ const url = `${API_URL}/api/customers/${customer.pk}`; return axios.put(url,customer); } }
CustomersService
类将调用以下 Axios 方法:
getCustomers()
:获取客户的第一页。getCustomersByURL()
:通过 URL 获取客户。 这样就可以通过传递/api/customers/?page=2
等链接来获取客户的下一页。getCustomer()
:通过主键获取客户。createCustomer()
:创建客户。updateCustomer()
:更新客户。deleteCustomer()
:删除客户。
现在,我们可以通过创建 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 组件:
~/djangoreactproject/frontend/src/CustomersList.js
import React, { Component } from 'react';
接下来,导入并实例化您在上一步中创建的 CustomersService
模块,该模块提供与 REST API 后端接口的方法:
~/djangoreactproject/frontend/src/CustomersList.js
... import CustomersService from './CustomersService'; const customersService = new CustomersService();
接下来,创建一个扩展 Component
的 CustomersList
组件以调用 REST API。 React 组件应该 扩展或子类化 Component 类 。 有关 E6 类和继承的更多信息,请参阅我们的 Understanding Classes in JavaScript 教程。
添加以下代码以创建一个扩展 react.Component
的 React 组件:
~/djangoreactproject/frontend/src/CustomersList.js
... class CustomersList extends Component { constructor(props) { super(props); this.state = { customers: [], nextPageURL: '' }; this.nextPage = this.nextPage.bind(this); this.handleDelete = this.handleDelete.bind(this); } } export default CustomersList;
在 constructor 内部,我们正在初始化 state 对象。 它使用一个空的 customers
array 保存我们组件的状态变量。 该数组将保存客户和一个 nextPageURL
,它将保存要从后端 API 检索的下一页的 URL。 我们还将 绑定 nextPage()
和 handleDelete()
方法 到 this
,因此可以从 HTML 代码访问它们。
接下来,在 CustomersList
类中,在右花括号之前添加 componentDidMount()
方法和对 getCustomers()
的调用。
componentDidMount()
方法是组件的生命周期方法,在组件创建并插入 DOM 时调用。 getCustomers()
调用Customers Service对象从Django后端获取第一页数据和下一页的链接:
~/djangoreactproject/frontend/src/CustomersList.js
... componentDidMount() { var self = this; customersService.getCustomers().then(function (result) { self.setState({ customers: result.data, nextPageURL: result.nextlink}) }); }
现在在 componentDidMount()
下方添加处理删除客户的 handleDelete()
方法:
~/djangoreactproject/frontend/src/CustomersList.js
... handleDelete(e,pk){ var self = this; customersService.deleteCustomer({pk : pk}).then(()=>{ var newArr = self.state.customers.filter(function(obj) { return obj.pk !== pk; }); self.setState({customers: newArr}) }); }
handleDelete()
方法调用 deleteCustomer()
方法以使用其 pk
(主键)删除客户。 如果操作成功,则为移除的客户过滤掉customers
数组。
接下来,添加一个 nextPage()
方法来获取下一页的数据并更新下一页链接:
~/djangoreactproject/frontend/src/CustomersList.js
... nextPage(){ var self = this; customersService.getCustomersByURL(this.state.nextPageURL).then((result) => { self.setState({ customers: result.data, nextPageURL: result.nextlink}) }); }
nextPage()
方法调用 getCustomersByURL()
方法,该方法从状态对象 this.state.nextPageURL
获取下一页 URL,并使用返回的数据更新 customers
数组.
最后,添加组件 render() 方法,它从组件状态呈现客户表:
~/djangoreactproject/frontend/src/CustomersList.js
... 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> ); }
这是文件的完整内容:
~/djangoreactproject/frontend/src/CustomersList.js
import React, { Component } from 'react'; import CustomersService from './CustomersService'; const customersService = new CustomersService(); class CustomersList extends Component { constructor(props) { super(props); this.state = { customers: [], nextPageURL: '' }; this.nextPage = this.nextPage.bind(this); this.handleDelete = this.handleDelete.bind(this); } componentDidMount() { var self = this; customersService.getCustomers().then(function (result) { console.log(result); self.setState({ customers: result.data, nextPageURL: result.nextlink}) }); } handleDelete(e,pk){ var self = this; customersService.deleteCustomer({pk : pk}).then(()=>{ var newArr = self.state.customers.filter(function(obj) { return obj.pk !== pk; }); self.setState({customers: newArr}) }); } nextPage(){ var self = this; console.log(this.state.nextPageURL); customersService.getCustomersByURL(this.state.nextPageURL).then((result) => { self.setState({ customers: result.data, nextPageURL: result.nextlink}) }); } render() { return ( <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
组件,我们可以添加处理客户创建和更新的组件。
第 8 步 - 添加客户创建和更新 React 组件
在这一步中,我们将创建 CustomerCreateUpdate
组件,它将处理创建和更新客户。 它将通过提供一个表单来实现这一点,用户可以使用该表单输入有关新客户的数据或更新现有条目。
在 frontend/src
中,创建一个 CustomerCreateUpdate.js
文件:
nano ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js
添加以下代码以创建 React 组件,导入 React
和 Component
:
~/djangoreactproject/frontend/src/CustomerCreateUpdate.js
import React, { Component } from 'react';
我们还可以导入并实例化我们在上一步中创建的 CustomersService
类,它提供了与 REST API 后端接口的方法:
~/djangoreactproject/frontend/src/CustomerCreateUpdate.js
... import CustomersService from './CustomersService'; const customersService = new CustomersService();
接下来,创建一个扩展 Component
的 CustomerCreateUpdate
组件来创建和更新客户:
~/djangoreactproject/frontend/src/CustomerCreateUpdate.js
... class CustomerCreateUpdate extends Component { constructor(props) { super(props); } } export default CustomerCreateUpdate;
在类定义中,添加组件的 render()
方法,该方法会呈现一个获取客户信息的 HTML 表单:
~/djangoreactproject/frontend/src/CustomerCreateUpdate.js
... render() { return ( <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)
方法,以便在用户单击提交按钮时具有正确的功能:
~/djangoreactproject/frontend/src/CustomerCreateUpdate.js
... handleSubmit(event) { const { match: { params } } = this.props; if(params && params.pk){ this.handleUpdate(params.pk); } else { this.handleCreate(); } event.preventDefault(); } ...
handleSubmit(event)
方法处理表单提交,并根据路由调用 handleUpdate(pk)
方法以使用传递的 pk
或 handleCreate()
更新客户] 方法来创建一个新客户。 我们将很快定义这些方法。
返回组件构造函数,将新添加的 handleSubmit()
方法绑定到 this
以便您可以在表单中访问它:
~/djangoreactproject/frontend/src/CustomerCreateUpdate.js
... class CustomerCreateUpdate extends Component { constructor(props) { super(props); this.handleSubmit = this.handleSubmit.bind(this); } ...
接下来,定义 handleCreate()
方法以从表单数据创建客户。 在 handleSubmit(event)
方法上方,添加以下代码:
~/djangoreactproject/frontend/src/CustomerCreateUpdate.js
... handleCreate(){ customersService.createCustomer( { "first_name": this.refs.firstName.value, "last_name": this.refs.lastName.value, "email": this.refs.email.value, "phone": this.refs.phone.value, "address": this.refs.address.value, "description": this.refs.description.value }).then((result)=>{ alert("Customer created!"); }).catch(()=>{ alert('There was an error! Please re-check your form.'); }); } ...
handleCreate()
方法将用于从输入的数据创建客户。 它调用相应的 CustomersService.createCustomer()
方法,对后端进行实际的 API 调用以创建客户。
接下来,在handleCreate()
方法下面,定义handleUpdate(pk)
方法来实现更新:
~/djangoreactproject/frontend/src/CustomerCreateUpdate.js
... handleUpdate(pk){ customersService.updateCustomer( { "pk": pk, "first_name": this.refs.firstName.value, "last_name": this.refs.lastName.value, "email": this.refs.email.value, "phone": this.refs.phone.value, "address": this.refs.address.value, "description": this.refs.description.value } ).then((result)=>{ alert("Customer updated!"); }).catch(()=>{ alert('There was an error! Please re-check your form.'); }); }
updateCustomer()
方法将使用来自客户信息表单的新信息通过 pk
更新客户。 它调用 customersService.updateCustomer()
方法。
接下来,添加一个 componentDidMount()
方法。 如果用户访问 customer/:pk
路由,我们希望使用 URL 中的主键填写与客户相关的信息。 为此,我们可以在组件挂载到 componentDidMount()
的生命周期事件后添加 getCustomer(pk)
方法。 在组件构造函数下方添加以下代码以添加此方法:
~/djangoreactproject/frontend/src/CustomerCreateUpdate.js
... componentDidMount(){ const { match: { params } } = this.props; if(params && params.pk) { customersService.getCustomer(params.pk).then((c)=>{ this.refs.firstName.value = c.first_name; this.refs.lastName.value = c.last_name; this.refs.email.value = c.email; this.refs.phone.value = c.phone; this.refs.address.value = c.address; this.refs.description.value = c.description; }) } }
这是文件的完整内容:
~/djangoreactproject/frontend/src/CustomerCreateUpdate.js
import React, { Component } from 'react'; import CustomersService from './CustomersService'; const customersService = new CustomersService(); class CustomerCreateUpdate extends Component { constructor(props) { super(props); this.handleSubmit = this.handleSubmit.bind(this); } componentDidMount(){ const { match: { params } } = this.props; if(params && params.pk) { customersService.getCustomer(params.pk).then((c)=>{ this.refs.firstName.value = c.first_name; this.refs.lastName.value = c.last_name; this.refs.email.value = c.email; this.refs.phone.value = c.phone; this.refs.address.value = c.address; this.refs.description.value = c.description; }) } } handleCreate(){ customersService.createCustomer( { "first_name": this.refs.firstName.value, "last_name": this.refs.lastName.value, "email": this.refs.email.value, "phone": this.refs.phone.value, "address": this.refs.address.value, "description": this.refs.description.value } ).then((result)=>{ alert("Customer created!"); }).catch(()=>{ alert('There was an error! Please re-check your form.'); }); } handleUpdate(pk){ customersService.updateCustomer( { "pk": pk, "first_name": this.refs.firstName.value, "last_name": this.refs.lastName.value, "email": this.refs.email.value, "phone": this.refs.phone.value, "address": this.refs.address.value, "description": this.refs.description.value } ).then((result)=>{ console.log(result); alert("Customer updated!"); }).catch(()=>{ alert('There was an error! Please re-check your form.'); }); } handleSubmit(event) { const { match: { params } } = this.props; if(params && params.pk){ this.handleUpdate(params.pk); } else { this.handleCreate(); } event.preventDefault(); } render() { return ( <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
组件,以添加指向我们创建的不同组件的链接。
第 9 步 — 更新主应用程序组件
在本节中,我们将更新应用程序的 App
组件,以创建指向我们在前面步骤中创建的组件的链接。
在 frontend
文件夹中,运行以下命令来安装 React Router,它允许您在各种 React 组件之间添加路由和导航:
cd ~/djangoreactproject/frontend npm install --save react-router-dom
接下来,打开~/djangoreactproject/frontend/src/App.js
:
nano ~/djangoreactproject/frontend/src/App.js
删除那里的所有内容并添加以下代码以导入添加路由所需的类。 其中包括创建路由组件的 BrowserRouter
和创建路由组件的 Route
:
~/djangoreactproject/frontend/src/App.js
import React, { Component } from 'react'; import { BrowserRouter } from 'react-router-dom' import { Route, Link } from 'react-router-dom' import CustomersList from './CustomersList' import CustomerCreateUpdate from './CustomerCreateUpdate' import './App.css';
BrowserRouter 使用 HTML5 历史 API 使 UI 与 URL 保持同步。
接下来,创建一个基本布局,提供要被 BrowserRouter
组件包装的基本组件:
~/djangoreactproject/frontend/src/App.js
... 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 应用程序的根或顶级组件:
~/djangoreactproject/frontend/src/App.js
... class App extends Component { render() { return ( <BrowserRouter> <BaseLayout/> </BrowserRouter> ); } } export default App;
我们已经用 BrowserRouter
组件包装了 BaseLayout
组件,因为我们的应用程序旨在在浏览器中运行。
完成的文件如下所示:
~/djangoreactproject/frontend/src/App.js
import React, { Component } from 'react'; import { BrowserRouter } from 'react-router-dom' import { Route, Link } from 'react-router-dom' import CustomersList from './CustomersList' import CustomerCreateUpdate from './CustomerCreateUpdate' import './App.css'; const BaseLayout = () => ( <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 集成的不同方法,请查看此 tutorial 和此 tutorial。
更多关于使用Django构建应用的信息,可以关注【X83X】Django开发系列【X112X】。 您还可以查看 官方 Django 文档 。