首页 > 技术文章 > 二.运维平台之用户组资源

dbslinux 2020-07-18 22:14 原文

 一.后端实现

(1).新建apps/groups包

(2)settings.py中注册-----'groups'

(3)groups/serializers.py:

from django.contrib.auth.models import Group
from rest_framework import serializers


class GroupSerializer(serializers.ModelSerializer):
    """
    group序列化类
    """
    class Meta:
        model = Group
        fields = ("id", "name")

(4)groups/views.py:

from django.contrib.auth.models import Group
from rest_framework import viewsets, mixins
from .serializers import GroupSerializer
from .filters import GroupFilter

class GroupViewset(viewsets.ModelViewSet):
    queryset = Group.objects.all()
    serializer_class = GroupSerializer
    filter_class = GroupFilter
    filter_fields = ("name",)

(5)groups/filters.py:

import django_filters
from django.contrib.auth.models import Group

class GroupFilter(django_filters.rest_framework.FilterSet):
    """
    group过滤类
    """
    name = django_filters.CharFilter(lookup_expr='icontains')
    class Meta:
        model = Group
        fields = ('name',)

(6)groups/router.py:

from rest_framework.routers import DefaultRouter
from .views import GroupViewset

group_router = DefaultRouter()
group_router.register('groups', GroupViewset, basename="groups")

(7)devops/urls.py:

from django.conf.urls import url, include
from rest_framework.routers import DefaultRouter
from rest_framework.documentation import include_docs_urls
from groupUsers .views import GroupUsersViewset
from users.router import router as user_router
from groups.router import group_router
router = DefaultRouter()
router.registry.extend(user_router.registry)
router.registry.extend(group_router.registry)


from rest_framework.authtoken.views import obtain_auth_token
urlpatterns = [
    url(r'^api/', include(router.urls)),
    url(r'^', include('resources.urls')),
    url(r'^api-auth/', include('rest_framework.urls')),
    url(r'^docs/', include_docs_urls("51reboot接口文档")),
    url(r'^api-token-auth/', obtain_auth_token)
]

(python36env) [vagrant@CentOS devops]$ python manage.py runserver 0.0.0.0:8000

 

 

 二.前端实现

1.页面展示 

(1).views/groups/index.vue

<template>
  <div class="app-container">
    <el-table
      :data="groupData"
      stripe
      style="width: 100%">
      <el-table-column
        type="index"
        width="50" />
      <el-table-column
        prop="name"
        label="姓名"/>
      <el-table-column
        fixed="right"
        label="操作">
        <template slot-scope="scope">
          <el-button type="text" size="small" @click="handleModifyGroup(scope.row)">修改</el-button>
        </template>
      </el-table-column>
    </el-table>
    <el-row v-show="total>10" type="flex" justify="center" style="padding-top:20px;">
      <el-pagination
        :total="total"
        layout="prev, pager, next"
        background
        @current-change="handleChange" />
    </el-row>
  </div>
</template>

<script>
import { getGroupList } from '@/api/group'
export default {
  name: 'Groups',
  data() {
    return {
      groupData: [],
      total: 0,
      params: {
        page: 1
      }
    }
  },
  created() {
    this.fetchGroupList()
  },
  methods: {
    fetchGroupList() {
      getGroupList().then(res => {
        this.groupData = res.results
        this.total = res.count
      })
    },
    handleChange(val) {
      this.params.page = val
      this.fetchGroupList()
    }
  }
}
</script>

(2).router/index.js

import Vue from 'vue'
import Router from 'vue-router'

// in development-env not use lazy-loading, because lazy-loading too many pages will cause webpack hot update too slow. so only in production use lazy-loading;
// detail: https://panjiachen.github.io/vue-element-admin-site/#/lazy-loading

Vue.use(Router)

/* Layout */
import Layout from '../views/layout/Layout'

/**
* hidden: true                   if `hidden:true` will not show in the sidebar(default is false)
* alwaysShow: true               if set true, will always show the root menu, whatever its child routes length
*                                if not set alwaysShow, only more than one route under the children
*                                it will becomes nested mode, otherwise not show the root menu
* redirect: noredirect           if `redirect:noredirect` will no redirct in the breadcrumb
* name:'router-name'             the name is used by <keep-alive> (must set!!!)
* meta : {
    title: 'title'               the name show in submenu and breadcrumb (recommend set)
    icon: 'svg-name'             the icon show in the sidebar,
  }
**/
export const constantRouterMap = [
  { path: '/login', component: () => import('@/views/login/index'), hidden: true },
  { path: '/404', component: () => import('@/views/404'), hidden: true },

  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    name: 'Dashboard',
    children: [{
      path: 'dashboard',
      component: () => import('@/views/dashboard/index'),
      meta: { title: 'dashboard', icon: 'example' }
    }]
  },
  {
    path: '/users',
    component: Layout,
    name: 'users',
    meta: { title: '用户管理', icon: 'example' },
    children: [
      {
        path: 'user',
        name: 'user',
        component: () => import('@/views/users/user'),
        meta: { title: '用户' }
      },
      {
        path: 'groups',
        name: 'groups',
        component: () => import('@/views/groups'),
        meta: { title: '用户组' }
      }
    ]
  },
  { path: '*', redirect: '/404', hidden: true }
]

export default new Router({
  mode: 'history',
  scrollBehavior: () => ({ y: 0 }),
  routes: constantRouterMap
})

(3)api/group.js:

import request from '@/utils/request'

export function getGroupList(params) {
  return request({
    url: '/api/groups/',
    method: 'get',
    params
  })
}

效果如下图:

 2.添加用户组

(1)groups/index.vue

<template>
  <div class="app-container">
    <el-row>
      <el-col :span="12">
        <el-input v-model="params.name" placeholder="搜索用户组" @keyup.enter.native="handleSearch">
          <el-button slot="append" icon="el-icon-search" @click="handleSearch"/>
        </el-input>
      </el-col>
      <el-col :span="12" align="right" style="padding-right:20px;">
        <el-button type="primary" @click="groupFormVisible=true">增加用户组</el-button>
      </el-col>
    </el-row>
    <el-table
      :data="groupData"
      stripe
      style="width: 100%">
      <el-table-column
        type="index"
        width="50" />
      <el-table-column
        prop="name"
        label="姓名"/>
      <el-table-column
        fixed="right"
        label="操作">
        <template slot-scope="scope">
          <el-button type="text" size="small" @click="handleModifyGroup(scope.row)">修改</el-button>
        </template>
      </el-table-column>
    </el-table>
    <el-row v-show="total>10" type="flex" justify="center" style="padding-top:20px;">
      <el-pagination
        :total="total"
        layout="prev, pager, next"
        background
        @current-change="handleChange" />
    </el-row>
    <GroupForm v-model="groupFormVisible" :gid="groupId" :gname="groupName" @fetch="handleFetch"/>
  </div>
</template>
<script>
  import { getGroupList } from '@/api/group'
  import GroupForm from './components/groupForm'
  export default {
    name: 'Groups',
    components: {
      GroupForm
    },
    data() {
      return {
        groupData: [],
        total: 0,
        params: {
          page: 1,
          name: ''
        },
        groupFormVisible: false,
        groupId: 0,
        groupName: ''
      }
    },
    created() {
      this.fetchGroupList()
    },
    methods: {
      fetchGroupList() {
        getGroupList(this.params).then(res => {
          this.groupData = res.results
          this.total = res.count
        })
      },
      handleChange(val) {
        this.params.page = val
        this.fetchGroupList()
      },
      handleSearch() {
        this.params.page = 1
        this.fetchGroupList()
      },
      handleFetch() {
        this.groupId = 0
        this.fetchGroupList()
      }
    }
  }
</script>

(2)groups/components/groupForm.vue

<template>
  <div class="group-form-container">
    <el-dialog :visible.sync="visible" :title="title" @close="handleClose">
      <el-form ref="groupForm" :model="form" :rules="rules" label-width="100px">
        <el-form-item label="用户组:" prop="name">
          <el-input v-model="form.name" placeholder="请输入用户组"/>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="submitForm">提交</el-button>
          <el-button @click="resetForm">重置</el-button>
        </el-form-item>
      </el-form>
    </el-dialog>
  </div>
