refactor(orgs): 重构组织与用户关系接口

pull/4451/head
xinwen 2020-08-03 16:17:46 +08:00 committed by 老广
parent f0d564180c
commit c3c5801d2e
11 changed files with 100 additions and 57 deletions

View File

@ -1,7 +1,6 @@
# coding:utf-8
from django.urls import path, re_path
from rest_framework_nested import routers
# from rest_framework.routers import DefaultRouter
from rest_framework_bulk.routes import BulkRouter
from common import api as capi

View File

@ -12,11 +12,12 @@
import uuid
from django.db.models import *
from django.db.models.functions import Concat
from django.utils.translation import ugettext_lazy as _
class Choice(str):
def __new__(cls, value, label):
def __new__(cls, value, label=''): # `deepcopy` 的时候不会传 `label`
self = super().__new__(cls, value)
self.label = label
return self
@ -77,3 +78,7 @@ class JMSModel(JMSBaseModel):
class Meta:
abstract = True
def concated_display(name1, name2):
return Concat(F(name1), Value('('), F(name2), Value(')'))

View File

@ -2,7 +2,8 @@ from rest_framework.viewsets import GenericViewSet, ModelViewSet
from rest_framework_bulk import BulkModelViewSet
from ..mixins.api import (
SerializerMixin2, QuerySetMixin, ExtraFilterFieldsMixin, PaginatedResponseMixin
SerializerMixin2, QuerySetMixin, ExtraFilterFieldsMixin, PaginatedResponseMixin,
RelationMixin, AllowBulkDestoryMixin
)
@ -26,5 +27,16 @@ class JMSBulkModelViewSet(SerializerMixin2,
QuerySetMixin,
ExtraFilterFieldsMixin,
PaginatedResponseMixin,
AllowBulkDestoryMixin,
BulkModelViewSet):
pass
class JMSBulkRelationModelViewSet(SerializerMixin2,
QuerySetMixin,
ExtraFilterFieldsMixin,
PaginatedResponseMixin,
RelationMixin,
AllowBulkDestoryMixin,
BulkModelViewSet):
pass

View File

@ -11,6 +11,8 @@ from django.core.cache import cache
from django.http import JsonResponse
from rest_framework.response import Response
from rest_framework.settings import api_settings
from rest_framework import status
from rest_framework_bulk.drf3.mixins import BulkDestroyModelMixin
from common.drf.filters import IDSpmFilter, CustomFilter, IDInFilter
from ..utils import lazyproperty
@ -223,10 +225,11 @@ class RelationMixin:
self.through = getattr(self.m2m_field.model, self.m2m_field.attname).through
def get_queryset(self):
# 注意,此处拦截了 `get_queryset` 没有 `super`
queryset = self.through.objects.all()
return queryset
def send_post_add_signal(self, instances):
def send_m2m_changed_signal(self, instances, action):
if not isinstance(instances, list):
instances = [instances]
@ -239,13 +242,17 @@ class RelationMixin:
for from_obj, to_ids in from_to_mapper.items():
m2m_changed.send(
sender=self.through, instance=from_obj, action='post_add',
sender=self.through, instance=from_obj, action=action,
reverse=False, model=self.to_model, pk_set=to_ids
)
def perform_create(self, serializer):
instance = serializer.save()
self.send_post_add_signal(instance)
self.send_m2m_changed_signal(instance, 'post_add')
def perform_destroy(self, instance):
instance.delete()
self.send_m2m_changed_signal(instance, 'post_remove')
class SerializerMixin2:
@ -275,3 +282,12 @@ class QuerySetMixin:
queryset = serializer_class.setup_eager_loading(queryset)
return queryset
class AllowBulkDestoryMixin:
def allow_bulk_destroy(self, qs, filtered):
"""
我们规定批量删除的情况必须用 `id` 指定要删除的数据
"""
query = str(filtered.query)
return '`id` IN (' in query or '`id` =' in query

View File

@ -7,14 +7,18 @@ from rest_framework.views import Response
from rest_framework_bulk import BulkModelViewSet
from common.permissions import IsSuperUserOrAppUser
from common.drf.api import JMSBulkRelationModelViewSet
from .models import Organization, ROLE
from .serializers import OrgSerializer, OrgReadSerializer, \
OrgAllUserSerializer, OrgRetrieveSerializer
from .serializers import (
OrgSerializer, OrgReadSerializer,
OrgRetrieveSerializer, OrgMemberSerializer
)
from users.models import User, UserGroup
from assets.models import Asset, Domain, AdminUser, SystemUser, Label
from perms.models import AssetPermission
from orgs.utils import current_org
from common.utils import get_logger
from .filters import OrgMemberRelationFilterSet
logger = get_logger(__file__)
@ -61,15 +65,13 @@ class OrgViewSet(BulkModelViewSet):
return Response({'msg': True}, status=status.HTTP_200_OK)
class OrgAllUserListApi(generics.ListAPIView):
class OrgMemberRelationBulkViewSet(JMSBulkRelationModelViewSet):
permission_classes = (IsSuperUserOrAppUser,)
serializer_class = OrgAllUserSerializer
filter_fields = ("username", "name")
search_fields = filter_fields
m2m_field = Organization.members.field
serializer_class = OrgMemberSerializer
filterset_class = OrgMemberRelationFilterSet
def get_queryset(self):
pk = self.kwargs.get("pk")
users = User.objects.filter(
orgs=pk, m2m_org_members__role=ROLE.USER
).only(*self.serializer_class.Meta.only_fields)
return users
def perform_bulk_destroy(self, queryset):
objs = list(queryset.all().prefetch_related('user', 'org'))
queryset.delete()
self.send_m2m_changed_signal(objs, action='post_remove')

16
apps/orgs/filters.py Normal file
View File

@ -0,0 +1,16 @@
from django_filters.rest_framework import filterset
from django_filters.rest_framework import filters
from .models import OrganizationMember
class UUIDInFilter(filters.BaseInFilter, filters.UUIDFilter):
pass
class OrgMemberRelationFilterSet(filterset.FilterSet):
id = UUIDInFilter(field_name='id', lookup_expr='in')
class Meta:
model = OrganizationMember
fields = ('org_id', 'user_id', 'role', 'id')

View File

@ -11,8 +11,7 @@ from ..utils import get_current_org_id_for_serializer
__all__ = [
"OrgResourceSerializerMixin", "BulkOrgResourceSerializerMixin",
"BulkOrgResourceModelSerializer", "OrgMembershipSerializerMixin",
"OrgResourceModelSerializerMixin",
"BulkOrgResourceModelSerializer", "OrgResourceModelSerializerMixin",
]
@ -53,9 +52,3 @@ class BulkOrgResourceSerializerMixin(BulkSerializerMixin, OrgResourceSerializerM
class BulkOrgResourceModelSerializer(BulkOrgResourceSerializerMixin, serializers.ModelSerializer):
pass
class OrgMembershipSerializerMixin:
def run_validation(self, initial_data=None):
initial_data['organization'] = str(self.context['org'].id)
return super().run_validation(initial_data)

View File

@ -149,6 +149,13 @@ class Organization(models.Model):
m2m_org_members__user_id=user.id
).distinct()
@classmethod
def get_user_all_orgs(cls, user):
return [
*cls.objects.filter(members=user).distinct(),
cls.default()
]
@classmethod
def get_user_admin_orgs(cls, user):
if user.is_anonymous:
@ -161,7 +168,10 @@ class Organization(models.Model):
def get_user_user_orgs(cls, user):
if user.is_anonymous:
return cls.objects.none()
return cls.get_user_orgs_by_role(user, ROLE.USER)
return [
*cls.get_user_orgs_by_role(user, ROLE.USER),
cls.default()
]
@classmethod
def get_user_audit_orgs(cls, user):

View File

@ -1,11 +1,12 @@
from django.db.models import F
from rest_framework.serializers import ModelSerializer
from rest_framework import serializers
from users.models.user import User
from common.serializers import AdaptedBulkListSerializer
from common.drf.serializers import BulkModelSerializer
from common.db.models import concated_display as display
from .models import Organization, OrganizationMember
from .mixins.serializers import OrgMembershipSerializerMixin
class OrgSerializer(ModelSerializer):
@ -50,30 +51,20 @@ class OrgReadSerializer(OrgSerializer):
pass
class OrgMembershipAdminSerializer(OrgMembershipSerializerMixin, ModelSerializer):
class OrgMemberSerializer(BulkModelSerializer):
org_display = serializers.CharField()
user_display = serializers.CharField()
class Meta:
model = Organization.members.through
list_serializer_class = AdaptedBulkListSerializer
fields = '__all__'
fields = ('id', 'org', 'user', 'role', 'org_display', 'user_display')
class OrgMembershipUserSerializer(OrgMembershipSerializerMixin, ModelSerializer):
class Meta:
model = Organization.members.through
list_serializer_class = AdaptedBulkListSerializer
fields = '__all__'
class OrgAllUserSerializer(serializers.Serializer):
user = serializers.UUIDField(read_only=True, source='id')
user_display = serializers.SerializerMethodField()
class Meta:
only_fields = ['id', 'username', 'name']
@staticmethod
def get_user_display(obj):
return str(obj)
@classmethod
def setup_eager_loading(cls, queryset):
return queryset.annotate(
org_display=F('org__name'),
user_display=display('user__name', 'user__username')
).distinct()
class OrgRetrieveSerializer(OrgReadSerializer):

View File

@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
#
from django.urls import re_path, path
from django.urls import re_path
from rest_framework.routers import DefaultRouter
from rest_framework_bulk.routes import BulkRouter
from common import api as capi
from .. import api
@ -10,15 +11,13 @@ from .. import api
app_name = 'orgs'
router = DefaultRouter()
bulk_router = BulkRouter()
router.register(r'orgs', api.OrgViewSet, 'org')
bulk_router.register(r'org-memeber-relation', api.OrgMemberRelationBulkViewSet, 'org-memeber-relation')
old_version_urlpatterns = [
re_path('(?P<resource>org)/.*', capi.redirect_plural_name_api)
]
urlpatterns = [
path('<uuid:pk>/users/all/', api.OrgAllUserListApi.as_view(), name='org-all-users'),
]
urlpatterns += router.urls + old_version_urlpatterns
urlpatterns = router.urls + bulk_router.urls + old_version_urlpatterns

View File

@ -18,7 +18,7 @@ __all__ = [
'ChangeUserPasswordSerializer', 'ResetOTPSerializer',
'UserProfileSerializer', 'UserOrgSerializer',
'UserUpdatePasswordSerializer', 'UserUpdatePublicKeySerializer',
'UserRetrieveSerializer'
'UserRetrieveSerializer', 'MiniUserSerializer',
]