当前位置: 首页 > 后台 > Django

利用 django-restframework 和 vue 实现增删改查

作者: admin 2019-12-27 12:04:14 阅读(275) 评论(0)

一. 前言

最近在学习 vue 和 django-restframework,准备把自己博客后台的 js 框架从 jquery 迁移到 vue。这篇文章将简单介绍如何结合 django-restframework 和 vue,并实现基本的增删改查,包含后台表单校验和前后端交互。

本教程测试环境为 win10 + python3.7.5 + django3.0.1 + node.js-v12.13.1 + vue2.9.6,采用 PyCharm 作为开发工具。

二. 环境准备

1. 安装 python

具体请参阅 Python 在 Win10 系统的安装和使用配置

建议安装 python3.6 及以上的版本。

2. python 环境依赖

2.1. django

建议在虚拟环境下, 利用pip install django命令安装 django。

2.2. django-restframework

利用pip install djangorestframework命令安装 django-restframework。

django-restframework 用来构建 restful API 服务。

2.3. django-filter

利用pip install django-filter命令来安装 django-filter。

django-filter 用于搜索条件过滤(实现多条件搜索)

2.4. django-crispy-forms

利用pip install django-crispy-forms安装 django-crispy-forms。

2.5. django-cors-headers

利用pip install django-cors-headers安装 django-cors-headers,用于解决 vue 和 django 之间的跨域问题。

3. 安装 node.js 和 vue

项目采用前后端分离模式,需要安装 node.js 和 vue,如何安装非本文重点,安装教程请参阅 Node.js 安装配置 与 vue.js 环境配置

三. 构建项目

1. 创建 django 项目

  • 进入虚拟环境,利用 django-admin startproject django_vue命令新建名称为 django_vue 的项目。

  • 利用cd django_vue命令进入项目目录,利用python manage.py startapp api命令创建名称为 api 的 app。

2. 创建 vue 项目

后台模板采用了开源项目——基于vue + element的后台管理系统解决方案

访问 https://github.com/lin-xin/vue-manage-system,复制clone地址。

利用cd django_vue命令进入 django 项目目录,依次执行以下命令。

git clone https://github.com/lin-xin/vue-manage-system.git    // 把模板下载到本地
cd vue-manage-system    // 进入模板目录
npm install    // 安装依赖

上述命令执行成功以后,目录结构如下图所示。

三. 编写API服务

  • 修改 django_vue 目录下的 settings.py 文件,将 api,rest_framework,django_filters,crispy_forms,corsheaders 加入INSTALLED_APPS中。同时添加跨域中间件,注意中间件的顺序。最后添加配置信息,允许跨域。如下所示:
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # 添加的 app
    'rest_framework',
    'django_filters',
    'crispy_forms',
    'corsheaders',
    'api',
    #
]

CORS_ORIGIN_ALLOW_ALL = True  # 允许跨域

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'corsheaders.middleware.CorsMiddleware',  # 跨域中间件,注意中间件顺序
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
  • 在 api 目录下新建一个 page.py 文件,用于处理分页,在其中添加以下内容。

    在其中定义了一个基类,重写了进行数据分页方法和返回分页数据方法,同时定义了一个标准分页类,写入了基本的分页参数信息。

from collections import OrderedDict

from django.core.paginator import PageNotAnInteger, EmptyPage
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response


class CommonPageMixin(PageNumberPagination):
    """分页基类"""

    def paginate_queryset(self, queryset, request, view=None):
        """重写获取分页数据方法"""
        page_size = self.get_page_size(request)
        if not page_size:
            return None
        paginator = self.django_paginator_class(queryset, page_size)
        page_number = request.query_params.get(self.page_query_param, 1)
        if page_number in self.last_page_strings:
            page_number = paginator.num_pages
        try:
            self.page = paginator.page(page_number)
        except PageNotAnInteger:  # 页码不是数字,跳到第一页
            self.page = paginator.page(1)
        except EmptyPage:  # 页码超出范围,跳到最后一页
            self.page = paginator.page(paginator.num_pages)
        if paginator.num_pages > 1 and self.template is not None:
            # The browsable API should display pagination controls.
            self.display_page_controls = True
        self.request = request
        return list(self.page)

    def get_paginated_response(self, data):
        """重发返回分页数据方法"""
        return Response(OrderedDict([
            ('count', self.page.paginator.count),  # 总记录数
            ('next', self.get_next_link()),  # 下一页URL
            ('previous', self.get_previous_link()),  # 上一页URL
            ('results', data),  # 分页数据
            ('total_pages', self.page.paginator.num_pages),  # 总页数
            ('current_page', self.page.number),  # 当前页
            ('page_size', self.page_size),  # 每页条数
        ]))


