From 64d093e67739d2a30351c69cc2c41e12e2bcbb45 Mon Sep 17 00:00:00 2001 From: xinwen Date: Wed, 19 Aug 2020 17:38:21 +0800 Subject: [PATCH] =?UTF-8?q?fix(users):=20=E7=94=A8=E6=88=B7=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E6=B7=BB=E5=8A=A0`org=5Froles`=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/zh/LC_MESSAGES/django.mo | Bin 56733 -> 56869 bytes apps/locale/zh/LC_MESSAGES/django.po | 112 ++++++++++-------- .../migrations/0008_auto_20200819_1732.py | 18 +++ apps/orgs/models.py | 78 +++++++++--- apps/users/api/user.py | 51 ++++---- apps/users/models/user.py | 18 ++- apps/users/serializers/user.py | 11 +- 7 files changed, 187 insertions(+), 101 deletions(-) create mode 100644 apps/orgs/migrations/0008_auto_20200819_1732.py diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index c5b96696ca0b9b37c9e1fca5948e2dbc149aef19..080d1a348e1567a19afadd49545a448086844351 100644 GIT binary patch delta 17081 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 CV+Ire delta 16963 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 QH+-;ncJd9uC$gmeAB=ATp8x;= diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index b0e758446..66fb19a2a 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-08-14 16:50+0800\n" +"POT-Creation-Date: 2020-08-19 17:34+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -29,7 +29,7 @@ msgstr "自定义" #: orgs/models.py:22 perms/models/base.py:48 settings/models.py:27 #: terminal/models.py:27 terminal/models.py:344 terminal/models.py:376 #: terminal/models.py:413 users/forms/profile.py:20 users/models/group.py:15 -#: users/models/user.py:489 users/templates/users/_select_user_modal.html:13 +#: users/models/user.py:495 users/templates/users/_select_user_modal.html:13 #: users/templates/users/user_asset_permission.html:37 #: users/templates/users/user_asset_permission.html:154 #: users/templates/users/user_database_app_permission.html:36 @@ -79,7 +79,7 @@ msgstr "数据库" #: assets/models/label.py:23 ops/models/adhoc.py:37 orgs/models.py:25 #: perms/models/base.py:56 settings/models.py:32 terminal/models.py:37 #: terminal/models.py:383 terminal/models.py:420 users/models/group.py:16 -#: users/models/user.py:522 users/templates/users/user_detail.html:115 +#: users/models/user.py:528 users/templates/users/user_detail.html:115 #: users/templates/users/user_granted_database_app.html:38 #: users/templates/users/user_granted_remote_app.html:37 #: users/templates/users/user_group_detail.html:62 @@ -146,8 +146,8 @@ msgstr "参数" #: assets/models/base.py:240 assets/models/cluster.py:28 #: assets/models/cmd_filter.py:26 assets/models/cmd_filter.py:60 #: assets/models/group.py:21 common/db/models.py:67 common/mixins/models.py:49 -#: orgs/models.py:23 orgs/models.py:326 perms/models/base.py:54 -#: users/models/user.py:530 users/serializers/group.py:35 +#: orgs/models.py:23 orgs/models.py:374 perms/models/base.py:54 +#: users/models/user.py:536 users/serializers/group.py:35 #: users/templates/users/user_detail.html:97 #: xpack/plugins/change_auth_plan/models.py:81 xpack/plugins/cloud/models.py:56 #: xpack/plugins/cloud/models.py:146 xpack/plugins/gathered_user/models.py:30 @@ -161,7 +161,7 @@ msgstr "创建者" #: assets/models/domain.py:23 assets/models/gathered_user.py:19 #: assets/models/group.py:22 assets/models/label.py:25 common/db/models.py:69 #: common/mixins/models.py:50 ops/models/adhoc.py:38 ops/models/command.py:27 -#: orgs/models.py:24 orgs/models.py:324 perms/models/base.py:55 +#: orgs/models.py:24 orgs/models.py:372 perms/models/base.py:55 #: users/models/group.py:18 users/templates/users/user_group_detail.html:58 #: xpack/plugins/cloud/models.py:59 xpack/plugins/cloud/models.py:149 msgid "Date created" @@ -354,7 +354,7 @@ msgstr "" #: audits/models.py:99 authentication/forms.py:11 #: authentication/templates/authentication/login.html:21 #: authentication/templates/authentication/xpack_login.html:101 -#: ops/models/adhoc.py:148 users/forms/profile.py:19 users/models/user.py:487 +#: ops/models/adhoc.py:148 users/forms/profile.py:19 users/models/user.py:493 #: users/templates/users/_select_user_modal.html:14 #: users/templates/users/user_detail.html:53 #: users/templates/users/user_list.html:15 @@ -395,7 +395,7 @@ msgstr "SSH公钥" #: assets/models/base.py:239 assets/models/gathered_user.py:20 #: common/db/models.py:70 common/mixins/models.py:51 ops/models/adhoc.py:39 -#: orgs/models.py:325 +#: orgs/models.py:373 msgid "Date updated" msgstr "更新日期" @@ -407,7 +407,7 @@ msgstr "带宽" msgid "Contact" msgstr "联系人" -#: assets/models/cluster.py:22 users/models/user.py:508 +#: assets/models/cluster.py:22 users/models/user.py:514 #: users/templates/users/user_detail.html:62 msgid "Phone" msgstr "手机" @@ -433,7 +433,7 @@ msgid "Default" msgstr "默认" #: assets/models/cluster.py:36 assets/models/label.py:14 -#: users/models/user.py:655 +#: users/models/user.py:661 msgid "System" msgstr "系统" @@ -547,7 +547,7 @@ msgstr "默认资产组" #: assets/models/label.py:15 audits/models.py:36 audits/models.py:56 #: audits/models.py:69 audits/serializers.py:77 authentication/models.py:46 -#: authentication/models.py:90 orgs/models.py:16 orgs/models.py:322 +#: authentication/models.py:90 orgs/models.py:370 #: perms/forms/asset_permission.py:83 perms/forms/database_app_permission.py:38 #: perms/forms/remote_app_permission.py:40 perms/models/base.py:49 #: templates/index.html:78 terminal/backends/command/models.py:18 @@ -555,8 +555,7 @@ msgstr "默认资产组" #: tickets/models/ticket.py:30 tickets/models/ticket.py:137 #: tickets/serializers/request_asset_perm.py:65 #: tickets/serializers/ticket.py:31 users/forms/group.py:15 -#: users/models/user.py:157 users/models/user.py:643 -#: users/serializers/group.py:20 +#: users/models/user.py:649 users/serializers/group.py:20 #: users/templates/users/user_asset_permission.html:38 #: users/templates/users/user_asset_permission.html:64 #: users/templates/users/user_database_app_permission.html:37 @@ -733,14 +732,14 @@ msgid "Backend" msgstr "后端" #: assets/serializers/asset_user.py:75 users/forms/profile.py:148 -#: users/models/user.py:519 users/templates/users/user_password_update.html:48 +#: users/models/user.py:525 users/templates/users/user_password_update.html:48 #: users/templates/users/user_profile.html:69 #: users/templates/users/user_profile_update.html:46 #: users/templates/users/user_pubkey_update.html:46 msgid "Public key" msgstr "SSH公钥" -#: assets/serializers/asset_user.py:79 users/models/user.py:516 +#: assets/serializers/asset_user.py:79 users/models/user.py:522 msgid "Private key" msgstr "ssh私钥" @@ -1025,7 +1024,7 @@ msgstr "Agent" #: audits/models.py:104 #: authentication/templates/authentication/_mfa_confirm_modal.html:14 #: authentication/templates/authentication/login_otp.html:6 -#: users/forms/profile.py:52 users/models/user.py:511 +#: users/forms/profile.py:52 users/models/user.py:517 #: users/serializers/user.py:240 users/templates/users/user_detail.html:77 #: users/templates/users/user_profile.html:87 msgid "MFA" @@ -1265,7 +1264,7 @@ msgid "Show" msgstr "显示" #: authentication/templates/authentication/_access_key_modal.html:66 -#: users/models/user.py:409 users/serializers/user.py:237 +#: users/models/user.py:415 users/serializers/user.py:237 #: users/templates/users/user_profile.html:94 #: users/templates/users/user_profile.html:163 #: users/templates/users/user_profile.html:166 @@ -1274,7 +1273,7 @@ msgid "Disable" msgstr "禁用" #: authentication/templates/authentication/_access_key_modal.html:67 -#: users/models/user.py:410 users/serializers/user.py:238 +#: users/models/user.py:416 users/serializers/user.py:238 #: users/templates/users/user_profile.html:92 #: users/templates/users/user_profile.html:170 msgid "Enable" @@ -1685,7 +1684,7 @@ msgid "The current organization cannot be deleted" msgstr "" #: orgs/mixins/models.py:56 orgs/mixins/serializers.py:25 orgs/models.py:40 -#: orgs/models.py:321 +#: orgs/models.py:369 msgid "Organization" msgstr "组织" @@ -1693,11 +1692,15 @@ msgstr "组织" msgid "Organization administrator" msgstr "组织管理员" +#: orgs/models.py:16 +msgid "Organization User" +msgstr "组织用户" + #: orgs/models.py:17 msgid "Organization auditor" msgstr "组织审计员" -#: orgs/models.py:323 users/forms/user.py:27 users/models/user.py:499 +#: orgs/models.py:371 users/forms/user.py:27 users/models/user.py:505 #: users/templates/users/_select_user_modal.html:15 #: users/templates/users/user_detail.html:73 #: users/templates/users/user_list.html:16 @@ -1722,7 +1725,7 @@ msgstr "提示:RDP 协议不支持单独控制上传或下载文件" #: perms/forms/asset_permission.py:86 perms/forms/database_app_permission.py:41 #: perms/forms/remote_app_permission.py:43 perms/models/base.py:50 #: templates/_nav.html:21 users/forms/user.py:168 users/models/group.py:31 -#: users/models/user.py:495 users/serializers/user.py:49 +#: users/models/user.py:501 users/serializers/user.py:48 #: users/templates/users/_select_user_modal.html:16 #: users/templates/users/user_asset_permission.html:39 #: users/templates/users/user_asset_permission.html:67 @@ -1789,7 +1792,7 @@ msgid "Asset permission" msgstr "资产授权" #: perms/models/base.py:53 tickets/serializers/request_asset_perm.py:31 -#: users/models/user.py:527 users/templates/users/user_detail.html:93 +#: users/models/user.py:533 users/templates/users/user_detail.html:93 #: users/templates/users/user_profile.html:120 msgid "Date expired" msgstr "失效日期" @@ -2537,36 +2540,37 @@ msgstr "结束日期" msgid "Args" msgstr "参数" -#: tickets/api/request_asset_perm.py:45 +#: tickets/api/request_asset_perm.py:46 #, python-format msgid "Ticket has %s" msgstr "工单已%s" -#: tickets/api/request_asset_perm.py:90 +#: tickets/api/request_asset_perm.py:91 msgid "Confirm assets first" msgstr "请先确认资产" -#: tickets/api/request_asset_perm.py:93 +#: tickets/api/request_asset_perm.py:94 msgid "Confirmed assets changed" msgstr "确认的资产变更了" -#: tickets/api/request_asset_perm.py:97 +#: tickets/api/request_asset_perm.py:98 msgid "Confirm system-user first" msgstr "请先确认系统用户" -#: tickets/api/request_asset_perm.py:101 +#: tickets/api/request_asset_perm.py:102 msgid "Confirmed system-user changed" msgstr "确认的系统用户变更了" -#: tickets/api/request_asset_perm.py:104 xpack/plugins/cloud/models.py:202 +#: tickets/api/request_asset_perm.py:105 tickets/api/request_asset_perm.py:112 +#: xpack/plugins/cloud/models.py:202 msgid "Succeed" msgstr "成功" -#: tickets/api/request_asset_perm.py:112 +#: tickets/api/request_asset_perm.py:120 msgid "From request ticket: {} {}" msgstr "来自工单申请: {} {}" -#: tickets/api/request_asset_perm.py:114 +#: tickets/api/request_asset_perm.py:122 msgid "{} request assets, approved by {}" msgstr "{} 申请资产,通过人 {}" @@ -2749,7 +2753,7 @@ msgstr "" " \n" " " -#: users/api/user.py:147 +#: users/api/user.py:158 msgid "Could not reset self otp, use profile reset instead" msgstr "不能在该页面重置多因子认证, 请去个人信息页面重置" @@ -2795,7 +2799,7 @@ msgstr "确认密码" msgid "Password does not match" msgstr "密码不一致" -#: users/forms/profile.py:89 users/models/user.py:491 +#: users/forms/profile.py:89 users/models/user.py:497 #: users/templates/users/user_detail.html:57 #: users/templates/users/user_profile.html:59 msgid "Email" @@ -2836,7 +2840,7 @@ msgstr "不能和原来的密钥相同" msgid "Not a valid ssh public key" msgstr "SSH密钥不合法" -#: users/forms/user.py:31 users/models/user.py:534 +#: users/forms/user.py:31 users/models/user.py:540 #: users/templates/users/user_detail.html:89 #: users/templates/users/user_list.html:18 #: users/templates/users/user_profile.html:102 @@ -2856,15 +2860,15 @@ msgstr "添加到用户组" msgid "* Your password does not meet the requirements" msgstr "* 您的密码不符合要求" -#: users/forms/user.py:124 users/serializers/user.py:37 +#: users/forms/user.py:124 users/serializers/user.py:36 msgid "Reset link will be generated and sent to the user" msgstr "生成重置密码链接,通过邮件发送给用户" -#: users/forms/user.py:125 users/serializers/user.py:38 +#: users/forms/user.py:125 users/serializers/user.py:37 msgid "Set password" msgstr "设置密码" -#: users/forms/user.py:132 users/serializers/user.py:45 +#: users/forms/user.py:132 users/serializers/user.py:44 #: xpack/plugins/change_auth_plan/models.py:61 #: xpack/plugins/change_auth_plan/serializers.py:30 msgid "Password strategy" @@ -2874,6 +2878,10 @@ msgstr "密码策略" msgid "System administrator" msgstr "系统管理员" +#: users/models/user.py:157 +msgid "System User" +msgstr "系统用户" + #: users/models/user.py:158 msgid "System auditor" msgstr "系统审计员" @@ -2882,67 +2890,67 @@ msgstr "系统审计员" msgid "Application" msgstr "应用程序" -#: users/models/user.py:411 users/templates/users/user_profile.html:90 +#: users/models/user.py:417 users/templates/users/user_profile.html:90 msgid "Force enable" msgstr "强制启用" -#: users/models/user.py:478 +#: users/models/user.py:484 msgid "Local" msgstr "数据库" -#: users/models/user.py:502 +#: users/models/user.py:508 msgid "Avatar" msgstr "头像" -#: users/models/user.py:505 users/templates/users/user_detail.html:68 +#: users/models/user.py:511 users/templates/users/user_detail.html:68 msgid "Wechat" msgstr "微信" -#: users/models/user.py:538 +#: users/models/user.py:544 msgid "Date password last updated" msgstr "最后更新密码日期" -#: users/models/user.py:651 +#: users/models/user.py:657 msgid "Administrator" msgstr "管理员" -#: users/models/user.py:654 +#: users/models/user.py:660 msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" -#: users/serializers/user.py:55 users/serializers/user.py:93 +#: users/serializers/user.py:53 users/serializers/user.py:88 msgid "Organization role name" msgstr "组织角色名称" -#: users/serializers/user.py:59 +#: users/serializers/user.py:55 msgid "Total role name" msgstr "汇总角色名称" -#: users/serializers/user.py:84 users/serializers/user.py:253 +#: users/serializers/user.py:79 users/serializers/user.py:253 msgid "Is first login" msgstr "首次登录" -#: users/serializers/user.py:85 +#: users/serializers/user.py:80 msgid "Is valid" msgstr "账户是否有效" -#: users/serializers/user.py:86 +#: users/serializers/user.py:81 msgid "Is expired" msgstr " 是否过期" -#: users/serializers/user.py:87 +#: users/serializers/user.py:82 msgid "Avatar url" msgstr "头像路径" -#: users/serializers/user.py:91 +#: users/serializers/user.py:86 msgid "Groups name" msgstr "用户组名" -#: users/serializers/user.py:92 +#: users/serializers/user.py:87 msgid "Source name" msgstr "用户来源名" -#: users/serializers/user.py:94 +#: users/serializers/user.py:89 msgid "Super role name" msgstr "超级角色名称" diff --git a/apps/orgs/migrations/0008_auto_20200819_1732.py b/apps/orgs/migrations/0008_auto_20200819_1732.py new file mode 100644 index 000000000..75a773626 --- /dev/null +++ b/apps/orgs/migrations/0008_auto_20200819_1732.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.13 on 2020-08-19 09:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('orgs', '0007_auto_20200728_1805'), + ] + + operations = [ + migrations.AlterField( + model_name='organizationmember', + name='role', + field=models.CharField(choices=[('Admin', 'Organization administrator'), ('User', 'Organization User'), ('Auditor', 'Organization auditor')], default='User', max_length=16, verbose_name='Role'), + ), + ] diff --git a/apps/orgs/models.py b/apps/orgs/models.py index c72d1ae82..1769787a4 100644 --- a/apps/orgs/models.py +++ b/apps/orgs/models.py @@ -13,7 +13,7 @@ from common.db.models import ChoiceSet class ROLE(ChoiceSet): ADMIN = choices.ADMIN, _('Organization administrator') - USER = choices.USER, _('User') + USER = choices.USER, _('Organization User') AUDITOR = choices.AUDITOR, _("Organization auditor") @@ -229,15 +229,29 @@ def _none2list(*args): return ([] if v is None else v for v in args) +class UserRoleMapper(dict): + def __init__(self, container=set): + super().__init__() + self.users = container() + self.admins = container() + self.auditors = container() + + self[ROLE.USER] = self.users + self[ROLE.ADMIN] = self.admins + self[ROLE.AUDITOR] = self.auditors + + class OrgMemeberManager(models.Manager): def remove_users_by_role(self, org, users=None, admins=None, auditors=None): + from users.models import User + if not any((users, admins, auditors)): return users, admins, auditors = _none2list(users, admins, auditors) send = partial(signals.m2m_changed.send, sender=self.model, instance=org, reverse=False, - model=Organization, pk_set=[*users, *admins, *auditors], using=self.db) + model=User, pk_set=[*users, *admins, *auditors], using=self.db) send(action="pre_remove") self.filter(org_id=org.id).filter( @@ -248,6 +262,8 @@ class OrgMemeberManager(models.Manager): send(action="post_remove") def add_users_by_role(self, org, users=None, admins=None, auditors=None): + from users.models import User + if not any((users, admins, auditors)): return users, admins, auditors = _none2list(users, admins, auditors) @@ -266,7 +282,7 @@ class OrgMemeberManager(models.Manager): oms_add.append(self.model(org_id=org.id, user_id=user, role=role)) send = partial(signals.m2m_changed.send, sender=self.model, instance=org, reverse=False, - model=Organization, pk_set=[*users, *admins, *auditors], using=self.db) + model=User, pk_set=[*users, *admins, *auditors], using=self.db) send(action='pre_add') self.bulk_create(oms_add) @@ -278,24 +294,56 @@ class OrgMemeberManager(models.Manager): new_users = _convert_to_uuid_set(new_users) return (old_users - new_users), (new_users - old_users) + def set_user_roles(self, org, user, roles): + """ + 设置某个用户在某个组织里的角色 + """ + old_roles = set(self.filter(org_id=org.id, user=user).values_list('role', flat=True)) + new_roles = set(roles) + + roles_remove = old_roles - new_roles + roles_add = new_roles - old_roles + + to_remove = UserRoleMapper() + to_add = UserRoleMapper() + + for role in roles_remove: + if role in to_remove: + to_remove[role].add(user) + for role in roles_add: + if role in to_add: + to_add[role].add(user) + + self.remove_users_by_role( + org, + to_remove.users, + to_remove.admins, + to_remove.auditors + ) + + self.add_users_by_role( + org, + to_add.users, + to_add.admins, + to_add.auditors + ) + def set_users_by_role(self, org, users=None, admins=None, auditors=None): + """ + 给组织设置带角色的用户 + """ + oms = self.filter(org_id=org.id).values_list('role', 'user_id') - old_users, old_admins, old_auditors = set(), set(), set() - - mapper = { - ROLE.USER: old_users, - ROLE.ADMIN: old_admins, - ROLE.AUDITOR: old_auditors - } + old_mapper = UserRoleMapper() for role, user_id in oms: - if role in mapper: - mapper[role].add(user_id) + if role in old_mapper: + old_mapper[role].add(user_id) - users_remove, users_add = self._get_remove_add_set(users, old_users) - admins_remove, admins_add = self._get_remove_add_set(admins, old_admins) - auditors_remove, auditors_add = self._get_remove_add_set(auditors, old_auditors) + users_remove, users_add = self._get_remove_add_set(users, old_mapper.users) + admins_remove, admins_add = self._get_remove_add_set(admins, old_mapper.admins) + auditors_remove, auditors_add = self._get_remove_add_set(auditors, old_mapper.auditors) self.remove_users_by_role( org, 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/models/user.py b/apps/users/models/user.py index b496f17bd..7a4dc2a4f 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -154,7 +154,7 @@ class AuthMixin: class RoleMixin: class ROLE(ChoiceSet): ADMIN = choices.ADMIN, _('System administrator') - USER = choices.USER, _('User') + USER = choices.USER, _('System User') AUDITOR = choices.AUDITOR, _('System auditor') APP = 'App', _('Application') @@ -164,15 +164,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 +184,14 @@ 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_role_display(self): + from orgs.models import ROLE as ORG_ROLE + return ' | '.join([str(ORG_ROLE[role]) for role in self.org_roles if role in ORG_ROLE]) def current_org_roles(self): from orgs.models import OrganizationMember, ROLE as ORG_ROLE diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index e246ba203..6d8caaf8a 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,13 @@ 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 - ) + org_roles = serializers.ListField(label=_('Organization role name'), allow_null=True, required=False, + child=serializers.ChoiceField(choices=ORG_ROLE.choices)) total_role_display = serializers.SerializerMethodField(label=_('Total role name')) key_prefix_block = "_LOGIN_BLOCK_{}" class Meta: model = User - list_serializer_class = AdaptedBulkListSerializer # mini 是指能识别对象的最小单元 fields_mini = ['id', 'name', 'username'] # small 指的是 不需要计算的直接能从一张表中获取到的数据 @@ -75,7 +70,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 = {