20 用户权限相关功能

文章目录

  • 利用 navicat 理解用户和权限
  • 后端功能实现
    • apps/admin/views.py
    • apps/admin/users/models.py
    • apps/admin/urls.py
  • 前端功能实现
    • templates/admin/user/groups_manage.html
    • static/js/admin/user/groups_manage.js
    • templates/admin/user/groups_add.html
    • static/js/admin/user/groups_add.js
    • templates/admin/user/users_manage.html
    • static/js/admin/user/users_manage.js
    • templates/admin/user/users_edit.html
  • 权限管理
    • 登录权限
    • 用户权限限制
    • 取消单个视图的 csrf 验证

利用 navicat 理解用户和权限

  • 不同的用户组有不同的权限,不同的用户或许有不同的权限
  • auth_group 外键关联 auth_group_permissions 和 tb_users_groups,tb_users_user_permissions 外键关联 tb_users 和 auth_permission,同理,六张表里的三张表互相外键关联
    20 用户权限相关功能_第1张图片

后端功能实现

apps/admin/views.py

import requests
import json
import logging
import qiniu

from datetime import datetime
from urllib.parse import urlencode

from django.core.paginator import Paginator, EmptyPage
from django.db.models import Count
from django.shortcuts import render
from django.views import View
from django.http import JsonResponse,Http404
from django.contrib.auth.models import Group, Permission
from django.contrib.auth.mixins import LoginRequiredMixin,PermissionRequiredMixin

from apps.news import models
from apps.admin import constants
from apps.doc.models import Doc
from apps.course.models import Course,Teacher,CourseCategory
from apps.users.models import Users
from . import forms

from web_prv import settings
from utils.json_fun import to_json_data
from utils.res_code import Code, error_map
from utils import paginator_script
from utils.fastdfs.fdfs import FDFS_Client
from utils.secrets import qiniu_secret_info

logger = logging.getLogger('django')


class IndexView(LoginRequiredMixin,View):
    """
    /admin/
    """
    login_url = 'users:login'   #没登录就跳转到登录页面,或者在 settings.py 中设置
    redirect_field_name = 'next'    #重定向,'next' 和前端对应

    def get(self,request):
        """

        :param request:
        :return:
        """
        return render(request,'admin/index/index.html')


class TagManagerView(PermissionRequiredMixin,View):
    """
    /admin/tag/
    """
    #'app_name.codename'
    #Tag 模型在 news app 中,对应的 codename 是 add_tag
    permission_required = ('news.add_tag','news.view_tag')
    raise_exception = True  #没权限就返回报错页面

    def handle_no_permission(self):
        """
        该方法默认返回报错页面
        get 返回页面渲染,不用改, post 返回 json 格式,改为报错的 json 格式
        :return:
        """
        # Python lower() 方法转换字符串中所有大写字符为小写,其效果和 casefold() 方法非常相似。
        # 两者的区别是:lower() 方法只对ASCII编码,也就是‘A - Z’有效,
        # 对于其他语言(非汉语或英文)中把大写转换为小写的情况只能用 casefold() 方法。
        #lower() 转换字符串中所有的大小写为小写, 对应的是 upper()
        if self.request.method.lower() != 'get':
            return to_json_data(errno=Code.ROLEERR,errmsg='没有操作权限')
        else:
            return super(TagManagerView,self).handle_no_permission()

    def get(self,request):
        """
        要返回给前端: tag_name news_name
        :param request:
        :return:
        """
        #分组查询 annotate 配合 values 使用     values 指定输出的内容(id: ,name: ),是字典格式
         #按标签名(tag_name)和 id(tag_id) 分组,统计每个标签(Tag)中 news(外键关联,
        # 通过 news的主键 id 统计) 的数量,并且按降序排列
        tags = models.Tag.objects.select_related('news').values('id','name').annotate(num_news = \
                  Count('news')).filter(is_delete=False).order_by('-num_news','update_time')
        return render(request,'admin/news/tag_manage.html',locals())

    def post(self,request):
        """
        get_or_create(defaults=None, **kwargs)

         一个通过给出的kwargs 来查询对象的便捷方法(如果你的模型中的所有字段都有默认值,可以为空),需要的话创建一个对象。
         返回一个由(object, created)组成的元组,元组中的object 是一个查询到的或者是被创建的对象,
         created 是一个表示是否创建了新的对象的布尔值。

        :param request:
        :return:
        """
        # 1. 获取参数
        json_data = request.body
        if not json_data:
            return to_json_data(errno=Code.PARAMERR,errmsg=error_map[Code.PARAMERR])
        dict_data = json.loads(json_data.decode('utf8'))
        #从前端传进来的要修改的标签名
        tag_name = dict_data.get('name')
        if tag_name:
            # tag_name = tag_name.strip()
            # a = tag_name
            #get_or_create 查名为 tag_name 的标签,无则增,有则查 得到的是元组类型    请确保只在 post 请求中使用它
            tag_tuple = models.Tag.objects.filter(is_delete=False).get_or_create(name=tag_name)
            #对得到的元组进行拆包
            tag_instance,tag_boolean = tag_tuple
            #如果 tag_boolean 为 True,即创建了名字为 tag_name 的标签
            # 否则就是查到了名字为 tag_name 的标签,即标签已存在,不创建
            if tag_boolean:
                news_tag_dict = {
                    'id':'tag_instance.id',
                    'name':'tag_instance.name',
                }
                return to_json_data(errmsg='标签创建成功!',data=news_tag_dict)
            else:
                return to_json_data(errno=Code.DATAEXIST,errmsg='标签已存在!')
        else:
            return to_json_data(errno=Code.PARAMERR,errmsg='标签不能为空!')


#传参:
class TagEditView(PermissionRequiredMixin,View):
    """
    /admin/tag//
    """
    permission_required = ('news.delete_tag','news.change_tag')
    raise_exception = True

    def handle_no_permission(self):
        """
        delete,put 都返回 json 格式,所以不用判断,都改为报错的 json 格式,和 TagManagerView 中的 handle_no_permission 不一样
        :return:
        """
        return to_json_data(errno=Code.ROLEERR,errmsg='没有操作权限')

    def delete(self,request,tag_id):
        """

        :param request:
        :param tag_id:
        :return:
        """
        tag = models.Tag.objects.only('id').filter(id=tag_id).first()
        if tag:
            tag.is_delete = True
            #只改 is_delete 字段,用于优化
            tag.save(update_fields=['is_delete'])
            #删除后局部刷新,用 ajax,返回给前端 json 格式
            return to_json_data(errmsg='标签删除成功!')
        else:
            return to_json_data(errno=Code.PARAMERR,errmsg='标签不存在!')

    def put(self,request,tag_id):
        """

        :param request:
        :param tag_id:
        :return:
        """
        #修改之后的 name 通过 ajax 获取  只有一个参数,用 ajax 即可
        # 1. 获取参数
        json_data = request.body    #byte str
        if not json_data:
            return to_json_data(errno=Code.PARAMERR,errmsg=error_map[Code.PARAMERR])
        dict_data = json.loads(json_data.decode('utf8'))
        # 从前端传进 要修改的 tag
        tag_name = dict_data.get('name')
        # 从数据库中拿到 tag
        tag = models.Tag.objects.only('name').filter(id=tag_id).first()
        if tag:
            #去掉两边的空格
            tag_name = tag_name.strip()
            #如果不加 is_delete 删除的标签再创建或者修改无法实现
            if tag.name == tag_name:
                return to_json_data(errno=Code.PARAMERR,errmsg='标签未变化!')
            if not models.Tag.objects.only('id').filter(name=tag_name, is_delete=False).exists():
            #如果标签没变化,提示报错
                # if tag.name == tag_name:
                #     return to_json_data(errno=Code.PARAMERR,errmsg='标签未变化!')
                tag.name = tag_name
                tag.save(update_fields=['name', 'update_time'])
                return to_json_data(errmsg='标签更新成功!')
            else:
                return to_json_data(errno=Code.PARAMERR,errmsg='标签已经存在!')
        else:
            #有异常的一定会用到 errno
            return to_json_data(errno=Code.PARAMERR,errmsg='标签不存在!')


