如何使用Django构建现代Web应用程序来管理客户信息并在Ubuntu 18.04上进行反应

在本教程中,您将使用React,Django和Django REST Framework构建一个带有单独REST API后端和前端的现代Web应用程序。通过将React与Django一起使用,您将能够从JavaScript和前端开发的最新进展中受益。您将构建的Web应用程序在数据库中存储有关客户的记录,您可以将其用作CRM应用程序的起点。

作者选择了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接口创建,读取,更新和删除记录。

先决条件

要完成本教程,您需要:

第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-namedjango-admin是一个命令行实用程序,用于完成Django的任务。 startproject命令创建一个新的Django项目。

  • python manage.py startapp myappmanage.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文件夹是项目的根目录。 在此文件夹中,有几个对您的工作很重要的文件:

  • 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_frameworkcorsheaders应用程序添加到列表的底部:

〜/ djangoreactproject / djangoreactproject / settings.py
...
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'corsheaders'
]

接下来,将corsheaders.middleware.CorsMiddleware中间件从先前安装的CORS包添加到MIDDLEWARE设置。 此设置是中间件列表,这是一个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 = FalseCORS_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

这将包含管理客户的模型视图 模型定义应用程序数据的字段和行为,而视图使我们的应用程序能够正确处理Web请求并返回所需的响应。

接下来,将此应用程序添加到项目的settings.py文件中已安装的应用程序列表中,以便Django将其识别为项目的一部分。 再次打开settings.py

nano ~/djangoreactproject/djangoreactproject/settings.py

添加customers应用程序:

〜/ djangoreactproject / djangoreactproject / settings.py
...
INSTALLED_APPS = [
    ...
    'rest_framework',
    'corsheaders',
    'customers'
]
...

接下来, 迁移数据库并启动本地开发服务器。 迁移是Django将您对模型所做的更改传播到数据库模式的方法。 例如,这些更改可能包括添加字段或删除模型等内容。 有关模型和迁移的更多信息,请参见如何创建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浏览器中导航到此地址,您应该看到以下页面:

Django演示页面

此时,让应用程序继续运行并打开一个新终端以继续开发项目。

第3步 - 创建React前端

在本节中,我们将使用React创建项目的前端应用程序。

React有一个官方实用程序,允许您快速生成React项目,而无需直接配置Webpack Webpack是一个模块捆绑器,用于捆绑Web资产,如JavaScript代码,CSS和图像。 通常,在使用Webpack之前,您需要设置各种配置选项,但是由于create-react-app实用程序,在您决定需要更多控制之前,您不必直接处理Webpack。 要运行create-react-app您可以使用npx ,这是一个执行npm包二进制文件的工具。

在第二个终端中,确保您在项目目录中:

cd ~/djangoreactproject

使用create-react-appnpx创建一个名为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接口的样式,因此我们将它包含在frontend/src/App.css文件中,该文件管理我们的CSS设置。 打开文件:

nano ~/djangoreactproject/frontend/src/App.css

将以下导入添加到文件的开头。 您可以删除文件的现有内容,但这不是必需的:

〜/ djangoreactproject /前端/ 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

该文件将包含以下内容:

〜/ djangoreactproject /客户/ 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中构造类和定义对象

将以下代码添加到文件中:

〜/ djangoreactproject /客户/ 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

您将获得如下所示的输出:

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

这是文件的初始内容:

〜/ djangoreactproject /客户/迁移/ 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类的定义之前添加以下方法:

〜/ djangoreactproject /客户/迁移/ 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对象表示已安装应用程序及其数据库模型的注册表

当我们使用它来运行create_data()时,将从RunPython()方法传递apps对象。 migrations.RunPython()方法添加到空operations列表:

〜/ djangoreactproject /客户/迁移/ 0002_customers.py

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

RunPython()是Migrations API的一部分,允许您在迁移中运行自定义Python代码。 我们的operations列表指定在应用迁移时将执行此方法。

这是完整的文件:

〜/ djangoreactproject /客户/迁移/ 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

您将看到确认迁移的输出:

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

