使用 Django 和 React 构建 CRUD 应用程序
作为开发者,CRUD 操作是最基本的概念之一。今天,我将向您展示如何使用 Django 和 Django Rest 构建 REST API,以及如何使用 React 构建单页应用 (SPA),我们将使用 SPA 来执行 CRUD 操作。
要求
要学习本教程,您需要对 Django 模型、Django Rest序列化器和ViewSets有一定的了解。
项目设置
首先,我们需要搭建开发环境。打开你常用的终端,确保已经安装了virtualenv。
安装完成后,创建一个环境并安装 Django 和 Django REST framework。
virtualenv --python=/usr/bin/python3.8 venv
source venv/bin/activate
pip install django django-rest-framework
安装完成后,我们就可以创建项目并开始工作了。
django-admin startproject restaurant .
注意:不要忘记命令末尾的点号。它会在当前目录中生成目录和文件,而不是在新目录中生成restaurant。
要确保项目已正确初始化,请尝试运行命令python manage.py runserver并按回车键127.0.0.1:8000。
现在让我们创建一个 Django 应用。
python manage.py startapp menu
menu所以请务必将应用程序添加rest_framework到INSTALLED_APPS文件中settings.py。
#restaurant/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'menu'
]
很好。我们可以开始着手实现本教程中想要达成的逻辑了。那么,我们将编写Menu:
- 模型
- 序列化器
- 视图集
- 最后,配置路由。
模型
该Menu模型仅包含 5 个字段。
#menu/models.py
from django.db import models
class Menu(models.Model):
name = models.CharField(max_length=255)
description = models.TextField()
price = models.IntegerField()
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
完成后,我们创建一个迁移并应用它。
迁移是 Django 将您对模型所做的更改(添加字段、删除模型等)传播到数据库模式的一种方式。
python manage.py makemigrations
python manage.py migrate
序列化器
序列化器允许我们将复杂的 Django 数据结构(例如模型实例)转换querysets为 Python 原生对象,这些对象可以轻松转换为 JSON/XML 格式。
在这里,我们将创建一个序列化器,将数据转换为 JSON 格式。
#menu/serializers.py
from rest_framework import serializers
from menu.models import Menu
class MenuSerializer(serializers.ModelSerializer):
class Meta:
model = Menu
fields = ['id', 'name', 'description', 'price', 'created', 'updated']
视图集
如果您之前使用过其他框架,那么在 Django 中, ViewSet可以被称为 Controller。ViewSet
是 DRF 开发的一个概念,它将给定模型的一组视图组合到一个 Python 类中。这组视图对应于预定义的 CRUD 操作(创建、读取、更新、删除),并与 HTTP 方法关联。每个操作都是一个 ViewSet 实例方法。这些默认操作包括:
- 列表
- 取回
- 更新
- 破坏
- 部分更新
- 创造
#menu/viewsets.py
from rest_framework import viewsets
from menu.models import Menu
from menu.serializers import MenuSerializer
class MenuViewSet(viewsets.ModelViewSet):
serializer_class = MenuSerializer
def get_queryset(self):
return Menu.objects.all()
太好了。逻辑部分我们已经搭建好了,但还需要添加API接口。
首先创建一个文件,routers.py.
#./routers.py
from rest_framework import routers
from menu.viewsets import MenuViewSet
router = routers.SimpleRouter()
router.register(r'menu', MenuViewSet, basename='menu')
#restaurant/urls.py
from django.contrib import admin
from django.urls import path, include
from routers import router
urlpatterns = [
# path('admin/', admin.site.urls),
path('api/', include((router.urls, 'restaurant'), namespace='restaurant'))
]
如果你还没有启动服务器。
python manage.py runserver
然后http://127.0.0.1:8000/api/menu/在浏览器中点击。
您的可浏览 API 已准备就绪。🙂
让我们添加 CORS 响应。添加 CORS 标头可以让其他域访问您的资源。
pip install django-cors-headers
然后,将其添加到INSTALLED_APPS。
# restaurant/settings.py
INSTALLED_APPS = [
...
'corsheaders',
...
]
您还需要添加一个中间件类来监听响应。
#restaurant/settings.py
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
...
]
在本教程中,我们将允许所有来源发起跨站 HTTP 请求。
但是,这样做很危险,绝对不应该在生产环境中使用。
# restaurant/settings.py
CORS_ORIGIN_ALLOW_ALL = True
在生产环境中,您可以改用CORS_ALLOWED_ORIGINS。
CORS_ALLOWED_ORIGINS = [
"https://example.com",
"https://sub.example.com",
"http://localhost:3000",
"http://127.0.0.1:3000",
]
更多信息,请参阅相关文档。
React.js CRUD REST API 的使用
请确保您已安装最新版本的 create-react-app。
yarn create-react-app restaurant-menu-front
cd restaurant-menu-front
yarn start
然后打开http://localhost:3000/查看您的应用。
现在我们可以添加该项目的依赖项了。
yarn add axios bootstrap react-router-dom
通过这行命令,我们安装了:
- axios:一个基于 Promise 的 HTTP 客户端
- Bootstrap:一个无需编写大量 CSS 代码即可快速构建应用程序原型的库。
- react-router-dom:一个用于应用程序路由的 React 库。
请确保文件夹内src/包含以下文件和目录。
该src/components/目录中包含三个组件:
AddMenu.jsUpdateMenu.jsMenuList.js
在src/services/目录中,创建menu.service.js并添加以下几行:
export const baseURL = "http://localhost:8000/api";
export const headers = {
"Content-type": "application/json",
};
请确保在文件react-router-dom中导入index.js并将其包装App在BrowserRouter对象中。
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import "./index.css";
import App from "./App";
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById("root")
);
完成后,我们可以App.js通过导入bootstrap、编写路由以及构建主页和导航栏来修改文件。
import React from "react";
import "bootstrap/dist/css/bootstrap.min.css";
import { Switch, Route, Link } from "react-router-dom";
import { AddMenu } from "./components/AddMenu";
import { MenuList } from "./components/MenuList";
import { UpdateMenu } from "./components/UpdateMenu";
function App() {
return (
<div>
<nav className="navbar navbar-expand navbar-dark bg-info">
<a href="/" className="navbar-brand">
Restaurant Menu
</a>
<div className="navbar-nav mr-auto">
<li className="nav-item">
<Link exact to={"/add/"} className="nav-link">
Add
</Link>
</li>
</div>
</nav>
<div className="container m-10">
// Add the routes
</div>
</div>
);
}
export default App;
导航栏已经完成,我们已经导入了 bootstrap 和组件,接下来我们需要编写路由,这些路由应该映射到我们创建的组件。
<Switch>
<Route exact path={["/", "/menus"]} component={MenuList} />
<Route exact path="/add/" component={AddMenu} />
<Route path="/menu/:id/update/" component={UpdateMenu} />
</Switch>
下一步是编写组件的 CRUD 逻辑和 HTML 代码。
我们先从 API 中列出菜单MenuList.js。
对于这个脚本,我们将有两个状态:
menus它将存储来自 API 的响应对象。deleted其中将包含一个布尔对象,用于显示消息。
以及三种方法:
retrieveAllMenus()使用 API 获取所有菜单,并将响应对象设置到菜单中setMenus。deleteMenu()删除菜单并将deleted状态设置为true,这将有助于我们在每次删除菜单时显示一条简单的消息。handleUpdateClick()跳转到新页面以更新菜单。
import axios from "axios";
import React, { useState, useEffect, useRef } from "react";
import { baseURL, headers } from "./../services/menu.service";
import { useHistory } from "react-router-dom";
export const MenuList = () => {
const [menus, setMenus] = useState([]);
const history = useHistory();
const countRef = useRef(0);
const [deleted, setDeleted] = useState(false);
useEffect(() => {
retrieveAllMenus();
}, [countRef]);
const retrieveAllMenus = () => {
axios
.get(`${baseURL}/menu/`, {
headers: {
headers,
},
})
.then((response) => {
setMenus(response.data);
})
.catch((e) => {
console.error(e);
});
};
const deleteMenu = (id) => {
axios
.delete(`${baseURL}/menu/${id}/`, {
headers: {
headers,
},
})
.then((response) => {
setDeleted(true);
retrieveAllMenus();
})
.catch((e) => {
console.error(e);
});
};
const handleUpdateClick = (id) => {
history.push(`/menu/${id}/update/`);
};
return (
// ...
);
};
完成后,我们来实现这个render()方法:
<div className="row justify-content-center">
<div className="col">
{deleted && (
<div
className="alert alert-danger alert-dismissible fade show"
role="alert"
>
Menu deleted!
<button
type="button"
className="close"
data-dismiss="alert"
aria-label="Close"
>
<span aria-hidden="true">×</span>
</button>
</div>
)}
{menus &&
menus.map((menu, index) => (
<div className="card my-3 w-25 mx-auto">
<div className="card-body">
<h2 className="card-title font-weight-bold">{menu.name}</h2>
<h4 className="card-subtitle mb-2">{menu.price}</h4>
<p className="card-text">{menu.description}</p>
</div>
<div classNameName="card-footer">
<div
className="btn-group justify-content-around w-75 mb-1 "
data-toggle="buttons"
>
<span>
<button
className="btn btn-info"
onClick={() => handleUpdateClick(menu.id)}
>
Update
</button>
</span>
<span>
<button
className="btn btn-danger"
onClick={() => deleteMenu(menu.id)}
>
Delete
</button>
</span>
</div>
</div>
</div>
))}
</div>
</div>
添加菜单
该AddMenu.js组件包含一个用于提交新菜单的表单。它包含三个字段:name,description和price。
import axios from "axios";
import React, { useState } from "react";
import { baseURL, headers } from "./../services/menu.service";
export const AddMenu = () => {
const initialMenuState = {
id: null,
name: "",
description: "",
price: 0,
};
const [menu, setMenu] = useState(initialMenuState);
const [submitted, setSubmitted] = useState(false);
const handleMenuChange = (e) => {
const { name, value } = e.target;
setMenu({ ...menu, [name]: value });
};
const submitMenu = () => {
let data = {
name: menu.name,
description: menu.description,
price: menu.price,
};
axios
.post(`${baseURL}/menu/`, data, {
headers: {
headers,
},
})
.then((response) => {
setMenu({
id: response.data.id,
name: response.data.name,
description: response.data.description,
price: response.data.price,
});
setSubmitted(true);
console.log(response.data);
})
.catch((e) => {
console.error(e);
});
};
const newMenu = () => {
setMenu(initialMenuState);
setSubmitted(false);
};
return (
// ...
);
};
对于这个脚本,我们将有两个状态:
menuinitialMenuState默认情况下,它将包含对象的值。submitted它将包含一个布尔对象,用于在菜单添加成功时显示消息。
以及三种方法:
handleInputChange()跟踪输入值并设置状态以进行更改。saveMenu()POST向 API发送请求。newMenu()允许用户在显示成功消息后再次添加新菜单。
<div className="submit-form">
{submitted ? (
<div>
<div
className="alert alert-success alert-dismissible fade show"
role="alert"
>
Menu Added!
<button
type="button"
className="close"
data-dismiss="alert"
aria-label="Close"
>
<span aria-hidden="true">×</span>
</button>
</div>
<button className="btn btn-success" onClick={newMenu}>
Add
</button>
</div>
) : (
<div>
<div className="form-group">
<label htmlFor="name">Name</label>
<input
type="text"
className="form-control"
id="name"
required
value={menu.name}
onChange={handleMenuChange}
name="name"
/>
</div>
<div className="form-group">
<label htmlFor="description">Description</label>
<input
type="text"
className="form-control"
id="description"
required
value={menu.description}
onChange={handleMenuChange}
name="description"
/>
</div>
<div className="form-group">
<label htmlFor="price">Price</label>
<input
type="number"
className="form-control"
id="price"
required
value={menu.price}
onChange={handleMenuChange}
name="price"
/>
</div>
<button onClick={submitMenu} className="btn btn-success">
Submit
</button>
</div>
)}
</div>
更新菜单
该组件与现有组件略有不同,但它包含一个 GET 方法,用于通过向 APIAddMenu发送请求并传入对象的参数来获取对象的当前值。 我们使用钩子函数将参数传递给组件,并使用钩子函数来获取它。GETiduseHistory()idUpdateMenuuseParams
import axios from "axios";
import React, { useState, useEffect, useRef } from "react";
import { useParams } from "react-router-dom";
import { baseURL, headers } from "./../services/menu.service";
export const UpdateMenu = () => {
const initialMenuState = {
id: null,
name: "",
description: "",
price: 0,
};
let { id } = useParams();
const [currentMenu, setCurrentMenu] = useState(initialMenuState);
const [submitted, setSubmitted] = useState(false);
const countRef = useRef(0);
useEffect(() => {
retrieveMenu();
}, [countRef]);
const handleMenuChange = (e) => {
const { name, value } = e.target;
setCurrentMenu({ ...currentMenu, [name]: value });
};
const retrieveMenu = () => {
axios
.get(`${baseURL}/menu/${id}/`, {
headers: {
headers,
},
})
.then((response) => {
setCurrentMenu({
id: response.data.id,
name: response.data.name,
description: response.data.description,
price: response.data.price,
});
console.log(currentMenu);
})
.catch((e) => {
console.error(e);
});
};
const updateMenu = () => {
let data = {
name: currentMenu.name,
description: currentMenu.description,
price: currentMenu.price,
};
axios
.put(`${baseURL}/menu/${id}/`, data, {
headers: {
headers,
},
})
.then((response) => {
setCurrentMenu({
id: response.data.id,
name: response.data.name,
description: response.data.description,
price: response.data.price,
});
setSubmitted(true);
console.log(response.data);
})
.catch((e) => {
console.error(e);
});
};
const newMenu = () => {
setCurrentMenu(initialMenuState);
setSubmitted(false);
};
return (
// ...
);
};
这是代码内部的内容return:
<div className="submit-form">
{submitted ? (
<div>
<div
className="alert alert-success alert-dismissible fade show"
role="alert"
>
Menu Updated!
<button
type="button"
className="close"
data-dismiss="alert"
aria-label="Close"
>
<span aria-hidden="true">×</span>
</button>
</div>
<button className="btn btn-success" onClick={newMenu}>
Update
</button>
</div>
) : (
<div>
<div className="form-group">
<label htmlFor="name">Name</label>
<input
type="text"
className="form-control"
id="name"
required
value={currentMenu.name}
onChange={handleMenuChange}
name="name"
/>
</div>
<div className="form-group">
<label htmlFor="description">Description</label>
<input
type="text"
className="form-control"
id="description"
required
value={currentMenu.description}
onChange={handleMenuChange}
name="description"
default
/>
</div>
<div className="form-group">
<label htmlFor="price">Price</label>
<input
type="number"
className="form-control"
id="price"
required
value={currentMenu.price}
onChange={handleMenuChange}
name="price"
/>
</div>
<button onClick={updateMenu} className="btn btn-success">
Submit
</button>
</div>
)}
</div>
现在一切就绪。
如果您点击Update菜单卡上的按钮,您将被重定向到一个新页面,其中包含此组件,字段中将包含默认值。
结论
在本文中,我们学习了如何使用 Django 和 React 构建 CRUD Web 应用程序。每篇文章都有改进的空间,欢迎在评论区提出您的建议或问题。😉
本文所有代码都可以在此仓库中找到。
这篇文章最初发表在我的博客上。
文章来源:https://dev.to/koladev/build-a-crud-application-using-django-and-react-5389