# 热门文章管理页面
#news.title,tag.name,hotnews.priority
class HotNewsManageView(PermissionRequiredMixin,View):
    """
    /admin/hotnews/
    """
    permission_required = ('news.view_hotnews')
    raise_exception = True
    #因为只有 get,返回的是页面,handle_no_permission 默认返回报错页面,所以不用重写

    def get(self,request):
        hot_news = models.HotNews.objects.select_related('news__tag').only('news__title','news__tag__name','news__id').\
            filter(is_delete=False).order_by('priority','-news__clicks')[:constants.SHOW_HOTNEWS_COUNT]
        return render(request,'admin/news/news_hot.html',locals())


class HotNewsEditView(PermissionRequiredMixin,View):
    """
    /admin/hotnews/
    """
    permission_required = ('news.delete_hotnews','news.change_hotnews')
    raise_exception = True

    def handle_no_permission(self):
        return to_json_data(errno=Code.ROLEERR,errmsg='没有操作权限')

    def delete(self,request,hotnews_id):
        hotnews = models.HotNews.objects.only('id').filter(id=hotnews_id).first()
        if hotnews:
            hotnews.is_delete = True
            hotnews.save(update_fields = ['is_delete','update_time'])
            return to_json_data(errmsg='热门文章删除成功!')
        else:
            return to_json_data(errno=Code.PARAMERR,errmsg='需要删除的热门文章不存在!')

    def put(self,request,hotnews_id):
        """
        更新热门新闻
        #1. 获取参数
        :param request:
        :param hotnews_id:
        :return:
        """
        json_data = request.body
        if not json_data:
            return to_json_data(errno=Code.PARAMERR,errmsg=error_map[Code.PARAMERR])
        dict_data = json.loads(json_data.decode('utf8'))
        try:
            priority = int(dict_data.get('priority'))
            #列表生成式 对于不需要的可以用 _ 代替
            priority_list = [i for i,_ in models.HotNews.PRI_CHOICES]
            if priority not in priority_list:
                return to_json_data(errno=Code.PARAMERR,errmsg='热门文章优先级设置错误!')
        except Exception as e:
            logger.info('热门文章优先级异常:\n{}'.format(e))
            return to_json_data(errno=Code.PARAMERR,errmsg='热门文章优先级设置错误!')

        hotnews = models.HotNews.objects.only('id').filter(id=hotnews_id).first()
        if not hotnews:
            return to_json_data(errno=Code.PARAMERR,errmsg='需要更新的热门新闻不存在!')
        if hotnews.priority == priority:
            return to_json_data(errno=Code.PARAMERR,errmsg='热门新闻优先级未改变!')
        hotnews.priority = priority
        hotnews.save(update_fields=['priority','update_time'])
        return to_json_data(errmsg='热门新闻优先级更新成功!')


class HotNewsAddView(PermissionRequiredMixin,View):
    """
    添加热门新闻,先选标签,才能选标签下的文章,选择优先级不受他们影响
    /addmin/hotnews/add/
    """
    permission_required = ('news.view_hotnews','news.add_hotnews')
    raise_exception = True

    def handle_no_permission(self):
        if self.request.method.lower() != 'get':
            return to_json_data(errno=Code.ROLEERR,errmsg='没有操作权限')
        else:
            return super().handle_no_permission()

    def get(self,request):
        #选择标签和优先级
        #有排序
        tags = models.Tag.objects.values('id','name').annotate(num_news = Count('news')).filter(is_delete=False).\
                order_by('-num_news','update_time')
        #将列表嵌套个元组转换成字典
        priority_dict = dict(models.HotNews.PRI_CHOICES)
        return render(request,'admin/news/news_hot_add.html',locals())

    def post(self,request):
        #添加热门新闻
        #1.获取参数
        json_data = request.body
        if not json_data:
            return to_json_data(errno=Code.PARAMERR,errmsg=error_map[Code.PARAMERR])
        dict_data = json.loads(json_data.decode('utf8'))
        #news_id priority
        try:
            news_id = int(dict_data.get('news_id'))
        except Exception as e:
            logger.info('热门文章:\n{}'.format(e))
            return to_json_data(errno=Code.PARAMERR,errmsg='参数错误')
        if not models.News.objects.filter(id=news_id).exists():
            return to_json_data(errno=Code.PARAMERR,errmsg='文章不存在!')
        try:
            priority = int(dict_data.get('priority'))
            priority_list = [i for i,_ in models.HotNews.PRI_CHOICES]
            if priority not in priority_list:
                return to_json_data(errno=Code.PARAMERR,errmsg='热门文章优先级设置错误!')
        except Exception as e:
            logger.info('热门文章优先级异常:\n{}'.format(e))
            return to_json_data(errno=Code.PARAMERR,errmsg='热门文章优先级设置错误')
        hotnews_tuple = models.HotNews.objects.get_or_create(news_id=news_id)
        hotnews,is_created = hotnews_tuple
        hotnews.priority = priority
        hotnews.save(update_fields=['priority','update_time'])
        return to_json_data(errmsg='热门文章创建成功!')


class NewsByTagIdView(PermissionRequiredMixin,View):
    """
    选完标签后进行这步,选标签下的文章
    不用展示 tag_id
    /admin/tags//news/
    """
    permission_required = ('news.view_news')
    raise_exception = True

    def handle_no_permission(self):
        return to_json_data(errno=Code.ROLEERR,errmsg='没有操作权限')

    def get(self,request,tag_id):
        news = models.News.objects.filter(is_delete=False,tag_id=tag_id).values('id','title')
        #将字典嵌套进列表
        news_list = [i for i in news]
        return to_json_data(data={
            'news':news_list
        })


