用户功能3

用户信息菜单

下拉菜单参考https://element.eleme.cn/#/zh-CN/component/dropdown

src/plugins/element.js

import Vue from 'vue'
import {
  Form, FormItem, Input, Button, Message, Container,
  Header, Aside, Main, Menu, MenuItem, Submenu, Breadcrumb,
  BreadcrumbItem, Card, Row, Col, Table, TableColumn,
  Dialog, Pagination, Switch, MessageBox, Tooltip, 
  DropdownMenu, DropdownItem, Dropdown
} from 'element-ui'
Vue.use(Dropdown)
Vue.use(DropdownMenu)
Vue.use(DropdownItem)

src/views/HomeView.vue

<template>
  <el-container>
    <el-header>
<div class="logo">
<img src="../assets/logo.png" alt="logo" />
<div class="title">马哥教育猛犸运维系统管理平台</div>
<div><i :class="isCollapsed ? 'el-icon-s-unfold' : 'el-icon-s-fold'"
@click="toggleMenu"></i></div>
</div>
<div class="info">
<el-button type="info" @click="logout">退出</el-button>
<el-dropdown @command="handleCommand">
<span class="el-dropdown-link">{{ user.username }}<i class="elicon-arrow-down el-icon--right"></i> </span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="chpwd">修改密码</el-dropdown-item>
<el-dropdown-item command="logout">退出</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
    </el-header>
    <el-container>
<el-aside :width="isCollapsed ? '64px' : '200px'">
<el-menu
background-color="#123"
text-color="#fff"
active-text-color="#ffd04b"
:collapse="isCollapsed"
:collapse-transition="true"
:router="true"
>
          <el-submenu v-for="item in menulist" :index="item.id + ''" :key="item.id">
            <template slot="title">
              <i class="el-icon-menu"></i><span slot="title">{{ item.itemName }}</span>
            </template>
            <el-menu-item v-for="subItem in item.children" :index="subItem.path" :key="subItem.id">
              <i class="el-icon-menu"></i><span slot="title">{{  subItem.itemName }}</span>
            </el-menu-item>
          </el-submenu>
          <el-menu-item index="/welcome">
            <i class="el-icon-setting"></i>
            <span slot="title">导航四</span>
          </el-menu-item>
          <el-menu-item index="/t1">
            <i class="el-icon-setting"></i>
            <span slot="title">t1</span>
          </el-menu-item>
        </el-menu>
      </el-aside>
      <el-main>
        <router-view></router-view>
      </el-main>
    </el-container>
  </el-container>
</template>

用户信息

登录成功,返回JSON的token,header部分显示用户信息,比如用户id、用户名等。

src/views/HomeView.vue

<script>
export default {
  created() {
    this.getMenuList() // vue组件创建时查菜单数据
    this.getUserInfo()
 },
  data() {
    return {
      menulist: [], // 菜单数据
      isCollapsed: false,
      user: {}, // 用户信息,
      chpwdDialogVisible: false // 修改密码对话框
   }
 },
  methods: {
    logout() {
      window.localStorage.removeItem('token')
      this.$router.push('/login')
   },
    async getMenuList() {
      const { data: response } = await this.$http.get('users/menulist/')
      if (response.code) {
        return this.$message.error(response.message)
     }
      this.menulist = response // 菜单项数组
   },
    handleCommand(command) {
      if (command === 'logout') {
        this.logout()
     } else if (command === 'chpwd') {
        this.chpwdDialogVisible = true
     }
   },
    async getUserInfo() {
      // 带着token发起请求,后台查询用户数据
      const { data: response } = await this.$http.get('users/whoami/')
      if (response.code) {
        return this.$message.error(response.message)
     }
      this.user = response.user
   },
    changePassword() {}
 }
}
</script>

后端

user/views.py

