基于Django的通用审核模块

本文介绍了一种Django通用审核模块的实现,包括模型层、序列化层和ViewSet层的设计。模型层定义了基础模型和审核模型,用于记录审批信息和状态;Serializers层提供了序列化功能,方便数据交换;ViewSet层则实现了视图集,包含审批操作接口。该模块适用于需要审批功能的管理系统,可避免重复开发。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


前言

一般来说,管理系统都会有审核功能,即:普通用户发起申请,拥有审核权限的管理员审核申请的功能。为了避免重复造轮子,设计了一个通用的审核模块,适用于大部分场景。


一、基础代码

1.模型层

通用模型代码如下:

# -*- coding: utf-8 -*-
import uuid
import logging
from django.conf import settings
from django.db import models
from django_extensions.db.fields import ModificationDateTimeField, CreationDateTimeField

__author__ = 'JayChen'


logger = logging.getLogger('django')


class PubCreateUpdateMixin(models.Model):
    """模型类的基础模块,适合所有的models继承"""
    
    updated_on = ModificationDateTimeField(
        verbose_name='更新时间',
        db_index=True
    )
    
    created_on = CreationDateTimeField(
        verbose_name='创建时间',
        db_index=True
    )
    
    updated_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, 
        verbose_name='修改数据的人',
        related_name='%(app_label)s_%(class)s_updated_by',
        blank=True,
        null=True,
        on_delete=models.SET_NULL
    )
    
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        related_name='%(app_label)s_%(class)s_created_by',
        verbose_name='创建数据的人',
        blank=True,
        null=True,
        on_delete=models.SET_NULL
    )
    
    last_comment = models.TextField(
        verbose_name='最后的评论',
        blank=True,
        null=True
    )
    
    uuid = models.UUIDField(
        verbose_name='UUID',
        default=uuid.uuid4,
        editable=False,
        db_index=True
    )
    
    uploads = models.JSONField(
        null=True,
        blank=True,
        verbose_name='上传的数据'
    )

    class Meta:
        abstract = True

    @property
    def display(self):
        raise NotImplementedError

    @property
    def resource_display(self):
        return {
            'updated_on': self.updated_on,
            'created_on': self.created_on,
            'updated_by': self.updated_by,
            'created_by': self.created_by,
            'last_comment': self.last_comment,
            'uploads': self.uploads,
            'uuid': self.uuid
        }

审核模块模型代码如下:

# -*- coding: utf-8 -*-
from django.db import models
from django.conf import settings
from app.models.base import PubCreateUpdateMixin
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType


__author__ = 'JayChen'


class Approval(PubCreateUpdateMixin):
    """审批信息表"""
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField(db_index=True)
    content_object = GenericForeignKey('content_type', 'object_id')

    handler = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        verbose_name='审批人',
        blank=True,
        null=True,
        on_delete=models.PROTECT,
    )

    status = models.CharField(
        verbose_name='审批状态',
        choices=settings.CFG['APPROVAL_STATUS'],
        default='Created',
        max_length=255,
        db_index=True
    )

    type = models.CharField(
        verbose_name='模型名称',
        blank=True,
        null=True,
        max_length=255,
        db_index=True
    )

    # 一旦所有 handler 审批通过, Content object 的字段会做相应改变 {'status': 'Reviewed', 'code': 'QA-001'}.
    co_changes = models.JSONField(
        verbose_name='预改变数据',
        blank=True,
        null=True
    )

    co_changes_applied_on = models.DateTimeField(
        verbose_name='预改变数据生效时间',
        blank=True,
        null=True,
        db_index=True
    )

    approved_on = models.DateTimeField(
        verbose_name='审批时间',
        blank=True,
        null=True,
        db_index=True
    )

    note = models.CharField(
        verbose_name='审批意见',
        blank=True,
        null=True,
        max_length=255,
        db_index=True
    )

    class Meta(PubCreateUpdateMixin.Meta):
        ordering = ['-id']
        app_label = 'app'
        db_table = 'app_approval'

    def save(self, force_insert=False, force_update=False, using=None, update_fields=None, *args, **kwargs):
        self.type = self.content_object.__class__.__name__

        super(Approval, self).save(force_insert=force_insert, force_update=force_update, using=using,
                                   update_fields=update_fields)

    @property
    def display(self):
        return '{}:{}'.format(self.status, self.approved_on)

    @property
    def resource_display(self):
        result = super(Approval, self).resource_display
        result['handler'] = self.handler.resource_display
        result['co_changes'] = self.co_changes
        result['status'] = self.status
        result['note'] = self.note
        result['approved_on'] = self.approved_on
        return result

    @classmethod
    def prefix(cls):
        """ Slug 前缀 """
        return 'APR'

    @classmethod
    def resource(cls):
        return 'approval'

    @classmethod
    def get_content_objects(cls, model: models.Model, handler, status: str = None):
        ct = ContentType.objects.get_for_model(model)
        qs = cls.objects.filter(content_type=ct, handler=handler)
        if status:
            qs = qs.filter(status=status)
        co_ids = list(set(list(
            qs.values_list('object_id', flat=True)
        )))
        return model.objects.filter(pk__in=co_ids)