#请求方式:get
#携带的方式:?start_time=&end_time=&title=&author_name=&tag_id=
#返回的参数:5个 title author_username tag_name update_time id
class NewsManageView(PermissionRequiredMixin,View):
    """
    /admin/news/
    """
    permission_required = ('news.view_news')
    raise_exception = True

    def get(self,request):
        #查询
        tags = models.Tag.objects.only('id','name').filter(is_delete=False)
        newses = models.News.objects.select_related('author','tag').only('title',\
                'author__username','tag__name','update_time').filter(is_delete=False)
        #通过时间进行过滤
        try:
            #用于查询 url 中的字符串参数
            # 寻找名为 start_time 的 GET 参数,而且如果参数没有提交,返回一个空的字符串
            # 这里的 get() 是每个python的的字典数据类型都有的方法
            # 使用 get('start_time', '') 提供一个缺省的返回值''(一个空字符串)
            # 如果只是使用 request.GET['q'] 访问变量,在Get数据时 q 不可得, 可能引发 KeyError
            #查询起始时间
            start_time = request.GET.get('start_time','')
            #对时间格式化
            start_time = datetime.strptime(start_time,'%Y/%m/%d') if start_time else ''
            #查询截止时间
            end_time = request.GET.get('end_time','')
            #对时间格式化
            end_time = datetime.strptime(end_time,'%Y/%m/%d') if end_time else ''
        except Exception as e:
            #获取不到起始,截止时间就报错,然后重置
            logger.info('用户输入的时间有误:\n{}'.format(e))
            start_time = end_time = ''
        if start_time and not end_time:
            #__gte 大于 __let  小于 __range 范围
            newses = newses.filter(update_time__gte=start_time)

        if end_time and not start_time:
            newses = newses.filter(update_time__lte=end_time)

        if start_time and end_time:
            newses = newses.filter(update_time__range=(start_time,end_time))
        #通过 title 进行过滤
        title = request.GET.get('title','')
        if title:
            #__icontains 包含并且忽略大小写
            newses = newses.filter(title__icontains=title)
        #通过作者名进行过滤
        author_name = request.GET.get('author_name','')
        if author_name:
            newses = newses.filter(author__username__icontains=author_name)
        #通过标签 id 进行过滤
        try:
            tag_id = int(request.GET.get('tag_id',0))
        except Exception as e:
            logger.info('标签错误:\n{}'.format(e))
            tag_id = 0
        #id 存在但没有 news 的时候,返回值为空
        if tag_id:
            newses = newses.filter(tag_id=tag_id)
        #id 存在但没有 news 的时候,返回前面查询到的 news
        # newses = newses.filter(is_delete=False,tag_id=tag_id) or newses.filter(is_delete=False)

        #获取第几页内容
        try:
            page = int(request.GET.get('page',1))
        except Exception as e:
            logger.info('当前页数错误:\n{}'.format(e))
            page = 1
        paginator = Paginator(newses,constants.PER_PAGE_NEWS_COUNT)
        try:
            news_info = paginator.page(page)
        except EmptyPage:
            #若用户访问的页数大于实际页数,则返回最后一页
            logging.info('用户返回的页数大于总页数!')
            news_info = paginator.page(paginator.num_pages)

        paginator_data = paginator_script.get_paginator_data(paginator,news_info)
        start_time = start_time.strftime('%Y/%m/%d') if start_time else ''
        end_time = end_time.strftime('%Y/%m/%d') if end_time else ''
        context = {
            'news_info': news_info,
            'tags': tags,
            'start_time': start_time,
            'end_time': end_time,
            'title': title,
            'author_name': author_name,
            'tag_id': tag_id,
            'other_param': urlencode({  #将以下参数拼接成 url 返回给前端
                'start_time': start_time,
                'end_time': end_time,
                'title': title,
                'author_name': author_name,
                'tag_id': tag_id,
            })
        }
        #将 paginator_script 里的返回值增加到上下文中,返回给前端
        #update 字典方法,将 paginator_data 加入到 context 中
        context.update(paginator_data)
        return render(request, 'admin/news/news_manage.html', context=context)


class NewsPubView(PermissionRequiredMixin,View):
    """
    /admin/news/pub/
    """
    permission_required = ('news.view_news','news.add_news')
    raise_exception = True

    def handle_no_permission(self):
        if self.request.method.lower() != 'get':
            return to_json_data(errno=Code.ROLEERR,errmsg='没有操作权限')
        else:
            return super().handle_no_permission()

    def get(self,request):
        tags = models.Tag.objects.only('id','name').filter(is_delete=False)
        return render(request,'admin/news/news_pub.html',locals())

    def post(self,request):
        json_data = request.body
        if not json_data:
            return to_json_data(errno=Code.PARAMERR,errmsg=error_map[Code.PARAMERR])
        dict_data = json.loads(json_data.decode('utf8'))
        form = forms.NewsPubForm(data=dict_data)
        if form.is_valid():
            #直接将验证通过的进行保存,不用一一获取再保存
            #因为还有 author 没有传,并且需要传,所以先缓存,将 author 单独列出,再一起保存
            news_instance = form.save(commit=False)
            news_instance.author = request.user
            news_instance = form.save()
            return to_json_data(errmsg='文章更新成功')
        else:
            err_msg_list = []
            for item in form.errors.get_json_data().values():
                err_msg_list.append(item[0].get('message'))
            err_msg_str = '/'.join(err_msg_list)
            return to_json_data(errno=Code.PARAMERR,errmsg=err_msg_str)


class NewsUploadImage(View):
    """

    """
    def post(self,request):
        image_file = request.FILES.get('image_file')
        if not image_file:
            return to_json_data(errno=Code.PARAMERR,errmsg='从前端获取图片失败!')
        if image_file.content_type not in ('image/jpeg', 'image/png', 'image/gif'):
            return to_json_data(errno=Code.DATAERR,errmsg='不能上传非图片文件!')
        try:
            image_ext_name = image_file.name.split('.')[-1]
        except Exception as e:
            logger.info('图片拓展名异常:\n{}'.format(e))
            image_ext_name = 'jpg'
        try:
            #upload_by_buffer 上传两个参数: 文件对象内容,文件对象拓展名
            upload_res = FDFS_Client.upload_by_buffer(image_file.read(),file_ext_name=image_ext_name)
        except Exception as e:
            logger.error('图片上传出现异常:\n{}'.format(e))
            return to_json_data(errno=Code.UNKOWNERR,errmsg='图片上传异常!')
        else:
            if upload_res.get('Status') != 'Upload successed.':
                logger.info('图片上传到服务器失败!')
                return to_json_data(Code.UNKOWNERR, errmsg='图片上传到服务器失败!')
            else:
                image_name = upload_res.get('Remote file_id')
                image_url = settings.FASTDFS_SERVER_DOMAIN + image_name
                return to_json_data(data={'image_url': image_url},errmsg='图片上传成功!')


class UploadToken(View):
    """

    """
    def get(self,request):
        access_key = qiniu_secret_info.QI_NIU_ACCESS_KEY
        secret_key = qiniu_secret_info.QI_NIU_SECRET_KEY
        bucket_name = qiniu_secret_info.QI_NIU_BUCKET_NAME
        #构建鉴权对象
        q = qiniu.Auth(access_key,secret_key)
        token = q.upload_token(bucket_name)
        return JsonResponse({'uptoken': token})


from django.utils.decorators import method_decorator    #可以将函数装饰器变成类装饰器
from django.views.decorators.csrf import csrf_exempt    #函数装饰器

