介绍
在本教程中,您将使用 Django 和 React 构建一个待办事项应用程序。
React 是用于开发 SPA(单页应用程序)的 JavaScript 框架。它有可靠的文档和充满活力的生态系统。
Django 是一个 Python Web 框架,它简化了 Web 开发中的常见做法。Django 是可靠的,并且还拥有一个充满活力的稳定库生态系统,支持常见的开发需求。
对于此应用程序,React 作为前端或客户端框架,通过对 Django 后端的请求处理用户界面并获取和设置数据,后端是使用 Django REST 框架 (DRF) 构建的 API。
在本教程结束时,您将拥有一个完整的应用程序:
注意:本教程的源代码可在 GitHub 上找到。
此应用程序将允许用户创建任务并将其标记为已完成或未完成。
先决条件
要学习本教程,您需要:
警告:本教程中提供的代码用于教育目的,不适用于生产用途。
本教程已通过 Python pip
v3.9.1 、v20.2.4、Django v3.1.6、djangorestframework
v3.12.2、django-cors-headers
v3.7.0、Node v15.8.0、npm
v7.5.4、React v17.0.1 和axios
v0.21.0 验证。
第 1 步 – 设置后端
在本节中,您将创建一个新的项目目录并安装 Django。
打开一个新的终端窗口并运行以下命令来创建一个新的项目目录:
- mkdir django-todo-react
接下来,导航到目录:
- cd django-todo-react
现在使用pip
以下命令安装 Pipenv :
- pip install pipenv
注意:根据您的安装,您可能需要使用pip3
代替pip
。
并激活一个新的虚拟环境:
- pipenv shell
使用 Pipenv 安装 Django:
- pipenv install django
然后创建一个名为的新项目backend
:
- django-admin startproject backend
接下来,导航到新创建的后端目录:
- cd backend
启动一个名为 的新应用程序todo
:
python manage.py startapp todo
运行迁移:
python manage.py migrate
并启动服务器:
python manage.py runserver
http://localhost:8000
在 Web 浏览器中导航到:
此时,您将看到一个成功运行的 Django 应用程序实例。完成后,您可以停止服务器(CONTROL+C
或CTRL+C
)。
注册todo
应用程序
现在您已经完成了后端的设置,您可以开始将该todo
应用程序注册为已安装的应用程序,以便 Django 可以识别它。
backend/settings.py
在代码编辑器中打开文件并添加todo
到INSTALLED_APPS
:
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'todo',
]
然后,保存您的更改。
定义Todo
模型
让我们创建一个模型来定义Todo
项目应该如何存储在数据库中。
todo/models.py
在代码编辑器中打开文件并添加以下代码行:
from django.db import models
# Create your models here.
class Todo(models.Model):
title = models.CharField(max_length=120)
description = models.TextField()
completed = models.BooleanField(default=False)
def _str_(self):
return self.title
上面的代码片段描述了 Todo 模型的三个属性:
title
description
completed
该completed
属性是任务的状态。任务将随时完成或未完成。因为您已经创建了一个Todo
模型,所以您需要创建一个迁移文件:
- python manage.py makemigrations todo
并将更改应用于数据库:
- python manage.py migrate todo
您可以Todo
使用 Django 默认提供的管理界面测试以查看 CRUD 操作是否适用于您创建的模型。
todo/admin.py
使用代码编辑器打开文件并添加以下代码行:
from django.contrib import admin
from .models import Todo
class TodoAdmin(admin.ModelAdmin):
list_display = ('title', 'description', 'completed')
# Register your models here.
admin.site.register(Todo, TodoAdmin)
然后,保存您的更改。
您需要创建一个“超级用户”帐户才能访问管理界面。在终端中运行以下命令:
- python manage.py createsuperuser
系统将提示您输入超级用户的用户名、电子邮件和密码。请务必输入您能记住的详细信息,因为您将需要它们来登录管理仪表板。
再次启动服务器:
- python manage.py runserver
http://localhost:8000/admin
在 Web 浏览器中导航到。并使用之前创建的用户名和密码登录:
您可以Todo
使用此界面创建、编辑和删除项目:
试用此界面后,您可以停止服务器(CONTROL+C
或CTRL+C
)。
第 2 步 – 设置 API
在本节中,您将使用 Django REST 框架创建 API。
安装djangorestframework
并django-cors-headers
使用 Pipenv:
- pipenv install djangorestframework django-cors-headers
您需要将rest_framework
和添加corsheaders
到已安装的应用程序列表中。backend/settings.py
在代码编辑器中打开文件并更新INSTALLED_APPS
和MIDDLEWARE
部分:
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'corsheaders',
'rest_framework',
'todo',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'corsheaders.middleware.CorsMiddleware',
]
然后,将这些代码行添加到backend/settings.py
文件底部:
CORS_ORIGIN_WHITELIST = [
'http://localhost:3000'
]
django-cors-headers
是一个 Python 库,可以防止由于 CORS 规则而通常会出现的错误。在CORS_ORIGIN_WHITELIST
代码中,您列入白名单localhost:3000
是因为您希望应用程序的前端(将在该端口上提供服务)与 API 进行交互。
创造 serializers
您将需要序列化程序将模型实例转换为 JSON,以便前端可以处理接收到的数据。
todo/serializers.py
使用您的代码编辑器创建一个文件。打开serializers.py
文件并使用以下代码行更新它:
from rest_framework import serializers
from .models import Todo
class TodoSerializer(serializers.ModelSerializer):
class Meta:
model = Todo
fields = ('id', 'title', 'description', 'completed')
此代码指定要使用的模型以及要转换为 JSON 的字段。
创建视图
您需要TodoView
在todo/views.py
文件中创建一个类。
todo/views.py
使用代码编辑器打开文件并添加以下代码行:
from django.shortcuts import render
from rest_framework import viewsets
from .serializers import TodoSerializer
from .models import Todo
# Create your views here.
class TodoView(viewsets.ModelViewSet):
serializer_class = TodoSerializer
queryset = Todo.objects.all()
该viewsets
基类提供了默认CRUD操作执行。此代码指定serializer_class
和queryset
。
backend/urls.py
使用您的代码编辑器打开该文件并将内容替换为以下代码行:
from django.contrib import admin
from django.urls import path, include
from rest_framework import routers
from todo import views
router = routers.DefaultRouter()
router.register(r'todos', views.TodoView, 'todo')
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include(router.urls)),
]
此代码指定 API 的 URL 路径。这是完成 API 构建的最后一步。
您现在可以对Todo
模型执行 CRUD 操作。路由器类允许您进行以下查询:
/todos/
– 返回所有Todo
项目的列表。CREATE
和READ
操作可以在这里进行。/todos/id
–Todo
使用id
主键返回单个项目。UPDATE
和DELETE
操作可以在这里进行。
让我们重新启动服务器:
- python manage.py runserver
http://localhost:8000/api/todos
在 Web 浏览器中导航到:
您可以CREATE
使用界面创建一个新的 Todo 项目:
如果 Todo 项创建成功,您将看到成功的响应:
您还可以使用主键对特定项目执行DELETE
和UPDATE
操作。使用地址结构并提供一个.Todo
id
/api/todos/{id}
id
添加1
到 URL 以检查带有id
“1”的 Todo 项。http://localhost:8000/api/todos/1
在 Web 浏览器中导航到:
这样就完成了应用程序后端的构建。
第 3 步 – 设置前端
现在您已经完成了应用程序的后端,您可以创建前端并让它通过您创建的接口与后端通信。
首先,打开一个新的终端窗口并导航到django-todo-react
项目目录。
为了设置前端,本教程将依赖于 Create React App。有几种方法可以使用create-react-app
. 一种方法是使用npx
运行包并创建项目:
- npx create-react-app frontend
您可以通过阅读如何使用 Create React App 设置 React 项目来了解有关此方法的更多信息。
项目创建完成后,可以切换到新创建的frontend
目录:
- cd frontend
然后,启动应用程序:
- npm start
您的 Web 浏览器将打开http://localhost:3000
,您将看到默认的 Create React App 屏幕:
接下来,安装bootstrap
并reactstrap
提供用户界面工具。
- npm install bootstrap@4.6.0 reactstrap@8.9.0 --legacy-peer-deps
注意:unable to resolve dependency tree
根据您的 React、Bootstrap 和 Reactstrap 版本,您可能会遇到错误。
修改时,最新版本popper.js
已被弃用,会与 React 17+ 冲突。这是一个已知问题,可以--legacy-peer-deps
在安装时使用该选项。
index.js
在您的代码编辑器中打开并添加bootstrap.min.css
:
import React from 'react';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.css';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
如果您在这一步遇到困难,可以参考官方文档添加bootstrap
.
App.js
在代码编辑器中打开并添加以下代码行:
import React, { Component } from "react";
const todoItems = [
{
id: 1,
title: "Go to Market",
description: "Buy ingredients to prepare dinner",
completed: true,
},
{
id: 2,
title: "Study",
description: "Read Algebra and History textbook for the upcoming test",
completed: false,
},
{
id: 3,
title: "Sammy's books",
description: "Go to library to return Sammy's books",
completed: true,
},
{
id: 4,
title: "Article",
description: "Write article on how to use Django with React",
completed: false,
},
];
class App extends Component {
constructor(props) {
super(props);
this.state = {
viewCompleted: false,
todoList: todoItems,
};
}
displayCompleted = (status) => {
if (status) {
return this.setState({ viewCompleted: true });
}
return this.setState({ viewCompleted: false });
};
renderTabList = () => {
return (
<div className="nav nav-tabs">
<span
className={this.state.viewCompleted ? "nav-link active" : "nav-link"}
onClick={() => this.displayCompleted(true)}
>
Complete
</span>
<span
className={this.state.viewCompleted ? "nav-link" : "nav-link active"}
onClick={() => this.displayCompleted(false)}
>
Incomplete
</span>
</div>
);
};
renderItems = () => {
const { viewCompleted } = this.state;
const newItems = this.state.todoList.filter(
(item) => item.completed == viewCompleted
);
return newItems.map((item) => (
<li
key={item.id}
className="list-group-item d-flex justify-content-between align-items-center"
>
<span
className={`todo-title mr-2 ${
this.state.viewCompleted ? "completed-todo" : ""
}`}
title={item.description}
>
{item.title}
</span>
<span>
<button
className="btn btn-secondary mr-2"
>
Edit
</button>
<button
className="btn btn-danger"
>
Delete
</button>
</span>
</li>
));
};
render() {
return (
<main className="container">
<h1 className="text-white text-uppercase text-center my-4">Todo app</h1>
<div className="row">
<div className="col-md-6 col-sm-10 mx-auto p-0">
<div className="card p-3">
<div className="mb-4">
<button
className="btn btn-primary"
>
Add task
</button>
</div>
{this.renderTabList()}
<ul className="list-group list-group-flush border-top-0">
{this.renderItems()}
</ul>
</div>
</div>
</div>
</main>
);
}
}
export default App;
此代码包括四个项目的一些硬编码值。这些将是临时值,直到从后端获取项目。
该renderTabList()
函数呈现两个跨度,帮助控制显示哪些项目集。单击“已完成”选项卡将显示已完成的任务。单击“未完成”选项卡将显示未完成的任务。
保存更改并在 Web 浏览器中观察应用程序:
要处理添加和编辑任务等操作,您需要创建一个模态组件。
首先,components
在src
目录中创建一个文件夹:
- mkdir src/components
然后,创建一个Modal.js
文件并使用您的代码编辑器打开它。添加以下代码行:
import React, { Component } from "react";
import {
Button,
Modal,
ModalHeader,
ModalBody,
ModalFooter,
Form,
FormGroup,
Input,
Label,
} from "reactstrap";
export default class CustomModal extends Component {
constructor(props) {
super(props);
this.state = {
activeItem: this.props.activeItem,
};
}
handleChange = (e) => {
let { name, value } = e.target;
if (e.target.type === "checkbox") {
value = e.target.checked;
}
const activeItem = { ...this.state.activeItem, [name]: value };
this.setState({ activeItem });
};
render() {
const { toggle, onSave } = this.props;
return (
<Modal isOpen={true} toggle={toggle}>
<ModalHeader toggle={toggle}>Todo Item</ModalHeader>
<ModalBody>
<Form>
<FormGroup>
<Label for="todo-title">Title</Label>
<Input
type="text"
id="todo-title"
name="title"
value={this.state.activeItem.title}
onChange={this.handleChange}
placeholder="Enter Todo Title"
/>
</FormGroup>
<FormGroup>
<Label for="todo-description">Description</Label>
<Input
type="text"
id="todo-description"
name="description"
value={this.state.activeItem.description}
onChange={this.handleChange}
placeholder="Enter Todo description"
/>
</FormGroup>
<FormGroup check>
<Label check>
<Input
type="checkbox"
name="completed"
checked={this.state.activeItem.completed}
onChange={this.handleChange}
/>
Completed
</Label>
</FormGroup>
</Form>
</ModalBody>
<ModalFooter>
<Button
color="success"
onClick={() => onSave(this.state.activeItem)}
>
Save
</Button>
</ModalFooter>
</Modal>
);
}
}
这段代码创建了一个CustomModal
类,它嵌套了从reactstrap
库派生的 Modal 组件。
这段代码还定义了表单中的三个字段:
title
description
completed
这些字段与我们在后端的 Todo 模型上定义为属性的字段相同。
的CustomModal
接收activeItem
,toggle
以及onSave
当道具:
activeItem
表示要编辑的 Todo 项。toggle
是用于控制模态状态(即打开或关闭模态)的函数。onSave
是一个函数,用于保存 Todo 项的编辑值。
接下来,您将把CustomModal
组件导入到App.js
文件中。
src/App.js
使用代码编辑器重新访问该文件,并使用以下代码行替换整个内容:
import React, { Component } from "react";
import Modal from "./components/Modal";
const todoItems = [
{
id: 1,
title: "Go to Market",
description: "Buy ingredients to prepare dinner",
completed: true,
},
{
id: 2,
title: "Study",
description: "Read Algebra and History textbook for the upcoming test",
completed: false,
},
{
id: 3,
title: "Sammy's books",
description: "Go to library to return Sammy's books",
completed: true,
},
{
id: 4,
title: "Article",
description: "Write article on how to use Django with React",
completed: false,
},
];
class App extends Component {
constructor(props) {
super(props);
this.state = {
viewCompleted: false,
todoList: todoItems,
modal: false,
activeItem: {
title: "",
description: "",
completed: false,
},
};
}
toggle = () => {
this.setState({ modal: !this.state.modal });
};
handleSubmit = (item) => {
this.toggle();
alert("save" + JSON.stringify(item));
};
handleDelete = (item) => {
alert("delete" + JSON.stringify(item));
};
createItem = () => {
const item = { title: "", description: "", completed: false };
this.setState({ activeItem: item, modal: !this.state.modal });
};
editItem = (item) => {
this.setState({ activeItem: item, modal: !this.state.modal });
};
displayCompleted = (status) => {
if (status) {
return this.setState({ viewCompleted: true });
}
return this.setState({ viewCompleted: false });
};
renderTabList = () => {
return (
<div className="nav nav-tabs">
<span
className={this.state.viewCompleted ? "nav-link active" : "nav-link"}
onClick={() => this.displayCompleted(true)}
>
Complete
</span>
<span
className={this.state.viewCompleted ? "nav-link" : "nav-link active"}
onClick={() => this.displayCompleted(false)}
>
Incomplete
</span>
</div>
);
};
renderItems = () => {
const { viewCompleted } = this.state;
const newItems = this.state.todoList.filter(
(item) => item.completed === viewCompleted
);
return newItems.map((item) => (
<li
key={item.id}
className="list-group-item d-flex justify-content-between align-items-center"
>
<span
className={`todo-title mr-2 ${
this.state.viewCompleted ? "completed-todo" : ""
}`}
title={item.description}
>
{item.title}
</span>
<span>
<button
className="btn btn-secondary mr-2"
onClick={() => this.editItem(item)}
>
Edit
</button>
<button
className="btn btn-danger"
onClick={() => this.handleDelete(item)}
>
Delete
</button>
</span>
</li>
));
};
render() {
return (
<main className="container">
<h1 className="text-white text-uppercase text-center my-4">Todo app</h1>
<div className="row">
<div className="col-md-6 col-sm-10 mx-auto p-0">
<div className="card p-3">
<div className="mb-4">
<button
className="btn btn-primary"
onClick={this.createItem}
>
Add task
</button>
</div>
{this.renderTabList()}
<ul className="list-group list-group-flush border-top-0">
{this.renderItems()}
</ul>
</div>
</div>
</div>
{this.state.modal ? (
<Modal
activeItem={this.state.activeItem}
toggle={this.toggle}
onSave={this.handleSubmit}
/>
) : null}
</main>
);
}
}
export default App;
保存更改并在 Web 浏览器中观察应用程序:
如果您尝试编辑和保存Todo
项目,您将收到显示Todo
项目对象的警报。单击“保存”或“删除”将对项目执行相应的操作Todo
。
注意:根据您的 React 和 Reactstrap 版本,您可能会遇到控制台错误。在修改的时候,Warning: Legacy context API has been detected within a strict-mode tree.
并且Warning: findDOMNode is deprecated in StrictMode.
是已知的 问题。
现在,您将修改应用程序,使其与您在上一节中构建的 Django API 进行交互。重新访问第一个终端窗口并确保服务器正在运行。如果未运行,请使用以下命令:
- python manage.py runserver
注意:如果您关闭了这个终端窗口,请记住您需要导航到该backend
目录并使用虚拟 Pipenv shell。
要向后端服务器上的 API 端点发出请求,您将安装一个名为axios
.
在第二个终端窗口中,确保您在frontend
目录中并安装axios
:
- npm install axios@0.21.1
然后frontend/package.json
在代码编辑器中打开文件并添加proxy
:
[...]
"name": "frontend",
"version": "0.1.0",
"private": true,
"proxy": "http://localhost:8000",
"dependencies": {
"axios": "^0.18.0",
"bootstrap": "^4.1.3",
"react": "^16.5.2",
"react-dom": "^16.5.2",
"react-scripts": "2.0.5",
"reactstrap": "^6.5.0"
},
[...]
代理将帮助将 API 请求隧道传送http://localhost:8000
到 Django 应用程序将处理它们的位置。没有这个proxy
,你需要指定完整路径:
axios.get("http://localhost:8000/api/todos/")
使用proxy
,您可以提供相对路径:
axios.get("/api/todos/")
注意:您可能需要重新启动开发服务器,代理才能向应用程序注册。
重新访问该frontend/src/App.js
文件并使用您的代码编辑器打开它。在此步骤中,您将从todoItems
对后端服务器的请求中删除硬编码和使用数据。handleSubmit
和handleDelete
打开App.js
文件并将其替换为这个最终版本:
import React, { Component } from "react";
import Modal from "./components/Modal";
import axios from "axios";
class App extends Component {
constructor(props) {
super(props);
this.state = {
viewCompleted: false,
todoList: [],
modal: false,
activeItem: {
title: "",
description: "",
completed: false,
},
};
}
componentDidMount() {
this.refreshList();
}
refreshList = () => {
axios
.get("/api/todos/")
.then((res) => this.setState({ todoList: res.data }))
.catch((err) => console.log(err));
};
toggle = () => {
this.setState({ modal: !this.state.modal });
};
handleSubmit = (item) => {
this.toggle();
if (item.id) {
axios
.put(`/api/todos/${item.id}/`, item)
.then((res) => this.refreshList());
return;
}
axios
.post("/api/todos/", item)
.then((res) => this.refreshList());
};
handleDelete = (item) => {
axios
.delete(`/api/todos/${item.id}/`)
.then((res) => this.refreshList());
};
createItem = () => {
const item = { title: "", description: "", completed: false };
this.setState({ activeItem: item, modal: !this.state.modal });
};
editItem = (item) => {
this.setState({ activeItem: item, modal: !this.state.modal });
};
displayCompleted = (status) => {
if (status) {
return this.setState({ viewCompleted: true });
}
return this.setState({ viewCompleted: false });
};
renderTabList = () => {
return (
<div className="nav nav-tabs">
<span
onClick={() => this.displayCompleted(true)}
className={this.state.viewCompleted ? "nav-link active" : "nav-link"}
>
Complete
</span>
<span
onClick={() => this.displayCompleted(false)}
className={this.state.viewCompleted ? "nav-link" : "nav-link active"}
>
Incomplete
</span>
</div>
);
};
renderItems = () => {
const { viewCompleted } = this.state;
const newItems = this.state.todoList.filter(
(item) => item.completed === viewCompleted
);
return newItems.map((item) => (
<li
key={item.id}
className="list-group-item d-flex justify-content-between align-items-center"
>
<span
className={`todo-title mr-2 ${
this.state.viewCompleted ? "completed-todo" : ""
}`}
title={item.description}
>
{item.title}
</span>
<span>
<button
className="btn btn-secondary mr-2"
onClick={() => this.editItem(item)}
>
Edit
</button>
<button
className="btn btn-danger"
onClick={() => this.handleDelete(item)}
>
Delete
</button>
</span>
</li>
));
};
render() {
return (
<main className="container">
<h1 className="text-white text-uppercase text-center my-4">Todo app</h1>
<div className="row">
<div className="col-md-6 col-sm-10 mx-auto p-0">
<div className="card p-3">
<div className="mb-4">
<button
className="btn btn-primary"
onClick={this.createItem}
>
Add task
</button>
</div>
{this.renderTabList()}
<ul className="list-group list-group-flush border-top-0">
{this.renderItems()}
</ul>
</div>
</div>
</div>
{this.state.modal ? (
<Modal
activeItem={this.state.activeItem}
toggle={this.toggle}
onSave={this.handleSubmit}
/>
) : null}
</main>
);
}
}
export default App;
该refreshList()
函数是可重用的,每次完成 API 请求时都会调用该函数。它更新 Todo 列表以显示最近添加的项目列表。
该handleSubmit()
函数负责创建和更新操作。如果作为参数传递的项目没有id
,则它可能尚未创建,因此函数会创建它。
At this point, verify that your backend server is running in your first terminal window:
- python manage.py runserver
Note: If you closed this terminal window, remember that you will need to navigate to the backend
directory and use the virtual Pipenv shell.
And in your second terminal window, ensure that you are in the frontend
directory and start your frontend application:
- npm start
Now when you visit http://localhost:3000
with your web browser, your application will allow you to READ
, CREATE
, UPDATE
, and DELETE
tasks.
This completes the frontend and backend of the Todo application.
Conclusion
In this article, you built a To-Do application using Django and React. You achieved this with the djangorestframework
, django-cors-headers
, axios
, bootstrap
, and reactstrap
libraries.
如果您想了解更多关于Django的,检查出[我们的Django的专题页面(hhttps:// www.digitalocean.com/community/tags/django)的练习题和编程。
如果您想了解有关 React 的更多信息,请查看我们的How To Code in React.js系列,或查看我们的 React 主题页面以获取练习和编程项目。