Unverified Commit 829e1f4c authored by BaiJiangJie's avatar BaiJiangJie Committed by GitHub

[Update] 修改用户详情页面 (#3555)

* [Update] 用户详情添加远程应用授权页面

* [Update] 用户详情添加授权的远程应用页面

* [Update] 用户详情添加授权的数据库应用页面

* [Update] 用户详情添加数据库应用授权页面

* [Update] 修改用户详情nav的active属性设置

* [Update] 修改用户详情页面导航

* [Update] 抽象用户详情页面

* [Update] 修改用户详情页面

* [Update] 修改用户详情页面nav header
parent b365ba79
...@@ -65,7 +65,7 @@ function initTable() { ...@@ -65,7 +65,7 @@ function initTable() {
{data: "port"}, {data: "port"},
{data: "database"}, {data: "database"},
{data: "comment"}, {data: "comment"},
{data: "id", orderable: false, width: "100px"} {data: "id", orderable: false, width: "120px"}
], ],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
......
...@@ -69,7 +69,7 @@ function initTable() { ...@@ -69,7 +69,7 @@ function initTable() {
{data: "get_type_display", orderable: false}, {data: "get_type_display", orderable: false},
{data: "asset_info", orderable: false}, {data: "asset_info", orderable: false},
{data: "comment"}, {data: "comment"},
{data: "id", orderable: false, width: "100px"} {data: "id", orderable: false, width: "120px"}
], ],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
......
...@@ -51,7 +51,7 @@ function initTable() { ...@@ -51,7 +51,7 @@ function initTable() {
columns: [ columns: [
{data: function(){return ""}}, {data: "name"}, {data: "username" }, {data: "assets_amount", orderable: false}, {data: function(){return ""}}, {data: "name"}, {data: "username" }, {data: "assets_amount", orderable: false},
{#{data: "connectivity_amount"}, {data: "connectivity_amount"}, {data: "connectivity_amount"},#} {#{data: "connectivity_amount"}, {data: "connectivity_amount"}, {data: "connectivity_amount"},#}
{data: "comment"}, {data: "id", orderable: false, width: "100px"} {data: "comment"}, {data: "id", orderable: false, width: "120px"}
] ]
}; };
return jumpserver.initServerSideDataTable(options); return jumpserver.initServerSideDataTable(options);
......
...@@ -99,7 +99,7 @@ function initTable() { ...@@ -99,7 +99,7 @@ function initTable() {
data: "connectivity", data: "connectivity",
orderable: false, orderable: false,
width: '60px' width: '60px'
}, {data: "id", orderable: false, width: "100px"} }, {data: "id", orderable: false, width: "120px"}
], ],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
......
...@@ -62,7 +62,7 @@ function initTable() { ...@@ -62,7 +62,7 @@ function initTable() {
columns: [ columns: [
{data: "id"}, {data: "name" }, {data: "rules", orderable: false}, {data: "id"}, {data: "name" }, {data: "rules", orderable: false},
{data: "system_users", orderable: false}, {data: "comment"}, {data: "system_users", orderable: false}, {data: "comment"},
{data: "id", orderable: false, width: "100px"} {data: "id", orderable: false, width: "120px"}
], ],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
......
...@@ -55,7 +55,7 @@ function initTable() { ...@@ -55,7 +55,7 @@ function initTable() {
ajax_url: '{% url "api-assets:domain-list" %}', ajax_url: '{% url "api-assets:domain-list" %}',
columns: [ columns: [
{data: "id"}, {data: "name" }, {data: "asset_count", orderable: false }, {data: "id"}, {data: "name" }, {data: "asset_count", orderable: false },
{data: "gateway_count", orderable: false }, {data: "comment" }, {data: "id", orderable: false, width: "100px"} {data: "gateway_count", orderable: false }, {data: "comment" }, {data: "id", orderable: false, width: "120px"}
], ],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
......
...@@ -45,7 +45,7 @@ function initTable() { ...@@ -45,7 +45,7 @@ function initTable() {
columns: [ columns: [
{data: "id"}, {data: "name" }, {data: "value" }, {data: "id"}, {data: "name" }, {data: "value" },
{data: "asset_count", orderable: false}, {data: "asset_count", orderable: false},
{data: "id", orderable: false, width: "100px"} {data: "id", orderable: false, width: "120px"}
], ],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
......
...@@ -50,7 +50,7 @@ function initTable() { ...@@ -50,7 +50,7 @@ function initTable() {
ajax_url: '{% url "api-assets:platform-list" %}', ajax_url: '{% url "api-assets:platform-list" %}',
columns: [ columns: [
{data: "id"}, {data: "name"}, {data: "base" }, {data: "id"}, {data: "name"}, {data: "base" },
{data: "comment"}, {data: "id", orderable: false, width: "100px"} {data: "comment"}, {data: "id", orderable: false, width: "120px"}
] ]
}; };
platformTable = jumpserver.initServerSideDataTable(options); platformTable = jumpserver.initServerSideDataTable(options);
......
...@@ -62,7 +62,7 @@ function initTable() { ...@@ -62,7 +62,7 @@ function initTable() {
columns: [ columns: [
{data: "id" }, {data: "name" }, {data: "username" }, {data: "protocol"}, {data: "id" }, {data: "name" }, {data: "username" }, {data: "protocol"},
{data: "login_mode"}, {data: "assets_amount", orderable: false }, {data: "login_mode"}, {data: "assets_amount", orderable: false },
{data: "comment" }, {data: "id", orderable: false, width: "100px"} {data: "comment" }, {data: "id", orderable: false, width: "120px"}
], ],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
......
...@@ -62,7 +62,7 @@ function initAccessKeyTable() { ...@@ -62,7 +62,7 @@ function initAccessKeyTable() {
}}, }},
{targets: 5, createdCell: function (td, cellData, rowData) { {targets: 5, createdCell: function (td, cellData, rowData) {
var btn = ''; var btn = '';
var btn_del = '<a class="btn btn-xs btn-danger m-l-xs btn-del" data-id="ID">{% trans "Delete" %}</a>'; var btn_del = '<a class="btn btn-xs btn-danger m-l-xs btn-api-keydel" data-id="ID">{% trans "Delete" %}</a>';
var btn_inactive = '<a class="btn btn-xs btn-info m-l-xs btn-inactive" data-id="ID">{% trans "Disable" %}</a>'; var btn_inactive = '<a class="btn btn-xs btn-info m-l-xs btn-inactive" data-id="ID">{% trans "Disable" %}</a>';
var btn_active = '<a class="btn btn-xs btn-primary m-l-xs btn-active" data-id="ID">{% trans "Enable" %}</a>'; var btn_active = '<a class="btn btn-xs btn-primary m-l-xs btn-active" data-id="ID">{% trans "Enable" %}</a>';
...@@ -107,7 +107,7 @@ $(document).ready(function () { ...@@ -107,7 +107,7 @@ $(document).ready(function () {
}).on("click", ".btn-secret", function () { }).on("click", ".btn-secret", function () {
var $this = $(this); var $this = $(this);
$this.parent().html($this.data("secret")) $this.parent().html($this.data("secret"))
}).on("click", ".btn-del", function () { }).on("click", ".btn-api-key-del", function () {
var url = "{% url "api-auth:access-key-detail" pk=DEFAULT_PK %}"; var url = "{% url "api-auth:access-key-detail" pk=DEFAULT_PK %}";
url = url.replace("{{ DEFAULT_PK }}", $(this).data("id")) ; url = url.replace("{{ DEFAULT_PK }}", $(this).data("id")) ;
objectDelete($(this), $(this).data("id"), url); objectDelete($(this), $(this).data("id"), url);
......
...@@ -12,7 +12,10 @@ __all__ = ['DatabaseAppPermissionViewSet'] ...@@ -12,7 +12,10 @@ __all__ = ['DatabaseAppPermissionViewSet']
class DatabaseAppPermissionViewSet(OrgBulkModelViewSet): class DatabaseAppPermissionViewSet(OrgBulkModelViewSet):
model = models.DatabaseAppPermission model = models.DatabaseAppPermission
serializer_class = serializers.DatabaseAppPermissionSerializer serializer_classes = {
'default': serializers.DatabaseAppPermissionSerializer,
'display': serializers.DatabaseAppPermissionListSerializer
}
filter_fields = ('name',) filter_fields = ('name',)
search_fields = filter_fields search_fields = filter_fields
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
...@@ -11,6 +11,7 @@ from ..serializers import ( ...@@ -11,6 +11,7 @@ from ..serializers import (
RemoteAppPermissionSerializer, RemoteAppPermissionSerializer,
RemoteAppPermissionUpdateUserSerializer, RemoteAppPermissionUpdateUserSerializer,
RemoteAppPermissionUpdateRemoteAppSerializer, RemoteAppPermissionUpdateRemoteAppSerializer,
RemoteAppPermissionListSerializer,
) )
...@@ -25,7 +26,10 @@ class RemoteAppPermissionViewSet(OrgModelViewSet): ...@@ -25,7 +26,10 @@ class RemoteAppPermissionViewSet(OrgModelViewSet):
model = RemoteAppPermission model = RemoteAppPermission
filter_fields = ('name', ) filter_fields = ('name', )
search_fields = filter_fields search_fields = filter_fields
serializer_class = RemoteAppPermissionSerializer serializer_classes = {
'default': RemoteAppPermissionSerializer,
'display': RemoteAppPermissionListSerializer,
}
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
......
# coding: utf-8 # coding: utf-8
# #
from rest_framework import serializers
from common.fields import StringManyToManyField
from common.serializers import AdaptedBulkListSerializer from common.serializers import AdaptedBulkListSerializer
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from .. import models from .. import models
__all__ = ['DatabaseAppPermissionSerializer'] __all__ = [
'DatabaseAppPermissionSerializer', 'DatabaseAppPermissionListSerializer'
]
class DatabaseAppPermissionSerializer(BulkOrgResourceModelSerializer): class DatabaseAppPermissionSerializer(BulkOrgResourceModelSerializer):
...@@ -19,3 +24,16 @@ class DatabaseAppPermissionSerializer(BulkOrgResourceModelSerializer): ...@@ -19,3 +24,16 @@ class DatabaseAppPermissionSerializer(BulkOrgResourceModelSerializer):
'created_by', 'date_created' 'created_by', 'date_created'
] ]
read_only_fields = ['created_by', 'date_created'] read_only_fields = ['created_by', 'date_created']
class DatabaseAppPermissionListSerializer(BulkOrgResourceModelSerializer):
users = StringManyToManyField(many=True, read_only=True)
user_groups = StringManyToManyField(many=True, read_only=True)
database_apps = StringManyToManyField(many=True, read_only=True)
system_users = StringManyToManyField(many=True, read_only=True)
is_valid = serializers.BooleanField()
is_expired = serializers.BooleanField()
class Meta:
model = models.DatabaseAppPermission
fields = '__all__'
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
from rest_framework import serializers from rest_framework import serializers
from common.fields import StringManyToManyField
from common.serializers import AdaptedBulkListSerializer from common.serializers import AdaptedBulkListSerializer
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from ..models import RemoteAppPermission from ..models import RemoteAppPermission
...@@ -12,6 +13,7 @@ __all__ = [ ...@@ -12,6 +13,7 @@ __all__ = [
'RemoteAppPermissionSerializer', 'RemoteAppPermissionSerializer',
'RemoteAppPermissionUpdateUserSerializer', 'RemoteAppPermissionUpdateUserSerializer',
'RemoteAppPermissionUpdateRemoteAppSerializer', 'RemoteAppPermissionUpdateRemoteAppSerializer',
'RemoteAppPermissionListSerializer',
] ]
...@@ -27,6 +29,19 @@ class RemoteAppPermissionSerializer(BulkOrgResourceModelSerializer): ...@@ -27,6 +29,19 @@ class RemoteAppPermissionSerializer(BulkOrgResourceModelSerializer):
read_only_fields = ['created_by', 'date_created'] read_only_fields = ['created_by', 'date_created']
class RemoteAppPermissionListSerializer(BulkOrgResourceModelSerializer):
users = StringManyToManyField(many=True, read_only=True)
user_groups = StringManyToManyField(many=True, read_only=True)
remote_apps = StringManyToManyField(many=True, read_only=True)
system_users = StringManyToManyField(many=True, read_only=True)
is_valid = serializers.BooleanField()
is_expired = serializers.BooleanField()
class Meta:
model = RemoteAppPermission
fields = '__all__'
class RemoteAppPermissionUpdateUserSerializer(serializers.ModelSerializer): class RemoteAppPermissionUpdateUserSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = RemoteAppPermission model = RemoteAppPermission
......
...@@ -156,7 +156,7 @@ function initTable() { ...@@ -156,7 +156,7 @@ function initTable() {
{data: "id"}, {data: "name"}, {data: "users", orderable: false}, {data: "id"}, {data: "name"}, {data: "users", orderable: false},
{data: "user_groups", orderable: false}, {data: "assets", orderable: false}, {data: "user_groups", orderable: false}, {data: "assets", orderable: false},
{data: "nodes", orderable: false}, {data: "system_users", orderable: false}, {data: "nodes", orderable: false}, {data: "system_users", orderable: false},
{data: "is_valid", orderable: false}, {data: "id", orderable: false, width: "100px"} {data: "is_valid", orderable: false}, {data: "id", orderable: false, width: "120px"}
], ],
select: {}, select: {},
op_html: $('#actions').html() op_html: $('#actions').html()
......
...@@ -75,7 +75,7 @@ function initTable() { ...@@ -75,7 +75,7 @@ function initTable() {
{data: "database_apps", orderable: false}, {data: "database_apps", orderable: false},
{data: "system_users", orderable: false}, {data: "system_users", orderable: false},
{data: "is_valid", orderable: false}, {data: "is_valid", orderable: false},
{data: "id", orderable: false, width: "100px"} {data: "id", orderable: false, width: "120px"}
], ],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
......
...@@ -75,7 +75,7 @@ function initTable() { ...@@ -75,7 +75,7 @@ function initTable() {
{data: "remote_apps", orderable: false}, {data: "remote_apps", orderable: false},
{data: "system_users", orderable: false}, {data: "system_users", orderable: false},
{data: "is_valid", orderable: false}, {data: "is_valid", orderable: false},
{data: "id", orderable: false, width: "100px"} {data: "id", orderable: false, width: "120px"}
], ],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
......
...@@ -158,7 +158,7 @@ function activeNav(prefix) { ...@@ -158,7 +158,7 @@ function activeNav(prefix) {
} else { } else {
$("#" + app).addClass('active'); $("#" + app).addClass('active');
$('#' + app + ' #' + resource).addClass('active'); $('#' + app + ' #' + resource).addClass('active');
$('#' + app + ' #' + resource.replaceAll('-', '_')).addClass('active'); $('#' + app + ' #' + resource.replace(/-/g, '_')).addClass('active');
} }
} }
......
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
{% include 'users/_user_detail_nav_header.html' %}
{% block content_nav_delete_update %}
{% endblock %}
</ul>
</div>
<div class="tab-content">
{% block content_table %}
{% endblock %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% load static %}
{% load i18n %}
<style>
.nav .open>a, .nav .open>a:hover, .nav .open>a:focus{
border-color: white;
}
</style>
<li id="id_nav_user_detail">
<a href="{% url 'users:user-detail' pk=object.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'User detail' %} </a>
</li>
<li id="id_nav_user_detail_user_permission" class="btn-group">
<a class="btn btn-sm dropdown-toggle" data-toggle="dropdown">
<span>{% trans "User permissions" %}</span>
<i class="caret"></i>
</a>
<ul class="dropdown-menu">
<li id="id_nav_user_detail_assets">
<a href="{% url 'users:user-granted-asset' pk=object.id %}" class="text-center">
<i class="fa fa-cubes"></i>
<span>{% trans 'Asset granted' %}</span>
<i class="highlight-circle"></i>
</a>
</li>
<li id="id_nav_user_detail_asset_permissions">
<a href="{% url 'users:user-asset-permission' pk=object.id %}" class="text-center">
<i class="fa fa-edit"></i>
<span>{% trans 'Asset permission' %}</span>
<i class="highlight-circle"></i>
</a>
</li>
{% if LICENSE_VALID %}
<li id="id_nav_user_detail_remote_apps">
<a href="{% url 'users:user-granted-remote-app' pk=object.id %}" class="text-center">
<i class="fa fa-desktop"></i>
<span>{% trans 'RemoteApp granted' %}</span>
<i class="highlight-circle"></i>
</a>
</li>
<li id="id_nav_user_detail_remote_app_permissions">
<a href="{% url 'users:user-remote-app-permission' pk=object.id %}" class="text-center">
<i class="fa fa-edit"></i>
<span>{% trans 'RemoteApp permission' %}</span>
<i class="highlight-circle"></i>
</a>
</li>
<li id="id_nav_user_detail_database_apps">
<a href="{% url 'users:user-granted-database-app' pk=object.id %}" class="text-center">
<i class="fa fa-database"></i>
<span>{% trans 'DatabaseApp granted' %}</span>
<i class="highlight-circle"></i>
</a>
</li>
<li id="id_nav_user_detail_database_app_permissions">
<a href="{% url 'users:user-database-app-permission' pk=object.id %}" class="text-center">
<i class="fa fa-edit"></i>
<span>{% trans 'DatabaseApp permission' %}</span>
<i class="highlight-circle"></i>
</a>
</li>
{% endif %}
</ul>
</li>
<script>
function activeUserDetailNav(prefix) {
var path = document.location.pathname;
if (prefix) {
path = path.replace(prefix, '');
console.log(path);
}
var navId, navId2 = '';
var idPrefix = 'id_nav_user_detail';
var navUserDetailId = "#" + idPrefix;
var navUserPermissionId = "#" + idPrefix + "_user_permission";
var urlArray = path.split("/");
var page = urlArray[urlArray.length-2];
if (page === "{{ object.id }}" || page === undefined){
navId = navUserDetailId;
}
else{
navId = navUserPermissionId;
navId2 = "#" + idPrefix + '_' + page.replace(/-/g, '_');
var highlightCircle = '<span class="fa fa-circle" style="padding-left: 5px; color: #1ab394"></span>';
$(navId2 + '>a>i.highlight-circle').html(highlightCircle);
$(navId + '>a>span').html($(navId2 + '>a>span').html());
}
$(navId).addClass('active')
}
activeUserDetailNav("{{ FORCE_SCRIPT_NAME }}");
</script>
\ No newline at end of file
{% extends 'base.html' %} {% extends 'users/_base_user_detail.html' %}
{% load static %} {% load static %}
{% load i18n %} {% load i18n %}
...@@ -6,26 +6,10 @@ ...@@ -6,26 +6,10 @@
<link href="{% static "css/plugins/sweetalert/sweetalert.css" %}" rel="stylesheet"> <link href="{% static "css/plugins/sweetalert/sweetalert.css" %}" rel="stylesheet">
<script src="{% static "js/plugins/sweetalert/sweetalert.min.js" %}"></script> <script src="{% static "js/plugins/sweetalert/sweetalert.min.js" %}"></script>
{% endblock %} {% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row"> {% block content_table %}
<div class="col-sm-12"> <div class="col-sm-10" style="padding-left: 0">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li>
<a href="{% url 'users:user-detail' pk=object.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'User detail' %} </a>
</li>
<li>
<a href="{% url 'users:user-granted-asset' pk=object.id %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Asset granted' %}</a>
</li>
<li class="active">
<a href="{% url 'users:user-asset-permission' pk=object.id %}" class="text-center"><i class="fa fa-edit"></i> {% trans 'Asset permission' %}</a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-10" style="padding-left: 0">
<div class="ibox float-e-margins"> <div class="ibox float-e-margins">
<div class="ibox-title"> <div class="ibox-title">
<span class="label"><b>{{ object.name }}</b></span> <span class="label"><b>{{ object.name }}</b></span>
...@@ -44,7 +28,6 @@ ...@@ -44,7 +28,6 @@
</div> </div>
</div> </div>
<div class="ibox-content"> <div class="ibox-content">
<div class="mail-box-header">
<table class="table table-striped table-bordered table-hover" <table class="table table-striped table-bordered table-hover"
id="permission_list_table" id="permission_list_table"
style="width: 100%"> style="width: 100%">
...@@ -66,15 +49,12 @@ ...@@ -66,15 +49,12 @@
</table> </table>
</div> </div>
</div> </div>
</div> </div>
</div>
</div> {% include '_filter_dropdown.html' %}
</div>
</div>
</div>
</div>
{% include '_filter_dropdown.html' %}
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
<script> <script>
jumpserver.nodes_selected = {}; jumpserver.nodes_selected = {};
...@@ -159,7 +139,7 @@ function initTable() { ...@@ -159,7 +139,7 @@ function initTable() {
{data: "id"}, {data: "name"}, {data: "users", orderable: false}, {data: "id"}, {data: "name"}, {data: "users", orderable: false},
{data: "user_groups", orderable: false}, {data: "assets", orderable: false}, {data: "user_groups", orderable: false}, {data: "assets", orderable: false},
{data: "nodes", orderable: false}, {data: "system_users", orderable: false}, {data: "nodes", orderable: false}, {data: "system_users", orderable: false},
{data: "is_valid", orderable: false}, {data: "id", orderable: false, width: "100px"} {data: "is_valid", orderable: false}, {data: "id", orderable: false, width: "120px"}
], ],
select: {}, select: {},
op_html: $('#actions').html() op_html: $('#actions').html()
...@@ -184,19 +164,6 @@ $(document).ready(function() { ...@@ -184,19 +164,6 @@ $(document).ready(function() {
]; ];
initTableFilterDropdown('#permission_list_table_filter input', filterMenu) initTableFilterDropdown('#permission_list_table_filter input', filterMenu)
}) })
.on('click', '#is_active', function() {
var the_url = "{% url 'api-users:user-detail' pk=object.id %}";
var checked = $(this).prop('checked');
var body = {
'is_active': checked
};
var success = '{% trans "Update successfully!" %}';
requestApi({
url: the_url,
body: JSON.stringify(body),
success_message: success
});
})
.on('click', '.toggle', function (e) { .on('click', '.toggle', function (e) {
e.preventDefault(); e.preventDefault();
var detailRows = []; var detailRows = [];
...@@ -222,5 +189,13 @@ $(document).ready(function() { ...@@ -222,5 +189,13 @@ $(document).ready(function() {
} }
} }
}) })
.on('click', '.btn-del', function () {
var $this = $(this);
var uid = $this.data('uid');
var name = $this.data('name');
var the_url = '{% url "api-perms:asset-permission-detail" pk=DEFAULT_PK %}'
.replace('{{ DEFAULT_PK }}', uid);
objectDelete($this, name, the_url);
})
</script> </script>
{% endblock %} {% endblock %}
{% extends 'users/_base_user_detail.html' %}
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
<link href="{% static "css/plugins/sweetalert/sweetalert.css" %}" rel="stylesheet">
<script src="{% static "js/plugins/sweetalert/sweetalert.min.js" %}"></script>
{% endblock %}
{% block content_table %}
<div class="col-sm-10" style="padding-left: 0">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span class="label"><b>{{ object.name }}</b></span>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<ul class="dropdown-menu dropdown-user">
</ul>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<table class="table table-striped table-bordered table-hover"
id="permission_list_table"
style="width: 100%">
<thead>
<tr>
<th></th>
<th>{% trans 'Name' %}</th>
<th class="text-center">{% trans 'User' %}</th>
<th class="text-center">{% trans 'User group' %}</th>
<th class="text-center">{% trans 'DatabaseApp' %}</th>
<th class="text-center">{% trans 'System user' %}</th>
<th class="text-center">{% trans 'Validity' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
function format(d) {
var data = "";
if (d.users.length > 0 ) {
data += makeLabel(["{% trans 'User' %}", d.users.join(", ")])
}
if (d.user_groups.length > 0) {
data += makeLabel(["{% trans 'User group' %}", d.user_groups.join(", ")])
}
if (d.database_apps.length > 0) {
data += makeLabel(["{% trans 'DatabaseApp' %}", d.database_apps.join(", ")])
}
if (d.system_users.length > 0) {
data += makeLabel(["{% trans 'System user' %}", d.system_users.join(", ")])
}
return data
}
function initTable() {
var options = {
ele: $('#permission_list_table'),
toggle: true,
columnDefs: [
{targets: 0, createdCell: function (td, cellData, rowData) {
$(td).addClass("toggle");
$(td).html("<i class='fa fa-angle-right'></i>");
}},
{targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
var detail_btn = '<a href="{% url "perms:database-app-permission-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 2, createdCell: function (td, cellData) {
var num = cellData.length;
$(td).html(num);
}},
{targets: 3, createdCell: function (td, cellData) {
var num = cellData.length;
$(td).html(num);
}},
{targets: 4, createdCell: function (td, cellData) {
var num = cellData.length;
$(td).html(num);
}},
{targets: 5, createdCell: function (td, cellData) {
var num = cellData.length;
$(td).html(num);
}},
{targets: 6, createdCell: function (td, cellData) {
if (!cellData) {
$(td).html('<i class="fa fa-times text-danger"></i>')
} else {
$(td).html('<i class="fa fa-check text-navy"></i>')
}
}},
{targets: 7, createdCell: function (td, cellData, rowData) {
var name = htmlEscape(rowData.name);
var update_btn = '<a href="{% url "perms:database-app-permission-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-del" data-uid="{{ DEFAULT_PK }}" mark=1 data-name="99991938">{% trans "Delete" %}</a>'
.replace('{{ DEFAULT_PK }}', cellData)
.replace('99991938', name);
$(td).html(update_btn + del_btn);
}}
],
ajax_url: '{% url "api-perms:database-app-permission-list" %}?user_id={{ object.id }}',
columns: [
{data: "id"}, {data: "name"}, {data: "users", orderable: false},
{data: "user_groups", orderable: false}, {data: "database_apps", orderable: false},
{data: "system_users", orderable: false}, {data: "is_valid", orderable: false},
{data: "id", orderable: false, width: "120px"}
],
select: {},
op_html: $('#actions').html()
};
table = jumpserver.initServerSideDataTable(options);
return table
}
$(document).ready(function() {
initTable();
})
.on('click', '.toggle', function (e) {
e.preventDefault();
var detailRows = [];
var tr = $(this).closest('tr');
var row = table.row(tr);
var idx = $.inArray(tr.attr('id'), detailRows);
if (row.child.isShown()) {
tr.removeClass('details');
$(this).children('i:first-child').removeClass('fa-angle-down').addClass('fa-angle-right');
row.child.hide();
// Remove from the 'open' array
detailRows.splice(idx, 1);
}
else {
tr.addClass('details');
$(this).children('i:first-child').removeClass('fa-angle-right').addClass('fa-angle-down');
row.child(format(row.data())).show();
// Add to the 'open' array
if ( idx === -1 ) {
detailRows.push(tr.attr('id'));
}
}
})
.on('click', '.btn-del', function () {
var $this = $(this);
var uid = $this.data('uid');
var name = $this.data('name');
var the_url = '{% url "api-perms:database-app-permission-detail" pk=DEFAULT_PK %}'
.replace('{{ DEFAULT_PK }}', uid);
objectDelete($this, name, the_url);
})
</script>
{% endblock %}
This diff is collapsed.
{% extends 'base.html' %} {% extends 'users/_base_user_detail.html' %}
{% load bootstrap3 %} {% load bootstrap3 %}
{% load static %} {% load static %}
{% load i18n %} {% load i18n %}
...@@ -7,32 +7,11 @@ ...@@ -7,32 +7,11 @@
<link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet"> <link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet">
<script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script> <script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>
{% endblock %} {% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight"> {% block content_table %}
<div class="row"> {% include 'users/_granted_assets.html' %}
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li>
<a href="{% url 'users:user-detail' pk=object.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'User detail' %} </a>
</li>
<li class="active">
<a href="{% url 'users:user-granted-asset' pk=object.id %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Asset granted' %}</a>
</li>
<li>
<a href="{% url 'users:user-asset-permission' pk=object.id %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Asset permission' %}</a>
</li>
</ul>
</div>
<div class="tab-content">
{% include 'users/_granted_assets.html' %}
</div>
</div>
</div>
</div>
</div>
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
<script> <script>
var assetTableUrl = "{% url 'api-perms:user-assets' pk=object.id %}?cache_policy=1"; var assetTableUrl = "{% url 'api-perms:user-assets' pk=object.id %}?cache_policy=1";
......
{% extends 'users/_base_user_detail.html' %}
{% load i18n static %}
{% block custom_head_css_js %}
<script src="{% static 'js/jquery.form.min.js' %}"></script>
{% endblock %}
{% block content_table %}
<div class="col-sm-10" style="padding-left: 0">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span class="label"><b>{{ object.name }}</b></span>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<ul class="dropdown-menu dropdown-user">
</ul>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<table class="table table-striped table-bordered table-hover " id="database_app_list_table" >
<thead>
<tr>
<th class="text-center">
<input type="checkbox" id="check_all" class="ipt_check_all" >
</th>
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'Type' %}</th>
<th class="text-center">{% trans 'Host' %}</th>
<th class="text-center">{% trans 'Database' %}</th>
<th class="text-center">{% trans 'Comment' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
var inited = false;
var database_app_table, url;
function initTable() {
if (inited){
return
} else {
inited = true;
}
url = '{% url "api-perms:user-database-apps" pk=object.id %}';
var options = {
ele: $('#database_app_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
{% url 'applications:database-app-detail' pk=DEFAULT_PK as the_url %}
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 2, createdCell: function (td, cellData, rowData) {
var type = htmlEscape(rowData.get_type_display);
$(td).html(type);
}},
{targets: 3, createdCell: function (td, cellData, rowData) {
var host = htmlEscape(cellData);
$(td).html(host);
}},
{targets: 4, createdCell: function (td, cellData, rowData) {
var database = htmlEscape(cellData);
$(td).html(database);
}}
],
ajax_url: url,
columns: [
{data: "id"},
{data: "name"},
{data: "type"},
{data: "host"},
{data: "database"},
{data: "comment", orderable: false},
]
};
database_app_table = jumpserver.initServerSideDataTable(options);
return database_app_table
}
$(document).ready(function(){
initTable();
})
</script>
{% endblock %}
{% extends 'users/_base_user_detail.html' %}
{% load i18n static %}
{% block custom_head_css_js %}
<script src="{% static 'js/jquery.form.min.js' %}"></script>
{% endblock %}
{% block content_table %}
<div class="col-sm-10" style="padding-left: 0">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span class="label"><b>{{ object.name }}</b></span>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<ul class="dropdown-menu dropdown-user">
</ul>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<table class="table table-striped table-bordered table-hover " id="remote_app_list_table" >
<thead>
<tr>
<th class="text-center">
<input type="checkbox" id="check_all" class="ipt_check_all" >
</th>
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'App type' %}</th>
<th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'Comment' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
var inited = false;
var remote_app_table, url;
function initTable() {
if (inited){
return
} else {
inited = true;
}
url = '{% url "api-perms:user-remote-apps" pk=object.id %}';
var options = {
ele: $('#remote_app_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
{% url 'applications:remote-app-detail' pk=DEFAULT_PK as the_url %}
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 1, createdCell: function (td, cellData, rowData) {
$(td).html(rowData.get_type_display)
}},
{targets: 3, createdCell: function (td, cellData, rowData) {
var hostname = htmlEscape(cellData.hostname);
$(td).html(hostname);
}}
],
ajax_url: url,
columns: [
{data: "id"},
{data: "name"},
{data: "type"},
{data: "asset_info", orderable: false},
{data: "comment", orderable: false}
]
};
remote_app_table = jumpserver.initServerSideDataTable(options);
return remote_app_table
}
$(document).ready(function(){
initTable();
})
</script>
{% endblock %}
...@@ -62,7 +62,7 @@ function initTable() { ...@@ -62,7 +62,7 @@ function initTable() {
], ],
ajax_url: '{% url "api-users:user-group-list" %}', ajax_url: '{% url "api-users:user-group-list" %}',
columns: [{data: "id"}, {data: "name" }, {data: "users_amount", orderable: false}, columns: [{data: "id"}, {data: "name" }, {data: "users_amount", orderable: false},
{data: "comment"}, {data: "id", orderable: false, width:"100px"}], {data: "comment"}, {data: "id", orderable: false, width: "120px"}],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
groupsTable = jumpserver.initServerSideDataTable(options); groupsTable = jumpserver.initServerSideDataTable(options);
......
...@@ -121,7 +121,7 @@ function initTable() { ...@@ -121,7 +121,7 @@ function initTable() {
{data: "groups_display", orderable: false}, {data: "groups_display", orderable: false},
{data: "source"}, {data: "source"},
{data: "is_valid", orderable: false, width: "50px"}, {data: "is_valid", orderable: false, width: "50px"},
{data: "id", orderable: false, width: "100px"} {data: "id", orderable: false, width: "120px"}
], ],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
......
{% extends 'users/_base_user_detail.html' %}
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
<link href="{% static "css/plugins/sweetalert/sweetalert.css" %}" rel="stylesheet">
<script src="{% static "js/plugins/sweetalert/sweetalert.min.js" %}"></script>
{% endblock %}
{% block content_table %}
<div class="col-sm-10" style="padding-left: 0">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span class="label"><b>{{ object.name }}</b></span>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<ul class="dropdown-menu dropdown-user">
</ul>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<table class="table table-striped table-bordered table-hover"
id="permission_list_table"
style="width: 100%">
<thead>
<tr>
<th></th>
<th>{% trans 'Name' %}</th>
<th class="text-center">{% trans 'User' %}</th>
<th class="text-center">{% trans 'User group' %}</th>
<th class="text-center">{% trans 'RemoteApp' %}</th>
<th class="text-center">{% trans 'System user' %}</th>
<th class="text-center">{% trans 'Validity' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
function format(d) {
var data = "";
if (d.users.length > 0 ) {
data += makeLabel(["{% trans 'User' %}", d.users.join(", ")])
}
if (d.user_groups.length > 0) {
data += makeLabel(["{% trans 'User group' %}", d.user_groups.join(", ")])
}
if (d.remote_apps.length > 0) {
data += makeLabel(["{% trans 'RemoteApp' %}", d.remote_apps.join(", ")])
}
if (d.system_users.length > 0) {
data += makeLabel(["{% trans 'System user' %}", d.system_users.join(", ")])
}
return data
}
function initTable() {
var options = {
ele: $('#permission_list_table'),
toggle: true,
columnDefs: [
{targets: 0, createdCell: function (td, cellData, rowData) {
$(td).addClass("toggle");
$(td).html("<i class='fa fa-angle-right'></i>");
}},
{targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
var detail_btn = '<a href="{% url "perms:remote-app-permission-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 2, createdCell: function (td, cellData) {
var num = cellData.length;
$(td).html(num);
}},
{targets: 3, createdCell: function (td, cellData) {
var num = cellData.length;
$(td).html(num);
}},
{targets: 4, createdCell: function (td, cellData) {
var num = cellData.length;
$(td).html(num);
}},
{targets: 5, createdCell: function (td, cellData) {
var num = cellData.length;
$(td).html(num);
}},
{targets: 6, createdCell: function (td, cellData) {
if (!cellData) {
$(td).html('<i class="fa fa-times text-danger"></i>')
} else {
$(td).html('<i class="fa fa-check text-navy"></i>')
}
}},
{targets: 7, createdCell: function (td, cellData, rowData) {
var name = htmlEscape(rowData.name);
var update_btn = '<a href="{% url "perms:remote-app-permission-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-del" data-uid="{{ DEFAULT_PK }}" mark=1 data-name="99991938">{% trans "Delete" %}</a>'
.replace('{{ DEFAULT_PK }}', cellData)
.replace('99991938', name);
$(td).html(update_btn + del_btn);
}}
],
ajax_url: '{% url "api-perms:remote-app-permission-list" %}?user_id={{ object.id }}',
columns: [
{data: "id"}, {data: "name"}, {data: "users", orderable: false},
{data: "user_groups", orderable: false}, {data: "remote_apps", orderable: false},
{data: "system_users", orderable: false}, {data: "is_valid", orderable: false},
{data: "id", orderable: false, width: "120px"}
],
select: {},
op_html: $('#actions').html()
};
table = jumpserver.initServerSideDataTable(options);
return table
}
$(document).ready(function() {
initTable();
})
.on('click', '.toggle', function (e) {
e.preventDefault();
var detailRows = [];
var tr = $(this).closest('tr');
var row = table.row(tr);
var idx = $.inArray(tr.attr('id'), detailRows);
if (row.child.isShown()) {
tr.removeClass('details');
$(this).children('i:first-child').removeClass('fa-angle-down').addClass('fa-angle-right');
row.child.hide();
// Remove from the 'open' array
detailRows.splice(idx, 1);
}
else {
tr.addClass('details');
$(this).children('i:first-child').removeClass('fa-angle-right').addClass('fa-angle-down');
row.child(format(row.data())).show();
// Add to the 'open' array
if ( idx === -1 ) {
detailRows.push(tr.attr('id'));
}
}
})
.on('click', '.btn-del', function () {
var $this = $(this);
var uid = $this.data('uid');
var name = $this.data('name');
var the_url = '{% url "api-perms:remote-app-permission-detail" pk=DEFAULT_PK %}'
.replace('{{ DEFAULT_PK }}', uid);
objectDelete($this, name, the_url);
})
</script>
{% endblock %}
...@@ -36,6 +36,10 @@ urlpatterns = [ ...@@ -36,6 +36,10 @@ urlpatterns = [
path('user/<uuid:pk>/', views.UserDetailView.as_view(), name='user-detail'), path('user/<uuid:pk>/', views.UserDetailView.as_view(), name='user-detail'),
path('user/<uuid:pk>/assets/', views.UserGrantedAssetView.as_view(), name='user-granted-asset'), path('user/<uuid:pk>/assets/', views.UserGrantedAssetView.as_view(), name='user-granted-asset'),
path('user/<uuid:pk>/asset-permissions/', views.UserAssetPermissionListView.as_view(), name='user-asset-permission'), path('user/<uuid:pk>/asset-permissions/', views.UserAssetPermissionListView.as_view(), name='user-asset-permission'),
path('user/<uuid:pk>/remote-apps/', views.UserGrantedRemoteAppView.as_view(), name='user-granted-remote-app'),
path('user/<uuid:pk>/remote-app-permissions/', views.UserRemoteAppPermissionListView.as_view(), name='user-remote-app-permission'),
path('user/<uuid:pk>/database-apps/', views.UserGrantedDatabasesAppView.as_view(), name='user-granted-database-app'),
path('user/<uuid:pk>/database-app-permissions/', views.UserDatabaseAppPermissionListView.as_view(), name='user-database-app-permission'),
path('user/<uuid:pk>/login-history/', views.UserDetailView.as_view(), name='user-login-history'), path('user/<uuid:pk>/login-history/', views.UserDetailView.as_view(), name='user-login-history'),
# User group view # User group view
......
...@@ -31,8 +31,10 @@ from ..signals import post_user_create ...@@ -31,8 +31,10 @@ from ..signals import post_user_create
__all__ = [ __all__ = [
'UserListView', 'UserCreateView', 'UserDetailView', 'UserListView', 'UserCreateView', 'UserDetailView',
'UserUpdateView', 'UserGrantedAssetView', 'UserUpdateView', 'UserBulkUpdateView',
'UserBulkUpdateView', 'UserAssetPermissionListView', 'UserGrantedAssetView', 'UserAssetPermissionListView',
'UserGrantedRemoteAppView', 'UserRemoteAppPermissionListView',
'UserGrantedDatabasesAppView', 'UserDatabaseAppPermissionListView',
] ]
logger = get_logger(__name__) logger = get_logger(__name__)
...@@ -164,7 +166,7 @@ class UserBulkUpdateView(PermissionsMixin, TemplateView): ...@@ -164,7 +166,7 @@ class UserBulkUpdateView(PermissionsMixin, TemplateView):
class UserDetailView(PermissionsMixin, DetailView): class UserDetailView(PermissionsMixin, DetailView):
model = User model = User
template_name = 'users/user_detail.html' template_name = 'users/user_detail.html'
context_object_name = "user_object" context_object_name = "object"
key_prefix_block = "_LOGIN_BLOCK_{}" key_prefix_block = "_LOGIN_BLOCK_{}"
permission_classes = [IsOrgAdmin] permission_classes = [IsOrgAdmin]
...@@ -220,3 +222,59 @@ class UserAssetPermissionListView(PermissionsMixin, DetailView): ...@@ -220,3 +222,59 @@ class UserAssetPermissionListView(PermissionsMixin, DetailView):
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class UserGrantedRemoteAppView(PermissionsMixin, DetailView):
model = User
template_name = 'users/user_granted_remote_app.html'
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs):
context = {
'app': _('Users'),
'action': _('User granted RemoteApp'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class UserRemoteAppPermissionListView(PermissionsMixin, DetailView):
model = User
template_name = 'users/user_remote_app_permission.html'
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs):
context = {
'app': _('Users'),
'action': _('RemoteApp permission'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class UserGrantedDatabasesAppView(PermissionsMixin, DetailView):
model = User
template_name = 'users/user_granted_database_app.html'
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs):
context = {
'app': _('Users'),
'action': _('User granted DatabaseApp'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class UserDatabaseAppPermissionListView(PermissionsMixin, DetailView):
model = User
template_name = 'users/user_database_app_permission.html'
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs):
context = {
'app': _('Users'),
'action': _('DatabaseApp permission'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment