十四、React 和 Django

到目前为止,我们已经使用了很多 Express,但是 Django 提供了标准 Express 应用无法开箱即用的功能。 通过其内置的脚手架、数据库集成和模板工具,它确实提供了一个诱人的后端解决方案。 然而,正如我们所了解到的,JavaScript 在前端解决方案中具有优越的能力。 那么,我们怎样才能把这两者结合起来呢?

我们要做的是创建一个 Django 后端来服务 React 应用,将两种伟大的技术结合在一起。

本章将涵盖以下主题:

  • Django 的设置
  • 创建 React 前端
  • 把它们放在一起

技术要求

准备使用存储库的chapter-14目录中提供的代码,可以在https://github.com/PacktPublishing/Hands-on-JavaScript-for-Python-Developers/tree/master/chapter-14上获得。 由于我们将使用命令行工具,还需要提供终端或命令行 shell。 我们需要一个现代化的浏览器和本地代码编辑器。

Django 的设置

有几种不同的方法可以组合 React 和 Django,其复杂性和集成级别各不相同。 我们将采用的方法是将 React 作为 Django 应用的前端,加载一个模板,然后让 React 处理前端。 然后,我们将使用一个标准的 Ajax 调用来与 Django 路由和数据存储逻辑交互。 这是将两种技术结合在一起的中庸方法,有点羞于将它们完全分开,但也没有为每种方法创建一个 React 应用。 我们将保持简单。

请问,我们要做什么呢? 说!

我们的应用将是一个聊天机器人,它将用剧作家大师莎士比亚的语言回应输入! 首先,我们将加载一个简单的 Django 实例的数据库,其中包含莎士比亚的完整文本; 接下来,我们将编写路由来搜索数据库中匹配的文本; 最后,我们将创建 React 应用作为用户和 Django 后台之间的管道。 我们不会对我们的 python 着迷——没有机器学习或复杂的语言处理等着我们,不过如果你愿意,你总是可以让我们的机器人更进一步!

Note that we'll be using Python 3. For more detailed information about installing and setting up Django, including use of virtual environments, visit the official documentation at https://docs.djangoproject.com/en/3.0/topics/install/.

首先,让我们按照以下步骤来设置 Django:

  1. 创建一个新的虚拟环境:python -m venv shakespeare
  2. 启动venv:source shakespeare/bin/activate
  3. 安装 Django:python -m pip install Django
  4. django-admin startproject shakespearebot开始一个新项目。
  5. 测试 Django 设置:cd shakespearebot ; python manage.py runserver
  6. 如果我们访问http://127.0.0.1:8000/,我们应该会看到默认的 Django 欢迎页面。
  7. 我们将需要一个应用工作:python manage.py startapp bot
  8. settings.py:'bot.apps.BotConfig'中将机器人 app 添加到INSTALLED_APPS中。

接下来,我们需要我们的莎士比亚数据集:

  1. 本书 GitHub 存储库中的chapter-14目录包含一个名为Shakespeare_data.csv.zip的文件。 解压缩这个文件,然后点击 voilà,你就可以轻松找到所有的莎士比亚作品集了。 我们会用一个基本模型把这个 CSV 导入 Django。
  2. bot目录下编辑models.py,如下所示:
from django.db import models

class Text(models.Model):
  PlayerLine = models.CharField(max_length=1000)

   def __str__(self):
       return self.PlayerLine

我们将保持数据库的简单性,只接收文本行,而不接收围绕该行内容的任何其他数据。 毕竟,我们只是在语料库上做一个简单的文本搜索,没有比这更复杂的了。 在我们导入数据的下一步之前,让我们加入一个 Django 模块来简化我们的工作:pip install django-import-export。 与命令行过程相比,这个模块允许我们轻松地导入文本,只需点击几下即可。

现在我们有了一个模型,我们需要在admin.py中注册它:

from import_export.admin import ImportExportModelAdmin
from django.contrib import admin
from .models import Text

@admin.register(Text)
class TextAdmin(ImportExportModelAdmin):
   pass

让我们登录到 Django 的管理部分,以确保一切正常运行。 我们必须首先运行数据库命令:

  1. 准备数据库命令:python manage.py makemigrations
  2. 接下来,使用python manage.py migrate执行更改。
  3. 使用python manage.py createsuperuser创建一个管理用户,并按照提示操作。 请注意,当您创建密码时,您将看不到输入,尽管它正在使用您的输入。
  4. 重启 Django:python manage.py runserver
  5. 访问http://127.0.0.1/admin,并使用您刚刚创建的凭据登录。