class StandardPagination(CommonPageMixin):
    """标准后台分页类"""
    page_size = 5  # 每页条数
    # page_size_query_param = "size"  # 每页条数名称
    max_page_size = 10000  # 最大页数
    page_query_param = "page"  # 页码参数名称
  • 在 api 目录下的 models.py 中定义友链模型类。
from django.db import models


class Link(models.Model):
    """友链"""
    is_show = models.BooleanField(default=True)  # 是否显示
    link_name = models.CharField(max_length=255)  # 友链名称
    link_url = models.CharField(max_length=255)  # 网址
    description = models.CharField(max_length=255)  # 描述
    created_time = models.DateTimeField(blank=True, null=True, auto_now_add=True)  # 创建时间
    modified_time = models.DateTimeField(blank=True, null=True, auto_now=True)  # 更新时间
    remark = models.CharField(max_length=255, blank=True, null=True)  # 备注

    class Meta:
        ordering = ('-created_time',)  # 按创建时间倒序
  • 在 api 目录下新建一个 serializers.py 文件,用于存放序列化类信息。

  • 在 serializers.py 中加入以下内容,新建一个 LinkSerializer 类保存序列化的字段和内容。
from rest_framework import serializers
from .models import Link


class LinkSerializer(serializers.ModelSerializer):
    """友链序列化类"""
    id = serializers.IntegerField(required=False)
    link_name = serializers.CharField(required=True, max_length=255, error_messages={
        'blank': '请输入友链名称',
        'required': '请输入友链名称',
        'max_length': '不能超过255个字符',
    })
    link_url = serializers.URLField(required=True, max_length=255, error_messages={
        'blank': '请输入网址',
        'required': '请输入网址',
        'invalid': '请输入正确的URL',
        'max_length': '不能超过255个字符',
    })
    description = serializers.CharField(required=True, max_length=255, error_messages={
        'blank': '请输入友链描述',
        'required': '请输入友链描述',
        'max_length': '不能超过255个字符',
    })
    created_time = serializers.DateTimeField(format="%Y-%m-%d", required=False)  # format="%Y-%m-%d %H:%M:%S"

    class Meta:
        fields = ('id', 'link_name', 'link_url', 'description', 'is_show', 'created_time')  # 序列化字段
        model = Link  # 模型类
  • 在 api 目录下新建一个 filters.py 文件,用于存放搜索过滤类信息。加入友链搜索过滤类 LinksFilter。
from django_filters import rest_framework as r_filters
from .models import Link


class LinksFilter(r_filters.FilterSet):
    """友链搜索过滤"""
    link_name = r_filters.CharFilter(field_name='link_name', lookup_expr='icontains')

    class Meta:
        model = Link
        fields = ['is_show', 'link_name']
  • 编写 api 目录下的 views.py 文件,加入用于构建 restful api 服务的视图类。

    ModelViewSet 类是对 get , post , put , patch , delete 类型请求对应的增删改查功能的封装和继承,也就是说 ModelViewSet 已经包含了基于 restful api 的基本增删改查方法。其中 multi_delete 是额外添加的视图方法,用于批量删除。

from rest_framework import viewsets, mixins, status
from rest_framework.decorators import action, api_view
from rest_framework.response import Response

from .filters import LinksFilter
from .models import Link
from .serializers import LinkSerializer
from .page import StandardPagination


class LinkViewSet(viewsets.ModelViewSet):
    """友链API"""
    queryset = Link.objects.all()  # 数据集
    serializer_class = LinkSerializer  # 序列化类
    pagination_class = StandardPagination  # 分页类
    filter_class = LinksFilter  # 搜索过滤类

    @action(methods=['delete'], detail=False)  # 批量删除
    def multi_delete(self, request, *args, **kwargs):
        ids = request.query_params.get('ids', None)
        if not ids or ids == '':  # 删除内容为空,返回404,表未找到删除内容
            return Response({
                'msg': '未找到删除内容',
                'code': 0
            }, status=status.HTTP_404_NOT_FOUND)
        delete_ids = ids.split(',')  # url传参形式为?ids=1,2,3,通过split方法将字符串转换为数组
        delete_ids = [int(x) for x in delete_ids if x.split()]
        try:
            Link.objects.filter(id__in=delete_ids).delete()  # 执行批量删除操作
            return Response(status=status.HTTP_204_NO_CONTENT)
        except Exception as e:  # 异常处理,返回500,表服务器内部错误
            return Response({
                'msg': e.args,
                'code': 0
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
0 条评论