Your has a new ticket
diff --git a/apps/users/api/user.py b/apps/users/api/user.py index b1c039318..ae4550931 100644 --- a/apps/users/api/user.py +++ b/apps/users/api/user.py @@ -56,31 +56,25 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet): def perform_create(self, serializer): validated_data = serializer.validated_data - if isinstance(validated_data, list): - org_roles = [item.pop('org_role', None) for item in validated_data] - else: - org_roles = [validated_data.pop('org_role', None)] + # `org_roles` 先 `pop` + if isinstance(validated_data, list): + org_roles = [item.pop('org_roles', []) for item in validated_data] + else: + org_roles = [validated_data.pop('org_roles', [])] + + # 创建用户 users = serializer.save() if isinstance(users, User): users = [users] - if current_org and current_org.is_real(): - mapper = { - ORG_ROLE.USER: [], - ORG_ROLE.ADMIN: [], - ORG_ROLE.AUDITOR: [] - } - for user, role in zip(users, org_roles): - if role in mapper: - mapper[role].append(user) - else: - mapper[ORG_ROLE.USER].append(user) - OrganizationMember.objects.set_users_by_role( - current_org, users=mapper[ORG_ROLE.USER], - admins=mapper[ORG_ROLE.ADMIN], - auditors=mapper[ORG_ROLE.AUDITOR] - ) + # 只有真实存在的组织才真正关联用户 + if current_org and current_org.is_real(): + for user, roles in zip(users, org_roles): + if not roles: + # 当前组织创建的用户,至少是该组织的`User` + roles.append(ORG_ROLE.USER) + OrganizationMember.objects.set_user_roles(current_org, user, roles) self.send_created_signal(users) def get_permissions(self): @@ -101,6 +95,23 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet): self.check_object_permissions(self.request, obj) self.perform_destroy(obj) + def perform_update(self, serializer): + validated_data = serializer.validated_data + # `org_roles` 先 `pop` + if isinstance(validated_data, list): + org_roles = [item.pop('org_roles', None) for item in validated_data] + else: + org_roles = [validated_data.pop('org_roles', None)] + + users = serializer.save() + if isinstance(users, User): + users = [users] + if current_org and current_org.is_real(): + for user, roles in zip(users, org_roles): + if roles is not None: + # roles 是 `Node` 表明不需要更新 + OrganizationMember.objects.set_user_roles(current_org, user, roles) + def perform_bulk_update(self, serializer): # TODO: 需要测试 users_ids = [ diff --git a/apps/users/migrations/0029_auto_20200814_1650.py b/apps/users/migrations/0029_auto_20200814_1650.py new file mode 100644 index 000000000..c6cf3517b --- /dev/null +++ b/apps/users/migrations/0029_auto_20200814_1650.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.13 on 2020-08-14 08:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0028_auto_20200728_1805'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='role', + field=models.CharField(blank=True, choices=[('Admin', 'System administrator'), ('User', 'User'), ('Auditor', 'System auditor'), ('App', 'Application')], default='User', max_length=10, verbose_name='Role'), + ), + ] diff --git a/apps/users/migrations/0030_auto_20200819_2041.py b/apps/users/migrations/0030_auto_20200819_2041.py new file mode 100644 index 000000000..775d38ac5 --- /dev/null +++ b/apps/users/migrations/0030_auto_20200819_2041.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.13 on 2020-08-19 12:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0029_auto_20200814_1650'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='role', + field=models.CharField(blank=True, choices=[('Admin', 'System administrator'), ('Auditor', 'System auditor'), ('User', 'User'), ('App', 'Application')], default='User', max_length=10, verbose_name='Role'), + ), + ] diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 6bda75b0a..f65b9398e 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -18,6 +18,7 @@ from django.shortcuts import reverse from common.local import LOCAL_DYNAMIC_SETTINGS from orgs.utils import current_org +from orgs.models import OrganizationMember from common.utils import date_expired_default, get_logger, lazyproperty from common import fields from common.const import choices @@ -153,9 +154,9 @@ class AuthMixin: class RoleMixin: class ROLE(ChoiceSet): - ADMIN = choices.ADMIN, _('Super administrator') + ADMIN = choices.ADMIN, _('System administrator') + AUDITOR = choices.AUDITOR, _('System auditor') USER = choices.USER, _('User') - AUDITOR = choices.AUDITOR, _('Super auditor') APP = 'App', _('Application') role = ROLE.USER @@ -164,15 +165,15 @@ class RoleMixin: def role_display(self): return self.get_role_display() - @property - def org_role_display(self): + @lazyproperty + def org_roles(self): from orgs.models import ROLE as ORG_ROLE if not current_org.is_real(): if self.is_superuser: - return ORG_ROLE.ADMIN.label + return [ORG_ROLE.ADMIN] else: - return ORG_ROLE.USER.label + return [ORG_ROLE.USER] if hasattr(self, 'gc_m2m_org_members__role'): names = self.gc_m2m_org_members__role @@ -184,8 +185,24 @@ class RoleMixin: roles = set(self.m2m_org_members.filter( org_id=current_org.id ).values_list('role', flat=True)) + roles = list(roles) + roles.sort() + return roles - return ' | '.join([str(ORG_ROLE[role]) for role in roles if role in ORG_ROLE]) + @lazyproperty + def org_roles_label_list(self): + from orgs.models import ROLE as ORG_ROLE + return [str(ORG_ROLE[role]) for role in self.org_roles if role in ORG_ROLE] + + @lazyproperty + def org_role_display(self): + return ' | '.join(self.org_roles_label_list) + + @lazyproperty + def total_role_display(self): + roles = list({self.role_display, *self.org_roles_label_list}) + roles.sort() + return ' | '.join(roles) def current_org_roles(self): from orgs.models import OrganizationMember, ROLE as ORG_ROLE @@ -314,12 +331,7 @@ class RoleMixin: def remove(self): if not current_org.is_real(): return - if self.can_user_current_org: - current_org.users.remove(self) - if self.can_admin_current_org: - current_org.admins.remove(self) - if self.can_audit_current_org: - current_org.auditors.remove(self) + OrganizationMember.objects.remove_users(current_org, [self]) class TokenMixin: diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index 908978d75..d6968a2d8 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -7,7 +7,6 @@ from rest_framework import serializers from common.utils import validate_ssh_public_key from common.mixins import CommonBulkSerializerMixin -from common.serializers import AdaptedBulkListSerializer from common.permissions import CanUpdateDeleteUser from common.drf.fields import GroupConcatedPrimaryKeyRelatedField from orgs.models import ROLE as ORG_ROLE @@ -51,17 +50,12 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer): login_blocked = serializers.SerializerMethodField() can_update = serializers.SerializerMethodField() can_delete = serializers.SerializerMethodField() - org_role = serializers.ChoiceField( - label=_('Organization role name'), write_only=True, - allow_null=True, required=False, allow_blank=True, - choices=ORG_ROLE.choices - ) - total_role_display = serializers.SerializerMethodField(label=_('Total role name')) + org_roles = serializers.ListField(label=_('Organization role name'), allow_null=True, required=False, + child=serializers.ChoiceField(choices=ORG_ROLE.choices)) key_prefix_block = "_LOGIN_BLOCK_{}" class Meta: model = User - list_serializer_class = AdaptedBulkListSerializer # mini 是指能识别对象的最小单元 fields_mini = ['id', 'name', 'username'] # small 指的是 不需要计算的直接能从一张表中获取到的数据 @@ -75,7 +69,7 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer): ] fields = fields_small + [ 'groups', 'role', 'groups_display', 'role_display', - 'can_update', 'can_delete', 'login_blocked', 'org_role' + 'can_update', 'can_delete', 'login_blocked', 'org_roles' ] extra_kwargs = { @@ -92,6 +86,7 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer): 'source_display': {'label': _('Source name')}, 'org_role_display': {'label': _('Organization role name')}, 'role_display': {'label': _('Super role name')}, + 'total_role_display': {'label': _('Total role name')} } def __init__(self, *args, **kwargs): @@ -110,9 +105,6 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer): choices.pop(User.ROLE.AUDITOR, None) role._choices = choices - def get_total_role_display(self, instance): - return ' | '.join({str(instance.role_display), str(instance.org_role_display)}) - def validate_role(self, value): request = self.context.get('request') if not request.user.is_superuser and value != User.ROLE.USER: