发布于 2026-01-06 4 阅读
0

使用 Django 和 React 构建 CRUD 应用程序

使用 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


Enter fullscreen mode Exit fullscreen mode

安装完成后,我们就可以创建项目并开始工作了。



django-admin startproject restaurant .


Enter fullscreen mode Exit fullscreen mode

注意:不要忘记命令末尾的点号。它会在当前目录中生成目录和文件,而不是在新目录中生成restaurant
要确保项目已正确初始化,请尝试运行命令python manage.py runserver并按回车键127.0.0.1:8000

现在让我们创建一个 Django 应用。



python manage.py startapp menu


Enter fullscreen mode Exit fullscreen mode

menu所以请务必将应用程序添加rest_frameworkINSTALLED_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'
    ]


Enter fullscreen mode Exit fullscreen mode

很好。我们可以开始着手实现本教程中想要达成的逻辑了。那么,我们将编写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


Enter fullscreen mode Exit fullscreen mode

完成后,我们创建一个迁移并应用它。

迁移是 Django 将您对模型所做的更改(添加字段、删除模型等)传播到数据库模式的一种方式。



python manage.py makemigrations
python manage.py migrate


Enter fullscreen mode Exit fullscreen mode

序列化器

序列化器允许我们将复杂的 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']  


Enter fullscreen mode Exit fullscreen mode

视图集

如果您之前使用过其他框架,那么在 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()


Enter fullscreen mode Exit fullscreen mode

太好了。逻辑部分我们已经搭建好了,但还需要添加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'))
]


Enter fullscreen mode Exit fullscreen mode

如果你还没有启动服务器。



python manage.py runserver


Enter fullscreen mode Exit fullscreen mode

然后http://127.0.0.1:8000/api/menu/在浏览器中点击。
您的可浏览 API 已准备就绪。🙂

让我们添加 CORS 响应。添加 CORS 标头可以让其他域访问您的资源。



    pip install django-cors-headers


Enter fullscreen mode Exit fullscreen mode

然后,将其添加到INSTALLED_APPS



# restaurant/settings.py
INSTALLED_APPS = [
    ...
    'corsheaders',
    ...
]


Enter fullscreen mode Exit fullscreen mode

您还需要添加一个中间件类来监听响应。



#restaurant/settings.py
MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    ...
]


Enter fullscreen mode Exit fullscreen mode

在本教程中,我们将允许所有来源发起跨站 HTTP 请求。
但是,这样做很危险,绝对不应该在生产环境中使用。



# restaurant/settings.py
CORS_ORIGIN_ALLOW_ALL = True


Enter fullscreen mode Exit fullscreen mode

在生产环境中,您可以改用CORS_ALLOWED_ORIGINS



CORS_ALLOWED_ORIGINS = [
    "https://example.com",
    "https://sub.example.com",
    "http://localhost:3000",
    "http://127.0.0.1:3000",
]


Enter fullscreen mode Exit fullscreen mode

更多信息,请参阅相关文档

React.js CRUD REST API 的使用

请确保您已安装最新版本的 create-react-app。



yarn create-react-app restaurant-menu-front
cd restaurant-menu-front
yarn start


Enter fullscreen mode Exit fullscreen mode

然后打开http://localhost:3000/查看您的应用。
现在我们可以添加该项目的依赖项了。



yarn add axios bootstrap react-router-dom


Enter fullscreen mode Exit fullscreen mode

通过这行命令,我们安装了:

  • axios:一个基于 Promise 的 HTTP 客户端
  • Bootstrap:一个无需编写大量 CSS 代码即可快速构建应用程序原型的库。
  • react-router-dom:一个用于应用程序路由的 React 库。

请确保文件夹内src/包含以下文件和目录。

目录映像

src/components/目录中包含三个组件:

  • AddMenu.js
  • UpdateMenu.js
  • MenuList.js

src/services/目录中,创建menu.service.js并添加以下几行:



    export const baseURL = "http://localhost:8000/api";
    export const headers = {
      "Content-type": "application/json",
    };


Enter fullscreen mode Exit fullscreen mode

请确保在文件react-router-dom中导入index.js并将其包装AppBrowserRouter对象中。



    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")
    );


Enter fullscreen mode Exit fullscreen mode

完成后,我们可以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;


Enter fullscreen mode Exit fullscreen mode

导航栏已经完成,我们已经导入了 bootstrap 和组件,接下来我们需要编写路由,这些路由应该映射到我们创建的组件。



    <Switch>
          <Route exact path={["/", "/menus"]} component={MenuList} />
          <Route exact path="/add/" component={AddMenu} />
          <Route path="/menu/:id/update/" component={UpdateMenu} />
    </Switch>


Enter fullscreen mode Exit fullscreen mode

下一步是编写组件的 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 (
        // ...
      );
    };


Enter fullscreen mode Exit fullscreen mode

完成后,我们来实现这个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">&times;</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>


Enter fullscreen mode Exit fullscreen mode

添加菜单

AddMenu.js组件包含一个用于提交新菜单的表单。它包含三个字段:namedescriptionprice



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


Enter fullscreen mode Exit fullscreen mode

对于这个脚本,我们将有两个状态:

  • 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">&times;</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>


Enter fullscreen mode Exit fullscreen mode

更新菜单

该组件与现有组件略有不同,但它包含一个 GET 方法,用于通过向 APIAddMenu发送请求并传入对象的参数来获取对象的当前值 我们使用钩子函数将参数传递组件,并使用钩子函数来获取它GETid
useHistory()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 (
          // ...
      );
    };


Enter fullscreen mode Exit fullscreen mode

这是代码内部的内容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">&times;</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>


Enter fullscreen mode Exit fullscreen mode

现在一切就绪。

如果您点击Update菜单卡上的按钮,您将被重定向到一个新页面,其中包含此组件,字段中将包含默认值。
查看演示

结论

在本文中,我们学习了如何使用 Django 和 React 构建 CRUD Web 应用程序。每篇文章都有改进的空间,欢迎在评论区提出您的建议或问题。😉
本文所有代码都可以在此仓库中找到。

这篇文章最初发表在我的博客上。

文章来源:https://dev.to/koladev/build-a-crud-application-using-django-and-react-5389