我们将看到我们的管理面板与我们的 bot 应用:

Figure 14.1 - Django's site administration panel

太好了,那只是个检查站。 我们还有一点工作要做! 既然我们有django-import-export,让我们把它连接起来:

settings.py文件中执行以下操作:

  1. import_export加入INSTALLED_APPS
  2. 正确的路径我们的静态文件,在设置部分的最后一行:STATIC_ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'static')
  3. 运行python manage.py collectstatic

现在你可以继续,点击管理面板中的文本,你会看到导入和导出按钮可供你使用:

Figure 14.2 - It's time to import our text!

单击 IMPORT 按钮,按照以下步骤导入包含莎士比亚文本的 CSV 文件:

Figure 14.3 - Import complete Note: The import will take a while, but not as long as it took Will to write them in the first place! Be sure to confirm the import after the preview.

我们请求路由

在 React 之前,我们需要构建的下一个部分是 API 它将内容提供给前端。 让我们看看这些步骤:

  1. nbot/views.py,设置索引路由,我们将用于测试,和 API 路由,我们将提供我们的信息:
from django.http import HttpResponse
from django.template import Context, loader
from bot.models import Text
import random
import json

def index(request):
   template = loader.get_template("bot/index.html")
   return HttpResponse(template.render())

def api(request):
   if request.method == 'POST':
       data = json.loads(request.body.decode("utf8"))
       query = data['chattext']
       responses = Text.objects.filter(PlayerLine__contains=" %s " 
       % (query))

   if len(responses) > 0:
       return HttpResponse(responses[random.randint(0,
       len(responses))])

   else:
       return HttpResponse("Get thee to a nunnery!")

所有这些都应该是直截了当的 Python,所以我们不会过多地讨论细节。 本质上,当我们向 API 发送一个 POST 请求时,Django 会在数据库中搜索包含通过 Ajax 发送的单词的一行文本。 如果它找到一个或多个,那么它将随机返回一个到前端。 如果不是,我们总是想处理我们的错误情况,所以它将以哈姆雷特的著名诗句回应,“带你去修道院!”

  1. 创建一个文件bot/urls.py,并插入以下代码:
from django.urls import path

from . import views

urlpatterns = [
 path('', views.index, name='index'),
 path('api', views.api, name='api'),
]
  1. 编辑shakespearebot/urls.py如下:
from django.contrib import admin
from django.urls import path, include
import bot

urlpatterns = [
   path('admin/', admin.site.urls),
   path('api/', include('bot.urls')),
   path('', include('bot.urls')),
]
  1. 还有一件事:在shakespearebot/settings.py中,移除 CSRF 中间件如下:
'django.middleware.csrf.CsrfViewMiddleware',
  1. 现在是有趣的部分:我们的测试前端。 创建一个名为bot/的文件

templates/bot/index.html并添加以下 HTML 设置:

<!DOCTYPE html>

<html>

<head>
 <style>
 textarea {
 height: 500px;
 width: 300px;
 }
 </style>
</head>

<body>
 <form method="POST" type="" id="chat">
 <input type="text" id="chattext"></textarea>
 <button id="submit">Chat</button>
 <textarea id="chatresponse"></textarea>
 </form>

</body>

</html>

在这里,我们可以看到一些基本的表单和一些样式—没有太多内容,因为这只是一个用来测试我们对 API 理解是否正确的页面。

  1. 在表单后面插入脚本:
<script>
   document.getElementById('submit').addEventListener('click', (e) 
   => {
     e.preventDefault()

     let term = document.getElementById('chattext').value.split('
      ')
     term = term[term.length - 2] || term[0]

     fetch("/api", {
       method: "POST",
       headers: {
         'Content-Type': 'application/json'
       },
       body: JSON.stringify({ chattext: term })
     })
       .then(response => response.text())
       .then(data => document.querySelector('#chatresponse').value
        += `\n${data}\n`)
   })
 </script>

现在,fetch 调用的结构应该很熟悉了,所以让我们快速地浏览一下:当单击按钮时,将文本分割为空格,选择倒数第二个单词(最后一个“单词”可能是标点符号),或者,如果是一个单词条目,则选择单词本身。 POST此术语将 API 并等待响应。

如果一切正常,我们将看到一个令人激动的篇章:

Figure 14.4 - It's a start!

这并不多,但应该足以测试我们的后端。 试着在聊天框中输入几个词,点击聊天,看看会发生什么。 希望你能听到一些很久以前在埃文河畔的斯特拉特福听到过的词语。

创建 React 前端

如前所述,有几种不同的方法来使用 Django 和 React。 我们会单独设置前端让 React 做它自己的事,让 Django 做它自己的事,让它们在中间握手。 正如我们将看到的,这种方法确实有其局限性,但这只是一个基本的介绍。 我们稍后会变得更复杂。

让我们来设置它,首先创建一个新的 React 应用:

  1. 切换到shakespearebot目录(不是bot),执行npx create-react-app react-frontend

  2. 继续执行cd react-frontend && yarn start并在http://localhost:3000访问开发服务器,以确保一切正常。 您应该在前面的 URL 处收到 React 演示页面。 使用Ctrl+C停止服务器。

  3. 执行yarn build

现在,事情就有点有限了。 我们现在所做的是执行创建站点的产品优化构建。 这是为了发布代码而设计的,而不是开发代码,所以限制是,如果不再次运行构建,您就不能编辑代码并将其反映出来。 记住这一点,让我们构建并继续我们的设置。

在我们的shakespearebot目录中,我们将对settings.pyurls.py做一些编辑:

  1. settings.pyTEMPLATES数组中,将DIRS改为'DIRS': [os.path.join(BASE_DIR, 'react-frontend')],
  2. settings.py中修改STATIC_URLSTATICFILES_DIRS变量如下:
STATIC_URL = '/static/'
STATICFILES_DIRS = (
 os.path.join(BASE_DIR, 'react-frontend', 'build', 'static'),

)
  1. urls.py添加一行,使urlpatterns数组读取如下内容:
urlpatterns = [
   path('admin/', admin.site.urls),
   path('api/', include('bot.urls')),
   path('', include('bot.urls')),
]
  1. bot目录中,是时候将前端指向静态目录了。 首先,编辑urls.py,创建一个urlpatterns节,如下所示:
urlpatterns = [
    path('api', views.api, name='api'),
    path('', views.index, name='index'),
]
  1. 接下来,我们的视图将需要静态目录的路径。 bot/views.py将需要改变index路线来使用我们的 React 前端:
def index(request):
    return render(request, "../react-frontend/build/index.html")

这应该是我们需要的。 通过运行python manage.py runserver在根级别启动服务器,然后访问http://127.0.0.1:8000,然后祈祷! 您应该看到 React 欢迎页面! 恭喜你如果是这样; 我们准备好继续了。 如果您有任何问题,请随时咨询 GitHub repo 上的第二个路径点目录。

脚手架完成后,让我们看一个 React 与 Django 对话的完整示例。

把它们放在一起

我们将与一个完整的莎士比亚机器人与前端和后端。 继续并导航到shakespearebot-complete目录。 在接下来的步骤中,我们将设置我们的应用,导入我们的数据,并与前端交互:

  1. 首先,用python manage.py migrate运行 Django 迁移,然后用python manage.py createsuperuser创建一个用户。
  2. 使用python manage.py runserver启动服务器。
  3. 登录http://localhost:8000/admin
  4. 导航到http://localhost:8000/admin/bot/text/并导入Shakespeare_text.csv文件(这将花费一些时间)。
  5. 在导入时,我们可以继续使用cd react-frontend命令检查前端。
  6. yarn install安装我们的依赖项。
  7. 使用yarn start启动服务器。
  8. 现在,如果你导航到http://localhost:3000,我们应该看到我们的前端:

Figure 14.5 - Our complete Shakespearebot

  1. 使用Ctrl+C停止开发服务器。
  2. 执行yarn build
  3. 当导入完成时,我们可以访问我们的前端,我们应该能够通过在框中输入文本并单击“现在说话”按钮与莎士比亚进行互动。 http://localhost:8000/

乐趣! 它有点粗糙,肯定可以从更多的 CSS 工作中受益,并通过自然语言处理在后端智能,但这不是我们现在的目标。 我们取得了什么成就? 为了创建一个完整的应用,我们利用了我们的 Python 知识并将其与 React 结合起来。 在下一节中,我们将进一步了解应用的 React 部分。

研究 React 前端

我们的 React 前端目录结构相当简单:

.
├── App.css
├── App.js
├── App.test.js
├── components
   ├── bot
    └── bot.jsx
   ├── chatpanel
    ├── chatpanel.css
    └── chatpanel.jsx
   └── talkinghead
       ├── shakespeare.png
       ├── talkinghead.css
       └── talkinghead.jsx
├── css
   ├── parchment.jpg
   └── styles.css
├── index.css
├── index.js
├── logo.svg
├── serviceWorker.js
└── setupTests.js

就像其他的 React 应用一样,我们将从根组件开始,在本例中是App.js:

import React from 'react';
import Bot from './components/bot/bot';
import './App.css';
import './css/styles.css'

function App() {
 return (
   <>
     <h1>Banter with the Bard</h1>
     <Bot />
   </>
 );
}

export default App;

到目前为止,它很简单:一个组件。 让我们看看components/bot/bot.jsx:

import React from 'react'
import TalkingHeadLayout from '../talkinghead/talkinghead'
import ChatPanel from '../chatpanel/chatpanel'
import { Col, Row, Container } from 'reactstrap'

export default class Bot extends React.Component {
 constructor() {
   super()

   this.state = {
     text: [
       "Away, you starvelling, you elf-skin, you dried neat's-tongue, 
        bull's-pizzle, you stock-fish!",
       "Thou art a boil, a plague sore.",
       "Speak, knave!",
       "Away, you three-inch fool!",
       "I scorn you, scurvy companion.",
       "Thou sodden-witted lord! Thou hast no more brain than I have in 
        mine elbows",
       "I am sick when I do look on thee",
       "Methink'st thou art a general offence and every man should beat 
        thee."
     ]
   }

   this.captureInput = this.captureInput.bind(this)
 }

到目前为止,除了常规设置外,还没有什么真正令人兴奋的事情:我们导入了reactstrap,我们将使用它来帮助布局,并定义一个文本数组,其中包含一些莎士比亚式的侮辱选项。 最后一行与captureInput方法有关。 它是这样的:

captureInput(e) {
   const question = document.querySelector('#question').value
   fetch(`/api?chattext="${question}"`)
     .then((response) => response.text())
     .then((data) => {
       this.setState({
         text: `${data}`
       })
     })
 }

可爱的! 我们知道它在做什么:它是对同一个服务器的标准 Ajax 调用,其中包含一个带有问题的 GET 请求。 这与我们在 Python 中完成这一切的时候有一点不同,因为我们使用 GET 而不是 POST 来方便设置,但这是一个微不足道的区别。

下一部分是简单的渲染:

render() {
   const { text } = this.state

   return (
     <div className="App">
       <Container>
         <Row>
           <Col>
             <ChatPanel speak={this.captureInput} />
           </Col>
           <Col>
             <TalkingHeadLayout response={text} />
           </Col>
         </Row>
       </Container>
     </div>
   )
 }
}

我们的说话头有一个动画效果,我们已经完成了一个 Node.js 模块在components/talkinghead/talkinghead.jsx:

import React from 'react'
import ReactTypingEffect from 'react-typing-effect';

import './talkinghead.css'
import TalkingHead from './shakespeare.png'

export default class TalkingHeadLayout extends React.Component {
 render() {
   return (
     <div id="talkinghead">
       <div className="text">
         <ReactTypingEffect text={this.props.response} speed="50" 
          typingDelay="0" />
       </div>
       <img src={TalkingHead} alt="Speak, knave!" />
     </div>
   )
 }
}

这就是我们的应用的全部内容!

我们在这一章中获得了一些乐趣,所以让我们回顾一下我们所学到的东西。

总结

虽然我们的关注点主要是通过选择 Node.js 和 Express 而不是 Python 和 Django 来摆脱 Python,但集成它们绝对是可行的。 这里我们使用一个特定的范式:一个反应应用坐在作为一个静态应用在 Django 应用。Django 应用路由 HTTP 请求 APIbot应用如果/api在 URL 中,或反应react-frontend应用一切。

将 Django 与 React 结合并不是世界上最简单的事情,而这只是将两者结合的一个可能的范例,我称之为紧密耦合脚手架。 如果我们让 React 和 Django 应用完全分离,只通过 XHR 调用与 Ajax 进行交互,这可能会是一个更真实的场景。 然而,这将涉及到为两个部分分别设置,而今天我们构建的是整个应用的单个服务器。

在下一章中,我们将使用 Express 和 React 进行更直接的互补技术应用。