如何使用 Django 和 React 构建待办事项应用程序

简介

在本教程中,您将使用Django和React构建一个To-Do应用程序。

Reaction是一个用于开发SPA(单页面应用程序)的JavaScript框架。它拥有Solid documentation]和一个充满活力的生态系统。

Django是一个Python Web框架,简化了Web开发中的常见实践。Django是可靠的,也有一个由稳定的库组成的充满活力的生态系统,支持共同的开发需求。

对于该应用程序,React充当前端或客户端框架,处理用户界面并通过对Django后端的请求获取和设置数据,Django后端是使用Django rest框架(DRF)构建的API。

At the end of this tutorial, you will have a fully working application:

用户与应用程序交互以创建新任务并在完成和不完成statuses.之间切换的动画gif

<$>[备注] 注意: 本教程的源代码可在GitHub.上找到 <$>

<$>[信息] 如果您想要部署本教程中的应用程序,您可以使用DigitalOcean App Platform.]直接从GitHub Repo进行部署 <$>

该应用程序将允许用户创建任务并将其标记为已完成或未完成。

前提条件

要遵循本教程,您需要:

1.安装和设置PYTHON 3的本地编程环境 2.安装Node.js并创建本地开发Environment

本教程通过了Python v3.9.1、piv20.2.4、Django v3.1.6、djangorest Frameworkv3.12.2、django-cors-headersv3.7.0、Node v15.8.0、npmv7.5.4、Reaction v17.0.1和axiosv0.21.0的验证。

第一步-设置后台

在本节中,您将创建一个新的项目目录并安装Django。

打开一个新的终端窗口,运行以下命令创建一个新的项目目录:

1mkdir django-todo-react

接下来,导航到目录:

1cd django-todo-react

现在使用piap安装Pipenv:

1pip install pipenv

<$>[备注] 注意: 根据您的安装情况,您可能需要使用pip3而不是bin。 <$>

并激活新的虚拟环境:

1pipenv shell

使用Pipenv安装Django:

1pipenv install django

然后创建一个名为backend的新工程:

1django-admin startproject backend

接下来,导航到新创建的后端目录:

1cd backend

启动一个名为todo的新应用:

1python manage.py startapp todo

运行迁移:

1python manage.py migrate

并启动服务器:

1python manage.py runserver

在您的Web浏览器中导航到http://localhost:8000

默认Django应用程序成功运行的屏幕截图。

此时,您将看到Django应用程序的一个实例成功运行。完成后,您可以停止服务器(CONTROL+CCTRL+C)。

注册todo应用

现在您已经完成了后台的设置,您可以开始将todo应用注册为已安装的应用程序,以便Django可以识别它。

在代码编辑器中打开Backend/settings.py文件,将todo添加到INSTALLED_APPS中:

 1[label backend/settings.py]
 2# Application definition
 3
 4INSTALLED_APPS = [
 5    'django.contrib.admin',
 6    'django.contrib.auth',
 7    'django.contrib.contenttypes',
 8    'django.contrib.sessions',
 9    'django.contrib.messages',
10    'django.contrib.staticfiles',
11    'todo',
12]

然后,保存您的更改。

定义Todo模型

让我们创建一个模型来定义Todo项应该如何存储在数据库中。

在代码编辑器中打开todo/mods.py文件,并添加以下代码行:

 1[label todo/models.py]
 2from django.db import models
 3
 4# Create your models here.
 5
 6class Todo(models.Model):
 7    title = models.CharField(max_length=120)
 8    description = models.TextField()
 9    completed = models.BooleanField(default=False)
10
11    def _str_(self):
12        return self.title

上面的代码片段描述了TODO模型的三个属性:

  • 标题
  • 描述
  • 已完成

