用户功能2

Vue插槽

参考 https://cn.vuejs.org/v2/guide/components-slots.html

插槽

插槽,指的是在子组件中挖一个空(slot插槽),在父组件中使用子组件标签内的内容,来填充子组件的空。

image

注意:以上是原理图,不能和Vue的实际代码完全对应。

插槽测试

新建T1.vue和T2.vue,T1是父组件,T2是子组件。

<template>
  <div>
   父组件T1内容
    <hr />
    <T2>父组件中在子组件插入的内容c</T2>
  </div>
</template>
<script>
import T2 from './T2.vue'
export default {
  components: {
    T2
 }
}
</script>
<style>
</style>
<template>
  <div>子组件T2内容</div>
</template>
<script>
export default {}
</script>
<style>
</style>

image

发现内容C并没有显示出来。这就需要在子组件中使用插槽来接受。

修改T2增加插槽,代码如下

<template>
  <div>
   子组件T2内容
    <div style="border: 1px solid #000"><slot></slot></div>
  </div>
</template>
<script>
export default {}
</script>
<style>
</style>

image

插槽:父组件通过子组件标签为子组件传递内容,但是子组件内部需要使用插槽接受

作用域插槽

插槽可以看作数据从父组件流向子组件,但有时如果子组件得到了数据,想在父组件中使用子组件中的这些数据,怎么办?

作用域插槽scoped Slot,在父组件中能访问到子组件的数据。

image

作用域插槽:父组件中为子组件传递得数据不在父组件中,数据在子组件中,只能通过作用域插槽从子组件向父组件传过来这些数据,最后这些数据可以在父组件中使用,但是显示在子组件得插槽中。

新语法

slot-scope="fromChildtoParent" 即将过期,非命名插槽,可以使用新语法#default="fromChildtoParent"

作用域插槽测试

修改子组件T2如下:

<template>
  <div>
   子组件T2内容
    <div style="border: 1px solid #000">
     通过插槽bind数据,这样父组件就可以使用这个数据了,user是属性名<br /><br />
      <slot :user="u"></slot>
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      u: { name: 'tom', age: 20 }
   }
 }
}
</script>
<style>
</style>

修改父组件T1如下,如何使用子组件中得user?

<template>
  <div>
   父组件T1内容
    <hr />
    <T2>
      <template v-slot:default="xyz"> 从子组件中传来的数据是:{{ xyz.user.name 
}} </template>
    </T2>
  </div>
</template>
<script>
import T2 from './T2.vue'
export default {
  components: {
    T2
 }
}
</script>
<style>
</style>

可以简化为

<T2 #default="xyz"> 从子组件中传来的数据是:{{ xyz.user.age }} </T2>

顺便可以结构

<T2 #default="{ user }"> 从子组件中传来的数据是:{{ user.name }}, {{ user.age }} </T2>

激活功能

使用switch开关组件表示是否激活状态。

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
} from 'element-ui'
Vue.use(Switch)

采用下面得方式,不能把数据注入给switch组件

<el-table-column prop="is_active" label="激活">
   <el-switch v-model="is_active"> </el-switch>
</el-table-column>

参考 https://element.eleme.cn/#/zh-CN/component/table#zi-ding-yi-lie-mo-ban

通过作用域插槽 Scoped slot 可以获取到 row, column, $index 和 store(table 内部的状态管理)的数据

这个例子可以看出,scope是来自表格子组件得数据,我们在父组件中想用这个值,template得内容,最终插入到表格得列中得插槽中。结构后得row就是取当前行数据对象

      <el-table :data="dataList" border style="width: 100%">
        <el-table-column type="index"> </el-table-column>
        <el-table-column prop="username" label="登录名"> </el-table-column>
        <el-table-column prop="is_active" label="激活">
          <template #default="{ row }">
            <el-switch v-model="row.is_active"> </el-switch>
          </template>
        </el-table-column>
        <el-table-column prop="is_superuser" label="管理员"> </el-tablecolumn>
        <el-table-column prop="phone" label="电话"> </el-table-column>
        <el-table-column label="操作">
          <el-button type="success" icon="el-icon-edit" size="mini"></elbutton>
          <el-button type="danger" icon="el-icon-delete" size="mini"></elbutton>
        </el-table-column>
      </el-table>