@method_decorator(csrf_exempt,name='post')
# @method_decorator(csrf_exempt,name='dispatch')    #将类里面所有方法都进行装饰
class MarkDownUploadImage(View):
    """

    """
    def post(self,request):
        image_file = request.FILES.get('editor-image-file')
        if not image_file:
            logger.info('从前端获取图片失败!')
            return JsonResponse({'success': 0,'message': '从前端获取图片失败!'})
        if image_file.content_type not in ('image/jpeg','image/png','image/gif'):
            return JsonResponse({'success': 0,'message':'不能上传非图片文件!'})
        try:
            image_ext_name = image_file.name.split('.')[-1]
        except Exception as e:
            logger.info('图片拓展名异常:{}'.format(e))
            image_ext_name = 'jpg'
        try:
            upload_res = FDFS_Client.upload_by_buffer(image_file.read(),file_ext_name=image_ext_name)
        except Exception as e:
            logger.error('图片上传出现异常:{}'.format(e))
            return JsonResponse({'success':0,'message':'图片上传异常!'})
        else:
            if upload_res.get('Status') != 'Upload successed.':
                logger.info('图片上传到FastDFS服务器失败!')
                return JsonResponse({'success':0,'message':'图片上传到服务器失败!'})
            else:
                image_name = upload_res.get('Remote file_id')
                image_url = settings.FASTDFS_SERVER_DOMAIN + image_name
                return JsonResponse({'success':1,'message':'图片上传成功!','url':image_url})


class NewsEditView(PermissionRequiredMixin,View):
    """
    /admin/news//
    """
    permission_required = ('news.delete_news','news.view_news','news.change_news')
    raise_exception = True

    def handle_no_permission(self):
        if self.request.method.lower() != 'get':
            return to_json_data(errno=Code.ROLEERR,errmsg='没有操作权限')
        else:
            return super().handle_no_permission()

    def delelte(self,request,news_id):
        news = models.News.objects.only('id').filter(id=news_id).first()
        if not news:
            return to_json_data(errno=Code.PARAMERR, errmsg='需要删除的文章不存在!')
        else:
            news.is_delete = True
            news.save(update_fields=['is_delete','update_time'])
            return to_json_data(errmsg='文章删除成功!')

    def get(self,request,news_id):
        #获取文章的对象
        news = models.News.objects.filter(id=news_id).first()
        if not news:
            return to_json_data(errno=Code.PARAMERR, errmsg='需要删除的文章不存在!')
        else:
            tags = models.Tag.objects.only('id','name').filter(is_delete=False)
            return render(request,'admin/news/news_pub.html',locals())

    def put(self,request,news_id):
        news = models.News.objects.filter(id=news_id).first()
        if not news:
            return to_json_data(errno=Code.PARAMERR,errmsg='需要更新的文章不存在!')
        json_data = request.body
        if not json_data:
            return to_json_data(errno=Code.PARAMERR,errmsg=error_map[Code.PARAMERR])
        #将 json 转化为 dict
        dict_data = json.loads(json_data.decode('utf8'))
        #用 form 验证
        form = forms.NewsPubForm(data=dict_data)
        #验证通过
        if form.is_valid():
            news.title = form.cleaned_data.get('title')
            news.digest = form.cleaned_data.get('digest')
            news.content = form.cleaned_data.get('content')
            news.image_url = form.cleaned_data.get('image_url')
            news.tag = form.cleaned_data.get('tag')
            news.save()
            return to_json_data(errmsg='文章更新成功')
        else:
            #定义一个错误信息列表
            err_msg_list = []
            for item in form.errors.get_json_data().values():
                err_msg_list.append(item[0].get('message'))
            err_msg_str = '/'.join(err_msg_list)    #拼接错误信息为一个字符串
            return to_json_data(errno=Code.PARAMERR,errmsg=err_msg_str)


class DocsManageView(PermissionRequiredMixin,View):
    """

    """
    permission_required = ('doc.view_doc')
    raise_exception = True

    def get(self,request):
        docs = Doc.objects.only('title','update_time').filter(is_delete=False)
        return render(request,'admin/doc/docs_manage.html',locals())


class DocsEditView(PermissionRequiredMixin,View):
    """
    /admin/docs//
    """
    permission_required = ('doc.view_doc','doc.delete_doc','doc.change_doc')
    raise_exception = True

    def handle_no_permission(self):
        if self.request.method.lower() != 'get':
            return to_json_data(errno=Code.ROLEERR,errmsg='没有操作权限')
        else:
            return super().handle_no_permission()

    def get(self,request,doc_id):
        doc = Doc.objects.filter(is_delete=False,id=doc_id).first()
        if not doc:
            return to_json_data(errno=Code.NODATA,errmsg='需要删除的文档不存在')
        else:
            return render(request,'admin/doc/docs_pub.html',locals())

    def delete(self,request,doc_id):
        doc = Doc.objects.filter(is_delete=False,id=doc_id).first()
        if not doc:
            return to_json_data(errno=Code.NODATA,errmsg='需要删除的文档不存在!')
        else:
            doc.is_delete = True
            doc.save(update_fields=['is_delete','update_time'])
            return to_json_data(errmsg='文档删除成功!')

    def put(self,request,doc_id):
        doc = Doc.objects.filter(is_delete=False,id=doc_id).first()
        if not doc:
            return to_json_data(errno=Code.NODATA,errmsg='需要删除的文档不存在')
        json_data = request.body
        if not json_data:
            return to_json_data(errno=Code.PARAMERR,errmsg=error_map[Code.PARAMERR])
        #将 json 转化为 dict
        dict_data = json.loads(json_data.decode('utf8'))
        form = forms.DocsPubForm(data=dict_data)
        if form.is_valid():
            #form.cleaned_data 是 dict 对象
            for attr, value in form.cleaned_data.items():
                #对 doc 对象的 attr 属性赋值 value
                setattr(doc,attr,value)
            doc.save()
            return to_json_data(errmsg='文档更新成功')
        else:
            #定义一个错误信息列表
            err_msg_list = []
            for item in form.errors.get_json_data().values():
                err_msg_list.append(item[0].get('message'))
            err_msg_str = ''.join(err_msg_list)     #拼接错误信息为一个字符串
            return to_json_data(errno=Code.PARAMERR,errmsg=err_msg_str)


class DocsUploadFile(View):
    """

    """
    def post(self,request):
        text_file = request.FILES.get('text_file')
        if not text_file:
            logger.info('从前端获取文件失败')
            return to_json_data(errno=Code.NODATA,errmsg='从前端获取文件失败')
        if text_file.content_type not in ('application/octet-stream','application/pdf','application/zip',\
              'text/plain','application/x-rar','application/msword'):
            print(text_file.content_type)
            return to_json_data(errno=Code.DATAERR,errmsg='不能上传的文件类型')
        try:
            text_ext_name = text_file.name.split('.')[-1]
        except Exception as e:
            logger.info('文件拓展名异常:{}'.format(e))
            text_ext_name = 'pdf'
        try:
            upload_res = FDFS_Client.upload_by_buffer(text_file.read(),file_ext_name=text_ext_name)
        except Exception as e:
            logger.error('文件上传出现异常:{}'.format(e))
            return to_json_data(errno=Code.UNKOWNERR,errmsg='文件上传异常')
        else:
            text_name = upload_res.get('Remote file_id')
            text_url = settings.FASTDFS_SERVER_DOMAIN + text_name
            return to_json_data(data={'text_file':text_url},errmsg='文件上传成功')


class DocsPubView(PermissionRequiredMixin,View):
    """
    /admin/docs/pub/
    """
    permission_required = ('doc.view_doc','doc.add_doc')
    raise_exception = True

    def handle_no_permission(self):
        if self.request.method.lower() != 'get':
            return to_json_data(errno=Code.ROLEERR,errmsg='没有操作权限')
        else:
            return super().handle_no_permission()

    def get(self,request):
        return render(request,'admin/doc/docs_pub.html')

    def post(self,request):
        json_data = request.body
        if not json_data:
            return to_json_data(errno=Code.PARAMERR,errmsg=error_map[Code.PARAMERR])
        #将 json 转换为 dict
        dict_data = json.loads(json_data.decode('utf8'))
        form = forms.DocsPubForm(data=dict_data)
        if form.is_valid():
            doc_instance = form.save(commit=False)
            doc_instance.author = request.user
            doc_instance.save()
            return to_json_data(errmsg='文档更新成功')
        else:
            #定义一个错误信息列表
            err_msg_list = []
            for item in form.errors.get_json_data().values():
                err_msg_list.append(item[0].get('message'))
            err_msg_str = ''.join(err_msg_list)     #拼接错误信息为一个字符串
            return to_json_data(errno=Code.PARAMERR,errmsg=err_msg_str)


class CoursesManageView(PermissionRequiredMixin,View):
    """

    """
    permission_required = ('course.view_course')
    raise_exception = True

    def get(self,request):
        courses = Course.objects.select_related('category','teacher').only('title','category__name',\
                  'teacher__name').filter(is_delete=False)
        return render(request,'admin/course/courses_manage.html',locals())


class CoursesEditView(PermissionRequiredMixin,View):
    """
    /admin/courses//
    """
    permission_required = ('course.view_course')
    raise_exception = True

    def handle_no_permission(self):
        if self.request.method.lower() != 'get':
            return to_json_data(errno=Code.ROLEERR,errmsg='没有操作权限')
        else:
            return super().handle_no_permission()

    def get(self,request,course_id):
        course = Course.objects.filter(is_delete=False,id=course_id).first()
        if course:
            teachers = Teacher.objects.only('name').filter(is_delete=False)
            categories = CourseCategory.objects.only('name').filter(is_delete=False)
            return render(request,'admin/course/courses_pub.html',locals())
        else:
            return to_json_data(errno=Code.NODATA,errmsg='需要更新的课程不存在')

    def delete(self,request,course_id):
        course = Course.objects.filter(is_delete=False,id=course_id).first()
        if course:
            course.is_delete = True
            course.save(update_fields=['is_delete','update_time'])
            return to_json_data(errmsg='课程删除成功')
        else:
            return to_json_data(errno=Code.PARAMERR,errmsg='需要删除的课程不存在')

    def put(self,request,course_id):
        course = Course.objects.filter(is_delete=False,id=course_id).first()
        if not course:
            return to_json_data(errno=Code.NODATA,errmsg='需要更新的课程不存在')
        json_data = request.body
        if not json_data:
            return to_json_data(errno=Code.PARAMERR,errmsg=error_map[Code.PARAMERR])
        #将 json 格式转换为 dict
        dict_data = json.loads(json_data.decode('utf8'))
        form = forms.CoursesPubForm(data=dict_data)
        if form.is_valid():
            for attr,value in form.cleaned_data.items():
                setattr(course,attr,value)
            course.save()
            return to_json_data(errmsg='课程更新成功')
        else:
            #定义一个错误信息列表
            err_msg_list = []
            for item in form.errors.get_json_data().values():
                err_msg_list.append(item[0].get('message'))
            err_msg_str = ''.join(err_msg_list)     #拼接错误信息为一个字符串
            return to_json_data(errno=Code.PARAMERR,errmsg=err_msg_str)


class CoursesPubView(PermissionRequiredMixin,View):
    """

    """
    permission_required = ('course.view_course','course.add_course')
    raise_exception = True

    def handle_no_permission(self):
        if self.request.method.lower() != 'get':
            return to_json_data(errno=Code.ROLEERR,errmsg='没有操作权限')
        else:
            return super().handle_no_permission()

    def get(self,request):
        teachers = Teacher.objects.only('name').filter(is_delete=False)
        categories = CourseCategory.objects.only('name').filter(is_delete=False)
        return render(request,'admin/course/courses_pub.html',locals())

    def post(self,request):
        json_data = request.body
        if not json_data:
            return to_json_data(errno=Code.PARAMERR,errmsg=error_map[Code.PARAMERR])
        dict_data = json.loads(json_data.decode('utf8'))
        form = forms.CoursesPubForm(data=dict_data)
        if form.is_valid():
            #此处模型里需要的字段都传了,所以不需要先缓存,直接存即可
            courses_instance = form.save()
            return to_json_data(errmsg='课程发布成功')
        else:
            #定义一个错误信息列表
            err_msg_list = []
            for item in form.errors.get_json_data().values():
                err_msg_list.append(item[0].get('message'))
            err_msg_str = ''.join(err_msg_list)     #拼接错误信息为一个字符串

            return to_json_data(errno=Code.PARAMERR,errmsg=err_msg_str)


class GroupsManageView(PermissionRequiredMixin,View):
    """

    """
    permission_required = ('group.view_group')
    raise_exception = True

    def get(self,request):
        groups = Group.objects.values('id','name').annotate(num_users=Count('user')).order_by('-num_users','id')
        return render(request,'admin/user/groups_manage.html',locals())


class GroupsEditView(PermissionRequiredMixin,View):
    """
    /admin/group//
    """
    def get(self,request,group_id):
        group = Group.objects.filter(id=group_id).first()
        if group:
            permissions = Permission.objects.only('id').all()
            return render(request,'admin/user/groups_add.html',locals())
        else:
            raise Http404('需要更新的组不存在')

    def delete(self,request,group_id):
        group = Group.objects.filter(id=group_id).first()
        if group:
            # group.permissions.clear()   #清空权限,多对多外键关联,不能直接删除,所以先清空
            # group.user_set.clear()
            group.delete()  #group 没有 is_delete,这里是物理删除,多对多关系的删除 django 会自动处理,级联删除
            return to_json_data(errmsg='用户组删除成功')
        else:
            return to_json_data(errno=Code.PARAMERR,errmsg='需要删除的用户组不存在!')

    def put(self,request,group_id):
        group = Group.objects.filter(id=group_id).first()
        if not group:
            return to_json_data(errno=Code.PARAMERR,errmsg='需要更新的用户组不存在')
        json_data = request.body
        if not json_data:
            return to_json_data(errno=Code.PARAMERR,errmsg=error_map[Code.PARAMERR])
        #将 json 转化为 dict
        dict_data = json.loads(json_data.decode('utf8'))
        group_name = dict_data.get('name','').strip()
        if not group_name:
            return to_json_data(errno=Code.PARAMERR,errmsg='组名为空')
        if group_name != group.name and Group.objects.filter(name=group_name).exists():
            return to_json_data(errno=Code.DATAEXIST,errmsg='组名已存在')
        #取出权限
        group_permissions = dict_data.get('group_permissions')
        if not group_permissions:
            return to_json_data(errno=Code.PARAMERR,errmsg='权限参数为空')

        try:
            # 得到的有可能有重复,用 set 集合去重
            permissions_set = set(int(i) for i in group_permissions)
        except Exception as e:
            logger.info('传的权限参数异常:\n{}'.format(e))
            return to_json_data(errno=Code.PARAMERR,errmsg='权限参数异常')
        all_permissions_set = set(i.id for i in Permission.objects.only('id'))
        if not permissions_set.issubset(all_permissions_set):
            return to_json_data(errno=Code.PARAMERR,errmsg='有不存在的权限参数')
        for perm_id in permissions_set:
            p = Permission.objects.get(id=perm_id)
            #添加权限   多对多数据操作用 add 添加
            group.permissions.add(p)
        group.name = group_name
        group.save()
        return to_json_data(errmsg='组更新成功')


