Merge branch 'v3' of github.com:jumpserver/jumpserver into v3

pull/9244/head
Bai 2022-12-26 19:16:18 +08:00
commit 7f6d13a5a6
19 changed files with 174 additions and 119 deletions

View File

@ -1,9 +1,6 @@
from assets import serializers
from assets.models import AccountTemplate
from rbac.permissions import RBACPermission
from authentication.const import ConfirmType
from common.mixins import RecordViewLogMixin
from common.permissions import UserConfirmation
from orgs.mixins.api import OrgBulkModelViewSet

View File

@ -82,6 +82,10 @@ class Account(AbsConnectivity, BaseAccount):
def __str__(self):
return '{}'.format(self.username)
@lazyproperty
def has_secret(self):
return bool(self.secret)
@classmethod
def get_manual_account(cls):
""" @INPUT 手动登录的账号(any) """

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from assets.models import BaseAccount
from assets.serializers.base import AuthValidateMixin
@ -9,6 +10,8 @@ __all__ = ['BaseAccountSerializer']
class BaseAccountSerializer(AuthValidateMixin, BulkOrgResourceModelSerializer):
has_secret = serializers.BooleanField(label=_("Has secret"), read_only=True)
class Meta:
model = BaseAccount
fields_mini = ['id', 'name', 'username']

View File

@ -1,5 +1,5 @@
from common.drf.serializers import SecretReadableMixin
from assets.models import AccountTemplate
from common.drf.serializers import SecretReadableMixin
from .base import BaseAccountSerializer

View File

@ -18,6 +18,23 @@
<style>
.login-content {
{#box-shadow: 0 5px 5px -3px rgb(0 0 0 / 15%), 0 8px 10px 1px rgb(0 0 0 / 14%), 0 3px 14px 2px rgb(0 0 0 / 12%);#}
}
.login-footer {
height: 50px;
width: 1000px;
margin: 40px auto;
text-align: center;
}
.footer-item {
padding: 5px 20px;
color: gray;
}
.footer-item a {
color: gray;
}
.help-block {
@ -52,15 +69,15 @@
}
.login-content {
height: 490px;
width: 1066px;
height: 500px;
width: 1000px;
margin-right: auto;
margin-left: auto;
margin-top: calc((100vh - 470px) / 3);
}
body {
background-color: #ffffff;
background-color: #f3f3f3;
height: calc(100vh - (100vh - 470px) / 3);
}
@ -120,12 +137,12 @@
}
.login-page-language {
font-size: 12px!important;
font-size: 12px !important;
margin-right: -32px !important;
padding-top: 12px !important;
padding-left: 0 !important;
padding-bottom: 8px !important;
color:#8F959E !important;
color: #8F959E !important;
font-weight: 350 !important;
min-height: auto !important;
}
@ -137,14 +154,16 @@
.jms-title {
font-size: 21px;
font-weight:400;
font-weight: 400;
color: #151515;
letter-spacing: 0;
}
.more-methods-title {
position: relative;
margin-top: 20px;
}
.more-methods-title:before, .more-methods-title:after {
position: absolute;
top: 50%;
@ -153,18 +172,23 @@
border: 1px dashed #e7eaec;
width: 35%;
}
.more-methods-title:before {
left: 0;
}
.more-methods-title:after {
right: 0;
}
.more-methods-title.ja:before, .more-methods-title.ja:after{
.more-methods-title.ja:before, .more-methods-title.ja:after {
width: 26%;
}
.captcha-field .form-group {
margin-bottom: 5px;
}
.auto-login.form-group .checkbox {
margin: 5px 0;
}
@ -176,16 +200,20 @@
.has-error .more-login {
margin-top: 0;
}
.welcome-message {
color: #646A73;
}
.navbar-right .dropdown-menu {
right: -24px!important;
right: -24px !important;
left: auto;
}
.auto_login_box {
display: inline-block;
}
.auto-login input[type=checkbox] {
-webkit-appearance: none;
-moz-appearance: none;
@ -201,9 +229,11 @@
outline: none;
cursor: pointer;
}
.auto-login input[type=checkbox]:checked {
border: 4px solid var(--primary-color);
}
.auto-login > .row::after {
clear: none;
}
@ -218,11 +248,31 @@
</a>
</div>
<div class="left-form-box {% if not form.challenge and not form.captcha %} no-captcha-challenge {% endif %}">
<div style="background-color: white">
<div class="jms-title">
<img src="{{ INTERFACE.logo_text_green }}" class="jms-title-img" />
<div style="position: relative;top: 50%;transform: translateY(-50%);">
<div style='padding: 15px 60px; text-align: left'>
<h2 style='font-weight: 400;display: inline'>
{% trans 'Login' %}
</h2>
<ul class=" nav navbar-top-links navbar-right">
<li class="dropdown">
<a class="dropdown-toggle login-page-language" data-toggle="dropdown" href="#"
target="_blank">
<i class="fa fa-globe fa-lg" style="margin-right: 2px"></i>
<span>{{ current_lang.title }}<b class="caret"></b></span>
</a>
<ul class="dropdown-menu profile-dropdown dropdown-menu-right">
{% for lang in langs %}
<li>
<a href="{% url 'i18n-switch' lang=lang.code %}">
<span>{{ lang.title }}</span>
</a>
</li>
{% endfor %}
</ul>
</li>
</ul>
</div>
<div class="contact-form col-md-10 col-md-offset-1">
<div class="contact-form col-md-10 col-md-offset-1" style='float: none; overflow: hidden'>
<form id="login-form" action="" method="post" role="form" novalidate="novalidate">
{% csrf_token %}
<div style="line-height: 17px;margin-bottom: 20px;color: #999999;">
@ -230,37 +280,17 @@
<p class="help-block red-fonts">
{{ form.non_field_errors.as_text }}
</p>
{% else %}
<p class="welcome-message">
{% trans 'Welcome back, please enter username and password to login' %}
</p>
{% endif %}
</div>
<ul class="nav navbar-top-links navbar-right">
<li class="dropdown">
<a class="dropdown-toggle login-page-language" data-toggle="dropdown" href="#" target="_blank">
<i class="fa fa-globe fa-lg" style="margin-right: 2px"></i>
<span>{{ current_lang.title }}<b class="caret"></b></span>
</a>
<ul class="dropdown-menu profile-dropdown dropdown-menu-right">
{% for lang in langs %}
<li>
<a href="{% url 'i18n-switch' lang=lang.code %}">
<span>{{ lang.title }}</span>
</a>
</li>
{% endfor %}
</ul>
</li>
</ul>
{% bootstrap_field form.username show_label=False %}
<div class="form-group {% if form.password.errors %} has-error {% endif %}">
<input type="password" class="form-control" id="password" placeholder="{% trans 'Password' %}" required>
<input id="password-hidden" type="text" style="display:none" name="{{ form.password.html_name }}">
<input type="password" class="form-control" id="password" placeholder="{% trans 'Password' %}"
required>
<input id="password-hidden" type="text" style="display:none"
name="{{ form.password.html_name }}">
{% if form.password.errors %}
<p class="help-block" style="text-align: left">
<p class="help-block" style="text-align: left">
{{ form.password.errors.as_text }}
</p>
{% endif %}
@ -306,7 +336,8 @@
<div class="more-login-items">
{% for method in auth_methods %}
<a href="{{ method.url }}" class="more-login-item">
<i class="fa"><img src="{{ method.logo }}" height="15" width="15"></i> {{ method.name }}
<i class="fa">
<img src="{{ method.logo }}" height="15" width="15"></i> {{ method.name }}
</a>
{% endfor %}
</div>
@ -320,6 +351,7 @@
</div>
</div>
</div>
</body>
{% include '_foot_js.html' %}
<script type="text/javascript" src="/static/js/plugins/jsencrypt/jsencrypt.min.js"></script>

View File

@ -1,14 +1,13 @@
# -*- coding: utf-8 -*-
#
from django.templatetags.static import static
from django.conf import settings
from django.templatetags.static import static
from django.utils.translation import ugettext_lazy as _
default_interface = dict((
('logo_logout', static('img/logo.png')),
('logo_index', static('img/logo_text_white.png')),
('logo_text_green', static('img/logo_text_green.png')),
('login_image', static('img/login_image.jpg')),
('login_image', static('img/login_image.png')),
('favicon', static('img/facio.ico')),
('login_title', _('JumpServer Open Source Bastion Host')),
('theme', 'classic_green'),
@ -37,6 +36,3 @@ def jumpserver_processor(request):
'SECURITY_VIEW_AUTH_NEED_MFA': settings.SECURITY_VIEW_AUTH_NEED_MFA,
})
return context

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b02c5d36ea6ea96590be9a25dc6d3f1340a5af2aa940764243f85da1c756c732
size 104221
oid sha256:aec4662e56ce44daac5eea9fe6d39c21ce9b2c55cfb60006ad6f0e639329c552
size 105895

View File

@ -340,9 +340,8 @@ msgid "App assets"
msgstr "资产管理"
#: assets/automations/base/manager.py:123
#, fuzzy
msgid "{} disabled"
msgstr "禁用"
msgstr "{} 已禁用"
#: assets/const/account.py:6 audits/const.py:6 audits/const.py:64
#: common/utils/ip/geoip/utils.py:31 common/utils/ip/geoip/utils.py:37
@ -374,52 +373,45 @@ msgid "Password"
msgstr "密码"
#: assets/const/account.py:13
#, fuzzy
#| msgid "SSH Key"
msgid "SSH key"
msgstr "SSH 密钥"
#: assets/const/account.py:14 authentication/models/access_key.py:33
msgid "Access key"
msgstr "Access key"
msgstr "访问密钥"
#: assets/const/account.py:15 assets/models/_user.py:38
#: authentication/models/sso_token.py:14
msgid "Token"
msgstr "Token"
msgstr "令牌"
#: assets/const/automation.py:13
msgid "Ping"
msgstr ""
#: assets/const/automation.py:14
#, fuzzy
msgid "Gather facts"
msgstr "收集账号"
msgstr "收集资产信息"
#: assets/const/automation.py:15
#, fuzzy
msgid "Create account"
msgstr "收集账号"
msgstr "创建账号"
#: assets/const/automation.py:16
#, fuzzy
msgid "Change secret"
msgstr "执行改密"
msgstr "更改密码"
#: assets/const/automation.py:17
#, fuzzy
msgid "Verify account"
msgstr "验证密码/密钥"
msgstr "验证账号"
#: assets/const/automation.py:18
#, fuzzy
msgid "Gather accounts"
msgstr "收集账号"
#: assets/const/automation.py:38 assets/serializers/account/base.py:26
msgid "Specific"
msgstr ""
msgstr "特有的"
#: assets/const/automation.py:39 ops/const.py:20
msgid "All assets use the same random password"
@ -450,7 +442,7 @@ msgstr "主机"
#: assets/const/category.py:12
msgid "Device"
msgstr ""
msgstr "网络设备"
#: assets/const/category.py:13 assets/models/asset/database.py:8
#: assets/models/asset/database.py:34
@ -458,14 +450,13 @@ msgid "Database"
msgstr "数据库"
#: assets/const/category.py:14
#, fuzzy
msgid "Cloud service"
msgstr "云管中心"
msgstr "云服务"
#: assets/const/category.py:15 audits/const.py:62
#: terminal/models/applet/applet.py:20
msgid "Web"
msgstr ""
msgstr "Web"
#: assets/const/device.py:7 terminal/models/applet/applet.py:19
#: tickets/const.py:8
@ -473,28 +464,24 @@ msgid "General"
msgstr "一般"
#: assets/const/device.py:8
#, fuzzy
msgid "Switch"
msgstr "切换自"
msgstr "交换机"
#: assets/const/device.py:9
msgid "Router"
msgstr ""
msgstr "路由器"
#: assets/const/device.py:10
msgid "Firewall"
msgstr ""
msgstr "防火墙"
#: assets/const/types.py:181
#, fuzzy
#| msgid "MFA type"
msgid "All types"
msgstr "MFA 类型"
msgstr "所有类型"
#: assets/const/web.py:7
#, fuzzy
msgid "Website"
msgstr "网站图标"
msgstr "网站"
#: assets/models/_user.py:24
msgid "Automatic managed"
@ -656,9 +643,8 @@ msgid "Can view asset account template secret"
msgstr "可以查看资产账号密码"
#: assets/models/account.py:108
#, fuzzy
msgid "Can change asset account template secret"
msgstr "可以更改资产账号密码"
msgstr "可以更改账号模版密码"
#: assets/models/asset/common.py:103 assets/models/platform.py:109
#: assets/serializers/asset/common.py:65
@ -702,9 +688,8 @@ msgid "Can test asset connectivity"
msgstr "可以测试资产连接性"
#: assets/models/asset/common.py:226
#, fuzzy
msgid "Can push account to asset"
msgstr "可以推送系统用户到资产"
msgstr "可以推送账号到资产"
#: assets/models/asset/common.py:227
msgid "Can match asset"
@ -956,7 +941,7 @@ msgstr "校验日期"
#: assets/models/base.py:70
msgid "Privileged"
msgstr ""
msgstr "特权账号"
#: assets/models/cmd_filter.py:28 perms/models/asset_permission.py:60
#: users/models/group.py:25 users/models/user.py:681
@ -996,9 +981,9 @@ msgid "No account"
msgstr "没有账号"
#: assets/models/gateway.py:84
#, fuzzy, python-brace-format
#, python-brace-format
msgid "Unable to connect to port {port} on {address}"
msgstr "无法连接到 {ip} 上的端口 {port}"
msgstr "无法连接到 {port} 上的端口 {address}"
#: assets/models/gateway.py:87 authentication/middleware.py:76
#: xpack/plugins/cloud/providers/fc.py:48
@ -1231,10 +1216,8 @@ msgid "Account template not found"
msgstr "账号模版未找到"
#: assets/serializers/account/account.py:72
#, fuzzy
#| msgid "Asset Info"
msgid "Asset not found"
msgstr "资产信息"
msgstr "资产不存在"
#: assets/serializers/account/backup.py:29
#: assets/serializers/automations/base.py:34 ops/mixin.py:22 ops/mixin.py:102
@ -2119,7 +2102,7 @@ msgstr "超级连接令牌"
#: authentication/models/private_token.py:9
msgid "Private Token"
msgstr "SSH密钥"
msgstr "私有令牌"
#: authentication/models/sso_token.py:15
msgid "Expired"
@ -2142,8 +2125,6 @@ msgid "binding reminder"
msgstr "绑定提醒"
#: authentication/serializers/connect_token_secret.py:105
#, fuzzy
#| msgid "Builtin"
msgid "Is builtin"
msgstr "内置的"
@ -2626,18 +2607,18 @@ msgid "This field is required."
msgstr "该字段是必填项。"
#: common/drf/fields.py:78
#, fuzzy, python-brace-format
#, python-brace-format
msgid "Invalid pk \"{pk_value}\" - object does not exist."
msgstr "%s对象不存在"
msgstr "错误的 id \"{pk_value}\" - 对象不存在"
#: common/drf/fields.py:79
#, python-brace-format
msgid "Incorrect type. Expected pk value, received {data_type}."
msgstr ""
msgstr "错误类型。期望 pk 值,收到 {data_type}。"
#: common/drf/fields.py:141
msgid "Invalid data type, should be list"
msgstr ""
msgstr "错误的数据类型,应该是列表"
#: common/drf/fields.py:156
msgid "Invalid choice: {}"
@ -2653,7 +2634,7 @@ msgstr "解析文件错误: {}"
#: common/drf/serializers/common.py:86
msgid "Children"
msgstr ""
msgstr "节点"
#: common/drf/serializers/common.py:94
msgid "File"
@ -3686,7 +3667,7 @@ msgstr "企业微信 认证"
#: settings/serializers/auth/base.py:18
msgid "SSO Auth"
msgstr "SSO Token 认证"
msgstr "SSO 令牌认证"
#: settings/serializers/auth/base.py:19
msgid "SAML2 Auth"
@ -3882,8 +3863,6 @@ msgid "Enable PKCE"
msgstr "启用 PKCE"
#: settings/serializers/auth/oidc.py:43
#, fuzzy
#| msgid "Connect method"
msgid "Code challenge method"
msgstr "连接方式"
@ -4047,7 +4026,7 @@ msgstr "模板+签名不能超过65个字"
#: settings/serializers/auth/sso.py:13
msgid "Enable SSO auth"
msgstr "启用 SSO Token 认证"
msgstr "启用 SSO 令牌认证"
#: settings/serializers/auth/sso.py:14
msgid "Other service can using SSO token login to JumpServer without password"
@ -4055,7 +4034,7 @@ msgstr "其它系统可以使用 SSO Token 对接 JumpServer, 免去登录的过
#: settings/serializers/auth/sso.py:17
msgid "SSO auth key TTL"
msgstr "Token 有效期"
msgstr "令牌有效期"
#: settings/serializers/auth/sso.py:17
#: xpack/plugins/cloud/serializers/account_attrs.py:176
@ -5662,8 +5641,6 @@ msgid "Run command"
msgstr "运行的命令"
#: tickets/models/ticket/command_confirm.py:19
#, fuzzy
#| msgid "Command filter"
msgid "Command filter acl"
msgstr "命令过滤器"
@ -5712,9 +5689,8 @@ msgid "Login asset"
msgstr "登录资产"
#: tickets/models/ticket/login_asset_confirm.py:17
#, fuzzy
msgid "Login account"
msgstr "登录访问控制"
msgstr "登录账号"
#: tickets/models/ticket/login_confirm.py:12
msgid "Login datetime"
@ -6342,7 +6318,7 @@ msgstr "非本地用户仅允许从第三方平台登录,不支持修改密码
#: users/views/profile/reset.py:149 users/views/profile/reset.py:160
msgid "Token invalid or expired"
msgstr "Token错误或失效"
msgstr "令牌错误或失效"
#: users/views/profile/reset.py:165
msgid "User auth from {}, go there change password"

View File

@ -13,7 +13,8 @@ class AdHocRunner:
"reboot", 'shutdown', 'poweroff', 'halt', 'dd', 'half', 'top'
]
def __init__(self, inventory, module, module_args='', pattern='*', project_dir='/tmp/', extra_vars={}):
def __init__(self, inventory, module, module_args='', pattern='*', project_dir='/tmp/', extra_vars={},
dry_run=False):
self.id = uuid.uuid4()
self.inventory = inventory
self.pattern = pattern
@ -23,6 +24,7 @@ class AdHocRunner:
self.cb = DefaultCallback()
self.runner = None
self.extra_vars = extra_vars
self.dry_run = dry_run
def check_module(self):
if self.module not in self.cmd_modules_choices:

View File

@ -1,3 +1,4 @@
from django.db.models import Count
from rest_framework.views import APIView
from django.shortcuts import get_object_or_404
from rest_framework.response import Response
@ -5,12 +6,14 @@ from rest_framework.response import Response
from ops.models import Job, JobExecution
from ops.serializers.job import JobSerializer, JobExecutionSerializer
__all__ = ['JobViewSet', 'JobExecutionViewSet', 'JobRunVariableHelpAPIView', 'JobAssetDetail', 'JobExecutionTaskDetail']
__all__ = ['JobViewSet', 'JobExecutionViewSet', 'JobRunVariableHelpAPIView',
'JobAssetDetail', 'JobExecutionTaskDetail','FrequentUsernames']
from ops.tasks import run_ops_job_execution
from ops.variables import JMS_JOB_VARIABLE_HELP
from orgs.mixins.api import OrgBulkModelViewSet
from orgs.utils import tmp_to_org, get_current_org_id, get_current_org
from assets.models import Account
def set_task_to_serializer_data(serializer, task):
@ -111,3 +114,12 @@ class JobExecutionTaskDetail(APIView):
'is_success': execution.is_success,
'time_cost': execution.time_cost,
})
class FrequentUsernames(APIView):
rbac_perms = ()
permission_classes = ()
def get(self, request, **kwargs):
top_accounts = Account.objects.all().values('username').annotate(total=Count('username')).order_by('total')
return Response(data=top_accounts)