</template>
<script>
  import { addGroup, modifyGroup } from '@/api/group'
  export default {
    name: 'GroupForm',
    props: {
      value: {
        type: Boolean,
        default: false
      },
      gid: {
        type: Number,
        default: 0
      },
      gname: {
        type: String,
        default: ''
      }
    },
    data() {
      return {
        visible: false,
        groupId: 0,
        form: {
          name: ''
        },
        rules: {
          name: [
            { required: true, message: '请输入用户组', trigger: 'blur' }
          ]
        }
      }
    },
    watch: {
      value(val) {
        this.visible = val
      },
      gid(val) {
        if (val < 0) return
        this.groupId = val
      }
    },
    methods: {
      resetForm() {
        this.$refs.groupForm.resetFields()
      },
      handleClose() {
        this.visible = false
        this.groupId = 0
        this.resetForm()
        this.$emit('input', false)
      },
      submitForm() {
        this.$refs.groupForm.validate((valid) => {
          if (valid) {
            this.save()
          } else {
            console.log('error submit!!')
            return false
          }
        })
      },
      save() {
        if (this.groupId === 0) {
          this.create()
        } else {
          this.update()
        }
      },
      create() {
        addGroup(this.form).then(() => {
          this.$message({
            message: `添加用户组 ${this.form.name} 成功`,
            type: 'success'
          })
          this.handleClose()
          this.$emit('fetch')
        })
      },
      update() {
        modifyGroup(this.groupId, this.form).then(() => {
          this.$message({
            message: `修改用户组 ${this.form.name} 成功`,
            type: 'success'
          })
          this.handleClose()
          this.$emit('fetch')
        })
      }
    }
  }
</script>

(3)api/group.js

import request from '@/utils/request'

export function getGroupList(params) {
  return request({
    url: '/api/groups/',
    method: 'get',
    params
  })
}

export function addGroup(data) {
  return request({
    url: '/api/groups/',
    method: 'post',
    data
  })
}

export function modifyGroup(id, data) {
  return request({
    url: `/api/groups/${id}/`,
    method: 'patch',
    data
  })
}

效果如下实现添加了:

 3.修改用户组

(1)groups/index.vue

<template>
  <div class="app-container">
    <el-row>
      <el-col :span="12">
        <el-input v-model="params.name" placeholder="搜索用户组" @keyup.enter.native="handleSearch">
          <el-button slot="append" icon="el-icon-search" @click="handleSearch"/>
        </el-input>
      </el-col>
      <el-col :span="12" align="right" style="padding-right:20px;">
        <el-button type="primary" @click="handleAddGroup">增加用户组</el-button>
      </el-col>
    </el-row>
    <el-table
      :data="groupData"
      stripe
      style="width: 100%">
      <el-table-column
        type="index"
        width="50" />
      <el-table-column
        prop="name"
        label="姓名"/>
      <el-table-column
        fixed="right"
        label="操作">
        <template slot-scope="scope">
          <el-button type="text" size="small" @click="handleModifyGroup(scope.row)">修改</el-button>
        </template>
      </el-table-column>
    </el-table>
    <el-row v-show="total>10" type="flex" justify="center" style="padding-top:20px;">
      <el-pagination
        :total="total"
        layout="prev, pager, next"
        background
        @current-change="handleChange" />
    </el-row>
    <GroupForm v-model="groupFormVisible" :gid="groupId" :gname="groupName" @fetch="handleFetch"/>
  </div>
</template>
<script>
  import { getGroupList } from '@/api/group'
  import GroupForm from './components/groupForm'
  export default {
    name: 'Groups',
    components: {
      GroupForm
    },
    data() {
      return {
        groupData: [],
        total: 0,
        params: {
          page: 1,
          name: ''
        },
        groupFormVisible: false,
        groupId: 0,
        groupName: ''
      }
    },
    created() {
      this.fetchGroupList()
    },
    methods: {
      fetchGroupList() {
        getGroupList(this.params).then(res => {
          this.groupData = res.results
          this.total = res.count
        })
      },
      handleChange(val) {
        this.params.page = val
        this.fetchGroupList()
      },
      handleSearch() {
        this.params.page = 1
        this.fetchGroupList()
      },
      handleAddGroup() {
        this.groupId = 0
        this.groupName = ''
        this.groupFormVisible = true
      },
      handleFetch() {
        this.groupId = 0
        this.fetchGroupList()
      },
      handleModifyGroup(obj) {
        this.groupId = obj.id
        this.groupName = obj.name
        this.groupFormVisible = true
      }
    }
  }
</script>

(2)groups/components/groupForm.vue

<template>
  <div class="group-form-container">
    <el-dialog :visible.sync="visible" :title="title" @close="handleClose">
      <el-form ref="groupForm" :model="form" :rules="rules" label-width="100px">
        <el-form-item label="用户组:" prop="name">
          <el-input v-model="form.name" placeholder="请输入用户组"/>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="submitForm">提交</el-button>
          <el-button @click="resetForm">重置</el-button>
        </el-form-item>
      </el-form>
    </el-dialog>
  </div>
</template>
<script>
  import { addGroup, modifyGroup } from '@/api/group'
  export default {
    name: 'GroupForm',
    props: {
      value: {
        type: Boolean,
        default: false
      },
      gid: {
        type: Number,
        default: 0
      },
      gname: {
        type: String,
        default: ''
      }
    },
    data() {
      return {
        visible: false,
        groupId: 0,
        form: {
          name: ''
        },
        rules: {
          name: [
            { required: true, message: '请输入用户组', trigger: 'blur' }
          ]
        }
      }
    },
    computed: {
      title() {
        if (this.groupId === 0) return '创建用户组'
        else return '修改用户组'
      }
    },
    watch: {
      value(val) {
        this.visible = val
      },
      gid(val) {
        if (val < 0) return
        this.groupId = val
      },
      gname(val) {
        if (val === '') return
        this.form.name = val
      }
    },
    methods: {
      resetForm() {
        this.$refs.groupForm.resetFields()
        if (this.groupId === 0) this.form.name = ''
      },
      handleClose() {
        this.visible = false
        this.groupId = 0
        this.resetForm()
        this.$emit('input', false)
      },
      submitForm() {
        this.$refs.groupForm.validate((valid) => {
          if (valid) {
            this.save()
          } else {
            console.log('error submit!!')
            return false
          }
        })
      },
      save() {
        if (this.groupId === 0) {
          this.create()
        } else {
          this.update()
        }
      },
      create() {
        addGroup(this.form).then(() => {
          this.$message({
            message: `添加用户组 ${this.form.name} 成功`,
            type: 'success'
          })
          this.handleClose()
          this.$emit('fetch')
        })
      },
      update() {
        modifyGroup(this.groupId, this.form).then(() => {
          this.$message({
            message: `修改用户组 ${this.form.name} 成功`,
            type: 'success'
          })
          this.handleClose()
          this.$emit('fetch')
        })
      }
    }
  }
</script>

效果如下图:

三. 将用户添加至指定用户组

 https://element.eleme.io/#/zh-CN/component/select#methods--基础多选组件

1.指定角色页面实现--前端

(1).users/components/assignGroup.vue

<template>
  <div class="assign-group">
    <el-dialog :visible.sync="visible" title="指定角色" @close="handleClose">
      <el-form ref="addUserForm" label-width="100px">
        <el-form-item label="用户名:">
          <el-input v-model="userName" readonly/>
        </el-form-item>
        <el-form-item label="姓名:">
          <el-select v-model="userGroups" multiple placeholder="请选择" style="width:100%">
            <el-option
              v-for="item in options"
              :key="item.id"
              :label="item.name"
              :value="item.id"/>
          </el-select>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="handleClose">取 消</el-button>
        <el-button type="primary" @click="handleSubmit">提交</el-button>
      </div>
    </el-dialog>
  </div>
</template>
<script>
  import { getGroupList, updateUserGroups } from '@/api/group'
  export default {
    name: 'AssignGroup',
    props: {
      value: {
        type: Boolean,
        default: false
      },
      userId: {
        type: Number,
        default: 0
      },
      userName: {
        type: String,
        default: ''
      }
    },
    data() {
      return {
        visible: false,
        options: [],
        userGroups: []
      }
    },
    watch: {
      value(val) {
        if (val <= 0) return
        this.visible = val
        this.fetchGroupList()
      }
    },
    methods: {
      handleClose() {
        this.visible = false
        this.$emit('input', false)
        setTimeout(() => {
          this.options = []
          this.userGroups = []
        }, 500)
      },
      fetchGroupList() {
        getGroupList({ page_size: 0 }).then(res => {
          this.options = res
        })
      },
    handleSubmit() {
console.log(this.userId, this.userGroups)
this.handleClose()
}
}
}
</script>