上面代码顺便增加了2个按钮编辑和删除。

switch组件会发生change事件,改变值,就需要取改变数据库中某个用户得is_active得值

src/views/user/UserView.vue 完整代码如下

<template>
  <div>
    <el-breadcrumb separator="/">
      <el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
      <el-breadcrumb-item>用户管理</el-breadcrumb-item>
      <el-breadcrumb-item>用户列表</el-breadcrumb-item>
    </el-breadcrumb>
    <el-card>
      <el-row :gutter="20">
        <el-col :span="12">
          <el-input placeholder="请输入内容" v-model="search">
            <el-button slot="append" icon="el-icon-search"></el-button>
          </el-input>
        </el-col>
        <el-col :span="12">
          <el-button type="primary" @click="addDialogVisible = true">增加用
户</el-button>
        </el-col>
      </el-row>
      <el-table :data="dataList" border style="width: 100%">
        <el-table-column type="index"> </el-table-column>
        <el-table-column prop="username" label="登录名"> </el-table-column>
        <el-table-column prop="is_active" label="激活">
          <template #default="{ row }">
            <el-switch v-model="row.is_active" @change="handleChange(row)">
</el-switch>
          </template>
        </el-table-column>
        <el-table-column prop="is_superuser" label="管理员"> </el-tablecolumn>
        <el-table-column prop="phone" label="电话"> </el-table-column>
        <el-table-column label="操作">
          <el-button type="success" icon="el-icon-edit" size="mini"></elbutton>
          <el-button type="danger" icon="el-icon-delete" size="mini"></elbutton>
        </el-table-column>
      </el-table>
      <el-pagination
        @current-change="handleCurrentChange"
        :current-page="pagination.page"
        :page-size="pagination.size"
        layout="total, prev, pager, next, jumper"
        :total="pagination.total"
      >
      </el-pagination>
    </el-card>
    <!-- 添加用户 -->
    <el-dialog title="增加用户" :visible.sync="addDialogVisible"
@close="resetForm('add')">
      <el-form :model="addForm" :rules="addRules" ref="add" labelwidth="100px">
        <el-form-item label="用户名" prop="username">
          <el-input v-model="addForm.username"></el-input>
        </el-form-item>
        <el-form-item label="密码" prop="password">
          <el-input type="password" v-model="addForm.password"></el-input>
        </el-form-item>
        <el-form-item label="确认密码" prop="checkPass">
          <el-input type="password" v-model="addForm.checkPass"></el-input>
        </el-form-item>
        <el-form-item label="电话" prop="phone">
          <el-input v-model="addForm.phone"></el-input>
        </el-form-item>
        <el-form-item label="邮箱" prop="email">
          <el-input v-model="addForm.email"></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="addDialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="add">确 定</el-button>
      </span>
    </el-dialog>
  </div>
