zoukankan      html  css  js  c++  java
  • DRF + react 实现TodoList

    在web项目构建中有很多框架可供选择,开发人员对项目的使用选择,有很多的影响因素,其中之一就是框架在定义该项目的单独任务时的复杂性。

    简介

    本文有如下几个部分:

    • 准备
    • 配置后端
    • 配置APIs
    • 配置前端
    • 测试

    使用Django和React 编写Todolist程序有如下原因:

    • React框架有广泛的适用范围,遇到错误的问题,可以很快面向Google解决
    • Django 是一个很强大的web框架,从会话到身份认证的功能实现将会节约大量的时间

    Django和React配合将可以方便的实现应用程序编写,为了使前后端交互(即检索和存储),为了创建交互界面,我们将使用Django REST框架(DRF)在后端构建API(应用程序编程接口)。

    最后实现的TodoList效果如下:

     准备

    本次环境如下:

    1 Ubuntu 18.4
    2 python 3.6.8
    3 pip
    4 pipenv

    之前一直是用的pip + 手动创建虚拟环境。pipenv 是生产就绪的工具,正如其名,他将pip和virtualenv整合在一起

    配置 backend

    按照如下步骤将建立一个比较好的项目目录

    1 mkdir django-todo-react
    2 cd django-todo-react

    使用pip安装pipenv,并激活一个虚拟环境

    1 pip install pipenv
    2 pipenv shell

    (配置国内镜像跟新文件和pip源,清华,以提高依赖安装速度)
    使用pipenv安装django,并新修建一个项目叫backend

    1 pipenv install django
    2 django-admin startproject backend

     进入backend文件夹 ,新创建一个todo的应用,并迁移数据库

    (如果你的机器里有python2.x和python3.x在后面使用python,pip命令时请分别使用python3,pip3)

    1 cd backend
    2 python3 manage.py startapp todo
    3 python3 manage.py migrate
    4 python3 manage.py runserver

    如上步骤输入都正确的话,输入地址http://127.0.0.1:8000
    会出现下图

    好了现在开始配置Todo应用

    注册应用Todo

    在上面已经完成了后端的基苯配置,现在可以将todo应用程序注册为已安装的应用程序,以便Django识别。打开/backend/settings.py文件,更新INSTALLED_APPS

     1 # backend/settings.py
     2 
     3 # Application definition
     4 INSTALLED_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 model

    打开文件todo/model.py,编写模型

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

    在以上Todo类中描述了三个属性:

    • Title
    • Description
    • Completed

    由于改变了Todo model,现在需要对数据做一个迁移,以确保数据库改变。

    1 python3 manage.py makemigrations todo
    2 python manage.py migrate todo

    对todo应用进行配置,修改文件todo/admin.py

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

    创建账户

    1 python3 manage.py createsuperuser

    启动服务器,访问地址-172.0.0.1:8000/admin

    1 python3 manage.py runserver

    登陆后,如下图所示,

     

    进入添加如下:

    配置 APIs

    使用 ctr + c 停止服务器,然后使用pipenv安装djangorestframework 和django-cors-headers

    1 pipenv install djangorestframework django-cors-headers

    现在需要把rest_framework和corsheaders 注册,

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

    网上教程在backend/settings.py最后添加以下,一直报错,后来只有不添加如下即可运行:

    1 # we whitelist localhost:3000 because that's where frontend will be served
    2 CORS_ORIGIN_WHITELIST = (
    3     'localhost:3000/'
    4 )

    在CORS_ORIGIN_WHITELIST片段中,我们将localhost:3000列入白名单,因为我们希望应用程序的前端(将在该端口上提供)与API进行交互

    Todo model 序列化

    序列化相关参见此文

    我们需要序列化程序将模型实例转换为JSON,以便前端可以轻松处理接收到的数据。
    新建文件todo/serializers.py

    1 touch todo/serializers.py

    编辑文件像如下代码:

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

    在上面代码片段中,我们指定了要使用的模型以及我们要转换为JSON的字段。

    编写View视图

    在todo/views.py新建Todoview类,

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

    视图集基类默认提供CRUD操作的实现,我们要做的是指定序列化程序类和查询集。
    编写后端backend/urls.py文件

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

    这是完成API构建的最后一步,我们现在可以在Todo模型上执行CRUD操作。路由器类允许我们进行以下查询:

    • /todos/ 这将返回所有Todo项的列表(可以在此处完成Create和Read操作)。
    • /todos/id 这将使用id主键返回单个Todo项(可以在此处执行Update和Delete操作)。

    运行下列地址访问服务器127.0.0.1:8000/api/todos

    1 python3 manage.py runserver

    如下图所示:

     

    可以新建TodoList,以测试:

    配置前端

    在一个新的命令行终端上执行命令

    1 cd django-todo-react
    2 npm install -g create-react-app
    3 create-react-app frontend
    4 cd frontend 
    5 yarn start

    访问127.0.0.1:3000将会看见如下:

    引入bootstrap和reactstrap来增加UI的功能:

    1 yarn add bootstrap reactstrap

    编辑文件src/index.css

     1 /__ frontend/src/index.css __/
     2 body {
     3   margin: 0;
     4   padding: 0;
     5   font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
     6     "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
     7     sans-serif;
     8   -webkit-font-smoothing: antialiased;
     9   -moz-osx-font-smoothing: grayscale;
    10   background-color: #282c34;
    11 }
    12 .todo-title {
    13   cursor: pointer;
    14 }
    15 .completed-todo {
    16   text-decoration: line-through;
    17 }
    18 .tab-list > span {
    19   padding: 5px 8px;
    20   border: 1px solid #282c34;
    21   border-radius: 10px;
    22   margin-right: 5px;
    23   cursor: pointer;
    24 }
    25 .tab-list > span.active {
    26   background-color: #282c34;
    27   color: #ffffff;
    28 }

    在src/index.js 添加下面引入文件

     1 import React from 'react';
     2 import ReactDOM from 'react-dom';
     3 import 'bootstrap/dist/css/bootstrap.min.css'; //add this
     4 import './index.css';
     5 import App from './App';
     6 import * as serviceWorker from './serviceWorker';
     7 
     8 ReactDOM.render(<App />, document.getElementById('root'));
     9 
    10 // If you want your app to work offline and load faster, you can change
    11 // unregister() to register() below. Note this comes with some pitfalls.
    12 // Learn more about service workers: https://bit.ly/CRA-PWA
    13 serviceWorker.unregister();
    安装axios
    1 yarn add axios

    把axios 添加进入package.json文件

     1 // 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 [...]

    要处理添加和编辑任务等操作,我们将使用模态,所以让我们在components文件夹中创建一个Modal组件。先在src目录下新建文件夹components,

    1 mkdir src/components
    2 touch src/components/Modal.js

    编辑Modal.js文件

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

    我们创建了一个CustomModal类,它嵌套了从reactstrap库派生的Modal组件。我们还在表单中定义了三个字段

    • Title
    • Description
    • Completed

    接下来就是 修改文件src/Appjs

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

    这时基本的代码已经写完,可以进行测试了

    测试

    分别在刚才两个命令行运行APIs端和前端

    1 # django-todo-react/backend
    2 python3 manage.py runserver # 运行DRF
    1 yarn start # 运行react

    最后访问 127.0.0.1:3000/

     


    访问127.0.0.1:8000/api

    参考

    原作者个人感觉写的很详细了

  • 相关阅读:
    jquery 不支持$.browser
    js 双向绑定
    css3 省略号
    js生成txt文件
    Browser-sync
    Generator & yield write in sync way
    Charles
    缓动函数与动画
    让Safari使用Chrome的代理
    React 同构
  • 原文地址:https://www.cnblogs.com/Rightsec/p/11182705.html
Copyright © 2011-2022 走看看