from rest_framework.views import Response, Request
from rest_framework.decorators import api_view, permission_classes, action
from rest_framework.permissions import IsAuthenticated, IsAdminUser
from rest_framework.viewsets import ModelViewSet
from django.contrib.auth import get_user_model
from .models import UserProfile
from .serializers import UserSerializer
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import filters
from django.http.response import Http404
class UserViewSet(ModelViewSet):
    queryset = get_user_model().objects.all()
    serializer_class = UserSerializer
    # permission_classes = [IsAdminUser] # 必须是管理员才能管理用户
    filter_backends = [filters.SearchFilter] # 指定filter
    # filterset_fields = ['username']
    search_fields = ['username']
    # 详情页禁止修改username,如果提供用户名,它就要验证用户名唯一性,且尝试修改用户名
    def partial_update(self, request, *args, **kwargs):
        request.data.pop('username', None) # 剔除不要更新的字段
        request.data.pop('id', None)
        request.data.pop('password', None)
        return super().partial_update(request, *args, **kwargs)
    def get_object(self):
        if self.request.method.lower() != 'get':
            pk = self.kwargs.get('pk')
            if pk == 1 or pk == '1':
                raise Http404
        return super().get_object()
    @action(['GET'], detail=False, url_path='whoami')
    def whoami(self, request): # detail=False,非详情页
        print(request.user)
        return Response({
            'user':{
                'id': request.user.id,
                'username': request.user.username
           }
       })

密码修改

使用对话框,提供当前密码和新密码

src/views/HomeView.vue

<template>
  <el-container>
    <el-header>
      <div class="logo">
        <img src="../assets/logo.png" alt="logo" />
        <div class="title">马哥教育猛犸运维系统管理平台</div>
        <div><i :class="isCollapsed ? 'el-icon-s-unfold' : 'el-icon-sfold'" @click="isCollapsed = !isCollapsed"></i></div>
      </div>
      <div class="info">
        <el-dropdown @command="handleCommand">
          <span class="el-dropdown-link">{{ user.username }}<i class="elicon-arrow-down el-icon--right"></i> </span>
          <el-dropdown-menu slot="dropdown">
            <el-dropdown-item command="chpwd">修改密码</el-dropdown-item>
            <el-dropdown-item command="logout">退出</el-dropdown-item>
          </el-dropdown-menu>
        </el-dropdown>
      </div>
      <!-- 修改密码对话框 -->
      <el-dialog title="修改密码" :visible.sync="chpwdDialogVisible"
@close="resetForm('chpwd')">
        <el-form :model="chpwdForm" :rules="chpwdRules" ref="chpwd" labelwidth="100px">
          <el-form-item label="用户名">{{ user.username }}</el-form-item>
          <el-form-item label="旧密码" prop="oldPassword">
            <el-input type="password" v-model="chpwdForm.oldPassword"></elinput>
          </el-form-item>
          <el-form-item label="新密码" prop="password">
            <el-input type="password" v-model="chpwdForm.password"></elinput>
          </el-form-item>
          <el-form-item label="确认新密码" prop="checkPass">
            <el-input type="password" v-model="chpwdForm.checkPass"></elinput>
          </el-form-item>
        </el-form>
        <span slot="footer" class="dialog-footer">
          <el-button @click="chpwdDialogVisible = false">取 消</el-button>
          <el-button type="primary" @click="changePassword">确 定</elbutton>
        </span>
      </el-dialog>
    </el-header>
    <el-container>
      <el-aside :width="isCollapsed ? '64px' : '200px'">
      </el-aside>
      <el-main>
        <router-view></router-view>
      </el-main>
    </el-container>
  </el-container>
</template>
<script>
export default {
  created() {
    this.getMenulist() // vue组件创建时查菜单数据
    this.getUserInfo()
 },
  data() {
    const validatePass = (rule, value, callback) => {
      if (value !== this.chpwdForm.password) {
        callback(new Error('两次输入密码不一致!'))
     } else {
        callback()
     }
   }
    return {
      menulist: [], // 菜单数据
      isCollapsed: false,
      user: {}, // 用户信息,
      chpwdDialogVisible: false, // 修改密码对话框
      chpwdForm: {
        oldPassword: '',
        password: '',
        checkPass: ''
     },
      chpwdRules: {
        oldPassword: [
         { required: true, message: '请输入旧密码', trigger: 'blur' },
         { min: 4, max: 16, message: '长度在 4 到 16 个字符', trigger: 
'blur' }
       ],
        password: [
         { required: true, message: '请输入新密码', trigger: 'blur' },
         { min: 4, max: 16, message: '长度在 4 到 16 个字符', trigger: 
'blur' }
       ],
        checkPass: [
         { required: true, message: '请输入新密码', trigger: 'blur' },
         { validator: validatePass, trigger: 'blur' }
       ]
     }
   }
 },
  methods: {
    async getMenulist() {
      const { data: response } = await this.$http.get('users/menulist/')
      if (response.code) {
        return this.$message.error(response.message)
     }
      this.menulist = response.data
   },
    logout() {
      window.localStorage.removeItem('token')
      this.$router.push('/login')
   },
    handleCommand(command) {
      if (command === 'logout') {
        this.logout()
     } else if (command === 'chpwd') {
        this.chpwdDialogVisible = true
     }
   },
    async getUserInfo() {
      // 带着token发起请求,后台查询用户数据
      const { data: response } = await this.$http.get('users/whoami/')
      if (response.code) {
        return this.$message.error(response.message)
     }
      this.user = response.user
   },
    async changePassword() {
      // 后台要验证旧密码
      const { data: response } = await
this.$http.post(`users/${this.user.id}/setpwd/`, this.chpwdForm)
      if (response.code) {
        return this.$message.error(response.message)
     }
      this.chpwdDialogVisible = false
      this.$message('密码修改成功')
   },
    resetForm(name) {
      this.$refs[name].resetFields()
   }
 }
}
</script>

