前言
一般来说,管理系统都会有审核功能,即:普通用户发起申请,拥有审核权限的管理员审核申请的功能。为了避免重复造轮子,设计了一个通用的审核模块,适用于大部分场景。
一、基础代码
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)