class GroupsAddView(PermissionRequiredMixin,View):
    """
    /admin/groups/add/
    """
    permission_required = ('group.view_group','group.add_group')
    raise_exception = True

    def handle_no_permission(self):
        if self.request.method.lower() != 'get':
            return to_json_data(errno=Code.ROLEERR,errmsg='没有操作权限')
        else:
            return super().handle_no_permission()

    def get(self,request):
        permissions = Permission.objects.only('id').all()
        return render(request,'admin/user/groups_add.html',locals())

    def post(self,request):
        json_data = request.body
        if not json_data:
            return to_json_data(errno=Code.PARAMERR,errmsg=error_map[Code.PARAMERR])
        dict_data = json.loads(json_data.decode('utf8'))
        #取出组名,进行判断
        group_name = dict_data.get('name','').strip()
        if not group_name:
            return to_json_data(errno=Code.PARAMERR,errmsg='组名为空')
        one_group,is_created = Group.objects.get_or_create(name=group_name)
        if not is_created:
            return to_json_data(errno=Code.DATAEXIST,errmsg='组名已存在')
        #取出权限
        group_permissions = dict_data.get('group_permissions')
        if not group_permissions:
            return to_json_data(errno=Code.PARAMERR,errmsg='权限参数为空')
        try:
            permissions_set = set(int(i) for i in group_permissions)
        except Exception as e:
            logger.info('传的权限参数异常:\n{}'.format(e))
            return to_json_data(errno=Code.PARAMERR,errmsg='权限参数异常')
        all_permissions_set = set(i.id for i in Permission.objects.only('id'))
        if not permissions_set.issubset(all_permissions_set):
            return to_json_data(errno=Code.PARAMERR,errmsg='有不存在的权限参数')
        #设置权限
        for perm_id in permissions_set:
            p = Permission.objects.get(id=perm_id)
            one_group.permissions.add(p)    #多对多数据操作
        one_group.save()
        return to_json_data(errmsg='组创建成功')


class UsersManageView(PermissionRequiredMixin,View):
    """
    /admin/users/
    """
    permission_required = ('users.view_users')
    raise_exception = True

    def get(self,request):
        users = Users.objects.only('username','is_staff','is_superuser').filter(is_active=True)
        return render(request,'admin/user/users_manage.html',locals())


class UsersEditView(PermissionRequiredMixin,View):
    """
    /admin/users//
    """
    permission_required = ('users.view_users','users.delete_users','users.change_users')
    raise_exception = True

    def handle_no_permission(self):
        if self.request.method.lower() != 'get':
            return to_json_data(errno=Code.ROLEERR,errmsg='没有操作权限')
        else:
            return super().handle_no_permission()

    def get(self,request,user_id):
        user_instance = Users.objects.filter(id=user_id).first()
        if user_instance:
            groups = Group.objects.only('name').all()
            return render(request,'admin/user/users_edit.html',locals())
        else:
            return Http404('需要更新的用户不存在!')

    def delete(self,request,user_id):
        user_instance = Users.objects.filter(id=user_id).first()
        if user_instance:
            user_instance.groups.clear()
            user_instance.user_permissions.clear()
            user_instance.is_active = False #逻辑删除,需要手动清空多对多表,权限,组信息
            user_instance.save()
            return to_json_data(errmsg='用户删除成功')
        else:
            return to_json_data(errno=Code.PARAMERR,errmsg='需要删除的用户不存在')

    def put(self,request,user_id):
        user_instance = Users.objects.filter(id=user_id).first()
        if not user_instance:
            return to_json_data(errno=Code.NODATA,errmsg='需要更新的用户不存在')
        json_data = request.body
        if not json_data:
            return to_json_data(errno=Code.PARAMERR,errmsg=error_map[Code.PARAMERR])
            #将 json 转化为 dict
        dict_data = json.loads(json_data.decode('utf8'))
        try:
            groups = dict_data.get('groups')
            is_staff = int(dict_data.get('is_staff'))
            is_superuser = int(dict_data.get('is_superuser'))
            is_active = int(dict_data.get('is_active'))
            params = (is_staff,is_superuser,is_active)
            #返回形如 [True,True,True],如果不全为 True
            # all() 函数用于判断给定的可迭代参数 iterable 中的所有元素是否都为 TRUE,如果是返回 True,否则返回 False。
            # 元素除了是 0、空、None、False 外都算 True。
            # iterable:元组或列表。
            if not all([p in (0,1) for p in params]):
               return to_json_data(errno=Code.PARAMERR,errmsg='参数错误')

        except Exception as e:
            logger.info('从前端获取参数出现异常:\n{}'.format(e))
            return to_json_data(errno=Code.PARAMERR,errmsg='参数错误')
        try:
            groups_set = set(int(i) for i in groups)
        except Exception as e:
            logger.info('传的用户组参数异常:\n{}'.format(e))
            return to_json_data(errno=Code.PARAMERR,errmsg='用户组参数异常')
        all_groups_set = set(i.id for i in Group.objects.only('id'))
        if not groups_set.issubset(all_groups_set):
            return to_json_data(errno=Code.PARAMERR,errmsg='有不存在的用户组参数')
        gs = Group.objects.filter(id__in=groups_set)
        user_instance.groups.clear()
        user_instance.groups.set(gs)    #多对多数据操作,也可以用 add
        user_instance.is_staff = bool(is_staff)
        user_instance.is_speruser = bool(is_superuser)
        user_instance.is_active = bool(is_active)
        user_instance.save()
        return to_json_data(errmsg='用户信息更新成功')

apps/admin/users/models.py

  • 在 Users 中添加如下方法
    def get_groups_name(self):   #用于实例的调用
        group_name_list = [i.name for i in self.groups.all()]
        return '|'.join(group_name_list)
    #多对多数据操作: add,remove,clear,属性,类名小写_set
  • 如图
    20 用户权限相关功能_第2张图片

apps/admin/urls.py

#!/home/xiaoge/env python3.6
# -*- coding: utf-8 -*-
"""
  __title__ = ' urls'
  __author__ = 'xiaoge'
  __mtime__ = '2019/6/26 下午8:55'
  # code is far away from bugs with the god animal protecting
      I love animals. They taste delicious.
               ┏┓      ┏┓
              ┏┛┻━━━━━━┛┻┓
              ┃        ☃ ┃
              ┃  ┳┛ ┗┳   ┃
              ┃    ┻     ┃
              ┗━┓      ┏━┛
                ┃      ┗━━━┓
                ┃ 神兽保佑  ┣┓
                ┃永无BUG!┏┛
                ┗┓┓┏━┳┓┏┛
                ┃┫┫  ┃┫┫
               ┗┻┛  ┗┻┛
"""
from apps.admin import views
from django.urls import path