有关此过程的更多详细信息,请参阅如何创建Django模型

通过创建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/customersapi/customers/<pk> )。

让我们从为Customer模型创建序列化器类开始。

添加Serializer类

为我们的Customer模型创建序列化程序类是将客户实例和QuerySet转换为JSON和从JSON转换的必要条件。 要创建序列化程序类,首先在customers应用程序中创建一个serializers.py文件:

cd ~/djangoreactproject/customers/
nano serializers.py

添加以下代码以导入序列化程序API和Customer模型:

〜/ djangoreactproject /客户/ serializers.py
from rest_framework import serializers
from .models import Customer

接下来,创建一个扩展serializers.ModelSerializer的序列化程序类,并指定将被序列化的字段:

〜/ djangoreactproject /客户/ serializers.py

...
class CustomerSerializer(serializers.ModelSerializer):

    class Meta:
        model = Customer 
        fields = ('pk','first_name', 'last_name', 'email', 'phone','address','description')

Meta类指定要序列化的模型和字段: pkfirst_namelast_nameemailphoneaddressdescription

这是文件的完整内容:

〜/ djangoreactproject /客户/ 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 /客户/ 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 /客户/ 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视图。 装饰器是一个函数,它接受另一个函数并动态扩展它。

在方法体中,我们使用request.method变量来检查当前的HTTP方法,并根据请求类型执行相应的逻辑:

  • 如果是GET请求,则该方法使用Django Paginator对数据进行分页,并返回序列化后的第一页数据,可用客户的数量,可用页面的数量以及前一页和下一页的链接。 Paginator是一个内置的Django类,它将数据列表分页到页面中,并提供访问每个页面的项目的方法。
  • 如果是POST请求,则该方法序列化收到的客户数据,然后调用序列化程序对象的save()方法。 然后它返回一个Response对象,一个HttpResponse实例,带有201状态代码。 您创建的每个视图都负责撤销HttpResponse对象。 save()方法将序列化数据保存在数据库中。

有关HttpResponse和视图的更多信息,请参阅有关创建视图函数的讨论。

现在添加API视图,该视图将负责处理通过pk (主键)获取,更新和删除客户的GET,PUT和DELETE请求:

〜/ djangoreactproject /客户/ 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请求,则该方法调用customer对象的delete()方法将其删除,然后返回一个没有数据的Response对象。

完成的文件如下所示:

〜/ djangoreactproject /客户/ 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

使用以下axiosnpm安装axios

npm install axios --save

--save选项将axios依赖项添加到应用程序的package.json文件中。

接下来,创建一个名为CustomersService.js的JavaScript文件,该文件将包含调用REST API的代码。 我们将在src文件夹中创建它,我们项目的应用程序代码将存在于该文件夹中:

cd src
nano CustomersService.js

添加以下代码,其中包含连接到Django REST API的方法:

〜/ djangoreactproject /前端/ 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

首先导入ReactComponent以创建React组件:

〜/ djangoreactproject /前端/ SRC / CustomersList.js
import  React, { Component } from  'react';

接下来,导入并实例化您在上一步中创建的CustomersService模块,该模块提供与REST API后端交互的方法:

〜/ djangoreactproject /前端/ SRC / CustomersList.js

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

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

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:

~/djangoreactproject/frontend/src/CustomersList.js

...
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() :

~/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})
    });
}

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:

~/djangoreactproject/frontend/src/CustomersList.js

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

~/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>
    );
}

This is the full content of the file:

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

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 :

~/djangoreactproject/frontend/src/CustomerCreateUpdate.js
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:

~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

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

const  customersService  =  new  CustomersService();

Next, create a CustomerCreateUpdate component that extends Component to create and update customers:

~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

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

~/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>
        );
  }

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:

~/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();
}

...

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:

~/djangoreactproject/frontend/src/CustomerCreateUpdate.js
...
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:

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

...

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:

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

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:

~/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;
        })
    }
}

This is the full content of the file:

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

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:

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

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

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:

~/djangoreactproject/frontend/src/App.js
...

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:

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

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:

Application Home Page

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 .


分享按钮