mirror of https://github.com/jumpserver/jumpserver
refactor(orgs): 重构组织表结构
parent
1bc913ab13
commit
de3865fa1d
|
@ -43,7 +43,7 @@ class UserLoginLogViewSet(ListModelMixin, CommonGenericViewSet):
|
|||
|
||||
@staticmethod
|
||||
def get_org_members():
|
||||
users = current_org.get_org_members().values_list('username', flat=True)
|
||||
users = current_org.get_members().values_list('username', flat=True)
|
||||
return users
|
||||
|
||||
def get_queryset(self):
|
||||
|
@ -79,7 +79,7 @@ class PasswordChangeLogViewSet(ListModelMixin, CommonGenericViewSet):
|
|||
ordering = ['-datetime']
|
||||
|
||||
def get_queryset(self):
|
||||
users = current_org.get_org_members()
|
||||
users = current_org.get_members()
|
||||
queryset = super().get_queryset().filter(
|
||||
user__in=[user.__str__() for user in users]
|
||||
)
|
||||
|
|
|
@ -20,7 +20,7 @@ class CurrentOrgMembersFilter(filters.BaseFilterBackend):
|
|||
]
|
||||
|
||||
def _get_user_list(self):
|
||||
users = current_org.get_org_members(exclude=('Auditor',))
|
||||
users = current_org.get_members(exclude=('Auditor',))
|
||||
return users
|
||||
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
|
|
|
@ -124,7 +124,7 @@ class UserLoginLog(models.Model):
|
|||
Q(username__contains=keyword)
|
||||
)
|
||||
if not current_org.is_root():
|
||||
username_list = current_org.get_org_members().values_list('username', flat=True)
|
||||
username_list = current_org.get_members().values_list('username', flat=True)
|
||||
login_logs = login_logs.filter(username__in=username_list)
|
||||
return login_logs
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.db.models import ChoiceSet
|
||||
|
||||
|
||||
ADMIN = 'Admin'
|
||||
USER = 'User'
|
||||
AUDITOR = 'Auditor'
|
|
@ -3,10 +3,10 @@ from django.db.models import Aggregate
|
|||
|
||||
class GroupConcat(Aggregate):
|
||||
function = 'GROUP_CONCAT'
|
||||
template = '%(function)s(%(distinct)s %(expressions)s %(order_by)s %(separator))'
|
||||
template = '%(function)s(%(expressions)s %(order_by)s %(separator)s)'
|
||||
allow_distinct = False
|
||||
|
||||
def __init__(self, expression, distinct=False, order_by=None, separator=',', **extra):
|
||||
def __init__(self, expression, order_by=None, separator=',', **extra):
|
||||
order_by_clause = ''
|
||||
if order_by is not None:
|
||||
order = 'ASC'
|
||||
|
@ -21,8 +21,7 @@ class GroupConcat(Aggregate):
|
|||
|
||||
super().__init__(
|
||||
expression,
|
||||
distinct='DISTINCT' if distinct else '',
|
||||
order_by=order_by_clause,
|
||||
separator=f'SEPARATOR {separator}',
|
||||
separator=f"SEPARATOR '{separator}'",
|
||||
**extra
|
||||
)
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
from functools import partial
|
||||
|
||||
|
||||
class Choice(str):
|
||||
def __new__(cls, value, label):
|
||||
self = super().__new__(cls, value)
|
||||
self.label = label
|
||||
return self
|
||||
|
||||
|
||||
class ChoiceSetType(type):
|
||||
def __new__(cls, name, bases, attrs):
|
||||
_choices = []
|
||||
collected = set()
|
||||
new_attrs = {}
|
||||
for k, v in attrs.items():
|
||||
if isinstance(v, tuple):
|
||||
v = Choice(*v)
|
||||
assert v not in collected, 'Cannot be defined repeatedly'
|
||||
_choices.append(v)
|
||||
collected.add(v)
|
||||
new_attrs[k] = v
|
||||
for base in bases:
|
||||
if hasattr(base, '_choices'):
|
||||
for c in base._choices:
|
||||
if c not in collected:
|
||||
_choices.append(c)
|
||||
collected.add(c)
|
||||
new_attrs['_choices'] = _choices
|
||||
new_attrs['_choices_dict'] = {c: c.label for c in _choices}
|
||||
return type.__new__(cls, name, bases, new_attrs)
|
||||
|
||||
def __contains__(self, item):
|
||||
return self._choices_dict.__contains__(item)
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self._choices_dict.__getitem__(item)
|
||||
|
||||
def get(self, item, default=None):
|
||||
return self._choices_dict.get(item, default=None)
|
||||
|
||||
@property
|
||||
def choices(self):
|
||||
return [(c, c.label) for c in self._choices]
|
||||
|
||||
|
||||
class ChoiceSet(metaclass=ChoiceSetType):
|
||||
choices = None # 用于 Django Model 中的 choices 配置, 为了代码提示在此声明
|
|
@ -0,0 +1,43 @@
|
|||
from uuid import UUID
|
||||
|
||||
from rest_framework.fields import get_attribute
|
||||
from rest_framework.relations import ManyRelatedField, PrimaryKeyRelatedField, MANY_RELATION_KWARGS
|
||||
|
||||
|
||||
class GroupConcatedManyRelatedField(ManyRelatedField):
|
||||
def get_attribute(self, instance):
|
||||
if hasattr(instance, 'pk') and instance.pk is None:
|
||||
return []
|
||||
|
||||
attr = self.source_attrs[-1]
|
||||
|
||||
# `gc` 是 `GroupConcat` 的缩写
|
||||
gc_attr = f'gc_{attr}'
|
||||
if hasattr(instance, gc_attr):
|
||||
gc_value = getattr(instance, gc_attr)
|
||||
if isinstance(gc_value, str):
|
||||
return [UUID(pk) for pk in set(gc_value.split(','))]
|
||||
else:
|
||||
return ''
|
||||
|
||||
relationship = get_attribute(instance, self.source_attrs)
|
||||
return relationship.all() if hasattr(relationship, 'all') else relationship
|
||||
|
||||
|
||||
class GroupConcatedPrimaryKeyRelatedField(PrimaryKeyRelatedField):
|
||||
@classmethod
|
||||
def many_init(cls, *args, **kwargs):
|
||||
list_kwargs = {'child_relation': cls(*args, **kwargs)}
|
||||
for key in kwargs:
|
||||
if key in MANY_RELATION_KWARGS:
|
||||
list_kwargs[key] = kwargs[key]
|
||||
return GroupConcatedManyRelatedField(**list_kwargs)
|
||||
|
||||
def to_representation(self, value):
|
||||
if self.pk_field is not None:
|
||||
return self.pk_field.to_representation(value.pk)
|
||||
|
||||
if hasattr(value, 'pk'):
|
||||
return value.pk
|
||||
else:
|
||||
return value
|
|
@ -128,7 +128,7 @@ class DatesLoginMetricMixin:
|
|||
|
||||
@lazyproperty
|
||||
def dates_total_count_inactive_users(self):
|
||||
total = current_org.get_org_members().count()
|
||||
total = current_org.get_members().count()
|
||||
active = self.dates_total_count_active_users
|
||||
count = total - active
|
||||
if count < 0:
|
||||
|
@ -137,7 +137,7 @@ class DatesLoginMetricMixin:
|
|||
|
||||
@lazyproperty
|
||||
def dates_total_count_disabled_users(self):
|
||||
return current_org.get_org_members().filter(is_active=False).count()
|
||||
return current_org.get_members().filter(is_active=False).count()
|
||||
|
||||
@lazyproperty
|
||||
def dates_total_count_active_assets(self):
|
||||
|
@ -207,7 +207,7 @@ class DatesLoginMetricMixin:
|
|||
class TotalCountMixin:
|
||||
@staticmethod
|
||||
def get_total_count_users():
|
||||
return current_org.get_org_members().count()
|
||||
return current_org.get_members().count()
|
||||
|
||||
@staticmethod
|
||||
def get_total_count_assets():
|
||||
|
|
Binary file not shown.
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: JumpServer 0.3.3\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-07-21 16:29+0800\n"
|
||||
"POT-Creation-Date: 2020-07-27 19:01+0800\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: ibuler <ibuler@qq.com>\n"
|
||||
"Language-Team: JumpServer team<ibuler@qq.com>\n"
|
||||
|
@ -25,10 +25,10 @@ msgstr "自定义"
|
|||
#: assets/models/asset.py:145 assets/models/base.py:232
|
||||
#: assets/models/cluster.py:18 assets/models/cmd_filter.py:21
|
||||
#: assets/models/domain.py:20 assets/models/group.py:20
|
||||
#: assets/models/label.py:18 ops/mixin.py:24 orgs/models.py:12
|
||||
#: assets/models/label.py:18 ops/mixin.py:24 orgs/models.py:22
|
||||
#: perms/models/base.py:48 settings/models.py:27 terminal/models.py:26
|
||||
#: terminal/models.py:342 terminal/models.py:374 terminal/models.py:411
|
||||
#: users/forms/profile.py:20 users/models/group.py:15 users/models/user.py:467
|
||||
#: users/forms/profile.py:20 users/models/group.py:15 users/models/user.py:473
|
||||
#: users/templates/users/_select_user_modal.html:13
|
||||
#: users/templates/users/user_asset_permission.html:37
|
||||
#: users/templates/users/user_asset_permission.html:154
|
||||
|
@ -75,9 +75,9 @@ msgstr "数据库"
|
|||
#: assets/models/cmd_filter.py:23 assets/models/cmd_filter.py:57
|
||||
#: assets/models/domain.py:21 assets/models/domain.py:54
|
||||
#: assets/models/group.py:23 assets/models/label.py:23 ops/models/adhoc.py:37
|
||||
#: orgs/models.py:18 perms/models/base.py:56 settings/models.py:32
|
||||
#: orgs/models.py:25 perms/models/base.py:56 settings/models.py:32
|
||||
#: terminal/models.py:36 terminal/models.py:381 terminal/models.py:418
|
||||
#: users/models/group.py:16 users/models/user.py:500
|
||||
#: users/models/group.py:16 users/models/user.py:506
|
||||
#: 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
|
||||
|
@ -131,8 +131,8 @@ msgstr "参数"
|
|||
#: applications/models/remote_app.py:39 assets/models/asset.py:224
|
||||
#: 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/mixins/models.py:49 orgs/models.py:16
|
||||
#: perms/models/base.py:54 users/models/user.py:508
|
||||
#: assets/models/group.py:21 common/mixins/models.py:49 orgs/models.py:23
|
||||
#: orgs/models.py:316 perms/models/base.py:54 users/models/user.py:514
|
||||
#: 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
|
||||
|
@ -146,8 +146,8 @@ msgstr "创建者"
|
|||
#: assets/models/domain.py:23 assets/models/gathered_user.py:19
|
||||
#: assets/models/group.py:22 assets/models/label.py:25
|
||||
#: common/mixins/models.py:50 ops/models/adhoc.py:38 ops/models/command.py:27
|
||||
#: orgs/models.py:17 perms/models/base.py:55 users/models/group.py:18
|
||||
#: users/templates/users/user_group_detail.html:58
|
||||
#: orgs/models.py:24 orgs/models.py:314 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"
|
||||
msgstr "创建日期"
|
||||
|
@ -336,10 +336,10 @@ msgid "AuthBook"
|
|||
msgstr ""
|
||||
|
||||
#: assets/models/base.py:233 assets/models/gathered_user.py:15
|
||||
#: audits/models.py:99 authentication/forms.py:10
|
||||
#: audits/models.py:99 authentication/forms.py:11
|
||||
#: authentication/templates/authentication/login.html:21
|
||||
#: authentication/templates/authentication/xpack_login.html:93
|
||||
#: ops/models/adhoc.py:148 users/forms/profile.py:19 users/models/user.py:465
|
||||
#: authentication/templates/authentication/xpack_login.html:101
|
||||
#: ops/models/adhoc.py:148 users/forms/profile.py:19 users/models/user.py:471
|
||||
#: users/templates/users/_select_user_modal.html:14
|
||||
#: users/templates/users/user_detail.html:53
|
||||
#: users/templates/users/user_list.html:15
|
||||
|
@ -350,9 +350,9 @@ msgid "Username"
|
|||
msgstr "用户名"
|
||||
|
||||
#: assets/models/base.py:234 assets/serializers/asset_user.py:71
|
||||
#: authentication/forms.py:12
|
||||
#: authentication/forms.py:13
|
||||
#: authentication/templates/authentication/login.html:29
|
||||
#: authentication/templates/authentication/xpack_login.html:101
|
||||
#: authentication/templates/authentication/xpack_login.html:109
|
||||
#: users/forms/user.py:22 users/forms/user.py:193
|
||||
#: users/templates/users/user_otp_check_password.html:13
|
||||
#: users/templates/users/user_password_update.html:43
|
||||
|
@ -379,7 +379,7 @@ msgid "SSH public key"
|
|||
msgstr "SSH公钥"
|
||||
|
||||
#: assets/models/base.py:239 assets/models/gathered_user.py:20
|
||||
#: common/mixins/models.py:51 ops/models/adhoc.py:39
|
||||
#: common/mixins/models.py:51 ops/models/adhoc.py:39 orgs/models.py:315
|
||||
msgid "Date updated"
|
||||
msgstr "更新日期"
|
||||
|
||||
|
@ -391,7 +391,7 @@ msgstr "带宽"
|
|||
msgid "Contact"
|
||||
msgstr "联系人"
|
||||
|
||||
#: assets/models/cluster.py:22 users/models/user.py:486
|
||||
#: assets/models/cluster.py:22 users/models/user.py:492
|
||||
#: users/templates/users/user_detail.html:62
|
||||
msgid "Phone"
|
||||
msgstr "手机"
|
||||
|
@ -417,7 +417,7 @@ msgid "Default"
|
|||
msgstr "默认"
|
||||
|
||||
#: assets/models/cluster.py:36 assets/models/label.py:14
|
||||
#: users/models/user.py:627
|
||||
#: users/models/user.py:635
|
||||
msgid "System"
|
||||
msgstr "系统"
|
||||
|
||||
|
@ -535,14 +535,15 @@ 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:43
|
||||
#: perms/forms/asset_permission.py:83 perms/forms/database_app_permission.py:38
|
||||
#: orgs/models.py:16 orgs/models.py:312 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
|
||||
#: terminal/backends/command/serializers.py:12 terminal/models.py:185
|
||||
#: tickets/models/ticket.py:35 tickets/models/ticket.py:130
|
||||
#: tickets/serializers/request_asset_perm.py:55
|
||||
#: tickets/serializers/ticket.py:27 users/forms/group.py:15
|
||||
#: users/models/user.py:160 users/models/user.py:176 users/models/user.py:615
|
||||
#: users/models/user.py:157 users/models/user.py:623
|
||||
#: users/serializers/group.py:20
|
||||
#: users/templates/users/user_asset_permission.html:38
|
||||
#: users/templates/users/user_asset_permission.html:64
|
||||
|
@ -707,14 +708,14 @@ msgid "Backend"
|
|||
msgstr "后端"
|
||||
|
||||
#: assets/serializers/asset_user.py:75 users/forms/profile.py:148
|
||||
#: users/models/user.py:497 users/templates/users/user_password_update.html:48
|
||||
#: users/models/user.py:503 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:494
|
||||
#: assets/serializers/asset_user.py:79 users/models/user.py:500
|
||||
msgid "Private key"
|
||||
msgstr "ssh私钥"
|
||||
|
||||
|
@ -999,8 +1000,8 @@ 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:489
|
||||
#: users/serializers/user.py:220 users/templates/users/user_detail.html:77
|
||||
#: users/forms/profile.py:52 users/models/user.py:495
|
||||
#: users/serializers/user.py:224 users/templates/users/user_detail.html:77
|
||||
#: users/templates/users/user_profile.html:87
|
||||
msgid "MFA"
|
||||
msgstr "多因子认证"
|
||||
|
@ -1169,7 +1170,10 @@ msgstr "等待登录复核处理"
|
|||
msgid "Login confirm ticket was {}"
|
||||
msgstr "登录复核 {}"
|
||||
|
||||
#: authentication/forms.py:29 users/forms/user.py:199
|
||||
#: authentication/forms.py:26 authentication/forms.py:34
|
||||
#: authentication/templates/authentication/login.html:38
|
||||
#: authentication/templates/authentication/xpack_login.html:118
|
||||
#: users/forms/user.py:199
|
||||
msgid "MFA code"
|
||||
msgstr "多因子认证验证码"
|
||||
|
||||
|
@ -1224,7 +1228,7 @@ msgid "Show"
|
|||
msgstr "显示"
|
||||
|
||||
#: authentication/templates/authentication/_access_key_modal.html:66
|
||||
#: users/models/user.py:387 users/serializers/user.py:217
|
||||
#: users/models/user.py:393 users/serializers/user.py:221
|
||||
#: users/templates/users/user_profile.html:94
|
||||
#: users/templates/users/user_profile.html:163
|
||||
#: users/templates/users/user_profile.html:166
|
||||
|
@ -1233,7 +1237,7 @@ msgid "Disable"
|
|||
msgstr "禁用"
|
||||
|
||||
#: authentication/templates/authentication/_access_key_modal.html:67
|
||||
#: users/models/user.py:388 users/serializers/user.py:218
|
||||
#: users/models/user.py:394 users/serializers/user.py:222
|
||||
#: users/templates/users/user_profile.html:92
|
||||
#: users/templates/users/user_profile.html:170
|
||||
msgid "Enable"
|
||||
|
@ -1274,29 +1278,29 @@ msgid "Code error"
|
|||
msgstr "代码错误"
|
||||
|
||||
#: authentication/templates/authentication/login.html:6
|
||||
#: authentication/templates/authentication/login.html:39
|
||||
#: authentication/templates/authentication/xpack_login.html:112
|
||||
#: authentication/templates/authentication/login.html:49
|
||||
#: authentication/templates/authentication/xpack_login.html:130
|
||||
#: templates/_base_only_msg_content.html:51 templates/_header_bar.html:83
|
||||
msgid "Login"
|
||||
msgstr "登录"
|
||||
|
||||
#: authentication/templates/authentication/login.html:17
|
||||
#: authentication/templates/authentication/xpack_login.html:87
|
||||
#: authentication/templates/authentication/xpack_login.html:95
|
||||
msgid "Captcha invalid"
|
||||
msgstr "验证码错误"
|
||||
|
||||
#: authentication/templates/authentication/login.html:50
|
||||
#: authentication/templates/authentication/xpack_login.html:116
|
||||
#: authentication/templates/authentication/login.html:60
|
||||
#: authentication/templates/authentication/xpack_login.html:134
|
||||
#: users/templates/users/forgot_password.html:7
|
||||
#: users/templates/users/forgot_password.html:8
|
||||
msgid "Forgot password"
|
||||
msgstr "忘记密码"
|
||||
|
||||
#: authentication/templates/authentication/login.html:57
|
||||
#: authentication/templates/authentication/login.html:67
|
||||
msgid "More login options"
|
||||
msgstr "更多登录方式"
|
||||
|
||||
#: authentication/templates/authentication/login.html:61
|
||||
#: authentication/templates/authentication/login.html:71
|
||||
msgid "OpenID"
|
||||
msgstr "OpenID"
|
||||
|
||||
|
@ -1337,11 +1341,11 @@ msgstr "返回"
|
|||
msgid "Copy success"
|
||||
msgstr "复制成功"
|
||||
|
||||
#: authentication/templates/authentication/xpack_login.html:74
|
||||
#: authentication/templates/authentication/xpack_login.html:78
|
||||
msgid "Welcome back, please enter username and password to login"
|
||||
msgstr "欢迎回来,请输入用户名和密码登录"
|
||||
|
||||
#: authentication/views/login.py:83
|
||||
#: authentication/views/login.py:82
|
||||
msgid "Please enable cookies and try again."
|
||||
msgstr "设置你的浏览器支持cookie"
|
||||
|
||||
|
@ -1602,11 +1606,11 @@ msgstr "命令 `{}` 不允许被执行 ......."
|
|||
msgid "Task end"
|
||||
msgstr "任务结束"
|
||||
|
||||
#: ops/tasks.py:68
|
||||
#: ops/tasks.py:71
|
||||
msgid "Clean task history period"
|
||||
msgstr "定期清除任务历史"
|
||||
|
||||
#: ops/tasks.py:81
|
||||
#: ops/tasks.py:84
|
||||
msgid "Clean celery log period"
|
||||
msgstr "定期清除Celery日志"
|
||||
|
||||
|
@ -1622,18 +1626,35 @@ msgstr "更新任务内容: {}"
|
|||
msgid "Disk used more than 80%: {} => {}"
|
||||
msgstr "磁盘使用率超过 80%: {} => {}"
|
||||
|
||||
#: orgs/api.py:57
|
||||
#: orgs/api.py:54
|
||||
msgid "Organization contains undeleted resources"
|
||||
msgstr "组织内包含未删除的资源"
|
||||
|
||||
#: orgs/api.py:61
|
||||
#: orgs/api.py:58
|
||||
msgid "The current organization cannot be deleted"
|
||||
msgstr "当能删除当前所在组织"
|
||||
|
||||
#: orgs/mixins/models.py:56 orgs/mixins/serializers.py:26 orgs/models.py:31
|
||||
#: orgs/mixins/models.py:56 orgs/mixins/serializers.py:26 orgs/models.py:40
|
||||
#: orgs/models.py:311
|
||||
msgid "Organization"
|
||||
msgstr "组织"
|
||||
|
||||
#: orgs/models.py:15
|
||||
msgid "Organization administrator"
|
||||
msgstr "组织管理员"
|
||||
|
||||
#: orgs/models.py:17
|
||||
msgid "Organization auditor"
|
||||
msgstr "组织审计员"
|
||||
|
||||
#: orgs/models.py:313 users/forms/user.py:27 users/models/user.py:483
|
||||
#: users/templates/users/_select_user_modal.html:15
|
||||
#: users/templates/users/user_detail.html:73
|
||||
#: users/templates/users/user_list.html:16
|
||||
#: users/templates/users/user_profile.html:55
|
||||
msgid "Role"
|
||||
msgstr "角色"
|
||||
|
||||
#: perms/const.py:7
|
||||
msgid "Ungrouped"
|
||||
msgstr "未分组"
|
||||
|
@ -1651,7 +1672,8 @@ 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:473 users/templates/users/_select_user_modal.html:16
|
||||
#: users/models/user.py:479 users/serializers/user.py:43
|
||||
#: users/templates/users/_select_user_modal.html:16
|
||||
#: users/templates/users/user_asset_permission.html:39
|
||||
#: users/templates/users/user_asset_permission.html:67
|
||||
#: users/templates/users/user_database_app_permission.html:38
|
||||
|
@ -1717,7 +1739,7 @@ msgid "Asset permission"
|
|||
msgstr "资产授权"
|
||||
|
||||
#: perms/models/base.py:53 tickets/serializers/request_asset_perm.py:20
|
||||
#: users/models/user.py:505 users/templates/users/user_detail.html:93
|
||||
#: users/models/user.py:511 users/templates/users/user_detail.html:93
|
||||
#: users/templates/users/user_profile.html:120
|
||||
msgid "Date expired"
|
||||
msgstr "失效日期"
|
||||
|
@ -2634,7 +2656,7 @@ msgstr ""
|
|||
" </div>\n"
|
||||
" "
|
||||
|
||||
#: users/api/user.py:119
|
||||
#: users/api/user.py:126
|
||||
msgid "Could not reset self otp, use profile reset instead"
|
||||
msgstr "不能在该页面重置多因子认证, 请去个人信息页面重置"
|
||||
|
||||
|
@ -2680,7 +2702,7 @@ msgstr "确认密码"
|
|||
msgid "Password does not match"
|
||||
msgstr "密码不一致"
|
||||
|
||||
#: users/forms/profile.py:89 users/models/user.py:469
|
||||
#: users/forms/profile.py:89 users/models/user.py:475
|
||||
#: users/templates/users/user_detail.html:57
|
||||
#: users/templates/users/user_profile.html:59
|
||||
msgid "Email"
|
||||
|
@ -2716,20 +2738,12 @@ msgid "Public key should not be the same as your old one."
|
|||
msgstr "不能和原来的密钥相同"
|
||||
|
||||
#: users/forms/profile.py:137 users/forms/user.py:90
|
||||
#: users/serializers/user.py:181 users/serializers/user.py:262
|
||||
#: users/serializers/user.py:320
|
||||
#: users/serializers/user.py:185 users/serializers/user.py:266
|
||||
#: users/serializers/user.py:324
|
||||
msgid "Not a valid ssh public key"
|
||||
msgstr "SSH密钥不合法"
|
||||
|
||||
#: users/forms/user.py:27 users/models/user.py:477
|
||||
#: users/templates/users/_select_user_modal.html:15
|
||||
#: users/templates/users/user_detail.html:73
|
||||
#: users/templates/users/user_list.html:16
|
||||
#: users/templates/users/user_profile.html:55
|
||||
msgid "Role"
|
||||
msgstr "角色"
|
||||
|
||||
#: users/forms/user.py:31 users/models/user.py:512
|
||||
#: users/forms/user.py:31 users/models/user.py:518
|
||||
#: users/templates/users/user_detail.html:89
|
||||
#: users/templates/users/user_list.html:18
|
||||
#: users/templates/users/user_profile.html:102
|
||||
|
@ -2749,105 +2763,105 @@ msgstr "添加到用户组"
|
|||
msgid "* Your password does not meet the requirements"
|
||||
msgstr "* 您的密码不符合要求"
|
||||
|
||||
#: users/forms/user.py:124 users/serializers/user.py:30
|
||||
#: users/forms/user.py:124 users/serializers/user.py:31
|
||||
msgid "Reset link will be generated and sent to the user"
|
||||
msgstr "生成重置密码链接,通过邮件发送给用户"
|
||||
|
||||
#: users/forms/user.py:125 users/serializers/user.py:31
|
||||
#: users/forms/user.py:125 users/serializers/user.py:32
|
||||
msgid "Set password"
|
||||
msgstr "设置密码"
|
||||
|
||||
#: users/forms/user.py:132 users/serializers/user.py:38
|
||||
#: users/forms/user.py:132 users/serializers/user.py:39
|
||||
#: xpack/plugins/change_auth_plan/models.py:61
|
||||
#: xpack/plugins/change_auth_plan/serializers.py:30
|
||||
msgid "Password strategy"
|
||||
msgstr "密码策略"
|
||||
|
||||
#: users/models/user.py:159 users/models/user.py:623
|
||||
msgid "Administrator"
|
||||
msgstr "管理员"
|
||||
#: users/models/user.py:156
|
||||
msgid "Super administrator"
|
||||
msgstr "超级管理员"
|
||||
|
||||
#: users/models/user.py:161
|
||||
#: users/models/user.py:158
|
||||
msgid "Super auditor"
|
||||
msgstr "超级审计员"
|
||||
|
||||
#: users/models/user.py:159
|
||||
msgid "Application"
|
||||
msgstr "应用程序"
|
||||
|
||||
#: users/models/user.py:162
|
||||
msgid "Auditor"
|
||||
msgstr "审计员"
|
||||
|
||||
#: users/models/user.py:172
|
||||
msgid "Org admin"
|
||||
msgstr "组织管理员"
|
||||
|
||||
#: users/models/user.py:174
|
||||
msgid "Org auditor"
|
||||
msgstr "组织审计员"
|
||||
|
||||
#: users/models/user.py:389 users/templates/users/user_profile.html:90
|
||||
#: users/models/user.py:395 users/templates/users/user_profile.html:90
|
||||
msgid "Force enable"
|
||||
msgstr "强制启用"
|
||||
|
||||
#: users/models/user.py:456
|
||||
#: users/models/user.py:462
|
||||
msgid "Local"
|
||||
msgstr "数据库"
|
||||
|
||||
#: users/models/user.py:480
|
||||
#: users/models/user.py:486
|
||||
msgid "Avatar"
|
||||
msgstr "头像"
|
||||
|
||||
#: users/models/user.py:483 users/templates/users/user_detail.html:68
|
||||
#: users/models/user.py:489 users/templates/users/user_detail.html:68
|
||||
msgid "Wechat"
|
||||
msgstr "微信"
|
||||
|
||||
#: users/models/user.py:516
|
||||
#: users/models/user.py:522
|
||||
msgid "Date password last updated"
|
||||
msgstr "最后更新密码日期"
|
||||
|
||||
#: users/models/user.py:626
|
||||
#: users/models/user.py:631
|
||||
msgid "Administrator"
|
||||
msgstr "管理员"
|
||||
|
||||
#: users/models/user.py:634
|
||||
msgid "Administrator is the super user of system"
|
||||
msgstr "Administrator是初始的超级管理员"
|
||||
|
||||
#: users/serializers/user.py:69 users/serializers/user.py:233
|
||||
#: users/serializers/user.py:72 users/serializers/user.py:237
|
||||
msgid "Is first login"
|
||||
msgstr "首次登录"
|
||||
|
||||
#: users/serializers/user.py:70
|
||||
#: users/serializers/user.py:73
|
||||
msgid "Is valid"
|
||||
msgstr "账户是否有效"
|
||||
|
||||
#: users/serializers/user.py:71
|
||||
#: users/serializers/user.py:74
|
||||
msgid "Is expired"
|
||||
msgstr " 是否过期"
|
||||
|
||||
#: users/serializers/user.py:72
|
||||
#: users/serializers/user.py:75
|
||||
msgid "Avatar url"
|
||||
msgstr "头像路径"
|
||||
|
||||
#: users/serializers/user.py:76
|
||||
#: users/serializers/user.py:79
|
||||
msgid "Groups name"
|
||||
msgstr "用户组名"
|
||||
|
||||
#: users/serializers/user.py:77
|
||||
#: users/serializers/user.py:80
|
||||
msgid "Source name"
|
||||
msgstr "用户来源名"
|
||||
|
||||
#: users/serializers/user.py:78
|
||||
msgid "Role name"
|
||||
msgstr "角色名"
|
||||
#: users/serializers/user.py:81
|
||||
msgid "Organization role name"
|
||||
msgstr "组织角色名称"
|
||||
|
||||
#: users/serializers/user.py:101
|
||||
#: users/serializers/user.py:82
|
||||
msgid "Super role name"
|
||||
msgstr "超级角色名称"
|
||||
|
||||
#: users/serializers/user.py:105
|
||||
msgid "Role limit to {}"
|
||||
msgstr "角色只能为 {}"
|
||||
|
||||
#: users/serializers/user.py:113 users/serializers/user.py:286
|
||||
#: users/serializers/user.py:117 users/serializers/user.py:290
|
||||
msgid "Password does not match security rules"
|
||||
msgstr "密码不满足安全规则"
|
||||
|
||||
#: users/serializers/user.py:278
|
||||
#: users/serializers/user.py:282
|
||||
msgid "The old password is incorrect"
|
||||
msgstr "旧密码错误"
|
||||
|
||||
#: users/serializers/user.py:292
|
||||
#: users/serializers/user.py:296
|
||||
msgid "The newly set password is inconsistent"
|
||||
msgstr "两次密码不一致"
|
||||
|
||||
|
@ -3969,6 +3983,15 @@ msgstr "企业版"
|
|||
msgid "Ultimate edition"
|
||||
msgstr "旗舰版"
|
||||
|
||||
#~ msgid "Auditor"
|
||||
#~ msgstr "审计员"
|
||||
|
||||
#~ msgid "Org admin"
|
||||
#~ msgstr "组织管理员"
|
||||
|
||||
#~ msgid "Role name"
|
||||
#~ msgstr "角色名"
|
||||
|
||||
#~ msgid "GUI copy"
|
||||
#~ msgstr "GUI 复制"
|
||||
|
||||
|
@ -5508,9 +5531,6 @@ msgstr "旗舰版"
|
|||
#~ msgid "Admin"
|
||||
#~ msgstr "管理员"
|
||||
|
||||
#~ msgid "Organizations"
|
||||
#~ msgstr "组织管理"
|
||||
|
||||
#~ msgid "Org detail"
|
||||
#~ msgstr "组织详情"
|
||||
|
||||
|
|
|
@ -1,23 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.translation import ugettext as _
|
||||
from rest_framework import status, generics
|
||||
from rest_framework.views import Response
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
|
||||
from common.permissions import IsSuperUserOrAppUser
|
||||
from .models import Organization
|
||||
from .models import Organization, ROLE
|
||||
from .serializers import OrgSerializer, OrgReadSerializer, \
|
||||
OrgMembershipUserSerializer, OrgMembershipAdminSerializer, \
|
||||
OrgAllUserSerializer, OrgRetrieveSerializer
|
||||
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 .mixins.api import OrgMembershipModelViewSetMixin
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
@ -39,7 +36,7 @@ class OrgViewSet(BulkModelViewSet):
|
|||
|
||||
def get_data_from_model(self, model):
|
||||
if model == User:
|
||||
data = model.objects.filter(related_user_orgs__id=self.org.id)
|
||||
data = model.objects.filter(orgs__id=self.org.id, m2m_org_members__role=ROLE.USER)
|
||||
else:
|
||||
data = model.objects.filter(org_id=self.org.id)
|
||||
return data
|
||||
|
@ -64,18 +61,6 @@ class OrgViewSet(BulkModelViewSet):
|
|||
return Response({'msg': True}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class OrgMembershipAdminsViewSet(OrgMembershipModelViewSetMixin, BulkModelViewSet):
|
||||
serializer_class = OrgMembershipAdminSerializer
|
||||
membership_class = Organization.admins.through
|
||||
permission_classes = (IsSuperUserOrAppUser, )
|
||||
|
||||
|
||||
class OrgMembershipUsersViewSet(OrgMembershipModelViewSetMixin, BulkModelViewSet):
|
||||
serializer_class = OrgMembershipUserSerializer
|
||||
membership_class = Organization.users.through
|
||||
permission_classes = (IsSuperUserOrAppUser, )
|
||||
|
||||
|
||||
class OrgAllUserListApi(generics.ListAPIView):
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
serializer_class = OrgAllUserSerializer
|
||||
|
@ -84,6 +69,7 @@ class OrgAllUserListApi(generics.ListAPIView):
|
|||
|
||||
def get_queryset(self):
|
||||
pk = self.kwargs.get("pk")
|
||||
org = get_object_or_404(Organization, pk=pk)
|
||||
users = org.get_org_users().only(*self.serializer_class.Meta.only_fields)
|
||||
users = User.objects.filter(
|
||||
orgs=pk, m2m_org_members__role=ROLE.USER
|
||||
).only(*self.serializer_class.Meta.only_fields)
|
||||
return users
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
# Generated by Django 2.2.10 on 2020-07-21 11:27
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('orgs', '0003_auto_20190916_1057'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='OrganizationMember',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('role', models.CharField(choices=[('Admin', 'Administrator'), ('User', 'User'), ('Auditor', 'Auditor')], default='User', max_length=16, verbose_name='Role')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')),
|
||||
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||
('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')),
|
||||
('org', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='m2m_org_members', to='orgs.Organization', verbose_name='Organization')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='m2m_org_members', to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'orgs_organization_members',
|
||||
'unique_together': {('org', 'user', 'role')},
|
||||
},
|
||||
),
|
||||
]
|
|
@ -0,0 +1,35 @@
|
|||
# Generated by Django 2.2.10 on 2020-07-21 11:37
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def migrate_old_organization_members(apps, schema_editor):
|
||||
org_model = apps.get_model("orgs", "Organization")
|
||||
org_member_model = apps.get_model('orgs', 'OrganizationMember')
|
||||
orgs = org_model.objects.all()
|
||||
|
||||
roles = ['User', 'Auditor', 'Admin']
|
||||
|
||||
for org in orgs:
|
||||
users = org.users.all().only('id')
|
||||
auditors = org.auditors.all().only('id')
|
||||
admins = org.admins.all().only('id')
|
||||
total_members = zip([users, auditors, admins], roles)
|
||||
|
||||
org_members = []
|
||||
for members, role in total_members:
|
||||
for user in members:
|
||||
org_user = org_member_model(user=user, org=org, role=role)
|
||||
org_members.append(org_user)
|
||||
org_member_model.objects.bulk_create(org_members)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('orgs', '0004_organizationmember'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(migrate_old_organization_members)
|
||||
]
|
|
@ -0,0 +1,32 @@
|
|||
# Generated by Django 2.2.10 on 2020-07-21 11:37
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('orgs', '0005_auto_20200721_1937'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='organization',
|
||||
name='admins',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='organization',
|
||||
name='auditors',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='organization',
|
||||
name='users',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='organization',
|
||||
name='members',
|
||||
field=models.ManyToManyField(related_name='orgs', through='orgs.OrganizationMember', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
|
@ -1,21 +1,30 @@
|
|||
import uuid
|
||||
from django.conf import settings
|
||||
from functools import partial
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import signals
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.utils import is_uuid, lazyproperty
|
||||
from common.utils import is_uuid
|
||||
from common.const import choices
|
||||
from common.db.models import ChoiceSet
|
||||
|
||||
|
||||
class ROLE(ChoiceSet):
|
||||
ADMIN = choices.ADMIN, _('Organization administrator')
|
||||
USER = choices.USER, _('User')
|
||||
AUDITOR = choices.AUDITOR, _("Organization auditor")
|
||||
|
||||
|
||||
class Organization(models.Model):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=128, unique=True, verbose_name=_("Name"))
|
||||
users = models.ManyToManyField('users.User', related_name='related_user_orgs', blank=True)
|
||||
admins = models.ManyToManyField('users.User', related_name='related_admin_orgs', blank=True)
|
||||
auditors = models.ManyToManyField('users.User', related_name='related_audit_orgs', blank=True)
|
||||
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
|
||||
date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created'))
|
||||
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
|
||||
members = models.ManyToManyField('users.User', related_name='orgs', through='orgs.OrganizationMember',
|
||||
through_fields=('org', 'user'))
|
||||
|
||||
orgs = None
|
||||
CACHE_PREFIX = 'JMS_ORG_{}'
|
||||
|
@ -72,29 +81,24 @@ class Organization(models.Model):
|
|||
org = cls.default() if default else None
|
||||
return org
|
||||
|
||||
# @lazyproperty
|
||||
# lazyproperty 导致用户列表中角色显示出现不稳定的情况, 如果不加会导致数据库操作次数太多
|
||||
def org_users(self):
|
||||
def get_org_members_by_role(self, role):
|
||||
from users.models import User
|
||||
if self.is_real():
|
||||
return self.users.all()
|
||||
users = User.objects.filter(role=User.ROLE_USER)
|
||||
if self.is_default() and not settings.DEFAULT_ORG_SHOW_ALL_USERS:
|
||||
users = users.filter(related_user_orgs__isnull=True)
|
||||
return self.members.filter(m2m_org_members__role=role)
|
||||
users = User.objects.filter(role=role)
|
||||
return users
|
||||
|
||||
def get_org_users(self):
|
||||
return self.org_users()
|
||||
@property
|
||||
def users(self):
|
||||
return self.get_org_members_by_role(ROLE.USER)
|
||||
|
||||
# @lazyproperty
|
||||
def org_admins(self):
|
||||
from users.models import User
|
||||
if self.is_real():
|
||||
return self.admins.all()
|
||||
return User.objects.filter(role=User.ROLE_ADMIN)
|
||||
@property
|
||||
def admins(self):
|
||||
return self.get_org_members_by_role(ROLE.ADMIN)
|
||||
|
||||
def get_org_admins(self):
|
||||
return self.org_admins()
|
||||
@property
|
||||
def auditors(self):
|
||||
return self.get_org_members_by_role(ROLE.AUDITOR)
|
||||
|
||||
def org_id(self):
|
||||
if self.is_real():
|
||||
|
@ -104,87 +108,76 @@ class Organization(models.Model):
|
|||
else:
|
||||
return ''
|
||||
|
||||
# @lazyproperty
|
||||
def org_auditors(self):
|
||||
def get_members(self, exclude=()):
|
||||
from users.models import User
|
||||
if self.is_real():
|
||||
return self.auditors.all()
|
||||
return User.objects.filter(role=User.ROLE_AUDITOR)
|
||||
members = self.members.exclude(m2m_org_members__role__in=exclude)
|
||||
else:
|
||||
members = User.objects.exclude(role__in=exclude)
|
||||
|
||||
def get_org_auditors(self):
|
||||
return self.org_auditors()
|
||||
|
||||
def get_org_members(self, exclude=()):
|
||||
from users.models import User
|
||||
members = User.objects.none()
|
||||
if 'Admin' not in exclude:
|
||||
members |= self.get_org_admins()
|
||||
if 'User' not in exclude:
|
||||
members |= self.get_org_users()
|
||||
if 'Auditor' not in exclude:
|
||||
members |= self.get_org_auditors()
|
||||
return members.exclude(role=User.ROLE_APP).distinct()
|
||||
return members.exclude(role=User.ROLE.APP).distinct()
|
||||
|
||||
def can_admin_by(self, user):
|
||||
if user.is_superuser:
|
||||
return True
|
||||
if self.get_org_admins().filter(id=user.id):
|
||||
if self.admins.filter(id=user.id).exists():
|
||||
return True
|
||||
return False
|
||||
|
||||
def can_audit_by(self, user):
|
||||
if user.is_super_auditor:
|
||||
return True
|
||||
if self.get_org_auditors().filter(id=user.id):
|
||||
if self.auditors.filter(id=user.id).exists():
|
||||
return True
|
||||
return False
|
||||
|
||||
def can_user_by(self, user):
|
||||
if self.get_org_users().filter(id=user.id):
|
||||
if self.users.filter(id=user.id).exists():
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_real(self):
|
||||
return self.id not in (self.DEFAULT_NAME, self.ROOT_ID, self.SYSTEM_ID)
|
||||
|
||||
@classmethod
|
||||
def get_user_orgs_by_role(cls, user, role):
|
||||
if not isinstance(role, (tuple, list)):
|
||||
role = (role, )
|
||||
|
||||
return cls.objects.filter(
|
||||
m2m_org_members__role__in=role,
|
||||
m2m_org_members__user_id=user.id
|
||||
).distinct()
|
||||
|
||||
@classmethod
|
||||
def get_user_admin_orgs(cls, user):
|
||||
admin_orgs = []
|
||||
if user.is_anonymous:
|
||||
return admin_orgs
|
||||
elif user.is_superuser:
|
||||
admin_orgs = list(cls.objects.all())
|
||||
admin_orgs.append(cls.default())
|
||||
elif user.is_org_admin:
|
||||
admin_orgs = user.related_admin_orgs.all()
|
||||
return admin_orgs
|
||||
return cls.objects.none()
|
||||
if user.is_superuser:
|
||||
return [*cls.objects.all(), cls.default()]
|
||||
return cls.get_user_orgs_by_role(user, ROLE.ADMIN)
|
||||
|
||||
@classmethod
|
||||
def get_user_user_orgs(cls, user):
|
||||
user_orgs = []
|
||||
if user.is_anonymous:
|
||||
return user_orgs
|
||||
user_orgs = user.related_user_orgs.all()
|
||||
return user_orgs
|
||||
return cls.objects.none()
|
||||
return cls.get_user_orgs_by_role(user, ROLE.USER)
|
||||
|
||||
@classmethod
|
||||
def get_user_audit_orgs(cls, user):
|
||||
audit_orgs = []
|
||||
if user.is_anonymous:
|
||||
return audit_orgs
|
||||
elif user.is_super_auditor:
|
||||
audit_orgs = list(cls.objects.all())
|
||||
audit_orgs.append(cls.default())
|
||||
elif user.is_org_auditor:
|
||||
audit_orgs = user.related_audit_orgs.all()
|
||||
return audit_orgs
|
||||
return cls.objects.none()
|
||||
if user.is_super_auditor:
|
||||
return [*cls.objects.all(), cls.default()]
|
||||
return cls.get_user_orgs_by_role(user, ROLE.AUDITOR)
|
||||
|
||||
@classmethod
|
||||
def get_user_admin_or_audit_orgs(self, user):
|
||||
admin_orgs = self.get_user_admin_orgs(user)
|
||||
audit_orgs = self.get_user_audit_orgs(user)
|
||||
orgs = set(admin_orgs) | set(audit_orgs)
|
||||
return orgs
|
||||
def get_user_admin_or_audit_orgs(cls, user):
|
||||
if user.is_anonymous:
|
||||
return cls.objects.none()
|
||||
if user.is_superuser or user.is_super_auditor:
|
||||
return [*cls.objects.all(), cls.default()]
|
||||
return cls.get_user_orgs_by_role(user, (ROLE.AUDITOR, ROLE.ADMIN))
|
||||
|
||||
@classmethod
|
||||
def default(cls):
|
||||
|
@ -211,8 +204,122 @@ class Organization(models.Model):
|
|||
from .utils import set_current_org
|
||||
set_current_org(self)
|
||||
|
||||
@classmethod
|
||||
def all_orgs(cls):
|
||||
orgs = list(cls.objects.all())
|
||||
orgs.append(cls.default())
|
||||
return orgs
|
||||
|
||||
def _convert_to_uuid_set(users):
|
||||
rst = set()
|
||||
for user in users:
|
||||
if isinstance(user, models.Model):
|
||||
rst.add(user.id)
|
||||
elif not isinstance(user, uuid.UUID):
|
||||
rst.add(uuid.UUID(user))
|
||||
return rst
|
||||
|
||||
|
||||
def _none2list(*args):
|
||||
return ([] if v is None else v for v in args)
|
||||
|
||||
|
||||
class OrgMemeberManager(models.Manager):
|
||||
|
||||
def remove_users_by_role(self, org, users=None, admins=None, auditors=None):
|
||||
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)
|
||||
|
||||
send(action="pre_remove")
|
||||
self.filter(org_id=org.id).filter(
|
||||
Q(user__in=users, role=ROLE.USER) |
|
||||
Q(user__in=admins, role=ROLE.ADMIN) |
|
||||
Q(user__in=auditors, role=ROLE.AUDITOR)
|
||||
).delete()
|
||||
send(action="post_remove")
|
||||
|
||||
def add_users_by_role(self, org, users=None, admins=None, auditors=None):
|
||||
if not any((users, admins, auditors)):
|
||||
return
|
||||
users, admins, auditors = _none2list(users, admins, auditors)
|
||||
|
||||
add_mapper = (
|
||||
(users, ROLE.USER),
|
||||
(admins, ROLE.ADMIN),
|
||||
(auditors, ROLE.AUDITOR)
|
||||
)
|
||||
|
||||
oms_add = []
|
||||
for users, role in add_mapper:
|
||||
for user in users:
|
||||
if isinstance(user, models.Model):
|
||||
user = user.id
|
||||
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)
|
||||
|
||||
send(action='pre_add')
|
||||
self.bulk_create(oms_add)
|
||||
send(action='post_add')
|
||||
|
||||
def _get_remove_add_set(self, new_users, old_users):
|
||||
if new_users is None:
|
||||
return None, None
|
||||
new_users = _convert_to_uuid_set(new_users)
|
||||
return (old_users - new_users), (new_users - old_users)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
for role, user_id in oms:
|
||||
if role in mapper:
|
||||
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)
|
||||
|
||||
self.remove_users_by_role(
|
||||
org,
|
||||
users_remove,
|
||||
admins_remove,
|
||||
auditors_remove
|
||||
)
|
||||
|
||||
self.add_users_by_role(
|
||||
org,
|
||||
users_add,
|
||||
admins_add,
|
||||
auditors_add
|
||||
)
|
||||
|
||||
|
||||
class OrganizationMember(models.Model):
|
||||
"""
|
||||
注意:直接调用该 `Model.delete` `Model.objects.delete` 不会触发清理该用户的信号
|
||||
"""
|
||||
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
org = models.ForeignKey(Organization, related_name='m2m_org_members', on_delete=models.CASCADE, verbose_name=_('Organization'))
|
||||
user = models.ForeignKey('users.User', related_name='m2m_org_members', on_delete=models.CASCADE, verbose_name=_('User'))
|
||||
role = models.CharField(max_length=16, choices=ROLE.choices, default=ROLE.USER, verbose_name=_("Role"))
|
||||
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created"))
|
||||
date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated"))
|
||||
created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))
|
||||
|
||||
objects = OrgMemeberManager()
|
||||
|
||||
class Meta:
|
||||
unique_together = [('org', 'user', 'role')]
|
||||
db_table = 'orgs_organization_members'
|
||||
|
||||
def __str__(self):
|
||||
return '{} is {}: {}'.format(self.user.name, self.org.name, self.role)
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework import serializers
|
||||
from users.models import UserGroup
|
||||
from assets.models import Asset, Domain, AdminUser, SystemUser, Label
|
||||
from perms.models import AssetPermission
|
||||
|
||||
from users.models.user import User
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from .utils import set_current_org, get_current_org
|
||||
from .models import Organization
|
||||
from .models import Organization, OrganizationMember
|
||||
from .mixins.serializers import OrgMembershipSerializerMixin
|
||||
|
||||
|
||||
class OrgSerializer(ModelSerializer):
|
||||
users = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects.all(), write_only=True)
|
||||
admins = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects.all(), write_only=True)
|
||||
auditors = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects.all(), write_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Organization
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
|
@ -21,11 +23,27 @@ class OrgSerializer(ModelSerializer):
|
|||
fields_m2m = ['users', 'admins', 'auditors']
|
||||
fields = fields_small + fields_m2m
|
||||
read_only_fields = ['created_by', 'date_created']
|
||||
extra_kwargs = {
|
||||
'admins': {'write_only': True},
|
||||
'users': {'write_only': True},
|
||||
'auditors': {'write_only': True},
|
||||
}
|
||||
|
||||
def create(self, validated_data):
|
||||
members = self._pop_memebers(validated_data)
|
||||
instance = Organization.objects.create(**validated_data)
|
||||
OrganizationMember.objects.add_users_by_role(instance, *members)
|
||||
return instance
|
||||
|
||||
def _pop_memebers(self, validated_data):
|
||||
return (
|
||||
validated_data.pop('users', None),
|
||||
validated_data.pop('admins', None),
|
||||
validated_data.pop('auditors', None)
|
||||
)
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
members = self._pop_memebers(validated_data)
|
||||
for attr, value in validated_data.items():
|
||||
setattr(instance, attr, value)
|
||||
instance.save()
|
||||
OrganizationMember.objects.set_users_by_role(instance, *members)
|
||||
return instance
|
||||
|
||||
|
||||
class OrgReadSerializer(OrgSerializer):
|
||||
|
@ -34,14 +52,14 @@ class OrgReadSerializer(OrgSerializer):
|
|||
|
||||
class OrgMembershipAdminSerializer(OrgMembershipSerializerMixin, ModelSerializer):
|
||||
class Meta:
|
||||
model = Organization.admins.through
|
||||
model = Organization.members.through
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class OrgMembershipUserSerializer(OrgMembershipSerializerMixin, ModelSerializer):
|
||||
class Meta:
|
||||
model = Organization.users.through
|
||||
model = Organization.members.through
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
fields = '__all__'
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ from django.db.models.signals import m2m_changed
|
|||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
|
||||
from .models import Organization
|
||||
from .models import Organization, OrganizationMember
|
||||
from .hands import set_current_org, current_org, Node, get_current_org
|
||||
from perms.models import AssetPermission
|
||||
from users.models import UserGroup
|
||||
|
@ -26,23 +26,31 @@ def on_org_create_or_update(sender, instance=None, created=False, **kwargs):
|
|||
instance.expire_cache()
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=Organization.users.through)
|
||||
def on_org_user_changed(sender, instance=None, **kwargs):
|
||||
if isinstance(instance, Organization):
|
||||
def _remove_users(model, users, org, reverse=False):
|
||||
if not isinstance(users, (tuple, list, set)):
|
||||
users = (users, )
|
||||
|
||||
m2m_model = model.users.through
|
||||
if reverse:
|
||||
m2m_field_name = model.users.field.m2m_reverse_field_name()
|
||||
else:
|
||||
m2m_field_name = model.users.field.m2m_field_name()
|
||||
m2m_model.objects.filter(**{'user__in': users, f'{m2m_field_name}__org_id': org.id}).delete()
|
||||
|
||||
|
||||
def _clear_users_from_org(org, users):
|
||||
if not users:
|
||||
return
|
||||
|
||||
old_org = current_org
|
||||
set_current_org(instance)
|
||||
if kwargs['action'] == 'pre_remove':
|
||||
users = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
||||
for user in users:
|
||||
perms = AssetPermission.objects.filter(users=user)
|
||||
user_groups = UserGroup.objects.filter(users=user)
|
||||
for perm in perms:
|
||||
perm.users.remove(user)
|
||||
for user_group in user_groups:
|
||||
user_group.users.remove(user)
|
||||
set_current_org(org)
|
||||
_remove_users(AssetPermission, users, org)
|
||||
_remove_users(UserGroup, users, org, reverse=True)
|
||||
set_current_org(old_org)
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=Organization.admins.through)
|
||||
def on_org_admin_change(sender, **kwargs):
|
||||
Organization._user_admin_orgs = None
|
||||
@receiver(m2m_changed, sender=OrganizationMember)
|
||||
def on_org_user_changed(sender, instance=None, action=None, pk_set=None, **kwargs):
|
||||
if action == 'post_remove':
|
||||
leaved_users = set(pk_set) - set(instance.members.values_list('id', flat=True))
|
||||
_clear_users_from_org(instance, leaved_users)
|
||||
|
|
|
@ -1,3 +1,16 @@
|
|||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
from rest_framework import status
|
||||
|
||||
from users.models.user import User
|
||||
|
||||
|
||||
class OrgTests(APITestCase):
|
||||
def test_create(self):
|
||||
print(User.objects.all())
|
||||
reverse('api-orgs:org-list')
|
||||
|
||||
|
||||
|
||||
{"name":"a-07","admins":["138167d2-6843-4e25-b838-59657157c6c6"],"auditors":["8d4b3ec4-8339-4a2c-b33c-c2633da62c84"],"users":["ea60e8ce-876d-493b-a641-ff836258629c"]}
|
||||
|
||||
# Create your tests here.
|
||||
|
|
|
@ -11,12 +11,6 @@ from .. import api
|
|||
app_name = 'orgs'
|
||||
router = DefaultRouter()
|
||||
|
||||
# 将会删除
|
||||
router.register(r'orgs/(?P<org_id>[0-9a-zA-Z\-]{36})/membership/admins',
|
||||
api.OrgMembershipAdminsViewSet, 'membership-admins')
|
||||
router.register(r'orgs/(?P<org_id>[0-9a-zA-Z\-]{36})/membership/users',
|
||||
api.OrgMembershipUsersViewSet, 'membership-users'),
|
||||
|
||||
router.register(r'orgs', api.OrgViewSet, 'org')
|
||||
|
||||
old_version_urlpatterns = [
|
||||
|
|
|
@ -9,7 +9,7 @@ from orgs.utils import current_org
|
|||
class UserQuerysetMixin:
|
||||
def get_queryset(self):
|
||||
if self.request.query_params.get('all') or not current_org.is_real():
|
||||
queryset = User.objects.exclude(role=User.ROLE_APP)
|
||||
queryset = User.objects.exclude(role=User.ROLE.APP)
|
||||
else:
|
||||
queryset = utils.get_current_org_members()
|
||||
return queryset
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
from django.core.cache import cache
|
||||
from django.db.models import CharField
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from rest_framework import generics
|
||||
from rest_framework.response import Response
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
|
||||
from common.db.aggregates import GroupConcat
|
||||
from common.permissions import (
|
||||
IsOrgAdmin, IsOrgAdminOrAppUser,
|
||||
CanUpdateDeleteUser, IsSuperUser
|
||||
|
@ -13,6 +15,7 @@ from common.permissions import (
|
|||
from common.mixins import CommonApiMixin
|
||||
from common.utils import get_logger
|
||||
from orgs.utils import current_org
|
||||
from orgs.models import ROLE as ORG_ROLE, OrganizationMember
|
||||
from .. import serializers
|
||||
from ..serializers import UserSerializer, UserRetrieveSerializer
|
||||
from .mixins import UserQuerysetMixin
|
||||
|
@ -39,7 +42,11 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet):
|
|||
extra_filter_backends = [OrgRoleUserFilterBackend]
|
||||
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().prefetch_related('groups')
|
||||
return super().get_queryset().annotate(
|
||||
gc_m2m_org_members__role=GroupConcat('m2m_org_members__role'),
|
||||
gc_groups__name=GroupConcat('groups__name'),
|
||||
gc_groups=GroupConcat('groups__id', output_field=CharField())
|
||||
)
|
||||
|
||||
def send_created_signal(self, users):
|
||||
if not isinstance(users, list):
|
||||
|
@ -48,11 +55,32 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet):
|
|||
post_user_create.send(self.__class__, user=user)
|
||||
|
||||
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)]
|
||||
|
||||
users = serializer.save()
|
||||
if isinstance(users, User):
|
||||
users = [users]
|
||||
if current_org and current_org.is_real():
|
||||
current_org.users.add(*users)
|
||||
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]
|
||||
)
|
||||
self.send_created_signal(users)
|
||||
|
||||
def get_permissions(self):
|
||||
|
@ -78,7 +106,7 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet):
|
|||
users_ids = [
|
||||
d.get("id") or d.get("pk") for d in serializer.validated_data
|
||||
]
|
||||
users = current_org.get_org_members().filter(id__in=users_ids)
|
||||
users = current_org.get_members().filter(id__in=users_ids)
|
||||
for user in users:
|
||||
self.check_object_permissions(self.request, user)
|
||||
return super().perform_bulk_update(serializer)
|
||||
|
|
|
@ -12,13 +12,13 @@ class OrgRoleUserFilterBackend(filters.BaseFilterBackend):
|
|||
return queryset
|
||||
|
||||
if org_role == 'admins':
|
||||
return queryset & (current_org.get_org_admins() | User.objects.filter(role=User.ROLE_ADMIN))
|
||||
return queryset & (current_org.admins | User.objects.filter(role=User.ROLE_ADMIN))
|
||||
elif org_role == 'auditors':
|
||||
return queryset & current_org.get_org_auditors()
|
||||
return queryset & current_org.auditors
|
||||
elif org_role == 'users':
|
||||
return queryset & current_org.get_org_users()
|
||||
return queryset & current_org.users
|
||||
elif org_role == 'members':
|
||||
return queryset & current_org.get_org_members()
|
||||
return queryset & current_org.get_members()
|
||||
|
||||
def get_schema_fields(self, view):
|
||||
return [
|
||||
|
|
|
@ -17,14 +17,14 @@ __all__ = [
|
|||
|
||||
|
||||
class UserCreateUpdateFormMixin(OrgModelForm):
|
||||
role_choices = ((i, n) for i, n in User.ROLE_CHOICES if i != User.ROLE_APP)
|
||||
role_choices = ((i, n) for i, n in User.ROLE.choices if i != User.ROLE.APP)
|
||||
password = forms.CharField(
|
||||
label=_('Password'), widget=forms.PasswordInput,
|
||||
max_length=128, strip=False, required=False,
|
||||
)
|
||||
role = forms.ChoiceField(
|
||||
choices=role_choices, required=True,
|
||||
initial=User.ROLE_USER, label=_("Role")
|
||||
initial=User.ROLE.USER, label=_("Role")
|
||||
)
|
||||
source = forms.ChoiceField(
|
||||
choices=get_source_choices, required=True,
|
||||
|
|
|
@ -18,8 +18,10 @@ from django.shortcuts import reverse
|
|||
|
||||
from common.local import LOCAL_DYNAMIC_SETTINGS
|
||||
from orgs.utils import current_org
|
||||
from common.utils import signer, date_expired_default, get_logger, lazyproperty
|
||||
from common.utils import date_expired_default, get_logger, lazyproperty
|
||||
from common import fields
|
||||
from common.const import choices
|
||||
from common.db.models import ChoiceSet
|
||||
from ..signals import post_user_change_password
|
||||
|
||||
|
||||
|
@ -150,45 +152,58 @@ class AuthMixin:
|
|||
|
||||
|
||||
class RoleMixin:
|
||||
ROLE_ADMIN = 'Admin'
|
||||
ROLE_USER = 'User'
|
||||
ROLE_APP = 'App'
|
||||
ROLE_AUDITOR = 'Auditor'
|
||||
class ROLE(ChoiceSet):
|
||||
ADMIN = choices.ADMIN, _('Super administrator')
|
||||
USER = choices.USER, _('User')
|
||||
AUDITOR = choices.AUDITOR, _('Super auditor')
|
||||
APP = 'App', _('Application')
|
||||
|
||||
ROLE_CHOICES = (
|
||||
(ROLE_ADMIN, _('Administrator')),
|
||||
(ROLE_USER, _('User')),
|
||||
(ROLE_APP, _('Application')),
|
||||
(ROLE_AUDITOR, _("Auditor"))
|
||||
)
|
||||
role = ROLE_USER
|
||||
role = ROLE.USER
|
||||
|
||||
@property
|
||||
def role_display(self):
|
||||
if not current_org.is_real():
|
||||
def super_role_display(self):
|
||||
return self.get_role_display()
|
||||
roles = []
|
||||
if self in current_org.get_org_admins():
|
||||
roles.append(str(_('Org admin')))
|
||||
if self in current_org.get_org_auditors():
|
||||
roles.append(str(_('Org auditor')))
|
||||
if self in current_org.get_org_users():
|
||||
roles.append(str(_('User')))
|
||||
return " | ".join(roles)
|
||||
|
||||
@property
|
||||
def org_role_display(self):
|
||||
from orgs.models import ROLE as ORG_ROLE
|
||||
|
||||
if not current_org.is_real():
|
||||
if self.is_superuser:
|
||||
return ORG_ROLE.ADMIN.label
|
||||
else:
|
||||
return ORG_ROLE.USER.label
|
||||
|
||||
if hasattr(self, 'gc_m2m_org_members__role'):
|
||||
names = self.gc_m2m_org_members__role
|
||||
if isinstance(names, str):
|
||||
roles = set(self.gc_m2m_org_members__role.split(','))
|
||||
else:
|
||||
roles = set()
|
||||
else:
|
||||
roles = set(self.m2m_org_members.filter(
|
||||
org_id=current_org.id
|
||||
).values_list('role', flat=True))
|
||||
|
||||
return ' | '.join([str(ORG_ROLE[role]) for role in roles if role in ORG_ROLE])
|
||||
|
||||
def current_org_roles(self):
|
||||
roles = []
|
||||
if self.can_admin_current_org:
|
||||
roles.append('Admin')
|
||||
if self.can_audit_current_org:
|
||||
roles.append('Auditor')
|
||||
from orgs.models import OrganizationMember, ROLE as ORG_ROLE
|
||||
if not current_org.is_real():
|
||||
if self.is_superuser:
|
||||
return [ORG_ROLE.ADMIN]
|
||||
else:
|
||||
roles.append('User')
|
||||
return [ORG_ROLE.USER]
|
||||
|
||||
roles = list(set(OrganizationMember.objects.filter(
|
||||
org_id=current_org.id, user=self
|
||||
).values_list('role', flat=True)))
|
||||
|
||||
return roles
|
||||
|
||||
@property
|
||||
def is_superuser(self):
|
||||
if self.role == 'Admin':
|
||||
if self.role == self.ROLE.ADMIN:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
@ -196,13 +211,13 @@ class RoleMixin:
|
|||
@is_superuser.setter
|
||||
def is_superuser(self, value):
|
||||
if value is True:
|
||||
self.role = 'Admin'
|
||||
self.role = self.ROLE.ADMIN
|
||||
else:
|
||||
self.role = 'User'
|
||||
self.role = self.ROLE.USER
|
||||
|
||||
@property
|
||||
def is_super_auditor(self):
|
||||
return self.role == 'Auditor'
|
||||
return self.role == self.ROLE.AUDITOR
|
||||
|
||||
@property
|
||||
def is_common_user(self):
|
||||
|
@ -216,7 +231,7 @@ class RoleMixin:
|
|||
|
||||
@property
|
||||
def is_app(self):
|
||||
return self.role == 'App'
|
||||
return self.role == self.ROLE.APP
|
||||
|
||||
@lazyproperty
|
||||
def user_orgs(self):
|
||||
|
@ -240,14 +255,16 @@ class RoleMixin:
|
|||
|
||||
@lazyproperty
|
||||
def is_org_admin(self):
|
||||
if self.is_superuser or self.related_admin_orgs.exists():
|
||||
from orgs.models import ROLE as ORG_ROLE
|
||||
if self.is_superuser or self.m2m_org_members.filter(role=ORG_ROLE.ADMIN).exists():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@lazyproperty
|
||||
def is_org_auditor(self):
|
||||
if self.is_super_auditor or self.related_audit_orgs.exists():
|
||||
from orgs.models import ROLE as ORG_ROLE
|
||||
if self.is_super_auditor or self.m2m_org_members.filter(role=ORG_ROLE.AUDITOR).exists():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
@ -283,7 +300,7 @@ class RoleMixin:
|
|||
def create_app_user(cls, name, comment):
|
||||
app = cls.objects.create(
|
||||
username=name, name=name, email='{}@local.domain'.format(name),
|
||||
is_active=False, role='App', comment=comment,
|
||||
is_active=False, role=cls.ROLE.APP, comment=comment,
|
||||
is_first_login=False, created_by='System'
|
||||
)
|
||||
access_key = app.create_access_key()
|
||||
|
@ -473,7 +490,7 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
|
|||
blank=True, verbose_name=_('User group')
|
||||
)
|
||||
role = models.CharField(
|
||||
choices=RoleMixin.ROLE_CHOICES, default='User', max_length=10,
|
||||
choices=RoleMixin.ROLE.choices, default='User', max_length=10,
|
||||
blank=True, verbose_name=_('Role')
|
||||
)
|
||||
avatar = models.ImageField(
|
||||
|
@ -526,6 +543,12 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
|
|||
|
||||
@property
|
||||
def groups_display(self):
|
||||
if hasattr(self, 'gc_groups__name'):
|
||||
names = self.gc_groups__name
|
||||
if isinstance(names, str):
|
||||
return ' '.join(set(self.gc_groups__name.split(',')))
|
||||
else:
|
||||
return ''
|
||||
return ' '.join([group.name for group in self.groups.all()])
|
||||
|
||||
@property
|
||||
|
@ -646,7 +669,7 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
|
|||
email=forgery_py.internet.email_address(),
|
||||
name=forgery_py.name.full_name(),
|
||||
password=make_password(forgery_py.lorem_ipsum.word()),
|
||||
role=choice(list(dict(User.ROLE_CHOICES).keys())),
|
||||
role=choice(list(dict(User.ROLE.choices).keys())),
|
||||
wechat=forgery_py.internet.user_name(True),
|
||||
comment=forgery_py.lorem_ipsum.sentence(),
|
||||
created_by=choice(cls.objects.all()).username)
|
||||
|
|
|
@ -9,7 +9,8 @@ from common.utils import validate_ssh_public_key
|
|||
from common.mixins import CommonBulkSerializerMixin
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from common.permissions import CanUpdateDeleteUser
|
||||
from ..models import User
|
||||
from common.drf.fields import GroupConcatedPrimaryKeyRelatedField
|
||||
from ..models import User, UserGroup
|
||||
|
||||
|
||||
__all__ = [
|
||||
|
@ -38,10 +39,16 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer):
|
|||
label=_('Password strategy'), write_only=True
|
||||
)
|
||||
mfa_level_display = serializers.ReadOnlyField(source='get_mfa_level_display')
|
||||
groups = GroupConcatedPrimaryKeyRelatedField(
|
||||
label=_('User group'), many=True, queryset=UserGroup.objects.all(), required=False
|
||||
)
|
||||
login_blocked = serializers.SerializerMethodField()
|
||||
can_update = serializers.SerializerMethodField()
|
||||
can_delete = serializers.SerializerMethodField()
|
||||
|
||||
org_role = serializers.CharField(
|
||||
label=_('Organization role name'), write_only=True,
|
||||
allow_null=True, required=False, allow_blank=True
|
||||
)
|
||||
key_prefix_block = "_LOGIN_BLOCK_{}"
|
||||
|
||||
class Meta:
|
||||
|
@ -52,7 +59,7 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer):
|
|||
# small 指的是 不需要计算的直接能从一张表中获取到的数据
|
||||
fields_small = fields_mini + [
|
||||
'password', 'email', 'public_key', 'wechat', 'phone', 'mfa_level', 'mfa_enabled',
|
||||
'mfa_level_display', 'mfa_force_enabled',
|
||||
'mfa_level_display', 'mfa_force_enabled', 'super_role_display',
|
||||
'comment', 'source', 'is_valid', 'is_expired',
|
||||
'is_active', 'created_by', 'is_first_login',
|
||||
'password_strategy', 'date_password_last_updated', 'date_expired',
|
||||
|
@ -60,7 +67,7 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer):
|
|||
]
|
||||
fields = fields_small + [
|
||||
'groups', 'role', 'groups_display', 'role_display',
|
||||
'can_update', 'can_delete', 'login_blocked',
|
||||
'can_update', 'can_delete', 'login_blocked', 'org_role'
|
||||
]
|
||||
|
||||
extra_kwargs = {
|
||||
|
@ -75,7 +82,8 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer):
|
|||
'can_delete': {'read_only': True},
|
||||
'groups_display': {'label': _('Groups name')},
|
||||
'source_display': {'label': _('Source name')},
|
||||
'role_display': {'label': _('Role name')},
|
||||
'role_display': {'label': _('Organization role name'), 'source': 'org_role_display'},
|
||||
'super_role_display': {'label': _('Super role name')},
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
@ -87,17 +95,17 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer):
|
|||
if not role:
|
||||
return
|
||||
choices = role._choices
|
||||
choices.pop(User.ROLE_APP, None)
|
||||
choices.pop(User.ROLE.APP, None)
|
||||
request = self.context.get('request')
|
||||
if request and hasattr(request, 'user') and not request.user.is_superuser:
|
||||
choices.pop(User.ROLE_ADMIN, None)
|
||||
choices.pop(User.ROLE_AUDITOR, None)
|
||||
choices.pop(User.ROLE.ADMIN, None)
|
||||
choices.pop(User.ROLE.AUDITOR, None)
|
||||
role._choices = choices
|
||||
|
||||
def validate_role(self, value):
|
||||
request = self.context.get('request')
|
||||
if not request.user.is_superuser and value != User.ROLE_USER:
|
||||
role_display = dict(User.ROLE_CHOICES)[User.ROLE_USER]
|
||||
if not request.user.is_superuser and value != User.ROLE.USER:
|
||||
role_display = User.ROLE.USER.label
|
||||
msg = _("Role limit to {}".format(role_display))
|
||||
raise serializers.ValidationError(msg)
|
||||
return value
|
||||
|
@ -121,7 +129,7 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer):
|
|||
role = self.initial_data.get('role')
|
||||
if self.instance:
|
||||
role = role or self.instance.role
|
||||
if role == User.ROLE_AUDITOR:
|
||||
if role == User.ROLE.AUDITOR:
|
||||
return []
|
||||
return groups
|
||||
|
||||
|
|
|
@ -71,7 +71,7 @@
|
|||
{% endif %}
|
||||
<tr>
|
||||
<td>{% trans 'Role' %}:</td>
|
||||
<td><b>{{ object.role_display }}</b></td>
|
||||
<td><b>{{ object.org_role_display }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'MFA' %}:</td>
|
||||
|
|
|
@ -315,7 +315,7 @@ def construct_user_email(username, email):
|
|||
|
||||
def get_current_org_members(exclude=()):
|
||||
from orgs.utils import current_org
|
||||
return current_org.get_org_members(exclude=exclude)
|
||||
return current_org.get_members(exclude=exclude)
|
||||
|
||||
|
||||
def get_source_choices():
|
||||
|
|
Loading…
Reference in New Issue