app_name = 'admin'

urlpatterns = [
    path('',views.IndexView.as_view(),name='index'),
    path('tags/',views.TagManagerView.as_view(),name='tags'),
    path('tags//',views.TagEditView.as_view(),name='tag_edit'),
    path('tags//news/',views.NewsByTagIdView.as_view(),name='hotnews_by_tagid'),

    path('hotnews/',views.HotNewsManageView.as_view(),name='hotnews'),
    path('hotnews//',views.HotNewsEditView.as_view(),name='hotnews_edit'),
    path('hotnews/add/',views.HotNewsAddView.as_view(),name='hotnews_add'),

    path('news/', views.NewsManageView.as_view(), name='news_manage'),
    path('news//',views.NewsEditView.as_view(),name='news_edit'),
    path('news/pub/',views.NewsPubView.as_view(),name='news_pub'),
    path('news/images/',views.NewsUploadImage.as_view(),name='upload_image'),

    path('docs/',views.DocsManageView.as_view(),name='docs_manage'),
    path('docs//',views.DocsEditView.as_view(),name='docs_edit'),
    path('docs/pub/',views.DocsPubView.as_view(),name='docs_pub'),
    path('docs/files/',views.DocsUploadFile.as_view(),name='upload_text'),

    path('courses/',views.CoursesManageView.as_view(),name='courses_manage'),
    path('courses//',views.CoursesEditView.as_view(),name='courses_edit'),
    path('courses/pub/',views.CoursesPubView.as_view(),name='courses_pub'),

    path('groups/',views.GroupsManageView.as_view(),name='groups_manage'),
    path('groups/add/',views.GroupsAddView.as_view(),name='groups_add'),
    path('groups//',views.GroupsEditView.as_view(),name='groups_edit'),
    path('users/',views.UsersManageView.as_view(),name='users_manage'),
    path('users//',views.UsersEditView.as_view(),name='users_edit'),

    path('token/',views.UploadToken.as_view(),name='upload_token'),
    path('markdown/images/',views.MarkDownUploadImage.as_view(),name='markdown_image_upload'),

]

前端功能实现

templates/admin/user/groups_manage.html

{% extends 'admin/base/base.html' %}
{% load static %}

{% block title %}
  用户组管理页
{% endblock %}

{% block content_header %}
  用户组管理
{% endblock %}

{% block header_option_desc %}
  用户组管理
{% endblock %}


{% block content %}
  
{% for one_group in groups %} {% endfor %}
用户组名 用户数 操作
{{ one_group.name }} {{ one_group.num_users }} 编辑
{% endblock %} {% block script %} {% endblock %}

static/js/admin/user/groups_manage.js

$(function () {

  // 删除组
  let $groupDel = $(".btn-del");  // 1. 获取删除按钮
  $groupDel.click(function () {   // 2. 点击触发事件
    let _this = this;
    let sGroupId = $(this).parents('tr').data('id');
    let sGroupName = $(this).parents('tr').data('name');
    // 获取用户个数
    let num_users = $(this).parents('tr').find('td:nth-child(2)').html();
    // 判断组成员是否为空
    // if (num_users > '0') {
    //   message.showError('组成员不为空,无法删除!');
    //   return
    // }

    fAlert.alertConfirm({
      title: `确定删除 ${sGroupName} 吗?`,
      type: "error",
      confirmText: "确认删除",
      cancelText: "取消删除",
      confirmCallback: function confirmCallback() {

        $.ajax({
          url: "/admin/groups/" + sGroupId + "/",  // url尾部需要添加/
          // 请求方式
          type: "DELETE",
          dataType: "json",
        })
          .done(function (res) {
            if (res.errno === "0") {
              message.showSuccess("用户组删除成功");
              $(_this).parents('tr').remove();
            } else {
              swal.showInputError(res.errmsg);
            }
          })
          .fail(function () {
            message.showError('服务器超时,请重试!');
          });
      }
    });
  });


  // get cookie using jQuery
  function getCookie(name) {
    let cookieValue = null;
    if (document.cookie && document.cookie !== '') {
      let cookies = document.cookie.split(';');
      for (let i = 0; i < cookies.length; i++) {
        let cookie = jQuery.trim(cookies[i]);
        // Does this cookie string begin with the name we want?
        if (cookie.substring(0, name.length + 1) === (name + '=')) {
          cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
          break;
        }
      }
    }
    return cookieValue;
  }

  function csrfSafeMethod(method) {
    // these HTTP methods do not require CSRF protection
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
  }

  // Setting the token on the AJAX request
  $.ajaxSetup({
    beforeSend: function (xhr, settings) {
      if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
        xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
      }
    }
  });

});

templates/admin/user/groups_add.html

{% extends 'admin/base/base.html' %}
{% load static %}

{% block title %}
  用户组添加页
{% endblock %}

{% block content_header %}
  用户组添加
{% endblock %}

{% block header_option_desc %}
  用户组添加
{% endblock %}


{% block content %}
  
{% if group %} {% else %} {% endif %}
{% endblock %} {% block script %} {% endblock %}

static/js/admin/user/groups_add.js

$(function () {

  // ================== 修改用户组 ================
  let $groupsBtn = $("#btn-pub-news");
  $groupsBtn.click(function () {
    // 判断用户组名是否为空
    let sName = $("#news-title").val();  // 获取用户组名
    if (!sName) {
      message.showError('请填写用户组名!');
      return
    }

    // 判断是否选择权限
    let sGroupPermissions = $("#group-permissions").val();
    if (!sGroupPermissions || sGroupPermissions === []) {
      message.showError('请选择权限');
      return
    }


    // 获取coursesId 存在表示更新 不存在表示发表
    let groupsId = $(this).data("news-id");
    let url = groupsId ? '/admin/groups/' + groupsId + '/' : '/admin/groups/add/';
    let data = {
      "name": sName,
      "group_permissions": sGroupPermissions

    };

    $.ajax({
      // 请求地址
      url: url,
      // 请求方式
      type: groupsId ? 'PUT' : 'POST',
      data: JSON.stringify(data),
      // 请求内容的数据类型(前端发给后端的格式)
      contentType: "application/json; charset=utf-8",
      // 响应数据的格式(后端返回给前端的格式)
      dataType: "json",
    })
      .done(function (res) {
        if (res.errno === "0") {
          if (groupsId) {
            fAlert.alertNewsSuccessCallback("用户组更新成功", '跳到用户组管理页', function () {
              window.location.href = '../../../../admin/groups/'
            });

          } else {
            fAlert.alertNewsSuccessCallback("用户组创建成功", '跳到用户组管理页', function () {
              window.location.href = '../../../../admin/groups/'
            });
          }
        } else {
          fAlert.alertErrorToast(res.errmsg);
        }
      })
      .fail(function () {
        message.showError('服务器超时,请重试!');
      });

  });


  // get cookie using jQuery
  function getCookie(name) {
    let cookieValue = null;
    if (document.cookie && document.cookie !== '') {
      let cookies = document.cookie.split(';');
      for (let i = 0; i < cookies.length; i++) {
        let cookie = jQuery.trim(cookies[i]);
        // Does this cookie string begin with the name we want?
        if (cookie.substring(0, name.length + 1) === (name + '=')) {
          cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
          break;
        }
      }
    }
    return cookieValue;
  }

  function csrfSafeMethod(method) {
    // these HTTP methods do not require CSRF protection
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
  }

  // Setting the token on the AJAX request
  $.ajaxSetup({
    beforeSend: function (xhr, settings) {
      if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
        xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
      }
    }
  });

});

templates/admin/user/users_manage.html

{% extends 'admin/base/base.html' %}

{% load static %}
{% block title %}
  用户管理页
{% endblock %}

{% block content_header %}
  用户管理
{% endblock %}

{% block header_option_desc %}
  用户管理
{% endblock %}


{% block content %}
  
{% for one_user in users %} {% if one_user.is_staff %} {% else %} {% endif %} {% if one_user.is_superuser %} {% else %} {% endif %} {% endfor %}
用户名 是否能登录后台? 是否是超级管理员? 所属组 操作
{{ one_user.username }}{{ one_user.get_groups_name }} 编辑
{% endblock %} {% block script %} {% endblock %}

static/js/admin/user/users_manage.js


$(function () {
  // 获取修改之前的值
  let noModifyIsStaff = $("input[name='login_admin']:checked").val();
  let noModifyIsSuperuser = $("input[name='is_superuser']:checked").val();
  let noModifyIsActive = $("input[name='is_active']:checked").val();
  let noModifyGroups = $("#add_group").val();

  // ================== 删除用户 ================
  let $userDel = $(".btn-del");  // 1. 获取删除按钮
  $userDel.click(function () {   // 2. 点击触发事件
    let _this = this;
    let sUserId = $(this).parents('tr').data('id');
    let sUserName = $(this).parents('tr').data('name');

    fAlert.alertConfirm({
      title: `确定删除 ${sUserName} 这个用户吗?`,
      type: "error",
      confirmText: "确认删除",
      cancelText: "取消删除",
      confirmCallback: function confirmCallback() {

        $.ajax({
          url: "/admin/users/" + sUserId + "/",  // url尾部需要添加/
          // 请求方式
          type: "DELETE",
          dataType: "json",
        })
          .done(function (res) {
            if (res.errno === "0") {
              message.showSuccess("用户删除成功");
              $(_this).parents('tr').remove();
            } else {
              swal.showInputError(res.errmsg);
            }
          })
          .fail(function () {
            message.showError('服务器超时,请重试!');
          });
      }
    });
  });


  // ================== 修改用户 ================
  let $usersBtn = $("#btn-edit-user");
  $usersBtn.click(function () {
    // 判断用户信息是否修改
    // 获取修改之后的值
    let modifiedIsStaff = $("input[name='login_admin']:checked").val();
    let modifiedIsSuperuser = $("input[name='is_superuser']:checked").val();
    let modifiedIsActive = $("input[name='is_active']:checked").val();
    let modifiedGroups = $("#add_group").val();

    if (noModifyIsStaff === modifiedIsStaff && noModifyIsSuperuser === modifiedIsSuperuser
      && noModifyIsActive === modifiedIsActive &&
      (JSON.stringify(noModifyGroups) === JSON.stringify(modifiedGroups))) {
      message.showError('用户信息未修改!');
      return
    }

    // 获取userId
    let userId = $(this).data("user-id");
    let url = '/admin/users/' + userId + '/';
    let data = {
      "is_staff": modifiedIsStaff,
      "is_superuser": modifiedIsSuperuser,
      "is_active": modifiedIsActive,
      "groups": modifiedGroups

    };

    $.ajax({
      // 请求地址
      url: url,
      // 请求方式
      type: 'PUT',
      data: JSON.stringify(data),
      // 请求内容的数据类型(前端发给后端的格式)
      contentType: "application/json; charset=utf-8",
      // 响应数据的格式(后端返回给前端的格式)
      dataType: "json",
    })
      .done(function (res) {
        if (res.errno === "0") {
          fAlert.alertNewsSuccessCallback("用户信息更新成功", '跳到用户管理页', function () {
            window.location.href = '../../../../admin/users/'
          })
        } else {
          fAlert.alertErrorToast(res.errmsg);
        }
      })
      .fail(function () {
        message.showError('服务器超时,请重试!');
      });

  });


  // get cookie using jQuery
  function getCookie(name) {
    let cookieValue = null;
    if (document.cookie && document.cookie !== '') {
      let cookies = document.cookie.split(';');
      for (let i = 0; i < cookies.length; i++) {
        let cookie = jQuery.trim(cookies[i]);
        // Does this cookie string begin with the name we want?
        if (cookie.substring(0, name.length + 1) === (name + '=')) {
          cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
          break;
        }
      }
    }
    return cookieValue;
  }

  function csrfSafeMethod(method) {
    // these HTTP methods do not require CSRF protection
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
  }

  // Setting the token on the AJAX request
  $.ajaxSetup({
    beforeSend: function (xhr, settings) {
      if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
        xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
      }
    }
  });

});

templates/admin/user/users_edit.html

{% extends 'admin/base/base.html' %}

{% load static %}
{% block title %}
  用户编辑页
{% endblock %}

{% block content_header %}
  用户编辑
{% endblock %}

{% block header_option_desc %}
  用户编辑
{% endblock %}


{% block content %}
  

  
用户名:{{ user_instance.username }}
是否能登录后台?(is_staff)
是否是超级管理员?(is_superuser)
是否激活?(is_active)
{% endblock %} {% block script %} {% endblock %}

权限管理

登录权限

  • 在 apps/admin/views.py 中的 Index_View 类的开头添加
    login_url = 'users:login'   #没登录就跳转到登录页面,在此处或者在 settings.py 中设置
    redirect_field_name = 'next'    #重定向,'next' 和前端对应,在此处设置
  • 或者在 settings.py 中设置
#登录页 URL
LOGIN_URL = 'users:login'
  • 同时将类 Index_View 的继承类由 View 改为 LoginRequiredMixin,View
  • 如图
    20 用户权限相关功能_第3张图片

用户权限限制

  • 在需要限制的类中添加继承类 PermissionRequiredMixin,该继承类添加到 View 类前,如图
    20 用户权限相关功能_第4张图片
    20 用户权限相关功能_第5张图片
  • 类中再添加如图模式的代码
    20 用户权限相关功能_第6张图片
    20 用户权限相关功能_第7张图片

取消单个视图的 csrf 验证

from django.utils.decorators import method_decorator    #可以将函数装饰器变成类装饰器
from django.views.decorators.csrf import csrf_exempt    #函数装饰器

@method_decorator(csrf_exempt,name='post')  #取消单个视图的 csrf 验证,只对特定的方法 'post' 进行装饰
# @method_decorator(csrf_exempt,name='dispatch')    #将类里面所有方法都进行装饰

你可能感兴趣的:(django)