当前用户修改密码,不要轻信user_id,在后端,一定要使用认证后的request.user。要求不能修改id为1的管理员密码

utils/exceptions.py

class InvalidPassword(MagBaseException):
    code = 101
    message = '密码验证错误'

user/view.py完整代码

from rest_framework.views import Response, Request
from rest_framework.decorators import api_view, permission_classes, action
from rest_framework.permissions import IsAuthenticated, IsAdminUser
from rest_framework.viewsets import ModelViewSet
from django.contrib.auth import get_user_model
from .models import UserProfile
from .serializers import UserSerializer
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import filters
from django.http.response import Http404
from utils.exceptions import InvalidPassword
class UserViewSet(ModelViewSet):
    queryset = get_user_model().objects.all()
    serializer_class = UserSerializer
    # permission_classes = [IsAdminUser] # 必须是管理员才能管理用户
    filter_backends = [filters.SearchFilter] # 指定filter
    # filterset_fields = ['username']
    search_fields = ['username']
    # 详情页禁止修改username,如果提供用户名,它就要验证用户名唯一性,且尝试修改用户名
    def partial_update(self, request, *args, **kwargs):
        request.data.pop('username', None) # 剔除不要更新的字段
        request.data.pop('id', None)
        request.data.pop('password', None)
        return super().partial_update(request, *args, **kwargs)
    def get_object(self):
        if self.request.method.lower() != 'get':
            pk = self.kwargs.get('pk')
            if pk == 1 or pk == '1':
                print('竟敢修改管理员!!!!')
                raise Http404
        return super().get_object()
    @action(['GET'], detail=False, url_path='whoami')
    def whoami(self, request): # detail=False,非详情页
        print(request.user)
        return Response({
            'user':{
                'id': request.user.id,
                'username': request.user.username
           }
       })
    @action(detail=True, methods=['post'], url_path='setpwd')
    def set_password(self, request, pk=None):
        user = self.get_object() # 能不能使用request.user,为什么
        # {'oldPassword': 'adminadmin', 'password': 'admin', 'checkPass': 'admin'}
        if user.check_password(request.data['oldPassword']):
            user.set_password(request.data['password'])
            user.save()
            return Response()
        else:
            raise InvalidPassword
class MenuItem(dict):
    def __init__(self, id, name, path=None):
        super().__init__() # 你就是字典
        self['id'] = id
        self['name'] = name
        self['path'] = path
        # self['children'] = []
    def append(self, item):
        # self['children'].append(item)
        self.setdefault('children', []).append(item)
@api_view(['GET'])
@permission_classes([IsAuthenticated, IsAdminUser]) # 覆盖全局配置
def menulist_view(request:Request):
    print(request.user, request.auth) # User
    print(dir(request.user)) # is_authenticated is_superuser
    menulist = []
    if request.user.is_superuser:
        m1 = MenuItem(1, '用户管理')
        m1.append(MenuItem(101, '用户列表', '/users'))
        m1.append(MenuItem(102, '角色列表', '/users/roles'))
        m1.append(MenuItem(103, '权限列表', '/users/perms'))
        menulist.append(m1)
    return Response(menulist)

set_password 中不使用request.user,原因是因为request.user 表示当前登录用户,但如果是当前登录用户重置其他用户密码,换id就可以了。

再一个,由于重写get_object,对id为1的用户不能修改密码,也是一种保护,至少有一个用户不能被修改密码。

提交合并代码

前台代码提交

$ git add .
$ git commit -m "User finished"
$ git push -u origin user
$ git checkout master
$ git merge user
$ git push