(2)users/user.vue

<template>
  <div class="app-container">
    <el-row>
      <el-col :span="12">
        <el-input v-model="params.username" placeholder="搜索用户名" @keyup.enter.native="handleSearch">
          <el-button slot="append" icon="el-icon-search" @click="handleSearch"/>
        </el-input>
      </el-col>
      <el-col :span="12" align="right" style="padding-right:20px;">
        <el-button type="primary" @click="addUserVisible=true">添加用户</el-button>
      </el-col>
    </el-row>
    <el-table
      :data="userList"
      stripe
      style="width: 100%">
      <el-table-column
        prop="username"
        label="username"/>
      <el-table-column
        prop="name"
        label="姓名"/>
      <el-table-column
        prop="phone"
        label="电话"/>
      <el-table-column
        prop="email"
        label="email"/>
      <el-table-column
        prop="is_active"
        label="状态">
        <template slot-scope="scope">
          <el-switch v-model="scope.row.is_active" @change="handleUserStatusChange(scope.row)"/>
        </template>
      </el-table-column>
      <el-table-column
        prop="last_login"
        label="last_login"/>
      <el-table-column
        fixed="right"
        label="操作">
        <template slot-scope="scope">
          <el-button type="text" size="small" @click="handleAssignGroup(scope.row)">指定角色</el-button>
          <el-button type="text" size="small" @click="handleModify(scope.row)">修改</el-button>
        </template>
      </el-table-column>
    </el-table>
    <el-row v-show="total>10" type="flex" justify="center" style="padding-top:20px;">
      <el-pagination
        :total="total"
        layout="prev, pager, next"
        background
        @current-change="handleChange" />
    </el-row>
    <AddUserForm v-model="addUserVisible" @fetch="handleFetch" />
    <ModifyUserForm v-model="modifyUserVisible" :user-id="userId" @fetch="handleFetch" />
    <AssignGroup v-model="assignGroupVisible" :user-id="userId" :user-name="userName" />
  </div>
</template>
<script>
  import { getUserList, modifyUser } from '@/api/user'
  import AddUserForm from './components/addUserForm'
  import ModifyUserForm from './components/modifyUser'
  import AssignGroup from './components/assignGroup'
  export default {
    name: 'UserList',
    components: {
      AddUserForm,
      ModifyUserForm,
      AssignGroup
    },
    data() {
      return {
        userList: [],
        addUserVisible: false,
        modifyUserVisible: false,
        userId: 0,
        userName: '',
        total: 0,
        params: {
          page: 1,
          username: ''
        },
        assignGroupVisible: false
      }
    },
    created() {
      this.fetchUserList()
    },
    methods: {
      fetchUserList() {
        getUserList(this.params).then(res => {
          this.userList = res.results
          this.total = res.count
        })
      },
      handleUserStatusChange(obj) {
        modifyUser(obj.id, { is_active: obj.is_active }).then(() => {
          this.$message({
            message: `修改 ${obj.name} 的状态成功`,
            type: 'success'
          })
        })
      },
      handleFetch() {
        this.fetchUserList()
      },
      handleModify(obj) {
        this.userId = obj.id
        this.modifyUserVisible = true
      },
      handleChange(val) {
        this.params.page = val
        this.fetchUserList()
      },
      handleSearch() {
        this.params.page = 1
        this.fetchUserList()
      },
      handleAssignGroup(obj) {
        this.userId = obj.id
        this.userName = obj.name
        this.assignGroupVisible = true
      }
    }
  }
</script>

(3)users/components/modifyUser.vue

效果如下图:

 这样前端就写好了!!

2.后端接口

(1)groups/views.py:

from django.contrib.auth.models import Group
from django.contrib.auth import get_user_model
from rest_framework import viewsets, mixins, response,status
from .serializers import GroupSerializer, UserGroupsSerializer
from .filters import GroupFilter
from users.serializers import UserSerializer

User = get_user_model()

class GroupViewset(viewsets.ModelViewSet):
    queryset = Group.objects.all()
    serializer_class = GroupSerializer
    filter_class = GroupFilter
    filter_fields = ("name",)


class UserGroupsViewset(viewsets.GenericViewSet,
                        mixins.UpdateModelMixin,
                        mixins.RetrieveModelMixin):
    """
    retrive:
        获取当前用户的呢的用户组列表
    update:
        修改当前用户的角色(角色)
    """
    queryset = User.objects.all()
    serializer_class = UserGroupsSerializer
    def retrieve(self, request, *args, **kwargs):
        userObj = self.get_object()
        queryset = userObj.groups.all()

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return response.Response(serializer.data)

    def update(self, request, *args, **kwargs):
        userObj = self.get_object()
        groupIds = request.data.get("gids", [])
        # userObj.groups = Group.objects.filter(id__in=groupIds)
        userObj.groups.set(Group.objects.filter(id__in=groupIds))

        return response.Response(status=status.HTTP_204_NO_CONTENT)

(2)groups/serializers.py:

from django.contrib.auth.models import Group
from rest_framework import serializers


class GroupSerializer(serializers.ModelSerializer):
    """
    group序列化类
    """
    class Meta:
        model = Group
        fields = ("id", "name")

class UserGroupsSerializer(serializers.Serializer):
    """
    group序列化类
    """
    id = serializers.ReadOnlyField()
    name = serializers.ReadOnlyField()
    class Meta:
        model = Group
        fields = ("id", "name")

(3)groups/router.py:

from rest_framework.routers import DefaultRouter
from .views import GroupViewset, UserGroupsViewset

group_router = DefaultRouter()
group_router.register('groups', GroupViewset, basename="groups")
group_router.register('userGroups', UserGroupsViewset, basename="userGroups")

3.写前端的api接口:

(1)api/group.js

import request from '@/utils/request'

export function getGroupList(params) {
  return request({
    url: '/api/groups/',
    method: 'get',
    params
  })
}

export function addGroup(data) {
  return request({
    url: '/api/groups/',
    method: 'post',
    data
  })
}

export function modifyGroup(id, data) {
  return request({
    url: `/api/groups/${id}/`,
    method: 'patch',
    data
  })
}

// 修改指定用户的角色
export function updateUserGroups(uid, data) {
  return request({
    url: `/api/userGroups/${uid}/`,
    method: 'patch',
    data
  })
}

// 获取指定用户的所有角色
export function getUserGroupList(uid, params) {
  return request({
    url: `/api/userGroups/${uid}/`,
    method: 'get',
    params
  })
}

(2)components/assignGroup.vue

<template>
  <div class="assign-group">
    <el-dialog :visible.sync="visible" title="指定角色" @close="handleClose">
      <el-form ref="addUserForm" label-width="100px">
        <el-form-item label="用户名:">
          <el-input v-model="userName" readonly/>
        </el-form-item>
        <el-form-item label="姓名:">
          <el-select v-model="userGroups" multiple placeholder="请选择" style="width:100%">
            <el-option
              v-for="item in options"
              :key="item.id"
              :label="item.name"
              :value="item.id"/>
          </el-select>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="handleClose">取 消</el-button>
        <el-button type="primary" @click="handleSubmit">提交</el-button>
      </div>
    </el-dialog>
  </div>
</template>
<script>
  import { getGroupList, updateUserGroups, getUserGroupList } from '@/api/group'
  export default {
    name: 'AssignGroup',
    props: {
      value: {
        type: Boolean,
        default: false
      },
      userId: {
        type: Number,
        default: 0
      },
      userName: {
        type: String,
        default: ''
      }
    },
    data() {
      return {
        visible: false,
        options: [],
        userGroups: []
      }
    },
    watch: {
      value(val) {
        if (val <= 0) return
        this.visible = val
        this.fetchGroupList()
        this.fetchUserGroups()
      }
    },
    methods: {
      handleClose() {
        this.visible = false
        this.$emit('input', false)
        setTimeout(() => {
          this.options = []
          this.userGroups = []
        }, 500)
      },
      fetchGroupList() {
        getGroupList({ page_size: 0 }).then(res => {
          this.options = res
        })
      },
      fetchUserGroups() {
        getUserGroupList(this.userId, { page_size: 0 }).then(res => {
          res.forEach((item) => {
            this.userGroups.push(item.id)
          })
        })
      },
      handleSubmit() {
        updateUserGroups(this.userId, { gids: this.userGroups }).then(() => {
          this.$message({
            message: `修改 ${this.userName} 用户组成功`,
            type: 'success'
          })
          this.handleClose()
        })
      }
    }
  }