Complete‘属性是任务的状态。任务可以随时完成,也可以随时不完成。由于您已经创建了Todo`模型,因此需要创建迁移文件:

1python manage.py makemigrations todo

并将更改应用到数据库:

1python manage.py migrate todo

您可以使用Django默认提供的管理界面来测试CRUD操作是否在您创建的Todo模型上工作。

使用代码编辑器打开todo/admin.py文件,并添加以下代码行:

 1[label todo/admin.py]
 2from django.contrib import admin
 3from .models import Todo
 4
 5class TodoAdmin(admin.ModelAdmin):
 6    list_display = ('title', 'description', 'completed')
 7
 8# Register your models here.
 9
10admin.site.register(Todo, TodoAdmin)

然后,保存您的更改。

您需要创建一个超级用户帐户才能访问管理界面。在您的终端中运行以下命令:

1python manage.py createsuperuser

系统将提示您输入超级用户的用户名、电子邮件和密码。确保输入您可以记住的详细信息,因为您将需要它们登录到管理仪表板。

再次启动服务器:

1python manage.py runserver

在您的Web浏览器中导航到http://localhost:8000/admin。并使用先前创建的用户名和密码登录:

Django application.的管理界面屏幕截图

您可以使用该界面创建、编辑、删除Todo项:

显示TODO items.的Django应用程序管理界面的屏幕截图

体验该界面后,您可以停止服务器(CONTROL+CCTRL+C)。

第二步-设置接口

在本节中,您将使用Django REST框架创建一个API。

使用Pipenv安装djangorest Frameworkdjango-cors-headers

1pipenv install djangorestframework django-cors-headers

您需要在已安装的应用列表中添加rest_Frameworkcorsheaders。在代码编辑器中打开Backend/settings.py文件,更新INSTALLED_APPSMIDDLEWARE部分:

 1[label backend/settings.py]
 2# Application definition
 3
 4INSTALLED_APPS = [
 5    'django.contrib.admin',
 6    'django.contrib.auth',
 7    'django.contrib.contenttypes',
 8    'django.contrib.sessions',
 9    'django.contrib.messages',
10    'django.contrib.staticfiles',
11    'corsheaders',
12    'rest_framework',
13    'todo',
14]
15
16MIDDLEWARE = [
17    'django.middleware.security.SecurityMiddleware',
18    'django.contrib.sessions.middleware.SessionMiddleware',
19    'django.middleware.common.CommonMiddleware',
20    'django.middleware.csrf.CsrfViewMiddleware',
21    'django.contrib.auth.middleware.AuthenticationMiddleware',
22    'django.contrib.messages.middleware.MessageMiddleware',
23    'django.middleware.clickjacking.XFrameOptionsMiddleware',
24    'corsheaders.middleware.CorsMiddleware',
25]

然后,将以下几行代码添加到backend/settings.py文件的底部:

1[label backend/settings.py]
2CORS_ORIGIN_WHITELIST = [
3     'http://localhost:3000'
4]

django-cors-headers是一个Python库,它将防止您通常会因为CORS规则而出现的错误。在CORS_ORIGIN_WHITELIST代码中,您将localhost:3000列入白名单,因为您希望应用程序的前端(将在该端口上服务)与API交互。

创建序列化程序

您将需要序列化程序将模型实例转换为JSON,以便前端可以处理接收到的数据。

使用代码编辑器创建一个todo/Serializers.py文件。打开Serializers.py文件,并使用以下代码行更新该文件:

1[label todo/serializers.py]
2from rest_framework import serializers
3from .models import Todo
4
5class TodoSerializer(serializers.ModelSerializer):
6    class Meta:
7        model = Todo
8        fields = ('id', 'title', 'description', 'completed')

此代码指定要使用的模型和要转换为JSON的字段。

创建视图

您需要在todo/views.py文件中创建一个TodoView类。

使用代码编辑器打开todo/views.py文件,并添加以下代码行:

 1[label todo/views.py]
 2from django.shortcuts import render
 3from rest_framework import viewsets
 4from .serializers import TodoSerializer
 5from .models import Todo
 6
 7# Create your views here.
 8
 9class TodoView(viewsets.ModelViewSet):
10    serializer_class = TodoSerializer
11    queryset = Todo.objects.all()

默认情况下,viewsets基类提供CRUD操作的实现。该代码指定了Serializer_Classqueryset

使用代码编辑器打开Backend/urls.py文件,并将其内容替换为以下代码行:

 1[label backend/urls.py]
 2from django.contrib import admin
 3from django.urls import path, include
 4from rest_framework import routers
 5from todo import views
 6
 7router = routers.DefaultRouter()
 8router.register(r'todos', views.TodoView, 'todo')
 9
10urlpatterns = [
11    path('admin/', admin.site.urls),
12    path('api/', include(router.urls)),
13]

此代码指定API的URL路径。这是完成API构建的最后一步。

您现在可以在Todo模型上执行CRUD操作。router类允许您进行以下查询:

  • /todos/-返回所有Todo项的列表。这里可以进行CREATEREAD操作。
  • /todos/id-使用id主键返回单个Todo项。这里可以进行UPDATEDELETE操作。

让我们重新启动服务器:

1python manage.py runserver

在Web浏览器中导航到http://localhost:8000/api/todos

!【Todo项目API结果截图】(https://assets.digitalocean.com/articles/build-a-to-do-application-using-django-and-react/n9m3vnno99gawpa12xbi.png)

您可以使用接口`CREATE‘新的TODO项:

创建新的TODO items.接口工具截图

如果TODO项创建成功,您将看到一个成功的响应:

Todo项目创建成功的API响应截图。

您还可以使用id主键对特定的Todo项进行DELETEUPDATE操作。使用地址结构/api/todos/{id},并提供id

1添加到URL中,以检查id1的Todo项目。在Web浏览器中导航到http://localhost:8000/api/todos/1

Delete和PUT.接口工具截图

这就完成了应用程序后端的构建。

Step 3 -设置前端

现在您已经完成了应用程序的后端,您可以创建前端,并让它通过您创建的接口与后端通信。

首先,打开一个新的终端窗口并导航到django-todo-react项目目录。

为了设置前端,本教程将依赖于Create Reaction App。有几种方法可以使用Create-Reaction-app。一种方法是使用npx运行包并创建工程:

1npx create-react-app frontend

您可以通过阅读如何使用创建React应用程序设置React项目了解更多有关此方法的信息。

工程创建完成后,您可以切换到新创建的Frontend目录:

1cd frontend

然后,启动应用程序:

1npm start

您的Web浏览器将打开http://localhost:3000,您将看到默认的Create React App屏幕:

默认创建Reaction应用程序application.的登录页面屏幕截图

接下来,安装bootstrapreactstrap以提供用户界面工具。

1npm install [email protected] [email protected] --legacy-peer-deps

<$>[备注] 注意: 根据您的Reaction、Bootstrap和Reactstrap版本不同,可能会遇到Unable to Resolve Tree错误。