View File

@ -166,6 +166,10 @@ class JobExecution(JMSOrgBaseModel):
return
result = self.current_job.args
result += " chdir={}".format(self.current_job.chdir)
if self.current_job.module in ['python']:
result += " executable={}".format(self.current_job.module)
print(result)
return self.job.args
def get_runner(self):
@ -187,9 +191,17 @@ class JobExecution(JMSOrgBaseModel):
if self.current_job.type == 'adhoc':
args = self.compile_shell()
module = "shell"
if self.current_job.module not in ['python']:
module = self.current_job.module
runner = AdHocRunner(
self.inventory_path, self.current_job.module, module_args=args,
pattern="all", project_dir=self.private_dir, extra_vars=extra_vars,
self.inventory_path,
module,
module_args=args,
pattern="all",
project_dir=self.private_dir,
extra_vars=extra_vars,
)
elif self.current_job.type == 'playbook':
runner = PlaybookRunner(

View File

@ -1,5 +1,7 @@
from django.utils.translation import ugettext as _
from rest_framework import serializers
from assets.models import Node
from common.drf.fields import ReadableHiddenField
from ops.mixin import PeriodTaskSerializerMixin
from ops.models import Job, JobExecution
@ -10,6 +12,16 @@ from orgs.mixins.serializers import BulkOrgResourceModelSerializer
class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin):
creator = ReadableHiddenField(default=serializers.CurrentUserDefault())
run_after_save = serializers.BooleanField(label=_("Run after save"), default=False, required=False)
nodes = serializers.ListField(required=False, child=serializers.CharField())
def create(self, validated_data):
assets = validated_data.__getitem__('assets')
node_ids = validated_data.pop('nodes')
if node_ids:
nodes = Node.objects.filter(id__in=node_ids)
assets.extend(
Node.get_nodes_all_assets(*nodes).exclude(id__in=[asset.id for asset in assets]))
return super().create(validated_data)
class Meta:
model = Job
@ -22,7 +34,7 @@ class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin):
"chdir",
"comment",
"summary",
"is_periodic", "interval", "crontab", "run_after_save"
"is_periodic", "interval", "crontab", "run_after_save", "nodes"
]

