From b4803453023a3129da34cf696baa5509b8c449a2 Mon Sep 17 00:00:00 2001 From: Czw996 <459749926@qq.com> Date: Sun, 12 Dec 2021 15:44:39 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=8F=96=E6=B6=88=E9=9A=90=E8=97=8F?= =?UTF-8?q?=E5=85=AC=E5=8F=B8=E7=BC=96=E5=8F=B7=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/goods/models.py | 8 +---- apps/system/models.py | 12 ++++--- apps/system/schemas.py | 5 +-- apps/system/serializers.py | 34 ++++++++++++++----- apps/system/views.py | 6 ++-- extensions/__init__.py | 0 extensions/authentications.py | 4 +-- extensions/common/__init__.py | 0 extensions/common/base.py | 10 ++++++ extensions/common/schema.py | 11 ++++++ extensions/paginations.py | 12 ++++--- extensions/permissions.py | 41 +++++++++++++++++++---- extensions/serializers.py | 16 +++------ extensions/viewsets.py | 63 ++++++++++++++++++++--------------- 14 files changed, 143 insertions(+), 79 deletions(-) create mode 100644 extensions/__init__.py create mode 100644 extensions/common/__init__.py create mode 100644 extensions/common/base.py create mode 100644 extensions/common/schema.py diff --git a/apps/goods/models.py b/apps/goods/models.py index 2e83179..879461d 100644 --- a/apps/goods/models.py +++ b/apps/goods/models.py @@ -44,12 +44,6 @@ class Goods(Model): return number - # def save(self, force_insert=False, force_update=False, using=None, update_fields=None): - # self.has_stock = self.remain_quantity > 0 - # if update_fields: - # update_fields.append('has_stock') - # return super().save(force_insert, force_update, using, update_fields) - class Batch(Model): """批次""" @@ -64,7 +58,7 @@ class Batch(Model): expiration_date = DateField(null=True, verbose_name='过期日期') initial_inventory = ForeignKey('goods.Inventory', on_delete=SET_NULL, null=True, related_name='batchs', verbose_name='初始库存') - has_stock = BooleanField(default=True, verbose_name='库存状态') + has_stock = BooleanField(verbose_name='库存状态') create_time = DateTimeField(auto_now_add=True, verbose_name='创建时间') team = ForeignKey('system.Team', on_delete=CASCADE, related_name='batchs') diff --git a/apps/system/models.py b/apps/system/models.py index 5669dfc..21542ae 100644 --- a/apps/system/models.py +++ b/apps/system/models.py @@ -7,20 +7,21 @@ class Team(Model): number = CharField(max_length=32, unique=True, verbose_name='编号') expiry_time = DateTimeField(verbose_name='到期时间') create_time = DateTimeField(auto_now_add=True, verbose_name='创建时间') + enable_auto_stock_in = BooleanField(default=False, verbose_name='启用自动入库') enable_auto_stock_out = BooleanField(default=False, verbose_name='启用自动出库') -class PermissionType(Model): - """权限类型""" +class PermissionGroup(Model): + """权限分组""" - name = CharField(max_length=64, verbose_name='类型名称') + name = CharField(max_length=64, verbose_name='分组名称') class Permission(Model): """权限""" - type = ForeignKey('system.PermissionType', on_delete=CASCADE, related_name='permissions', verbose_name='权限类型') + group = ForeignKey('system.PermissionGroup', on_delete=CASCADE, related_name='permissions', verbose_name='权限分组') name = CharField(max_length=64, verbose_name='权限名称') code = CharField(max_length=64, verbose_name='权限代码') @@ -53,6 +54,7 @@ class User(Model): email = CharField(max_length=256, null=True, blank=True, verbose_name='邮箱') sex = CharField(max_length=32, choices=Sex.choices, verbose_name='性别') roles = ManyToManyField('system.Role', blank=True, related_name='users', verbose_name='角色') + permissions = JSONField(default=list, verbose_name='权限') is_manager = BooleanField(default=False, verbose_name='管理员状态') is_active = BooleanField(default=True, verbose_name='激活状态') create_time = DateTimeField(auto_now_add=True, verbose_name='创建时间') @@ -63,5 +65,5 @@ class User(Model): __all__ = [ - 'Team', 'PermissionType', 'Permission', 'Role', 'User', + 'Team', 'PermissionGroup', 'Permission', 'Role', 'User', ] diff --git a/apps/system/schemas.py b/apps/system/schemas.py index 3a3ab78..830abb6 100644 --- a/apps/system/schemas.py +++ b/apps/system/schemas.py @@ -24,10 +24,7 @@ class UserInfoResponse(Serializer): username = CharField(label='用户名') name = CharField(label='名称') is_manager = BooleanField(label='管理员状态') - permissions = SerializerMethodField(label='权限') - - def get_permissions(self, instance): - return instance.roles.all().values_list('permissions__code', flat=True) + permissions = JSONField(label='权限') class SetPasswordRequest(Serializer): diff --git a/apps/system/serializers.py b/apps/system/serializers.py index f4e8e41..997fdf2 100644 --- a/apps/system/serializers.py +++ b/apps/system/serializers.py @@ -4,18 +4,18 @@ from extensions.exceptions import * from apps.system.models import * -class PermissionSerializer(BaseSerializer): +class PermissionGroupSerializer(BaseSerializer): - class Meta: - model = Permission - fields = ['id', 'name', 'code'] + class PermissionSerializer(BaseSerializer): + class Meta: + model = Permission + fields = ['id', 'name', 'code'] -class PermissionTypeSerializer(BaseSerializer): permission_items = PermissionSerializer(source='permissions', many=True, label='权限') class Meta: - model = PermissionType + model = PermissionGroup fields = ['id', 'name', 'permission_items'] @@ -28,11 +28,19 @@ class RoleSerializer(BaseSerializer): class UserSerializer(BaseSerializer): + + class UserRoleSerializer(BaseSerializer): + + class Meta: + model = Role + fields = ['id', 'name'] + sex_display = CharField(source='get_sex_display', read_only=True, label='性别') + role_items = UserRoleSerializer(source='roles', many=True, read_only=True, label='角色Item') class Meta: model = User - read_only_fields = ['id', 'sex_display', 'is_manager', 'create_time'] + read_only_fields = ['id', 'sex_display', 'is_manager', 'role_items', 'permissions', 'create_time'] fields = ['username', 'name', 'phone', 'email', 'sex', 'roles', 'is_active', *read_only_fields] def validate_username(self, value): @@ -47,7 +55,17 @@ class UserSerializer(BaseSerializer): validated_data['password'] = make_password(self.team.number) return super().create(validated_data) + def save(self, **kwargs): + permissions = [] + if roles := self.validated_data.get('roles'): + permissions = {permission.code for role in roles.prefetch_related('permissions').all() + for permission in role.permissions.all()} + + kwargs['permissions'] = list(permissions) + return super().save(**kwargs) + __all__ = [ - 'PermissionSerializer', 'PermissionTypeSerializer', 'RoleSerializer', 'UserSerializer', + 'PermissionGroupSerializer', + 'RoleSerializer', 'UserSerializer', ] diff --git a/apps/system/views.py b/apps/system/views.py index 40baefd..08c9007 100644 --- a/apps/system/views.py +++ b/apps/system/views.py @@ -26,7 +26,7 @@ class RoleViewSet(BaseViewSet, ReadWriteMixin): """角色""" serializer_class = RoleSerializer - permission_classes = [IsManagerPermission] + permission_classes = [IsAuthenticated, IsManagerPermission] search_fields = ['name', 'remark'] queryset = Role.objects.all() @@ -35,7 +35,7 @@ class UserViewSet(BaseViewSet, ReadWriteMixin): """用户""" serializer_class = UserSerializer - permission_classes = [IsManagerPermission] + permission_classes = [IsAuthenticated, IsManagerPermission] filterset_fields = ['sex', 'is_active'] search_fields = ['username', 'name', 'phone', 'email'] ordering_fields = ['id', 'username', 'name'] @@ -62,7 +62,7 @@ class UserViewSet(BaseViewSet, ReadWriteMixin): return Response(status=status.HTTP_200_OK) -class UserActionViewSet(ActionViewSet): +class UserActionViewSet(FunctionViewSet): """用户操作""" @extend_schema(request=GetTokenRequest, responses={200: GetTokenResponse}) diff --git a/extensions/__init__.py b/extensions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/extensions/authentications.py b/extensions/authentications.py index 86aedad..a900f4d 100644 --- a/extensions/authentications.py +++ b/extensions/authentications.py @@ -8,8 +8,8 @@ from django.conf import settings class BaseAuthentication(JWTAuthentication): def authenticate(self, request): - if settings.DEBUG: - return User.objects.all().first(), {} + # if settings.DEBUG: + # return User.objects.all().first(), {} if (header := self.get_header(request)) is None: return None diff --git a/extensions/common/__init__.py b/extensions/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/extensions/common/base.py b/extensions/common/base.py new file mode 100644 index 0000000..2651ab2 --- /dev/null +++ b/extensions/common/base.py @@ -0,0 +1,10 @@ +from django.db import transaction +from django.conf import settings +from number_precision import NP +import pendulum +import re + + +__all__ = [ + 'transaction', 'settings', 'NP', 'pendulum', 're', +] diff --git a/extensions/common/schema.py b/extensions/common/schema.py new file mode 100644 index 0000000..5366ca1 --- /dev/null +++ b/extensions/common/schema.py @@ -0,0 +1,11 @@ +from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiResponse +from drf_spectacular.types import OpenApiTypes +from rest_framework.response import Response +from rest_framework.decorators import action +from rest_framework import status + + +__all__ = [ + 'extend_schema', 'OpenApiParameter', 'OpenApiResponse', 'OpenApiTypes', + 'Response', 'action', 'status', +] diff --git a/extensions/paginations.py b/extensions/paginations.py index 37b1fb1..6dea10e 100644 --- a/extensions/paginations.py +++ b/extensions/paginations.py @@ -3,16 +3,18 @@ from rest_framework.pagination import PageNumberPagination class BasePagination(PageNumberPagination): invalid_page_message = '无效页面' - page_size_query_param = 'page_size' - max_page_size = 64 + + +class LimitedPagination(BasePagination): + max_page_size = 16 page_size = 16 -class OptionPagination(BasePagination): +class InfinitePagination(BasePagination): max_page_size = 999999 - page_size = 16 + page_size = 999999 __all__ = [ - 'BasePagination', 'OptionPagination', + 'LimitedPagination', 'InfinitePagination', ] diff --git a/extensions/permissions.py b/extensions/permissions.py index d0ed6b0..3b1be71 100644 --- a/extensions/permissions.py +++ b/extensions/permissions.py @@ -1,6 +1,6 @@ from extensions.exceptions import NotAuthenticated, ValidationError from rest_framework.permissions import BasePermission -from apps.system.models import Permission, User +from apps.system.models import User import pendulum @@ -19,24 +19,51 @@ class IsAuthenticated(BasePermission): return True -class IsManagerPermission(IsAuthenticated): +class IsManagerPermission(BasePermission): def has_permission(self, request, view): - return super().has_permission(request, view) and request.user.is_manager + return request.user.is_manager -class InterfacePermission(BasePermission): +class ModelPermission(BasePermission): def has_permission(self, request, view): if request.user.is_manager: return True - roles = request.user.roles.all() - if Permission.objects.filter(roles__in=roles, code=self.code).exists(): + if self.code in request.user.permissions: + return True + + return False + + +class FunctionPermission(BasePermission): + """功能权限""" + + def has_permission(self, request, view): + if request.user.is_manager: + return True + + if self.code in request.user.permissions: + return True + + return False + + +class DataPermission: + """数据权限""" + + @classmethod + def has_permission(cls, request): + if request.user.is_manager: + return True + + if cls.code in request.user.permissions: return True return False __all__ = [ - 'BasePermission', 'IsAuthenticated', 'IsManagerPermission', 'InterfacePermission', + 'IsAuthenticated', 'IsManagerPermission', + 'ModelPermission', 'FunctionPermission', 'DataPermission', ] diff --git a/extensions/serializers.py b/extensions/serializers.py index 687cb86..0e7224c 100644 --- a/extensions/serializers.py +++ b/extensions/serializers.py @@ -1,13 +1,8 @@ -from rest_framework.fields import IntegerField, FloatField, DecimalField, BooleanField, NullBooleanField from rest_framework.fields import SerializerMethodField, ImageField, FileField, JSONField +from rest_framework.fields import IntegerField, FloatField, DecimalField, BooleanField from rest_framework.fields import CharField, DateField, DateTimeField from rest_framework.serializers import Serializer, ModelSerializer -from django.db.models import Sum, Count, Value, F, Q from extensions.exceptions import ValidationError -from django.db.models.functions import Coalesce -from django.db import transaction -from number_precision import NP -import pendulum class BaseSerializer(ModelSerializer): @@ -57,16 +52,15 @@ class AmountField(DecimalField): def __init__(self, coerce_to_string=None, max_value=None, min_value=None, localize=False, rounding=None, **kwargs): + kwargs['max_digits'], kwargs['decimal_places'] = 16, 2 - super().__init__(coerce_to_string=None, max_value=None, min_value=None, localize=False, - rounding=None, **kwargs) + super().__init__(coerce_to_string=coerce_to_string, max_value=max_value, min_value=min_value, + localize=localize, rounding=rounding, **kwargs) __all__ = [ 'Serializer', 'ModelSerializer', 'BaseSerializer', 'SerializerMethodField', 'ImageField', 'FileField', 'JSONField', - 'BooleanField', 'NullBooleanField', 'IntegerField', 'FloatField', 'DecimalField', 'AmountField', + 'BooleanField', 'IntegerField', 'FloatField', 'AmountField', 'CharField', 'DateField', 'DateTimeField', - 'transaction', 'pendulum', 'NP', - 'Sum', 'Count', 'Value', 'F', 'Q', 'Coalesce', ] diff --git a/extensions/viewsets.py b/extensions/viewsets.py index 2708c0e..757b159 100644 --- a/extensions/viewsets.py +++ b/extensions/viewsets.py @@ -1,27 +1,17 @@ -from rest_framework.mixins import ListModelMixin, RetrieveModelMixin, CreateModelMixin, UpdateModelMixin, DestroyModelMixin -from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiResponse -from extensions.paginations import BasePagination, OptionPagination +from rest_framework.mixins import ListModelMixin, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin +from extensions.paginations import LimitedPagination, InfinitePagination from rest_framework.filters import SearchFilter, OrderingFilter -from django.db.models import Sum, Count, Value, F, Q, Prefetch from django_filters.rest_framework import DjangoFilterBackend +from django.db.models.deletion import ProtectedError from rest_framework.viewsets import GenericViewSet from extensions.exceptions import ValidationError -from django.db.models.functions import Coalesce -from drf_spectacular.types import OpenApiTypes from django.http.response import HttpResponse -from rest_framework.response import Response -from rest_framework.decorators import action from openpyxl import Workbook, load_workbook from rest_framework.viewsets import ViewSet -from django.db import transaction -from rest_framework import status -from django.conf import settings -from number_precision import NP -import pendulum -import re -class ActionViewSet(ViewSet): +class FunctionViewSet(ViewSet): + """功能视图""" @property def team(self): @@ -31,9 +21,13 @@ class ActionViewSet(ViewSet): def user(self): return self.request.user + @property + def context(self): + return {'request': self.request, 'format': self.format_kwarg, 'view': self} + class BaseViewSet(GenericViewSet): - pagination_class = BasePagination + pagination_class = LimitedPagination filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter] ordering_fields = ['id'] ordering = ['-id'] @@ -59,18 +53,36 @@ class BaseViewSet(GenericViewSet): return queryset -class OptionViewSet(BaseViewSet, ListModelMixin): - """选项""" +class ModelViewSet(BaseViewSet, ListModelMixin, CreateModelMixin, + RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin): + """模型视图""" - pagination_class = OptionPagination + +class PersonalViewSet(BaseViewSet): + """个人试图""" + + def get_queryset(self): + return super().get_queryset().filter(creator=self.user) + + +class OptionViewSet(BaseViewSet, ListModelMixin): + """选项视图""" + + pagination_class = InfinitePagination class ReadOnlyMixin(RetrieveModelMixin, ListModelMixin): """只读""" -class ReadWriteMixin(ListModelMixin, RetrieveModelMixin, CreateModelMixin, UpdateModelMixin, DestroyModelMixin): - """读写""" +class DataProtectMixin: + """数据保护""" + + def perform_destroy(self, instance): + try: + instance.delete() + except ProtectedError: + raise ValidationError('已被数据引用, 无法删除') class ExportMixin: @@ -169,10 +181,7 @@ class ImportMixin: __all__ = [ - 'GenericViewSet', 'ViewSet', 'ActionViewSet', 'BaseViewSet', 'OptionViewSet', - 'ListModelMixin', 'RetrieveModelMixin', 'CreateModelMixin', 'UpdateModelMixin', 'DestroyModelMixin', - 'ReadOnlyMixin', 'ReadWriteMixin', 'ExportMixin', 'ImportMixin', - 'action', 'transaction', 'status', 'Response', 'pendulum', 'NP', 'settings', 're', - 'Sum', 'Count', 'Value', 'F', 'Q', 'Prefetch', 'Coalesce', - 'extend_schema', 'OpenApiParameter', 'OpenApiResponse', 'OpenApiTypes', + 'FunctionViewSet', 'BaseViewSet', 'ModelViewSet', 'PersonalViewSet', 'OptionViewSet', + 'ReadOnlyMixin', 'DataProtectMixin', 'ExportMixin', 'ImportMixin', + 'ListModelMixin', 'CreateModelMixin', 'RetrieveModelMixin', 'UpdateModelMixin', 'DestroyModelMixin', ]