在进行修订时,popper.js的最新版本已被弃用,并将与Reaction 17+发生冲突。这是一个[已知的传统],安装时可以使用--issue](https://github.com/reactstrap/reactstrap/issues/2045)-Peer-dess选项。 <$>

在代码编辑器中打开index.js,添加bootstrap.min.css

 1[label frontend/src/index.js]
 2import React from 'react';
 3import ReactDOM from 'react-dom';
 4import 'bootstrap/dist/css/bootstrap.css';
 5import './index.css';
 6import App from './App';
 7import reportWebVitals from './reportWebVitals';
 8
 9ReactDOM.render(
10  <React.StrictMode>
11    <App />
12  </React.StrictMode>,
13  document.getElementById('root')
14);
15
16// If you want to start measuring performance in your app, pass a function
17// to log results (for example: reportWebVitals(console.log))
18// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
19reportWebVitals();

如果您在这一步遇到困难,您可以参考官方文档添加bootstrap.

在代码编辑器中打开App.js并添加以下代码行:

  1[label frontend/src/App.js]
  2import React, { Component } from "react";
  3
  4const todoItems = [
  5  {
  6    id: 1,
  7    title: "Go to Market",
  8    description: "Buy ingredients to prepare dinner",
  9    completed: true,
 10  },
 11  {
 12    id: 2,
 13    title: "Study",
 14    description: "Read Algebra and History textbook for the upcoming test",
 15    completed: false,
 16  },
 17  {
 18    id: 3,
 19    title: "Sammy's books",
 20    description: "Go to library to return Sammy's books",
 21    completed: true,
 22  },
 23  {
 24    id: 4,
 25    title: "Article",
 26    description: "Write article on how to use Django with React",
 27    completed: false,
 28  },
 29];
 30
 31class App extends Component {
 32  constructor(props) {
 33    super(props);
 34    this.state = {
 35      viewCompleted: false,
 36      todoList: todoItems,
 37    };
 38  }
 39
 40  displayCompleted = (status) => {
 41    if (status) {
 42      return this.setState({ viewCompleted: true });
 43    }
 44
 45    return this.setState({ viewCompleted: false });
 46  };
 47
 48  renderTabList = () => {
 49    return (
 50      <div className="nav nav-tabs">
 51        <span
 52          className={this.state.viewCompleted ? "nav-link active" : "nav-link"}
 53          onClick={() => this.displayCompleted(true)}
 54        >
 55          Complete
 56        </span>
 57        <span
 58          className={this.state.viewCompleted ? "nav-link" : "nav-link active"}
 59          onClick={() => this.displayCompleted(false)}
 60        >
 61          Incomplete
 62        </span>
 63      </div>
 64    );
 65  };
 66
 67  renderItems = () => {
 68    const { viewCompleted } = this.state;
 69    const newItems = this.state.todoList.filter(
 70      (item) => item.completed == viewCompleted
 71    );
 72
 73    return newItems.map((item) => (
 74      <li
 75        key={item.id}
 76        className="list-group-item d-flex justify-content-between align-items-center"
 77      >
 78        <span
 79          className={`todo-title mr-2 ${
 80            this.state.viewCompleted ? "completed-todo" : ""
 81          }`}
 82          title={item.description}
 83        >
 84          {item.title}
 85        </span>
 86        <span>
 87          <button
 88            className="btn btn-secondary mr-2"
 89          >
 90            Edit
 91          </button>
 92          <button
 93            className="btn btn-danger"
 94          >
 95            Delete
 96          </button>
 97        </span>
 98      </li>
 99    ));
100  };
101
102  render() {
103    return (
104      <main className="container">
105        <h1 className="text-white text-uppercase text-center my-4">Todo app</h1>
106        <div className="row">
107          <div className="col-md-6 col-sm-10 mx-auto p-0">
108            <div className="card p-3">
109              <div className="mb-4">
110                <button
111                  className="btn btn-primary"
112                >
113                  Add task
114                </button>
115              </div>
116              {this.renderTabList()}
117              <ul className="list-group list-group-flush border-top-0">
118                {this.renderItems()}
119              </ul>
120            </div>
121          </div>
122        </div>
123      </main>
124    );
125  }
126}
127
128export default App;

此代码包括四个项目的一些硬编码值。在从后端获取项目之前,这些将是临时值。

renderTabList()函数呈现两个跨度,这有助于控制显示哪组项目。点击已完成 选项卡,将显示已完成的任务。点击** 未完成** 选项卡,将显示未完成的任务。

保存您的更改并在Web浏览器中观察应用程序:

Screenshot of the application currently displaying tasks for Study and Article.

要处理添加和编辑任务等操作,您需要创建一个模式组件。

首先,在src目录下创建一个Components文件夹:

1mkdir src/components

然后,创建一个modal.js文件,并使用代码编辑器打开它。添加以下代码行:

 1[label frontend/src/components/Modal.js]
 2import React, { Component } from "react";
 3import {
 4  Button,
 5  Modal,
 6  ModalHeader,
 7  ModalBody,
 8  ModalFooter,
 9  Form,
10  FormGroup,
11  Input,
12  Label,
13} from "reactstrap";
14
15export default class CustomModal extends Component {
16  constructor(props) {
17    super(props);
18    this.state = {
19      activeItem: this.props.activeItem,
20    };
21  }
22
23  handleChange = (e) => {
24    let { name, value } = e.target;
25
26    if (e.target.type === "checkbox") {
27      value = e.target.checked;
28    }
29
30    const activeItem = { ...this.state.activeItem, [name]: value };
31
32    this.setState({ activeItem });
33  };
34
35  render() {
36    const { toggle, onSave } = this.props;
37
38    return (
39      <Modal isOpen={true} toggle={toggle}>
40        <ModalHeader toggle={toggle}>Todo Item</ModalHeader>
41        <ModalBody>
42          <Form>
43            <FormGroup>
44              <Label for="todo-title">Title</Label>
45              <Input
46                type="text"
47                id="todo-title"
48                name="title"
49                value={this.state.activeItem.title}
50                onChange={this.handleChange}
51                placeholder="Enter Todo Title"
52              />
53            </FormGroup>
54            <FormGroup>
55              <Label for="todo-description">Description</Label>
56              <Input
57                type="text"
58                id="todo-description"
59                name="description"
60                value={this.state.activeItem.description}
61                onChange={this.handleChange}
62                placeholder="Enter Todo description"
63              />
64            </FormGroup>
65            <FormGroup check>
66              <Label check>
67                <Input
68                  type="checkbox"
69                  name="completed"
70                  checked={this.state.activeItem.completed}
71                  onChange={this.handleChange}
72                />
73                Completed
74              </Label>
75            </FormGroup>
76          </Form>
77        </ModalBody>
78        <ModalFooter>
79          <Button
80            color="success"
81            onClick={() => onSave(this.state.activeItem)}
82          >
83            Save
84          </Button>
85        </ModalFooter>
86      </Modal>
87    );
88  }
89}

这段代码创建了一个CustomModal类,它嵌套了从reactstrap库派生的Modal组件。

此代码还在表单中定义了三个域:

  • 标题
  • 描述
  • 已完成

这些字段与我们在后端中定义为TODO模型属性的字段相同。

CustomModal接受activeItemtoggleonSave作为道具:

1.activeItem表示需要编辑的ToDo项。 2.toggle是用于控制模式的状态(即打开或关闭模式)的函数。 3.onSave是用来保存ToDo项编辑后的值的函数。

接下来,将CustomModal组件导入到App.js文件中。

使用代码编辑器重新访问src/App.js文件,并使用以下代码行替换整个内容:

  1[label frontend/src/App.js]
  2import React, { Component } from "react";
  3import Modal from "./components/Modal";
  4
  5const todoItems = [
  6  {
  7    id: 1,
  8    title: "Go to Market",
  9    description: "Buy ingredients to prepare dinner",
 10    completed: true,
 11  },
 12  {
 13    id: 2,
 14    title: "Study",
 15    description: "Read Algebra and History textbook for the upcoming test",
 16    completed: false,
 17  },
 18  {
 19    id: 3,
 20    title: "Sammy's books",
 21    description: "Go to library to return Sammy's books",
 22    completed: true,
 23  },
 24  {
 25    id: 4,
 26    title: "Article",
 27    description: "Write article on how to use Django with React",
 28    completed: false,
 29  },
 30];
 31
 32class App extends Component {
 33  constructor(props) {
 34    super(props);
 35    this.state = {
 36      viewCompleted: false,
 37      todoList: todoItems,
 38      modal: false,
 39      activeItem: {
 40        title: "",
 41        description: "",
 42        completed: false,
 43      },
 44    };
 45  }
 46
 47  toggle = () => {
 48    this.setState({ modal: !this.state.modal });
 49  };
 50
 51  handleSubmit = (item) => {
 52    this.toggle();
 53
 54    alert("save" + JSON.stringify(item));
 55  };
 56
 57  handleDelete = (item) => {
 58    alert("delete" + JSON.stringify(item));
 59  };
 60
 61  createItem = () => {
 62    const item = { title: "", description: "", completed: false };
 63
 64    this.setState({ activeItem: item, modal: !this.state.modal });
 65  };
 66
 67  editItem = (item) => {
 68    this.setState({ activeItem: item, modal: !this.state.modal });
 69  };
 70
 71  displayCompleted = (status) => {
 72    if (status) {
 73      return this.setState({ viewCompleted: true });
 74    }
 75
 76    return this.setState({ viewCompleted: false });
 77  };
 78
 79  renderTabList = () => {
 80    return (
 81      <div className="nav nav-tabs">
 82        <span
 83          className={this.state.viewCompleted ? "nav-link active" : "nav-link"}
 84          onClick={() => this.displayCompleted(true)}
 85        >
 86          Complete
 87        </span>
 88        <span
 89          className={this.state.viewCompleted ? "nav-link" : "nav-link active"}
 90          onClick={() => this.displayCompleted(false)}
 91        >
 92          Incomplete
 93        </span>
 94      </div>
 95    );
 96  };
 97
 98  renderItems = () => {
 99    const { viewCompleted } = this.state;
100    const newItems = this.state.todoList.filter(
101      (item) => item.completed === viewCompleted
102    );
103
104    return newItems.map((item) => (
105      <li
106        key={item.id}
107        className="list-group-item d-flex justify-content-between align-items-center"
108      >
109        <span
110          className={`todo-title mr-2 ${
111            this.state.viewCompleted ? "completed-todo" : ""
112          }`}
113          title={item.description}
114        >
115          {item.title}
116        </span>
117        <span>
118          <button
119            className="btn btn-secondary mr-2"
120            onClick={() => this.editItem(item)}
121          >
122            Edit
123          </button>
124          <button
125            className="btn btn-danger"
126            onClick={() => this.handleDelete(item)}
127          >
128            Delete
129          </button>
130        </span>
131      </li>
132    ));
133  };
134
135  render() {
136    return (
137      <main className="container">
138        <h1 className="text-white text-uppercase text-center my-4">Todo app</h1>
139        <div className="row">
140          <div className="col-md-6 col-sm-10 mx-auto p-0">
141            <div className="card p-3">
142              <div className="mb-4">
143                <button
144                  className="btn btn-primary"
145                  onClick={this.createItem}
146                >
147                  Add task
148                </button>
149              </div>
150              {this.renderTabList()}
151              <ul className="list-group list-group-flush border-top-0">
152                {this.renderItems()}
153              </ul>
154            </div>
155          </div>
156        </div>
157        {this.state.modal ? (
158          <Modal
159            activeItem={this.state.activeItem}
160            toggle={this.toggle}
161            onSave={this.handleSubmit}
162          />
163        ) : null}
164      </main>
165    );
166  }
167}
168
169export default App;

保存您的更改并在Web浏览器中观察应用程序:

模式组件的屏幕截图显示了一个新任务,标题为-学习,描述为-为即将到来的test阅读代数和历史教科书

如果您尝试编辑并保存Todo项目,您将收到一个警告,显示Todo项目的对象。单击【保存】或【删除】,将对待办事项项执行相应的操作。

<$>[备注] 注意:: 根据您的Reaction和Reactstrap版本,您可能会遇到控制台错误。在修订时,已经在严格模式树中检测到Warning:遗留上下文API。Warning:findDOMNode在StrictMode.中是knownissues.。 <$>

现在,您将修改该应用程序,使其与您在上一节中构建的Django API交互。重新访问第一个终端窗口,并确保服务器正在运行。如果它未运行,请使用以下命令:

1python manage.py runserver

<$>[备注] 注意: 如果您关闭了该终端窗口,请记住您需要导航到backend目录并使用虚拟的Pipenv外壳。 <$>

要向后端服务器上的API端点发出请求,需要安装一个名为axios的JavaScript库。

在第二个终端窗口中,确保您在Frontend目录中,并安装axios

1npm install [email protected]

然后在您的代码编辑器中打开Fronend/Package.json文件,添加一个proxy

 1[label frontend/package.json]
 2[...]
 3  "name": "frontend",
 4  "version": "0.1.0",
 5  "private": true,
 6  "proxy": "http://localhost:8000",
 7  "dependencies": {
 8    "axios": "^0.18.0",
 9    "bootstrap": "^4.1.3",
10    "react": "^16.5.2",
11    "react-dom": "^16.5.2",
12    "react-scripts": "2.0.5",
13    "reactstrap": "^6.5.0"
14  },
15[...]

该代理将帮助将API请求隧道传输到http://localhost:8000,Django应用程序将在那里处理这些请求。如果没有此proxy,则需要指定完整路径:

1axios.get("http://localhost:8000/api/todos/")

通过proxy,可以提供相对路径:

1axios.get("/api/todos/")

<$>[注] 注意: 您可能需要重新启动开发服务器,代理才能注册到应用程序。 <$>

重新访问frontend/src/App.js文件,并使用代码编辑器打开它。在此步骤中,您将删除硬编码的todoItems,并使用来自后端服务器的请求的数据。删除提交删除删除

打开App.js文件,并将其替换为以下最终版本:

  1[label frontend/src/App.js]
  2import React, { Component } from "react";
  3import Modal from "./components/Modal";
  4import axios from "axios";
  5
  6class App extends Component {
  7  constructor(props) {
  8    super(props);
  9    this.state = {
 10      viewCompleted: false,
 11      todoList: [],
 12      modal: false,
 13      activeItem: {
 14        title: "",
 15        description: "",
 16        completed: false,
 17      },
 18    };
 19  }
 20
 21  componentDidMount() {
 22    this.refreshList();
 23  }
 24
 25  refreshList = () => {
 26    axios
 27      .get("/api/todos/")
 28      .then((res) => this.setState({ todoList: res.data }))
 29      .catch((err) => console.log(err));
 30  };
 31
 32  toggle = () => {
 33    this.setState({ modal: !this.state.modal });
 34  };
 35
 36  handleSubmit = (item) => {
 37    this.toggle();
 38
 39    if (item.id) {
 40      axios
 41        .put(`/api/todos/${item.id}/`, item)
 42        .then((res) => this.refreshList());
 43      return;
 44    }
 45    axios
 46      .post("/api/todos/", item)
 47      .then((res) => this.refreshList());
 48  };
 49
 50  handleDelete = (item) => {
 51    axios
 52      .delete(`/api/todos/${item.id}/`)
 53      .then((res) => this.refreshList());
 54  };
 55
 56  createItem = () => {
 57    const item = { title: "", description: "", completed: false };
 58
 59    this.setState({ activeItem: item, modal: !this.state.modal });
 60  };
 61
 62  editItem = (item) => {
 63    this.setState({ activeItem: item, modal: !this.state.modal });
 64  };
 65
 66  displayCompleted = (status) => {
 67    if (status) {
 68      return this.setState({ viewCompleted: true });
 69    }
 70
 71    return this.setState({ viewCompleted: false });
 72  };
 73
 74  renderTabList = () => {
 75    return (
 76      <div className="nav nav-tabs">
 77        <span
 78          onClick={() => this.displayCompleted(true)}
 79          className={this.state.viewCompleted ? "nav-link active" : "nav-link"}
 80        >
 81          Complete
 82        </span>
 83        <span
 84          onClick={() => this.displayCompleted(false)}
 85          className={this.state.viewCompleted ? "nav-link" : "nav-link active"}
 86        >
 87          Incomplete
 88        </span>
 89      </div>
 90    );
 91  };
 92
 93  renderItems = () => {
 94    const { viewCompleted } = this.state;
 95    const newItems = this.state.todoList.filter(
 96      (item) => item.completed === viewCompleted
 97    );
 98
 99    return newItems.map((item) => (
100      <li
101        key={item.id}
102        className="list-group-item d-flex justify-content-between align-items-center"
103      >
104        <span
105          className={`todo-title mr-2 ${
106            this.state.viewCompleted ? "completed-todo" : ""
107          }`}
108          title={item.description}
109        >
110          {item.title}
111        </span>
112        <span>
113          <button
114            className="btn btn-secondary mr-2"
115            onClick={() => this.editItem(item)}
116          >
117            Edit
118          </button>
119          <button
120            className="btn btn-danger"
121            onClick={() => this.handleDelete(item)}
122          >
123            Delete
124          </button>
125        </span>
126      </li>
127    ));
128  };
129
130  render() {
131    return (
132      <main className="container">
133        <h1 className="text-white text-uppercase text-center my-4">Todo app</h1>
134        <div className="row">
135          <div className="col-md-6 col-sm-10 mx-auto p-0">
136            <div className="card p-3">
137              <div className="mb-4">
138                <button
139                  className="btn btn-primary"
140                  onClick={this.createItem}
141                >
142                  Add task
143                </button>
144              </div>
145              {this.renderTabList()}
146              <ul className="list-group list-group-flush border-top-0">
147                {this.renderItems()}
148              </ul>
149            </div>
150          </div>
151        </div>
152        {this.state.modal ? (
153          <Modal
154            activeItem={this.state.activeItem}
155            toggle={this.toggle}
156            onSave={this.handleSubmit}
157          />
158        ) : null}
159      </main>
160    );
161  }
162}
163
164export default App;

renhList()函数是可重用的,每次API请求完成时都会调用该函数。它会更新待办事项列表,以显示最近添加的项目列表。

handleSubmit()函数负责创建和更新操作。如果作为参数传递的项没有id,那么它可能还没有被创建,所以函数会创建它。

此时,验证您的后端服务器是否在第一个终端窗口中运行:

1python manage.py runserver

<$>[备注] 注意: 如果您关闭了该终端窗口,请记住您需要导航到backend目录并使用虚拟的Pipenv外壳。 <$>

在您的第二个终端窗口中,确保您在Frontend目录中并启动您的前端应用程序:

1npm start

现在,当您使用Web浏览器访问http://localhost:3000时,您的应用程序将允许您READCREATEUPDATEDELETE任务。

用户与应用程序交互以创建新项目并在完成和不完整statuses.之间切换的动画gif

这就完成了TODO应用程序的前端和后端。

结论

在本文中,您使用Django和Reaction构建了一个待办应用程序。您可以通过djangorest Frameworkdjango-cors-headersaxiosbootstrapreactstRap库实现这一点。

如果你想了解更多关于Django的知识,请查看我们的Django主题页面获取练习和编程项目。

如果您想了解有关Reaction的更多信息,请查看我们的如何在React.js中编程]系列文章,或查看我们的Reaction主题页面以获取练习和编程项目。

Published At
Categories with 技术
comments powered by Disqus