</script>

报500错误:

 

 TypeError: Direct assignment to the forward side of a many-to-many set is prohibited. Use groups.set() instead

 

 解决:原因Django从1.升级到2.时:orm多对多外健不再用=等赋值,改为set方法

# userObj.groups = Group.objects.filter(id__in=groupIds)
    userObj.groups.set(Group.objects.filter(id__in=groupIds))

效果如图:

 

 4.用户组成员列表

写后端:

groups/views.py:

from django.contrib.auth.models import Group
from django.contrib.auth import get_user_model
from rest_framework import viewsets, mixins, response,status
from .serializers import GroupSerializer, UserGroupsSerializer
from .filters import GroupFilter
from users.serializers import UserSerializer

User = get_user_model()

class GroupViewset(viewsets.ModelViewSet):
    queryset = Group.objects.all()
    serializer_class = GroupSerializer
    filter_class = GroupFilter
    filter_fields = ("name",)


class UserGroupsViewset(viewsets.GenericViewSet,
                        mixins.UpdateModelMixin,
                        mixins.RetrieveModelMixin):
    """
    retrive:
        获取当前用户的呢的用户组列表
    update:
        修改当前用户的角色(角色)
    """
    queryset = User.objects.all()
    serializer_class = UserGroupsSerializer
    def retrieve(self, request, *args, **kwargs):
        userObj = self.get_object()
        queryset = userObj.groups.all()

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return response.Response(serializer.data)

    def update(self, request, *args, **kwargs):
        userObj = self.get_object()
        groupIds = request.data.get("gids", [])
        # userObj.groups = Group.objects.filter(id__in=groupIds)
        userObj.groups.set(Group.objects.filter(id__in=groupIds))

        return response.Response(status=status.HTTP_204_NO_CONTENT)


class GroupMembersViewset(viewsets.GenericViewSet,
                        mixins.RetrieveModelMixin,
                        mixins.DestroyModelMixin):
    """
    角色成员管理

    retrieve:
        获取指定组下的成员列表
    """
    queryset = Group.objects.all()
    serializer_class = UserSerializer

    def retrieve(self, request, *args, **kwargs):
        groupObj = self.get_object()
        queryset = groupObj.user_set.all()

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return response.Response(serializer.data)

    def destroy(self, request, *args, **kwargs):
        groupObj = self.get_object()
        userId = request.data.get("uid", 0)
        ret = {"status":0}
        try:
            userObj = User.objects.get(pk=userId)
            groupObj.user_set.remove(userObj)
        except User.DoesNotExist:
            ret["status"] = 1
            ret["errmsg"] = "用户错误"
        return response.Response(ret, status=status.HTTP_200_OK)

groups/router.py:

from rest_framework.routers import DefaultRouter
from .views import GroupViewset, UserGroupsViewset, GroupMembersViewset

group_router = DefaultRouter()
group_router.register('groups', GroupViewset, basename="groups")
group_router.register('userGroups', UserGroupsViewset, basename="userGroups")
group_router.register('groupMembers', GroupMembersViewset, basename="groupMembers")

groups/serializers.py:

from django.contrib.auth.models import Group
from rest_framework import serializers


class GroupSerializer(serializers.ModelSerializer):
    """
    group序列化类
    """
    def to_representation(self, instance):
        ret = super(GroupSerializer, self).to_representation(instance)
        ret["members"] = instance.user_set.count()
        return ret
    class Meta:
        model = Group
        fields = ("id", "name")

class UserGroupsSerializer(serializers.Serializer):
    """
    group序列化类
    """
    id = serializers.ReadOnlyField()
    name = serializers.ReadOnlyField()
    class Meta:
        model = Group
        fields = ("id", "name")

写前端:

(1)groups/components/groupMembers.vue

<template>
  <div class="group-members">
    <el-dialog :visible.sync="visible" :title="title" @close="handleClose">
      <el-table
        v-loading="loading"
        :data="memberList"
        stripe
        style="width: 100%">
        <el-table-column
          type="index"/>
        <el-table-column
          prop="phone"
          label="电话"/>
        <el-table-column
          prop="email"
          label="email"/>
        <el-table-column
          prop="name"
          label="姓名"/>
      </el-table>
      <el-row v-show="total>params.page_size" type="flex" justify="center" style="padding-top:20px;">
        <el-pagination
          :total="total"
          :page-size="params.page_size"
          :current-page.sync="params.page"
          layout="total, prev, pager, next"
          background
          @current-change="handleChange" />
      </el-row>
    </el-dialog>
  </div>
</template>
<script>
  import { getGroupMemberList } from '@/api/group'
  export default {
    name: 'GroupMember',
    props: {
      value: {
        type: Boolean,
        default: false
      },
      gid: {
        type: Number,
        default: 0
      },
      gname: {
        type: String,
        default: ''
      }
    },
    data() {
      return {
        visible: false,
        loading: false,
        memberList: [],
        total: 0,
        params: {
          page: 1,
          page_size: 6
        }
      }
    },
    computed: {
      title() {
        return `${this.gname} 的成员列表`
      }
    },
    watch: {
      value(val) {
        if (val !== true) return
        this.visible = val
        this.params.page = 1
        this.fetchGroupMemberList()
      }
    },
    methods: {
      handleClose() {
        this.visible = false
        this.$emit('input', false)
        this.$emit('fetch')
        setTimeout(() => {
          this.memberList = []
        }, 500)
      },
      fetchGroupMemberList() {
        this.loading = true
        getGroupMemberList(this.gid, this.params).then(res => {
          this.memberList = res.results
          this.total = res.count
          this.loading = false
        })
      },
      handleChange(val) {
        this.params.page = val
        this.fetchGroupMemberList()
      }
    }
  }
</script>

(2)groups/index.vue

<template>
  <div class="app-container">
    <el-row>
      <el-col :span="12">
        <el-input v-model="params.name" placeholder="搜索用户组" @keyup.enter.native="handleSearch">
          <el-button slot="append" icon="el-icon-search" @click="handleSearch"/>
        </el-input>
      </el-col>
      <el-col :span="12" align="right" style="padding-right:20px;">
        <el-button type="primary" @click="handleAddGroup">增加用户组</el-button>
      </el-col>
    </el-row>
    <el-table
      :data="groupData"
      stripe
      style="width: 100%">
      <el-table-column
        type="index"
        width="50" />
      <el-table-column
        prop="name"
        label="姓名"/>
      <el-table-column
        fixed="right"
        label="成员管理">
        <template slot-scope="scope">
          <el-button type="text" size="small" @click="handleGroupMember(scope.row)">成员列表 <el-badge :value="scope.row.members" class="mark" /></el-button>
        </template>
      </el-table-column>
      <el-table-column
        fixed="right"
        label="操作">
        <template slot-scope="scope">
          <el-button type="text" size="small" @click="handleModifyGroup(scope.row)">修改</el-button>
        </template>
      </el-table-column>
    </el-table>
    <el-row v-show="total>10" type="flex" justify="center" style="padding-top:20px;">
      <el-pagination
        :total="total"
        layout="prev, pager, next"
        background
        @current-change="handleChange" />
    </el-row>
    <GroupForm v-model="groupFormVisible" :gid="groupId" :gname="groupName" @fetch="handleFetch"/>
    <GroupMember v-model="groupMemberVisible" :gid="groupId" :gname="groupName" @fetch="handleFetch"/>
  </div>
</template>
<script>
  import { getGroupList } from '@/api/group'
  import GroupForm from './components/groupForm'
  import GroupMember from './components/groupMembers'
  export default {
    name: 'Groups',
    components: {
      GroupForm,
      GroupMember
    },
    data() {
      return {
        groupData: [],
        total: 0,
        params: {
          page: 1,
          name: ''
        },
        groupFormVisible: false,
        groupMemberVisible: false,
        groupId: 0,
        groupName: ''
      }
    },
    created() {
      this.fetchGroupList()
    },
    methods: {
      fetchGroupList() {
        getGroupList(this.params).then(res => {
          this.groupData = res.results
          this.total = res.count
        })
      },
      handleChange(val) {
        this.params.page = val
        this.fetchGroupList()
      },
      handleSearch() {
        this.params.page = 1
        this.fetchGroupList()
      },
      handleAddGroup() {
        this.groupId = 0
        this.groupName = ''
        this.groupFormVisible = true
      },
      handleFetch() {
        this.groupId = 0
        this.fetchGroupList()
      },
      handleModifyGroup(obj) {
        this.groupId = obj.id
        this.groupName = obj.name
        this.groupFormVisible = true
      },
      handleGroupMember(obj) {
        this.groupId = obj.id
        this.groupName = obj.name
        this.groupMemberVisible = true
      }
    }
  }
</script>

(3)api/group.js

import request from '@/utils/request'

export function getGroupList(params) {
  return request({
    url: '/api/groups/',
    method: 'get',
    params
  })
}

export function addGroup(data) {
  return request({
    url: '/api/groups/',
    method: 'post',
    data
  })
}

export function modifyGroup(id, data) {
  return request({
    url: `/api/groups/${id}/`,
    method: 'patch',
    data
  })
}

// 修改指定用户的角色
export function updateUserGroups(uid, data) {
  return request({
    url: `/api/userGroups/${uid}/`,
    method: 'patch',
    data
  })
}

// 获取指定用户的所有角色
export function getUserGroupList(uid, params) {
  return request({
    url: `/api/userGroups/${uid}/`,
    method: 'get',
    params
  })
}

// 获取指定用户组下的成员列表
export function getGroupMemberList(gid, params) {
  return request({
    url: `/api/groupMembers/${gid}/`,
    method: 'get',
    params
  })
}

效果如下:

 

 

 

 

 5.从用户组中移除成员

(1)groups/components/groupMembers.vue

<template>
  <div class="group-members">
    <el-dialog :visible.sync="visible" :title="title" @close="handleClose">
      <el-table
        v-loading="loading"
        :data="memberList"
        stripe
        style="width: 100%">
        <el-table-column
          type="index"/>
        <el-table-column
          prop="phone"
          label="电话"/>
        <el-table-column
          prop="email"
          label="email"/>
        <el-table-column
          prop="name"
          label="姓名"/>
        <el-table-column
          fixed="right"
          label="成员管理">
          <template slot-scope="scope">
            <el-button type="text" size="small" @click="handleRemoveMember(scope.row)">移除成员</el-button>
          </template>
        </el-table-column>
      </el-table>
      <el-row v-show="total>params.page_size" type="flex" justify="center" style="padding-top:20px;">
        <el-pagination
          :total="total"
          :page-size="params.page_size"
          :current-page.sync="params.page"
          layout="total, prev, pager, next"
          background
          @current-change="handleChange" />
      </el-row>
    </el-dialog>
  </div>
</template>
<script>
  import { getGroupMemberList, removeGroupMember } from '@/api/group'
  export default {
    name: 'GroupMember',
    props: {
      value: {
        type: Boolean,
        default: false
      },
      gid: {
        type: Number,
        default: 0
      },
      gname: {
        type: String,
        default: ''
      }
    },
    data() {
      return {
        visible: false,
        loading: false,
        memberList: [],
        total: 0,
        params: {
          page: 1,
          page_size: 6
        }
      }
    },
    computed: {
      title() {
        return `${this.gname} 的成员列表`
      }
    },
    watch: {
      value(val) {
        if (val !== true) return
        this.visible = val
        this.params.page = 1
        this.fetchGroupMemberList()
      }
    },
    methods: {
      handleClose() {
        this.visible = false
        this.$emit('input', false)
        this.$emit('fetch')
        setTimeout(() => {
          this.memberList = []
        }, 500)
      },
      fetchGroupMemberList() {
        this.loading = true
        getGroupMemberList(this.gid, this.params).then(res => {
          this.memberList = res.results
          this.total = res.count
          this.loading = false
        })
      },
      handleChange(val) {
        this.params.page = val
        this.fetchGroupMemberList()
      },
      handleRemoveMember(obj) {
        removeGroupMember(this.gid, { uid: obj.id }).then(res => {
          if (res.status === 0) {
            this.$message({
              message: `从 ${this.gname} 组中移除 ${obj.name} 成功`,
              type: 'success'
            })
            this.fetchGroupMemberList()
          } else {
            this.$message({
              message: `从 ${this.gname} 组中移除 ${obj.name} 失败: ${res.errmsg}`,
              type: 'error'
            })
          }
        })
      }
    }
  }
</script>

(2)api/groups.js

....
// 从用户组中移除指定用户
export function removeGroupMember(gid, data) {
  return request({
    url: `/api/groupMembers/${gid}/`,
    method: 'delete',
    data
  })
}

效果如下图:

6. 修改用户组权限

https://element.eleme.cn/#/zh-CN/component/transfer#transfer-chuan-suo-kuang

 

(1)groups/index.vue

<template>
  <div class="app-container">
    <el-row>
      <el-col :span="12">
        <el-input v-model="params.name" placeholder="搜索用户组" @keyup.enter.native="handleSearch">
          <el-button slot="append" icon="el-icon-search" @click="handleSearch"/>
        </el-input>
      </el-col>
      <el-col :span="12" align="right" style="padding-right:20px;">
        <el-button type="primary" @click="handleAddGroup">增加用户组</el-button>
      </el-col>
    </el-row>
    <el-table
      :data="groupData"
      stripe
      style="width: 100%">
      <el-table-column
        type="index"
        width="50" />
      <el-table-column
        prop="name"
        label="姓名"/>
      <el-table-column
        fixed="right"
        label="成员管理">
        <template slot-scope="scope">
          <el-button type="text" size="small" @click="handleGroupMember(scope.row)">成员列表 <el-badge :value="scope.row.members" class="mark" /></el-button>
        </template>
      </el-table-column>
      <el-table-column
        fixed="right"
        label="权限管理">
        <template slot-scope="scope">
          <el-button type="text" size="small" @click="handleGroupPermission(scope.row)">修改权限</el-button>
        </template>
      </el-table-column>
      <el-table-column
        fixed="right"
        label="操作">
        <template slot-scope="scope">
          <el-button type="text" size="small" @click="handleModifyGroup(scope.row)">修改</el-button>
        </template>
      </el-table-column>
    </el-table>
    <el-row v-show="total>10" type="flex" justify="center" style="padding-top:20px;">
      <el-pagination
        :total="total"
        layout="prev, pager, next"
        background
        @current-change="handleChange" />
    </el-row>
    <GroupForm v-model="groupFormVisible" :gid="groupId" :gname="groupName" @fetch="handleFetch"/>
    <GroupMember v-model="groupMemberVisible" :gid="groupId" :gname="groupName" @fetch="handleFetch"/>
    <GroupPermission v-model="groupPermissionVisible" :gid="groupId" :gname="groupName"/>
  </div>
</template>
<script>
  import { getGroupList } from '@/api/group'
  import GroupForm from './components/groupForm'
  import GroupMember from './components/groupMembers'
  import GroupPermission from './components/groupPermission'
  export default {
    name: 'Groups',
    components: {
      GroupForm,
      GroupMember,
      GroupPermission
    },
    data() {
      return {
        groupData: [],
        total: 0,
        params: {
          page: 1,
          name: ''
        },
        groupFormVisible: false,
        groupMemberVisible: false,
        groupPermissionVisible: false,
        groupId: 0,
        groupName: ''
      }
    },
    created() {
      this.fetchGroupList()
    },
    methods: {
      fetchGroupList() {
        getGroupList(this.params).then(res => {
          this.groupData = res.results
          this.total = res.count
        })
      },
      handleChange(val) {
        this.params.page = val
        this.fetchGroupList()
      },
      handleSearch() {
        this.params.page = 1
        this.fetchGroupList()
      },
      handleAddGroup() {
        this.groupId = 0
        this.groupName = ''
        this.groupFormVisible = true
      },
      handleFetch() {
        this.groupId = 0
        this.fetchGroupList()
      },
      handleModifyGroup(obj) {
        this.groupId = obj.id
        this.groupName = obj.name
        this.groupFormVisible = true
      },
      handleGroupMember(obj) {
        this.groupId = obj.id
        this.groupName = obj.name
        this.groupMemberVisible = true
      },
      handleGroupPermission(obj) {
        this.groupId = obj.id
        this.groupName = obj.name
        this.groupPermissionVisible = true
      }
    }
  }
</script>

(2)groups/components/groupPermission.vue

<template>
  <div class="group-permission">
    <el-dialog :visible.sync="visible" :title="title" @close="handleClose">
      <el-transfer
        v-model="groupPermission"
        :titles="transferTitle"
        :data="data"
        filterable />
      <span slot="footer" class="dialog-footer">
        <el-button @click="handleClose">取 消</el-button>
        <el-button type="primary" @click="handleUpdateGroupPermission">提交修改</el-button>
      </span>
    </el-dialog>
  </div>
</template>
<script>
import { getPermissionList, updateGroupPermissionList, getGroupPermissionList } from '@/api/permission'
export default {
  name: 'GroupPermission',
  props: {
    value: {
      type: Boolean,
      default: false
    },
    gid: {
      type: Number,
      default: 0
    },
    gname: {
      type: String,
      default: ''
    }
  },
  data() {
    return {
      visible: false,
      data: [],
      groupPermission: []
    }
  },
  computed: {
    title() {
      return `修改 ${this.gname} 的权限`
    },
    transferTitle() {
      return ['权限点', `${this.gname} 的权限`]
    }
  },
  watch: {
    value(val) {
      if (val === false) return
      this.visible = val
      this.fetchPermissionList()
      this.fetchGroupPermissionList()
    }
  },
  methods: {
    handleClose() {
      this.visible = false
      this.$emit('input', false)
      setTimeout(() => {
        this.groupPermission = []
      }, 500)
    },
    fetchPermissionList() {
      getPermissionList({ page_size: 0 }).then(res => {
        console.log(res)
        this.data = res
      })
    },
    handleUpdateGroupPermission() {
      updateGroupPermissionList(this.gid, { pids: this.groupPermission }).then(res => {
        if (res.status === 0) {
          this.$message({
            message: `修改 ${this.gname} 组的权限成功`,
            type: 'success'
          })
          this.handleClose()
        }
      })
    },
    fetchGroupPermissionList() {
      getGroupPermissionList(this.gid).then(res => {
        res.forEach((item) => {
          this.groupPermission.push(item.key)
        })
      })
    }
  }
}
</script>
<style>
  .el-transfer-panel{width:40%}
</style>

(3)api/permission.js

import request from '@/utils/request'

export function getPermissionList(params) {
  return request({
    url: '/api/permission/',
    method: 'get',
    params
  })
}

// 更新指定用户组的权限列表
export function updateGroupPermissionList(gid, data) {
  return request({
    url: `/api/groupPermission/${gid}/`,
    method: 'patch',
    data
  })
}

// 获取指定用户组的权限列表
export function getGroupPermissionList(gid, params) {
  return request({
    url: `/api/groupPermission/${gid}/`,
    method: 'get',
    params
  })
}

后端:

(1)settings.py

INSTALLED_APPS = [
'permissions',

(2)apps/permissions/views.py

from rest_framework import viewsets, mixins, response, status
from django.contrib.auth.models import Permission, Group
from .serializers import PermissionSerializer

class PermissionViewset(viewsets.GenericViewSet,
                        mixins.ListModelMixin):
    """
    权限列表
    list:
        获取权限列表
    """
    queryset = Permission.objects.all()
    serializer_class = PermissionSerializer

class GroupPermissionViewset(viewsets.GenericViewSet,
                             mixins.RetrieveModelMixin,
                             mixins.UpdateModelMixin):
    """
    用户组的权限

    retrieve:
        返回用户组的权限列表
    update:
        更新指定用户组的权限
    """
    queryset = Group.objects.all()
    serializer_class = PermissionSerializer

    def retrieve(self, request, *args, **kwargs):
        groupObj = self.get_object()
        queryset = groupObj.permissions.all()
        serializer = self.get_serializer(queryset, many=True)
        return response.Response(serializer.data)


    def update(self, request, *args, **kwargs):
        ret = {"status":0}
        groupObj = self.get_object()
        pids = request.data.get("pids",[])
        groupObj.permissions = Permission.objects.filter(pk__in=pids)
        return response.Response(ret, status=status.HTTP_200_OK)

(3)apps/permissions/router.py

from rest_framework.routers import DefaultRouter
from .views import PermissionViewset, GroupPermissionViewset

permission_router = DefaultRouter()
permission_router.register('permission', PermissionViewset, basename="permission")
permission_router.register('groupPermission', GroupPermissionViewset, basename="groupPermission")

(4)apps/permissions/serializers.py

from rest_framework import serializers
from django.contrib.auth.models import Permission, ContentType

class PermissionSerializer(serializers.ModelSerializer):
    def to_representation(self, instance):
        ret = {}
        ret["key"] = instance.id
        ret["label"] = "{}.{}".format(instance.content_type.app_label, instance.codename)
        return ret

    class Meta:
        model = Permission
        fields = "__all__"

效果如下:

 四.权限控制应用

 1.用户登录(gwt)与退出

https://baijiahao.baidu.com/s?id=1608021814182894637&wfr=spider&for=pc

https://github.com/GetBlimp/django-rest-framework-jwt  官方文档

https://jpadilla.github.io/django-rest-framework-jwt/  使用文档

后端:

(python36env) [vagrant@CentOS devops]$ pip install djangorestframework-jw 安装

(1)settings.py:

import os
import sys
import datetime


REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'devops.paginations.Pagination',
    'PAGE_SIZE': 10,
    'DEFAULT_FILTER_BACKENDS': (
        'django_filters.rest_framework.DjangoFilterBackend',
    ),
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
        'rest_framework.permissions.DjangoModelPermissions',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication',
    ),
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
}
JWT_AUTH = {
    'JWT_EXPIRATION_DELTA': datetime.timedelta(minutes=10),
    'JWT_AUTH_HEADER_PREFIX': 'JWT',
}

(2)devops/urls.py:

from rest_framework_jwt.views import obtain_jwt_token
..
from permissions.router import permission_router

router.registry.extend(permission_router.registry)