</template>
<script>
export default {
  created() {
    this.getList()
 },
  data() {
    var validatePass = (rule, value, callback) => {
      if (value !== this.addForm.password) {
        callback(new Error('两次输入密码不一致!'))
     } else {
        callback()
     }
   }
    return {
      search: '', // 搜索
      // 表格
      dataList: [],
      pagination: { page: 1, size: 20, total: 0 },
      // 新增
      addDialogVisible: false,
      addForm: { username: '', password: '', checkPass: '', phone: '', 
email: '' },
      addRules: {
        username: [
         { 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: {
    resetForm(name) {
      console.log('closing~~~')
      this.$refs[name].resetFields()
   },
    handleCurrentChange(val) {
      this.getList(val)
   },
    add() {
      const name = 'add'
      this.$refs[name].validate(async (valid) => {
        if (valid) {
          const { data: response } = await this.$http.post('users/', 
this.addForm)
          if (response.code) {
            return this.$message.error(response.message)
         }
          this.addDialogVisible = false
          this.getList() // 刷新列表
       }
     })
   },
    async getList(page = 1) {
      if (!page) {
        page = 1
     }
      const { data: response } = await this.$http.get('users/', {
        params: { page }
     })
      if (response.code) {
        return this.$message.error(response.message)
     }
      this.dataList = response.results
      this.pagination = response.pagination
   },
    async handleChange(row) {
      // 激活按钮变化,去后台部分更新字段
      await this.$http.patch(`users/${row.id}/`, {
        is_active: row.is_active
     })
   }
 }
}
</script>
<style lang="less" scoped>
</style>

用户搜索功能

仔细思考一下,这个功能也是用的list方法,发送GET请求到/users/,只不过需要带上参数,也就是查询字符串。

阅读Axios文档,GET请求可以使用 axios.get(url[, config]) ,而config可以用下面的方式传参。

这种方式本质上还是查询字符串方式。

axios.get('users/', {
    params: {
        page: 4
   }
})

所以,可以将getList(page),修改如下

    async getList(page = 1) {
      if (!page) {
        page = 1
     }
      const { data: response } = await this.$http.get('users/', {
        params: {
          page,
          username: this.search
       }
     })
      if (response.code) {
        return this.$message.error(response.message)
     }
      this.dataList = response.results
      this.pagination = response.pagination
   }

下面为了聚焦在改动的代码上,省略一些没改过的代码

<template>
  <div>
    <el-card class="box-card">
      <el-row :gutter="20">
        <el-col :span="12">
          <el-input placeholder="请输入内容" v-model="keywords">
            <el-button slot="append" icon="el-icon-search"
@click="getList(1)"></el-button>
          </el-input>
        </el-col>
        <el-col :span="12">
          <el-button type="primary" @click="addDialogVisible = true">增加用户
</el-button>
        </el-col>
      </el-row>
    </el-card>
  </div>
</template>
<script>
export default {
  created() {
    this.getList()
 },
  data() {
    return {
      keywords: '',
      // 用户列表表格
      dataList: [],
      pagination: { page: 1, size: 20, total: 0 }
   }
 },
  methods: {
    async getList(page = 1) {
      if (!page) {
        page = 1
     }
      const { data: response } = await this.$http.get('users/', {
        params: {
          page,
          username: this.keywords
       }
     })
      if (response.code) {
        return this.$message.error(response.message)
     }
      this.dataList = response.results
      this.pagination = response.pagination
   }
 }
}
</script>

发起请求时,username是发到后台了,但是没有用,如何解决,这个就必须要覆盖GenericAPIView得get_queryset()了

from rest_framework.viewsets import ModelViewSet
from django.contrib.auth import get_user_model
from .serializers import UserSerializer
class UserViewSet(ModelViewSet):
    queryset = get_user_model().objects.all()
    serializer_class = UserSerializer
    # permission_classes = [IsAdminUser] # 必须是管理员才能管理用户
    def get_queryset(self):
        queryset = super().get_queryset() # 调用父类的处理
        username = self.request.query_params.get('username', None)
        if username:
            queryset = queryset.filter(username__icontains=username)
        return queryset

由于这个用到了列表页得list方法,查看ListModelMixin源码中,list方法返回列表前调用了filter_queryset,就是用来过滤得,覆盖filter_queryset也行。

这是一个通用的功能,以后免不了经常查询,但是学习分页的时候,不是客户端提交什么参数,服务器都允许的,这需要配置。这里,查询参数也需要服务器允许才行。

通用查询

参考:https://www.django-rest-framework.org/api-guide/filtering/#djangofilterbackend

image

pip install django-filter

在settings.py中注册应用

INSTALLED_APPS = [
   ...
    'django_filters',
   ...
]
REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': 
['django_filters.rest_framework.DjangoFilterBackend'] }

在view中配置

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
class UserViewSet(ModelViewSet):
    queryset = get_user_model().objects.all()
    serializer_class = UserSerializer
    # permission_classes = [IsAdminUser] # 必须是管理员才能管理用户
    filter_backends = [DjangoFilterBackend] #指定filter
    filterset_fields = ['username']  #指定要搜索的字段

请求为: http://localhost:8080/api/v1/users/?page=1&username=admin

搜索SQL语句条件为FROM auth_uer WHERE auth_user.username = 'admin'

这是等值条件,要求查询参数名必须为filterset_fields中指定的名称。

实现模糊搜索,代码如下,注意:前端发送请求时url参数中必须传入search,值为search_fields配置的username指定的值

from rest_framework import filters
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']

请求为 http://localhost:8080/api/v1/users/?page=1&search=ay ,注意是search=

搜索SQL语句条件为 FROM auth_user WHERE auth_user.username LIKE '%ay%'

search_fields = ['username'] 中如果写多个字段将是或查询。

修改用户

前台实现,仿照增加用户,部分代码如下:

<template>
  <div>
    <el-breadcrumb separator-class="el-icon-arrow-right">
      <el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
      <el-breadcrumb-item>用户管理</el-breadcrumb-item>
      <el-breadcrumb-item>用户列表</el-breadcrumb-item>
    </el-breadcrumb>
    <el-card class="box-card">
      <el-row :gutter="20">
        <el-col :span="12">
          <el-input placeholder="请输入内容" v-model="search">
            <el-button slot="append" icon="el-icon-search" @click="getList(1)"></el-button>
          </el-input>
        </el-col>
        <el-col :span="12">
          <el-button type="primary" @click="addDialogVisible = true">增加用
户</el-button>
        </el-col>
      </el-row>
      <el-table :data="dataList" border style="width: 100%">
        <el-table-column type="index" label="序号"> </el-table-column>
        <el-table-column prop="username" label="用户名"> </el-table-column>
        <el-table-column prop="is_active" label="激活">
          <template #default="{ row }">
            <el-switch v-model="row.is_active" @change="handleChange(row)">
</el-switch>
          </template>
        </el-table-column>
        <el-table-column prop="is_superuser" label="管理员"> </el-tablecolumn>
        <el-table-column prop="phone" label="电话"> </el-table-column>
        <el-table-column label="操作">
          <template #default="{ row }">
            <el-button @click="handleEdit(row)" type="success" icon="elicon-edit" size="mini"></el-button>
            <el-button type="danger" icon="el-icon-delete" size="mini">
</el-button>
          </template>
        </el-table-column>
      </el-table>
      <el-pagination
        @current-change="handleCurrentChange"
        :current-page="pagination.page"
        :page-size="pagination.size"
        layout="total, prev, pager, next, jumper"
        :total="pagination.total"
      >
      </el-pagination>
    </el-card>
    <!-- 添加用户 -->
    <el-dialog title="增加用户" :visible.sync="addDialogVisible"
@close="resetForm('add')">
      <el-form :model="addForm" :rules="addRules" ref="add" labelwidth="100px">
        <el-form-item label="用户名" prop="username">
          <el-input v-model="addForm.username"></el-input>
        </el-form-item>
        <el-form-item label="密码" prop="password">
          <el-input type="password" v-model="addForm.password"></el-input>
        </el-form-item>
        <el-form-item label="确认密码" prop="checkPass">
          <el-input type="password" v-model="addForm.checkPass"></el-input>
        </el-form-item>
        <el-form-item label="电话" prop="phone">
          <el-input v-model="addForm.phone"></el-input>
        </el-form-item>
        <el-form-item label="邮箱" prop="email">
          <el-input v-model="addForm.email"></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="addDialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="add">确 定</el-button>
      </span>
    </el-dialog>
    <!-- 修改用户 -->
    <el-dialog title="修改" :visible.sync="editDialogVisible"
@close="resetForm('edit')">
      <el-form :model="editForm" :rules="editRules" ref="edit" labelwidth="100px">
        <el-form-item label="用户名" prop="username">{{ editForm.username }}
</el-form-item>
        <el-form-item label="电话" prop="phone">
          <el-input v-model="editForm.phone"></el-input>
        </el-form-item>
        <el-form-item label="邮箱" prop="email">
          <el-input v-model="editForm.email"></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="editDialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="edit">确 定</el-button>
      </span>
    </el-dialog>
  </div>
</template>
<script>
export default {
  created() {
    this.getList()
 },
  data() {
    const validatePass = (rule, value, callback) => {
      if (value !== this.addForm.password) {
        callback(new Error('两次输入密码不一致!'))
     } else {
        callback()
     }
   }
    return {
      search: '',
      // 用户列表表格
      dataList: [],
      pagination: { page: 1, size: 20, total: 0 },
      // 新增用户对话框
      addDialogVisible: false,
      addForm: {
        username: '',
        password: '',
        checkPass: '',
        phone: '',
        email: ''
     },
      addRules: {
        username: [
         { 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' }
       ]
     },
      // 修改用户对话框
      editDialogVisible: false,
      editForm: {
        username: '',
        phone: '',
        email: ''
     },
      editRules: {}
   }
 },
  methods: {
    resetForm(name) {
      this.$refs[name].resetFields()
   },
    handleCurrentChange(val) {
      this.getList(val)
   },
    add() {
      const name = 'add'
      this.$refs[name].validate(async valid => {
        if (valid) {
          const { data: response } = await this.$http.post('users/', 
this.addForm)
          if (response.code) {
            return this.$message.error(response.message)
         }
          console.log(response)
          this.addDialogVisible = false
          this.getList()
       }
     })
   },
    async getList(page = 1) {
      if (!page) {
        page = 1
     }
      const { data: response } = await this.$http.get('users/', {
        params: { page, search: this.search }
     })
      if (response.code) {
        return this.$message.error(response.message)
     }
      this.dataList = response.results
      this.pagination = response.pagination
   },
    async handleChange(row) {
      // 激活按钮变化,去后台部分更新字段
      await this.$http.patch(`users/${row.id}/`, {
        is_active: row.is_active
     })
   },
    handleEdit(row) {
      this.editForm = row
      this.editDialogVisible = true
   },
    edit() {
      const { id } = this.editForm
      const name = 'edit'
      this.$refs[name].validate(async valid => {
        if (valid) {
          const { data: response } = await this.$http.patch(`users/${id}/`, 
this.editForm)
          if (response.code) {
            return this.$message.error(response.message)
         }
          this.editDialogVisible = false
          this.getList(this.pagination.page) // 刷新用户列表
       }
     })
   }
 }
}
</script>

后台如何做到,修改用户时,有id,用的是详情页,但是有人故意构造提交数据中包含username,如果后端也更新了,就修改了用户名,按照要求不允许修改用户名,怎么办?还是用PATCH。部分更新(本质上就是先查后部分更新),在后端剔除掉request数据中的usename,这需要覆盖partial_update(self, request, *args, **kwargs)

image

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

is_active、is_superuser是需要通过patch修改的

确认消息框和提示

确认框参考 https://element.eleme.cn/#/zh-CN/component/message-box#que-ren-xiao-xi

提示文字参考 https://element.eleme.cn/#/zh-CN/component/tooltip

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
} from 'element-ui'
Vue.use(Tooltip)
// 全局导入
Vue.prototype.$message = Message
Vue.prototype.$msgbox = MessageBox
      <el-table :data="dataList" border style="width: 100%">
        <el-table-column type="index" label="序号"> </el-table-column>
        <el-table-column prop="username" label="用户名"> </el-table-column>
        <el-table-column prop="is_active" label="激活">
          <template #default="{ row }">
            <el-switch v-model="row.is_active" @change="handleChange(row)">
</el-switch>
          </template>
        </el-table-column>
        <el-table-column prop="is_superuser" label="管理员"> </el-tablecolumn>
        <el-table-column prop="phone" label="电话"> </el-table-column>
        <el-table-column label="操作">
          <template #default="{ row }">
            <el-tooltip v-if="row.id !== 1" content="修改" effect="light">
              <el-button @click="handleEdit(row)" type="success" icon="elicon-edit" size="mini"></el-button>
            </el-tooltip>
            <el-tooltip v-if="row.id !== 1" content="删除" effect="light">
              <el-button @click="handleDel(row.id)" type="danger" icon="elicon-delete" size="mini"></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>

删除用户

前端对id为1的用户,使用v-if禁止修改、删除,使用disabled禁止激活。注意,前端的禁止行为只是一种样子,依然可以绕过他,后端禁止才是关键

<script>
export default {
  methods: {
    handleDel(id) {
      console.log(id)
      this.$msgbox
       .confirm('删除该用户, 是否继续?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'danger'
       })
       .then(async () => {
          const { data: response } = await this.$http.delete(`users/${id}/`)
          if (response.code) {
            return this.$message.error(response.message)
         }
          this.getList()
       })
       .catch(() => {})
   }    
 }
}
</script>

顺便考虑一下id为1,就是管理员,后端禁止修改和删除,这个需要什么方法?

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()