View File

@ -26,6 +26,7 @@ urlpatterns = [
path('variables/help/', api.JobRunVariableHelpAPIView.as_view(), name='variable-help'),
path('job-execution/asset-detail/', api.JobAssetDetail.as_view(), name='asset-detail'),
path('job-execution/task-detail/', api.JobExecutionTaskDetail.as_view(), name='task-detail'),
path('frequent-username/', api.FrequentUsernames.as_view(), name='frequent-usernames'),
path('ansible/job-execution/<uuid:pk>/log/', api.AnsibleTaskLogApi.as_view(), name='job-execution-log'),
path('celery/task/<uuid:name>/task-execution/<uuid:pk>/log/', api.CeleryTaskExecutionLogApi.as_view(),

View File

@ -25,23 +25,25 @@ user_permission_urlpatterns = [
name='user-direct-assets-as-tree'),
path('<str:user>/ungroup/assets/tree/', api.UserUngroupAssetsAsTreeApi.as_view(),
name='user-ungroup-assets-as-tree'),
# tree-node
# tree-node不包含资产
path('<str:user>/nodes/tree/', api.UserAllPermedNodesAsTreeApi.as_view(),
name='user-all-nodes-as-tree'),
path('<str:user>/nodes/children/tree/', api.UserPermedNodeChildrenAsTreeApi.as_view(),
name='user-node-children-as-tree'),
# tree-node-with-asset
# 异步树
path('<str:user>/nodes/children-with-assets/tree/',
api.UserPermedNodeChildrenWithAssetsAsTreeApi.as_view(),
name='user-node-children-with-assets-as-tree'),
# 同步树
path('<str:user>/nodes/all-with-assets/tree/',
api.UserPermedNodesWithAssetsAsTreeApi.as_view(),
name='user-nodes-with-assets-as-tree'),
path('<str:user>/nodes/children-with-k8s/tree/',
api.UserGrantedK8sAsTreeApi.as_view(),
name='user-nodes-children-with-k8s-as-tree'),
# 同步树
path('<str:user>/nodes-with-assets/tree/', api.UserPermedNodesWithAssetsAsTreeApi.as_view(),
name='user-nodes-with-assets-as-tree'),
# accounts
path('<str:user>/assets/<uuid:asset_id>/accounts/', api.UserPermedAssetAccountsApi.as_view(),
name='user-permed-asset-accounts'),

Binary file not shown.

Before

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -26,8 +26,14 @@ logger = get_logger(__file__)
class RolesSerializerMixin(serializers.Serializer):
system_roles = ObjectRelatedField(queryset=Role.system_roles, label=_("System roles"), many=True)
org_roles = ObjectRelatedField(queryset=Role.org_roles, label=_("Org roles"), many=True)
system_roles = ObjectRelatedField(
queryset=Role.system_roles, attrs=('id', 'display_name'),
label=_("System roles"), many=True
)
org_roles = ObjectRelatedField(
queryset=Role.org_roles, attrs=('id', 'display_name'),
label=_("Org roles"), many=True
)
@staticmethod
def get_system_roles_display(user):