...
urlpatterns = [
...
    url(r'^api-token-auth/', obtain_jwt_token),
]
(python36env) [vagrant@CentOS devops]$ curl -X POST -H "Content-Type: application/json" -d '{"username":"admin","password":"123456"}' http://localhost:8000/api-token-auth/
{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo3MSwidXNlcm5hbWUiOiJhZG1pbiIsImV4cCI6MTU5NTIzMjkzNiwiZW1haWwiOiJhZG1pbkA1MXJlYm9vdC5jb20ifQ.hdgMbP-8ohsCayslX_ZvyMo5DUi65DjUcrvXpp5Ps4g"}

(3)users/views.py:

from rest_framework import viewsets, permissions, mixins, response
from .serializers import UserSerializer, UserRegSerializer
from .filters import UserFilter
from django_filters.rest_framework import DjangoFilterBackend
from django.contrib.auth import get_user_model
User = get_user_model()


class UserViewset(mixins.RetrieveModelMixin,
                   mixins.UpdateModelMixin,
                   mixins.DestroyModelMixin,
                   mixins.ListModelMixin,
                   viewsets.GenericViewSet):
    """
    retrieve:
        获取指定user记录
    list:
        获取user列表
    update:
        更新user记录
    partial_update:
        更新user的部门字段
    destroy:
        删除user记录
    """
    queryset = User.objects.filter(is_superuser=False)
    serializer_class = UserSerializer
    filter_class=UserFilter
    filter_fields = ("username",)

class UserRegViewset(viewsets.GenericViewSet,
                     mixins.CreateModelMixin,
                     mixins.UpdateModelMixin):
    """
    create:
        用户注册
    partial_update:
        修改密码
    update:
        修改密码
    """
    queryset = User.objects.all()
    serializer_class = UserRegSerializer


class UserInfoViewset(viewsets.ViewSet):
    permission_classes = (permissions.IsAuthenticated,)
    def list(self, request, *args, **kwargs):
        data = {
            "username": self.request.user.username,
            "name": self.request.user.name,
            "permission": self.request.user.get_all_permissions()
        }
        return response.Response(data)

(4)users/router.py:

from rest_framework.routers import DefaultRouter
from .views import UserViewset, UserRegViewset, UserInfoViewset
router = DefaultRouter()
router.register("users", UserViewset, basename="users")
router.register("userReg", UserRegViewset, basename="userReg")
router.register("UserInfo", UserInfoViewset, basename="UserInfo")

效果如图:

前端:

(1)src/permission.jss

import router from './router'
import store from './store'
import NProgress from 'nprogress' // Progress 进度条
import 'nprogress/nprogress.css'
import { getToken } from './utils/auth'
// Progress 进度条样式
import { Message } from 'element-ui'
// import { getToken } from '@/utils/auth' // 验权

const whiteList = ['/login'] // 不重定向白名单
router.beforeEach((to, from, next) => {
  NProgress.start()
  if (getToken()) {
    if (to.path === '/login') {
      next({ path: '/' })
      NProgress.done()
    } else {
      if (store.getters.name.length === 0) {
        store.dispatch('GetInfo').then(() => { // 拉取用户信息
          next()
        }).catch((err) => {
          store.dispatch('FedLogOut').then(() => {
            Message.error(err || 'Verification failed, please login again')
            next({ path: '/' })
          })
        })
      } else {
        next()
      }
    }
  } else {
    if (whiteList.indexOf(to.path) !== -1) {
      next()
    } else {
      next(`/login?redirect=${to.path}`)
      NProgress.done()
    }
  }
})

router.afterEach(() => {
  NProgress.done() // 结束Progress
})

(2)login/index.vue

<template>
  <div class="login-container">
    <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" auto-complete="on" label-position="left">
      <h3 class="title">51reboot运维平台</h3>
      <el-form-item prop="username">
        <span class="svg-container">
          <svg-icon icon-class="user" />
        </span>
        <el-input v-model="loginForm.username" type="text" auto-complete="off" placeholder="请输入用户名" />
      </el-form-item>
      <el-form-item prop="password">
        <span class="svg-container">
          <svg-icon icon-class="password" />
        </span>
        <el-input
          :type="pwdType"
          v-model="loginForm.password"
          auto-complete="off"
          placeholder="请输入密码"
          @keyup.enter.native="handleLogin" />
        <span class="show-pwd" @click="showPwd">
          <svg-icon icon-class="eye" />
        </span>
      </el-form-item>
      <el-form-item>
        <el-button :loading="loading" type="primary" style="width:100%;" @click.native.prevent="handleLogin">
          登陆
        </el-button>
      </el-form-item>
    </el-form>
  </div>
</template>

<script>
  export default {
    name: 'Login',
    data() {
      return {
        loginForm: {
          username: '',
          password: ''
        },
        loginRules: {
          username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
          password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
        },
        loading: false,
        pwdType: 'password',
        redirect: undefined
      }
    },
    methods: {
      showPwd() {
        if (this.pwdType === 'password') {
          this.pwdType = ''
        } else {
          this.pwdType = 'password'
        }
      },
      handleLogin() {
        this.$refs.loginForm.validate(valid => {
          if (valid) {
            this.loading = true
            this.$store.dispatch('Login', this.loginForm).then(() => {
              this.loading = false
              this.$router.push({ path: this.redirect || '/' })
            }).catch(() => {
              this.loading = false
            })
          } else {
            console.log('error submit!!')
            return false
          }
        })
      }
    }
  }
</script>

<style rel="stylesheet/scss" lang="scss">
  $bg:#2d3a4b;
  $light_gray:#eee;

  /* reset element-ui css */
  .login-container {
    .el-input {
      display: inline-block;
      height: 47px;
      width: 85%;
      input {
        background: transparent;
        border: 0px;
        -webkit-appearance: none;
        border-radius: 0px;
        padding: 12px 5px 12px 15px;
        color: $light_gray;
        height: 47px;
        &:-webkit-autofill {
          -webkit-box-shadow: 0 0 0px 1000px $bg inset !important;
          -webkit-text-fill-color: #fff !important;
        }
      }
    }
    .el-form-item {
      border: 1px solid rgba(255, 255, 255, 0.1);
      background: rgba(0, 0, 0, 0.1);
      border-radius: 5px;
      color: #454545;
    }
  }

</style>

<style rel="stylesheet/scss" lang="scss" scoped>
  $bg:#2d3a4b;
  $dark_gray:#889aa4;
  $light_gray:#eee;
  .login-container {
    position: fixed;
    height: 100%;
    width: 100%;
    background-color: $bg;
    .login-form {
      position: absolute;
      left: 0;
      right: 0;
      width: 520px;
      max-width: 100%;
      padding: 35px 35px 15px 35px;
      margin: 120px auto;
    }
    .tips {
      font-size: 14px;
      color: #fff;
      margin-bottom: 10px;
      span {
        &:first-of-type {
          margin-right: 16px;
        }
      }
    }
    .svg-container {
      padding: 6px 5px 6px 15px;
      color: $dark_gray;
      vertical-align: middle;
      width: 30px;
      display: inline-block;
    }
    .title {
      font-size: 26px;
      font-weight: 400;
      color: $light_gray;
      margin: 0px auto 40px auto;
      text-align: center;
      font-weight: bold;
    }
    .show-pwd {
      position: absolute;
      right: 10px;
      top: 7px;
      font-size: 16px;
      color: $dark_gray;
      cursor: pointer;
      user-select: none;
    }
  }
</style>

(3)api/login.js

import request from '@/utils/request'

export function login(username, password) {
  return request({
    url: '/api-token-auth/',
    method: 'post',
    data: {
      username,
      password
    }
  })
}

export function getInfo() {
  return request({
    url: '/api/UserInfo',
    method: 'get'
  })
}

export function logout() {
  return request({
    url: '/user/logout',
    method: 'post'
  })
}

(4)src\store\modules\user.js

import { login, getInfo } from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth'

const user = {
  state: {
    token: getToken(),
    name: '',
    username: '',
    permission: [],
    avatar: '',
    roles: []
  },

  mutations: {
    SET_TOKEN: (state, token) => {
      state.token = token
    },
    SET_NAME: (state, name) => {
      if (name === null) return
      state.name = name
    },
    SET_AVATAR: (state, avatar) => {
      state.avatar = avatar
    },
    SET_ROLES: (state, roles) => {
      state.roles = roles
    },
    SET_USERNAME: (state, username) => {
      state.username = username
    },
    SET_PERMISSION: (state, permission) => {
      state.permission = permission
    }
  },

  actions: {
    // 登录
    Login({ commit }, userInfo) {
      const username = userInfo.username.trim()
      return new Promise((resolve, reject) => {
        login(username, userInfo.password).then(response => {
          console.log(response)
          setToken(response.token)
          commit('SET_TOKEN', response.token)
          resolve()
        }).catch(error => {
          reject(error)
        })
      })
    },

    // 获取用户信息
    GetInfo({ commit, state }) {
      return new Promise((resolve, reject) => {
        getInfo(state.token).then(response => {
          commit('SET_USERNAME', response.username)
          commit('SET_NAME', response.name)
          commit('SET_PERMISSION', response.permission)
          resolve(response)
        }).catch(error => {
          reject(error)
        })
      })
    },

    // 登出
    LogOut({ commit, state }) {
      return new Promise((resolve, reject) => {
        commit('SET_TOKEN', '')
        commit('SET_NAME', '')
        removeToken()
        resolve()
      })
    },

    // 前端 登出
    FedLogOut({ commit }) {
      return new Promise(resolve => {
        commit('SET_TOKEN', '')
        commit('SET_NAME', '')
        removeToken()
        resolve()
      })
    }
  }
}

export default user

(5)src/utils/auth.js

import Cookies from 'js-cookie'
import store from '../store'
const TokenKey = 'token'

export function getToken() {
  return Cookies.get(TokenKey)
}

export function setToken(token) {
  return Cookies.set(TokenKey, token)
}

export function removeToken() {
  return Cookies.remove(TokenKey)
}

export function checkPermission(perm) {
  if (store.getters.permission.indexOf(perm) > -1) {
    return true
  }
  return false
}

(6)utils/request.js

import axios from 'axios'
import { Message } from 'element-ui'
// import { Message, MessageBox } from 'element-ui'
import store from '../store'
import { getToken } from '@/utils/auth'
import router from '../router'
// 创建axios实例
const service = axios.create({
  baseURL: process.env.BASE_API, // api 的 base_url
  timeout: 5000 // 请求超时时间
})

// request拦截器
service.interceptors.request.use(
  config => {
    if (store.getters.token) {
      config.headers['Authorization'] = 'JWT ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
    }
    return config
  },
  error => {
    // Do something with request error
    console.log(error) // for debug
    Promise.reject(error)
  }
)

// response 拦截器
service.interceptors.response.use(
  response => {
    /**
     * code为非20000是抛错 可结合自己业务进行修改
     */
    return response.data
  },
  error => {
    console.log('err' + error) // for debug
    if (!error.response) {
      Message.error('系统错误')
    } else if (error.response.status === 401) {
      store.dispatch('FedLogOut').then(() => {
        router.push({ path: '/login' })
      })
    } else if (error.response.status === 403) {
      Message({
        message: '权限拒绝',
        type: 'error',
        duration: 800,
        onClose: function() {
          router.push({ path: '/' })
        }
      })
    } else if (error.response.status === 400) {
      Message({
        message: '认证失败, 用户名或密码错误',
        type: 'error'
      })
    } else if (error.response.status === 500) {
      Message({
        message: '服务内部错误',
        type: 'error'
      })
    }
    return Promise.reject(error)
  }
)

export default service

(7)users/user.vue

<template>
  <div class="app-container">
    <el-row>
      <el-col :span="12">
        <el-input v-model="params.username" placeholder="搜索用户名" @keyup.enter.native="handleSearch">
          <el-button slot="append" icon="el-icon-search" @click="handleSearch"/>
        </el-input>
      </el-col>
      <el-col :span="12" align="right" style="padding-right:20px;">
        <el-button v-if="addGroupPerm" type="primary" @click="addUserVisible=true">添加用户</el-button>
      </el-col>
    </el-row>
    <el-table
      :data="userList"
      stripe
      style="width: 100%">
      <el-table-column
        prop="username"
        label="username"/>
      <el-table-column
        prop="name"
        label="姓名"/>
      <el-table-column
        prop="phone"
        label="电话"/>
      <el-table-column
        prop="email"
        label="email"/>
      <el-table-column
        prop="is_active"
        label="状态">
        <template slot-scope="scope">
          <el-switch v-model="scope.row.is_active" @change="handleUserStatusChange(scope.row)"/>
        </template>
      </el-table-column>
      <el-table-column
        prop="last_login"
        label="last_login"/>
      <el-table-column
        fixed="right"
        label="操作">
        <template slot-scope="scope">
          <el-button type="text" size="small" @click="handleAssignGroup(scope.row)">指定角色</el-button>
          <el-button type="text" size="small" @click="handleModify(scope.row)">修改</el-button>
        </template>
      </el-table-column>
    </el-table>
    <el-row v-show="total>10" type="flex" justify="center" style="padding-top:20px;">
      <el-pagination
        :total="total"
        layout="prev, pager, next"
        background
        @current-change="handleChange" />
    </el-row>
    <AddUserForm v-model="addUserVisible" @fetch="handleFetch" />
    <ModifyUserForm v-model="modifyUserVisible" :user-id="userId" @fetch="handleFetch" />
    <AssignGroup v-model="assignGroupVisible" :user-id="userId" :user-name="userName" />
  </div>
</template>
<script>
  import { getUserList, modifyUser } from '@/api/user'
  import { checkPermission } from '@/utils/auth'
  import AddUserForm from './components/addUserForm'
  import ModifyUserForm from './components/modifyUser'
  import AssignGroup from './components/assignGroup'
  export default {
    name: 'UserList',
    components: {
      AddUserForm,
      ModifyUserForm,
      AssignGroup
    },
    data() {
      return {
        userList: [],
        addUserVisible: false,
        modifyUserVisible: false,
        userId: 0,
        userName: '',
        total: 0,
        params: {
          page: 1,
          username: ''
        },
        assignGroupVisible: false
      }
    },
    computed: {
      addGroupPerm: function() {
        return checkPermission('users.add_user')
      }
    },
    created() {
      this.fetchUserList()
    },
    methods: {
      fetchUserList() {
        getUserList(this.params).then(res => {
          this.userList = res.results
          this.total = res.count
        })
      },
      handleUserStatusChange(obj) {
        modifyUser(obj.id, { is_active: obj.is_active }).then(() => {
          this.$message({
            message: `修改 ${obj.name} 的状态成功`,
            type: 'success'
          })
        })
      },
      handleFetch() {
        this.fetchUserList()
      },
      handleModify(obj) {
        this.userId = obj.id
        this.modifyUserVisible = true
      },
      handleChange(val) {
        this.params.page = val
        this.fetchUserList()
      },
      handleSearch() {
        this.params.page = 1
        this.fetchUserList()
      },
      handleAssignGroup(obj) {
        this.userId = obj.id
        this.userName = obj.name
        this.assignGroupVisible = true
      }
    }
  }
</script>

(8) src/store/getters.js

const getters = {
  sidebar: state => state.app.sidebar,
  device: state => state.app.device,
  token: state => state.user.token,
  avatar: state => state.user.avatar,
  name: state => state.user.name,
  roles: state => state.user.roles,
  permission: state => state.user.permission
}
export default getters

(9)src/router/index.js

import Vue from 'vue'
import Router from 'vue-router'

// in development-env not use lazy-loading, because lazy-loading too many pages will cause webpack hot update too slow. so only in production use lazy-loading;
// detail: https://panjiachen.github.io/vue-element-admin-site/#/lazy-loading

Vue.use(Router)

/* Layout */
import Layout from '../views/layout/Layout'

/**
 * hidden: true                   if `hidden:true` will not show in the sidebar(default is false)
 * alwaysShow: true               if set true, will always show the root menu, whatever its child routes length
 *                                if not set alwaysShow, only more than one route under the children
 *                                it will becomes nested mode, otherwise not show the root menu
 * redirect: noredirect           if `redirect:noredirect` will no redirect in the breadcrumb
 * name:'router-name'             the name is used by <keep-alive> (must set!!!)
 * meta : {
    title: 'title'               the name show in submenu and breadcrumb (recommend set)
    icon: 'svg-name'             the icon show in the sidebar,
  }
 **/
export const constantRouterMap = [
  { path: '/login', component: () => import('@/views/login/index'), hidden: true },
  { path: '/404', component: () => import('@/views/404'), hidden: true },
  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    name: 'Dashboard',
    children: [{
      path: 'dashboard',
      component: () => import('@/views/dashboard/index'),
      meta: { title: 'dashboard', icon: 'example' }
    }]
  },
  {
    path: '/users',
    component: Layout,
    name: 'users',
    meta: { title: '用户管理', icon: 'example' },
    children: [
      {
        path: 'user',
        name: 'user',
        component: () => import('@/views/users/user'),
        meta: { title: '用户' }
      },
      {
        path: 'groups',
        name: 'groups',
        permission: 'resources.add_ip',
        component: () => import('@/views/groups'),
        meta: { title: '用户组' }
      }
    ]
  },

  { path: '*', redirect: '/404', hidden: true }
]

export default new Router({
  mode: 'history',
  scrollBehavior: () => ({ y: 0 }),
  routes: constantRouterMap
})

(10)layout/components/sidebar/sidebarltem.vue

<template>
  <div class="menu-wrapper">
    <template v-for="item in routes" v-if="!item.hidden&&item.children">

      <router-link v-if="item.children.length===1 && !item.children[0].children && !item.alwaysShow" :to="item.path+'/'+item.children[0].path" :key="item.children[0].name">
        <el-menu-item :index="item.path+'/'+item.children[0].path" :class="{'submenu-title-noDropdown':!isNest}">
          <svg-icon v-if="item.children[0].meta&&item.children[0].meta.icon" :icon-class="item.children[0].meta.icon"></svg-icon>
          <span v-if="item.children[0].meta&&item.children[0].meta.title">{{item.children[0].meta.title}}</span>
        </el-menu-item>
      </router-link>

      <el-submenu v-else :index="item.name||item.path" :key="item.name">
        <template slot="title">
          <svg-icon v-if="item.meta&&item.meta.icon" :icon-class="item.meta.icon"></svg-icon>
          <span v-if="item.meta&&item.meta.title">{{item.meta.title}}</span>
        </template>

        <template v-for="child in item.children" v-if="!child.hidden">
          <sidebar-item :is-nest="true" class="nest-menu" v-if="child.children&&child.children.length>0" :routes="[child]" :key="child.path"></sidebar-item>

          <router-link v-else-if="handleCheckPermission(child.permission)" :to="item.path+'/'+child.path" :key="child.name">
            <el-menu-item :index="item.path+'/'+child.path">
              <svg-icon v-if="child.meta&&child.meta.icon" :icon-class="child.meta.icon"></svg-icon>
              <span v-if="child.meta&&child.meta.title">{{child.meta.title}}</span>
            </el-menu-item>
          </router-link>
        </template>
      </el-submenu>

    </template>
  </div>
</template>

<script>
import { checkPermission } from '@/utils/auth'

export default {
  name: 'SidebarItem',
  props: {
    routes: {
      type: Array
    },
    isNest: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      onlyOneChild: null
    }
  },
  methods: {
    handleCheckPermission(perm) {
      if (perm) {
        return checkPermission(perm)
      }
      return true
    }
  }
}
</script>

 效果如下图:

 

 

 

 

 

 

 

 总结:

 

 

 

 

用户权限的三种使用方法:

 

 

推荐阅读