提示: 在执行 python manage.py makemigrations 和 python manage.py migrate 之前,各位根据自己的项目风格,在settings.py 的INSTALLED_APPS中添加项目或者在models文件夹下的__init__.py中导入上面新增的Approval模型。

2.Serializers层

代码如下:

# -*- coding: utf-8 -*-
from rest_framework import serializers
from app.models import Approval


__author__ = 'JayChen'


class ApprovalSerializer(serializers.ModelSerializer):
    """序列化列表对象"""

    class Meta:
        model = Approval
        exclude = ()


class ApprovalDetailSerializer(ApprovalSerializer):
    """序列化单个对象"""
    
    class Meta(ApprovalSerializer.Meta):
        exclude = ()

3.ViewSet层

Mixin代码如下:

from rest_framework.generics import GenericAPIView


class ListDetailMixin(GenericAPIView):
    def get_serializer_class(self):
        if hasattr(self, 'action_serializers') and hasattr(self, 'action'):
            if self.action in self.action_serializers:
                return self.action_serializers[self.action]
        return super(ListDetailMixin, self).get_serializer_class()

接口代码如下:

# -*- coding: utf-8 -*-
import rest_framework_filters as filters
from rest_framework import viewsets
from app.mixin import ListDetailMixin
from app.models import Approval, MyUser
from app.serializers.public.approval import ApprovalDetailSerializer, ApprovalSerializer


__author__ = 'JayChen'


class ApprovalFilter(filters.FilterSet):
    handler = filters.RelatedFilter(
        'app.viewsets.public.user.MyUserFilter',
        field_name='handler',
        queryset=MyUser.objects.all()
    )

    class Meta:
        model = Approval
        fields = {
            'note': '__all__',
            'type': '__all__',
            'status': '__all__',
            'co_changes': 'icontains',
            'co_changes_applied_on': '__all__',
            'approved_on': '__all__',
        }


class ApprovalViewSet(viewsets.ModelViewSet, ListDetailMixin):
    queryset = Approval.objects.all()
    serializer_class = ApprovalSerializer

    action_serializers = {
        'retrieve': ApprovalDetailSerializer,
        'list': ApprovalSerializer,
    }
    filter_class = ApprovalFilter
    search_fields = ('note', 'handler__name_cn', 'handler__email', 'type')

二、使用步骤

建立序列化基础类

# -*- coding: utf-8 -*-
from django.contrib.contenttypes.models import ContentType
from rest_framework import serializers
from app.models import Approval, MyUser

__author__ = 'JayChen'


class MyBaseSerializer(serializers.ModelSerializer):
    _approvals = serializers.SerializerMethodField()

    def save_handlers(self, instance, handlers):
        user = None
        request = self.context.get("request")
        if request and hasattr(request, "user"):
            user = request.user

        content_type = ContentType.objects.get_for_model(instance)
        ids = []
        for handler in handlers:
            if handler['id'] in ids:
                # 跳过重复的审核人
                continue
            approval_count = 1  # 默认都是第一轮审核
            old_last_app = Approval.objects.filter(
                object_id=instance.id,
                content_type=content_type,
                status__in=['Accepted', 'Rejected']
            ).order_by('-approved_on').first()
            if old_last_app and old_last_app.co_changes.get('approval_count'):
                approval_count = old_last_app.co_changes.get('approval_count') + 1
            handler.get('co_changes').update({'approval_count': approval_count})

            app, _ = Approval.objects.get_or_create(
                object_id=instance.id,
                content_type=content_type,
                handler=MyUser.objects.get(pk=handler['id']),
                co_changes=handler.get('co_changes', {}),
                status='Created'
            )
            app.created_by = user
            if handler.get('note'):
                app.note = handler.get('note')
            app.save()
            ids.append(handler['id'])

    def get__approvals(self, obj):
        result = []
        for a in obj.get_approvals():
            result.append({
                'handler': a.handler.resource_display,
                'note': a.note,
                'status': a.status,
                '_status': a.get_status_display(),
                'co_changes': a.co_changes,
            })
        return result

    def create(self, validated_data):
        handlers = self._kwargs['data'].get('handlers', [])
        instance = super(MyBaseSerializer, self).create(validated_data)
        if handlers:
            self.save_handlers(instance, handlers)
        return instance

    def update(self, instance, validated_data):
        handlers = self._kwargs['data'].get('handlers', [])
        instance = super(MyBaseSerializer, self).update(instance, validated_data)
        if handlers:
            self.save_handlers(instance, handlers)
        return instance

建立viewset基础类

# -*- coding: utf-8 -*-
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework import exceptions
from django.db import transaction
from django.utils import timezone
from django.contrib.contenttypes.models import ContentType
from app.mixin import ListDetailMixin
from app.models import Approval

__author__ = 'JayChen'


class BaseModelViewSet(viewsets.ModelViewSet, ListDetailMixin):

    @action(detail=True, methods=['POST'], permission_classes=[IsAuthenticated])
    def approve(self, request, pk):
        status = request.data.get('status')
        note = request.data.get('note')
        obj = self.get_object()

        ct = ContentType.objects.get_for_model(obj)
        qs = Approval.objects.filter(
            content_type=ct, handler=request.user, object_id=obj.pk, status='Created'
        ).order_by('-id')
        if qs.count() == 0:
            raise exceptions.APIException(detail='没有审核的权限')
        app = qs.first()

        with transaction.atomic():
            if status in ['Accepted', 'Rejected']:
                qs.update(status=status, approved_on=timezone.now())
                if status == 'Accepted':
                    # 如果自己是最后一个审核通过者,会设置 Content_object 的状态
                    app_status = set(list(
                        Approval.objects.filter(
                            content_type=ct, object_id=obj.pk, co_changes=app.co_changes
                        ).values_list('status', flat=True))
                    )
                    # print(app_status)
                    if app_status == {'Accepted'}:
                        for attr, v in app.co_changes.get('accepted', {}).items():
                            if hasattr(obj, attr):
                                old_val = getattr(obj, attr)
                                setattr(obj, attr, v)
                        obj.save()
                        # 记录改变被执行的时间
                        qs.update(co_changes_applied_on=timezone.now())

                elif status == 'Rejected':
                    if note:
                        qs.update(note=note)
                    # 拒绝的操作
                    for attr, v in app.co_changes.get('rejected', {}).items():
                        if hasattr(obj, attr):
                            old_val = getattr(obj, attr)
                            setattr(obj, attr, v)
                    obj.save()
            else:
                qs.update(status=status)
        obj.refresh_from_db()
        _serializer = self.get_serializer_class()
        return Response(_serializer(obj).data)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈大憨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值