mirror of https://github.com/jumpserver/jumpserver
Merge with master
commit
a04d772501
|
@ -78,4 +78,121 @@ th a {
|
||||||
border-top: none !important;
|
border-top: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table.dataTable tbody > tr.selected, table.dataTable tbody > tr > .selected {
|
||||||
|
background-color: #1ab394;
|
||||||
|
}
|
||||||
|
table.dataTable tbody tr.selected a,
|
||||||
|
table.dataTable tbody th.selected a,
|
||||||
|
table.dataTable tbody td.selected a,
|
||||||
|
table.dataTable tbody tr.selected td i.text-navy,
|
||||||
|
table.dataTable tbody th.selected td i.text-navy,
|
||||||
|
table.dataTable tbody td.selected td i.text-navy
|
||||||
|
{
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-0 {
|
||||||
|
margin: 0px !important;
|
||||||
|
}
|
||||||
|
.m-t-0 {
|
||||||
|
margin-top: 0px !important;
|
||||||
|
}
|
||||||
|
.m-b-0 {
|
||||||
|
margin-bottom: 0px !important;
|
||||||
|
}
|
||||||
|
.m-l-0 {
|
||||||
|
margin-left: 0px !important;
|
||||||
|
}
|
||||||
|
.m-r-0 {
|
||||||
|
margin-right: 0px !important;
|
||||||
|
}
|
||||||
|
.m-5 {
|
||||||
|
margin: 5px !important;
|
||||||
|
}
|
||||||
|
.m-t-5 {
|
||||||
|
margin-top: 5px !important;
|
||||||
|
}
|
||||||
|
.m-b-5 {
|
||||||
|
margin-bottom: 5px !important;
|
||||||
|
}
|
||||||
|
.m-l-5 {
|
||||||
|
margin-left: 5px !important;
|
||||||
|
}
|
||||||
|
.m-r-5 {
|
||||||
|
margin-right: 5px !important;
|
||||||
|
}
|
||||||
|
.m-10 {
|
||||||
|
margin: 10px !important;
|
||||||
|
}
|
||||||
|
.m-t-10 {
|
||||||
|
margin-top: 10px !important;
|
||||||
|
}
|
||||||
|
.m-b-10 {
|
||||||
|
margin-bottom: 10px !important;
|
||||||
|
}
|
||||||
|
.m-l-10 {
|
||||||
|
margin-left: 10px !important;
|
||||||
|
}
|
||||||
|
.m-r-10 {
|
||||||
|
margin-right: 10px !important;
|
||||||
|
}
|
||||||
|
.m-15 {
|
||||||
|
margin: 15px !important;
|
||||||
|
}
|
||||||
|
.m-t-15 {
|
||||||
|
margin-top: 15px !important;
|
||||||
|
}
|
||||||
|
.m-b-15 {
|
||||||
|
margin-bottom: 15px !important;
|
||||||
|
}
|
||||||
|
.m-l-15 {
|
||||||
|
margin-left: 15px !important;
|
||||||
|
}
|
||||||
|
.m-r-15 {
|
||||||
|
margin-right: 15px !important;
|
||||||
|
}
|
||||||
|
.m-20 {
|
||||||
|
margin: 20px !important;
|
||||||
|
}
|
||||||
|
.m-t-20 {
|
||||||
|
margin-top: 20px !important;
|
||||||
|
}
|
||||||
|
.m-b-20 {
|
||||||
|
margin-bottom: 20px !important;
|
||||||
|
}
|
||||||
|
.m-l-20 {
|
||||||
|
margin-left: 20px !important;
|
||||||
|
}
|
||||||
|
.m-r-20 {
|
||||||
|
margin-right: 20px !important;
|
||||||
|
}
|
||||||
|
.m-25 {
|
||||||
|
margin: 25px !important;
|
||||||
|
}
|
||||||
|
.m-t-25 {
|
||||||
|
margin-top: 25px !important;
|
||||||
|
}
|
||||||
|
.m-b-25 {
|
||||||
|
margin-bottom: 25px !important;
|
||||||
|
}
|
||||||
|
.m-l-25 {
|
||||||
|
margin-left: 25px !important;
|
||||||
|
}
|
||||||
|
.m-r-25 {
|
||||||
|
margin-right: 25px !important;
|
||||||
|
}
|
||||||
|
.m-30 {
|
||||||
|
margin: 30px !important;
|
||||||
|
}
|
||||||
|
.m-t-30 {
|
||||||
|
margin-top: 30px !important;
|
||||||
|
}
|
||||||
|
.m-b-30 {
|
||||||
|
margin-bottom: 30px !important;
|
||||||
|
}
|
||||||
|
.m-l-30 {
|
||||||
|
margin-left: 30px !important;
|
||||||
|
}
|
||||||
|
.m-r-30 {
|
||||||
|
margin-right: 30px !important;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,251 @@
|
||||||
|
.checkbox {
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
.checkbox label {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
position: relative;
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
.checkbox label::before {
|
||||||
|
content: "";
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
width: 17px;
|
||||||
|
height: 17px;
|
||||||
|
left: 0;
|
||||||
|
margin-left: -20px;
|
||||||
|
border: 1px solid #cccccc;
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: #fff;
|
||||||
|
-webkit-transition: border 0.15s ease-in-out, color 0.15s ease-in-out;
|
||||||
|
-o-transition: border 0.15s ease-in-out, color 0.15s ease-in-out;
|
||||||
|
transition: border 0.15s ease-in-out, color 0.15s ease-in-out;
|
||||||
|
}
|
||||||
|
.checkbox label::after {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
margin-left: -20px;
|
||||||
|
padding-left: 3px;
|
||||||
|
padding-top: 1px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: #555555;
|
||||||
|
}
|
||||||
|
.checkbox input[type="checkbox"],
|
||||||
|
.checkbox input[type="radio"] {
|
||||||
|
opacity: 0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.checkbox input[type="checkbox"]:focus + label::before,
|
||||||
|
.checkbox input[type="radio"]:focus + label::before {
|
||||||
|
outline: thin dotted;
|
||||||
|
outline: 5px auto -webkit-focus-ring-color;
|
||||||
|
outline-offset: -2px;
|
||||||
|
}
|
||||||
|
.checkbox input[type="checkbox"]:checked + label::after,
|
||||||
|
.checkbox input[type="radio"]:checked + label::after {
|
||||||
|
font-family: "FontAwesome";
|
||||||
|
content: "\f00c";
|
||||||
|
}
|
||||||
|
.checkbox input[type="checkbox"]:disabled + label,
|
||||||
|
.checkbox input[type="radio"]:disabled + label {
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
|
.checkbox input[type="checkbox"]:disabled + label::before,
|
||||||
|
.checkbox input[type="radio"]:disabled + label::before {
|
||||||
|
background-color: #eeeeee;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
.checkbox.checkbox-circle label::before {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
.checkbox.checkbox-inline {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-primary input[type="checkbox"]:checked + label::before,
|
||||||
|
.checkbox-primary input[type="radio"]:checked + label::before {
|
||||||
|
background-color: #337ab7;
|
||||||
|
border-color: #337ab7;
|
||||||
|
}
|
||||||
|
.checkbox-primary input[type="checkbox"]:checked + label::after,
|
||||||
|
.checkbox-primary input[type="radio"]:checked + label::after {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-danger input[type="checkbox"]:checked + label::before,
|
||||||
|
.checkbox-danger input[type="radio"]:checked + label::before {
|
||||||
|
background-color: #d9534f;
|
||||||
|
border-color: #d9534f;
|
||||||
|
}
|
||||||
|
.checkbox-danger input[type="checkbox"]:checked + label::after,
|
||||||
|
.checkbox-danger input[type="radio"]:checked + label::after {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-info input[type="checkbox"]:checked + label::before,
|
||||||
|
.checkbox-info input[type="radio"]:checked + label::before {
|
||||||
|
background-color: #5bc0de;
|
||||||
|
border-color: #5bc0de;
|
||||||
|
}
|
||||||
|
.checkbox-info input[type="checkbox"]:checked + label::after,
|
||||||
|
.checkbox-info input[type="radio"]:checked + label::after {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-warning input[type="checkbox"]:checked + label::before,
|
||||||
|
.checkbox-warning input[type="radio"]:checked + label::before {
|
||||||
|
background-color: #f0ad4e;
|
||||||
|
border-color: #f0ad4e;
|
||||||
|
}
|
||||||
|
.checkbox-warning input[type="checkbox"]:checked + label::after,
|
||||||
|
.checkbox-warning input[type="radio"]:checked + label::after {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-success input[type="checkbox"]:checked + label::before,
|
||||||
|
.checkbox-success input[type="radio"]:checked + label::before {
|
||||||
|
background-color: #5cb85c;
|
||||||
|
border-color: #5cb85c;
|
||||||
|
}
|
||||||
|
.checkbox-success input[type="checkbox"]:checked + label::after,
|
||||||
|
.checkbox-success input[type="radio"]:checked + label::after {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio {
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
.radio label {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
position: relative;
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
.radio label::before {
|
||||||
|
content: "";
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
width: 17px;
|
||||||
|
height: 17px;
|
||||||
|
left: 0;
|
||||||
|
margin-left: -20px;
|
||||||
|
border: 1px solid #cccccc;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #fff;
|
||||||
|
-webkit-transition: border 0.15s ease-in-out;
|
||||||
|
-o-transition: border 0.15s ease-in-out;
|
||||||
|
transition: border 0.15s ease-in-out;
|
||||||
|
}
|
||||||
|
.radio label::after {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
content: " ";
|
||||||
|
width: 11px;
|
||||||
|
height: 11px;
|
||||||
|
left: 3px;
|
||||||
|
top: 3px;
|
||||||
|
margin-left: -20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #555555;
|
||||||
|
-webkit-transform: scale(0, 0);
|
||||||
|
-ms-transform: scale(0, 0);
|
||||||
|
-o-transform: scale(0, 0);
|
||||||
|
transform: scale(0, 0);
|
||||||
|
-webkit-transition: -webkit-transform 0.1s cubic-bezier(0.8, -0.33, 0.2, 1.33);
|
||||||
|
-moz-transition: -moz-transform 0.1s cubic-bezier(0.8, -0.33, 0.2, 1.33);
|
||||||
|
-o-transition: -o-transform 0.1s cubic-bezier(0.8, -0.33, 0.2, 1.33);
|
||||||
|
transition: transform 0.1s cubic-bezier(0.8, -0.33, 0.2, 1.33);
|
||||||
|
}
|
||||||
|
.radio input[type="radio"] {
|
||||||
|
opacity: 0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.radio input[type="radio"]:focus + label::before {
|
||||||
|
outline: thin dotted;
|
||||||
|
outline: 5px auto -webkit-focus-ring-color;
|
||||||
|
outline-offset: -2px;
|
||||||
|
}
|
||||||
|
.radio input[type="radio"]:checked + label::after {
|
||||||
|
-webkit-transform: scale(1, 1);
|
||||||
|
-ms-transform: scale(1, 1);
|
||||||
|
-o-transform: scale(1, 1);
|
||||||
|
transform: scale(1, 1);
|
||||||
|
}
|
||||||
|
.radio input[type="radio"]:disabled + label {
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
|
.radio input[type="radio"]:disabled + label::before {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
.radio.radio-inline {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-primary input[type="radio"] + label::after {
|
||||||
|
background-color: #337ab7;
|
||||||
|
}
|
||||||
|
.radio-primary input[type="radio"]:checked + label::before {
|
||||||
|
border-color: #337ab7;
|
||||||
|
}
|
||||||
|
.radio-primary input[type="radio"]:checked + label::after {
|
||||||
|
background-color: #337ab7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-danger input[type="radio"] + label::after {
|
||||||
|
background-color: #d9534f;
|
||||||
|
}
|
||||||
|
.radio-danger input[type="radio"]:checked + label::before {
|
||||||
|
border-color: #d9534f;
|
||||||
|
}
|
||||||
|
.radio-danger input[type="radio"]:checked + label::after {
|
||||||
|
background-color: #d9534f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-info input[type="radio"] + label::after {
|
||||||
|
background-color: #5bc0de;
|
||||||
|
}
|
||||||
|
.radio-info input[type="radio"]:checked + label::before {
|
||||||
|
border-color: #5bc0de;
|
||||||
|
}
|
||||||
|
.radio-info input[type="radio"]:checked + label::after {
|
||||||
|
background-color: #5bc0de;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-warning input[type="radio"] + label::after {
|
||||||
|
background-color: #f0ad4e;
|
||||||
|
}
|
||||||
|
.radio-warning input[type="radio"]:checked + label::before {
|
||||||
|
border-color: #f0ad4e;
|
||||||
|
}
|
||||||
|
.radio-warning input[type="radio"]:checked + label::after {
|
||||||
|
background-color: #f0ad4e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-success input[type="radio"] + label::after {
|
||||||
|
background-color: #5cb85c;
|
||||||
|
}
|
||||||
|
.radio-success input[type="radio"]:checked + label::before {
|
||||||
|
border-color: #5cb85c;
|
||||||
|
}
|
||||||
|
.radio-success input[type="radio"]:checked + label::after {
|
||||||
|
background-color: #5cb85c;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"].styled:checked + label:after,
|
||||||
|
input[type="radio"].styled:checked + label:after {
|
||||||
|
font-family: 'FontAwesome';
|
||||||
|
content: "\f00c";
|
||||||
|
}
|
||||||
|
input[type="checkbox"] .styled:checked + label::before,
|
||||||
|
input[type="radio"] .styled:checked + label::before {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
input[type="checkbox"] .styled:checked + label::after,
|
||||||
|
input[type="radio"] .styled:checked + label::after {
|
||||||
|
color: #fff;
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
|
@ -178,8 +178,8 @@ function activeNav() {
|
||||||
var url_array = document.location.pathname.split("/");
|
var url_array = document.location.pathname.split("/");
|
||||||
var app = url_array[1];
|
var app = url_array[1];
|
||||||
var resource = url_array[2];
|
var resource = url_array[2];
|
||||||
if (app == ''){
|
if (app === ''){
|
||||||
$('#index').addClass('active')
|
$('#index').addClass('active');
|
||||||
} else {
|
} else {
|
||||||
$("#" + app).addClass('active');
|
$("#" + app).addClass('active');
|
||||||
$('#' + app + ' #' + resource).addClass('active');
|
$('#' + app + ' #' + resource).addClass('active');
|
||||||
|
@ -236,8 +236,112 @@ function objectDelete(obj, name, url){
|
||||||
swal('Deleted!' , "【"+name+"】"+"has been deleted.", "success");
|
swal('Deleted!' , "【"+name+"】"+"has been deleted.", "success");
|
||||||
$(obj).parent().parent().remove();
|
$(obj).parent().parent().remove();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$.fn.serializeObject = function()
|
||||||
|
{
|
||||||
|
var o = {};
|
||||||
|
var a = this.serializeArray();
|
||||||
|
$.each(a, function() {
|
||||||
|
if (o[this.name] !== undefined) {
|
||||||
|
if (!o[this.name].push) {
|
||||||
|
o[this.name] = [o[this.name]];
|
||||||
|
}
|
||||||
|
o[this.name].push(this.value || '');
|
||||||
|
} else {
|
||||||
|
o[this.name] = this.value || '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return o;
|
||||||
|
};
|
||||||
var jumpserver = {};
|
var jumpserver = {};
|
||||||
|
jumpserver.checked = false;
|
||||||
|
jumpserver.initDataTable = function (options) {
|
||||||
|
// options = {
|
||||||
|
// ele *: $('#dataTable_id'),
|
||||||
|
// ajax_url *: '{% url 'users:user-list-api' %}',
|
||||||
|
// columns *: [{data: ''}, ....],
|
||||||
|
// dom: 'fltip',
|
||||||
|
// i18n_url: '{% static "js/...../en-us.json" %}',
|
||||||
|
// order: [[1, 'asc'], [2, 'asc'], ...],
|
||||||
|
// buttons: ['excel', 'pdf', 'print'],
|
||||||
|
// columnDefs: [{target: 0, createdCell: ()=>{}}, ...],
|
||||||
|
// uc_html: '<a>header button</a>',
|
||||||
|
// op_html: 'div.btn-group?'
|
||||||
|
// }
|
||||||
|
var ele = options.ele || $('.dataTable');
|
||||||
|
var columnDefs = [
|
||||||
|
{
|
||||||
|
targets: 0, orderable: false,
|
||||||
|
createdCell: function(td) {
|
||||||
|
$(td).html('<div class="checkbox checkbox-default"><input type="checkbox" class="ipt_check"><label></label></div>');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{className: 'text-center', targets: '_all'},
|
||||||
|
];
|
||||||
|
columnDefs = options.columnDefs ? options.columnDefs.concat(columnDefs) : columnDefs;
|
||||||
|
var table = ele.DataTable({
|
||||||
|
pageLength: options.pageLength || 25,
|
||||||
|
dom: options.dom || '<"#uc.pull-left"><"html5buttons"B>flti<"row m-t"<"#op.col-md-6"><"col-md-6"p>>',
|
||||||
|
language: {
|
||||||
|
url: options.i18n_url || "/static/js/plugins/dataTables/i18n/zh-hans.json"
|
||||||
|
},
|
||||||
|
order: options.order || [[ 1, 'asc' ]],
|
||||||
|
buttons: options.buttons || [
|
||||||
|
{extend: 'excel',
|
||||||
|
exportOptions: {
|
||||||
|
modifier: {
|
||||||
|
selected: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{extend: 'pdf',
|
||||||
|
exportOptions: {
|
||||||
|
modifier: {
|
||||||
|
selected: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{extend: 'print',
|
||||||
|
customize: function (win){
|
||||||
|
$(win.document.body).addClass('white-bg');
|
||||||
|
$(win.document.body).css('font-size', '10px');
|
||||||
|
$(win.document.body).find('table')
|
||||||
|
.addClass('compact')
|
||||||
|
.css('font-size', 'inherit');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
columnDefs: columnDefs,
|
||||||
|
select: options.select || {style: 'multi'},
|
||||||
|
ajax: {
|
||||||
|
url: options.ajax_url ,
|
||||||
|
dataSrc: ""
|
||||||
|
},
|
||||||
|
columns: options.columns || []
|
||||||
|
});
|
||||||
|
table.on('select', function(e, dt, type, indexes) {
|
||||||
|
var $node = table[ type ]( indexes ).nodes().to$();
|
||||||
|
$node.find('input.ipt_check').prop('checked', true);
|
||||||
|
}).on('deselect', function(e, dt, type, indexes) {
|
||||||
|
var $node = table[ type ]( indexes ).nodes().to$();
|
||||||
|
$node.find('input.ipt_check').prop('checked', false);
|
||||||
|
}).on('draw', function(){
|
||||||
|
$('#op').html(options.op_html || '');
|
||||||
|
$('#uc').html(options.uc_html || '');
|
||||||
|
});
|
||||||
|
$('.ipt_check_all').on('click', function() {
|
||||||
|
if (!jumpserver.checked) {
|
||||||
|
$(this).closest('table').find('.ipt_check').prop('checked', true);
|
||||||
|
jumpserver.checked = true;
|
||||||
|
table.rows().select();
|
||||||
|
} else {
|
||||||
|
$(this).closest('table').find('.ipt_check').prop('checked', false);
|
||||||
|
jumpserver.checked = false;
|
||||||
|
table.rows().deselect();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -3,6 +3,9 @@
|
||||||
{% load common_tags %}
|
{% load common_tags %}
|
||||||
{% block custom_head_css_js %}
|
{% block custom_head_css_js %}
|
||||||
<link href="{% static "css/plugins/dataTables/dataTables.min.css" %}" rel="stylesheet">
|
<link href="{% static "css/plugins/dataTables/dataTables.min.css" %}" rel="stylesheet">
|
||||||
|
<link href="{% static "css/plugins/awesome-bootstrap-checkbox/awesome-bootstrap-checkbox.css" %}" rel="stylesheet">
|
||||||
|
<link href="{% static "css/plugins/select2/select2.min.css" %}" rel="stylesheet">
|
||||||
|
<script src="{% static "js/plugins/select2/select2.full.min.js" %}"></script>
|
||||||
<script src="{% static "js/plugins/dataTables/dataTables.min.js" %}"></script>
|
<script src="{% static "js/plugins/dataTables/dataTables.min.js" %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
|
@ -7,17 +7,9 @@
|
||||||
<link href="{% static "css/plugins/sweetalert/sweetalert.css" %}" rel="stylesheet">
|
<link href="{% static "css/plugins/sweetalert/sweetalert.css" %}" rel="stylesheet">
|
||||||
<link href="{% static "css/style.css" %}" rel="stylesheet">
|
<link href="{% static "css/style.css" %}" rel="stylesheet">
|
||||||
<link href="{% static "css/plugins/vaildator/jquery.validator.css" %}" rel="stylesheet">
|
<link href="{% static "css/plugins/vaildator/jquery.validator.css" %}" rel="stylesheet">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- scripts -->
|
<!-- scripts -->
|
||||||
<script src="{% static 'js/jquery-2.1.1.js' %}"></script>
|
<script src="{% static 'js/jquery-2.1.1.js' %}"></script>
|
||||||
|
|
||||||
|
|
||||||
<!-- Sweet alert -->
|
<!-- Sweet alert -->
|
||||||
<script src="{% static 'js/plugins/sweetalert/sweetalert.min.js' %}"></script>
|
<script src="{% static 'js/plugins/sweetalert/sweetalert.min.js' %}"></script>
|
||||||
|
|
||||||
<script src="{% static 'js/bootstrap.min.js' %}"></script>
|
<script src="{% static 'js/bootstrap.min.js' %}"></script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<div aria-hidden="true" role="dialog" tabindex="-1" id="{% block modal_id %}{% endblock %}" class="modal inmodal" style="display: none;">
|
<div aria-hidden="true" role="dialog" id="{% block modal_id %}{% endblock %}" class="modal inmodal">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog {% block modal_class %}{% endblock %}">
|
||||||
<div class="modal-content animated fadeIn">
|
<div class="modal-content animated fadeIn">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<button data-dismiss="modal" class="close" type="button"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
|
<button data-dismiss="modal" class="close" type="button"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
|
||||||
|
|
|
@ -3,10 +3,13 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from rest_framework import generics
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
|
from rest_framework import generics, status
|
||||||
|
from rest_framework.response import Response
|
||||||
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
|
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
|
||||||
|
|
||||||
from .serializers import UserSerializer, UserGroupSerializer, UserAttributeSerializer, UserGroupEditSerializer, \
|
from .serializers import UserSerializer, UserGroupSerializer, UserAttributeSerializer, GroupUserEditSerializer, \
|
||||||
GroupEditSerializer, UserPKUpdateSerializer, UserBulkUpdateSerializer
|
GroupEditSerializer, UserPKUpdateSerializer, UserBulkUpdateSerializer
|
||||||
from .models import User, UserGroup
|
from .models import User, UserGroup
|
||||||
|
|
||||||
|
@ -27,11 +30,6 @@ class UserDetailDeleteUpdateApi(generics.RetrieveUpdateDestroyAPIView):
|
||||||
print(self.request.data)
|
print(self.request.data)
|
||||||
return super(UserDetailDeleteUpdateApi, self).delete(request, *args, **kwargs)
|
return super(UserDetailDeleteUpdateApi, self).delete(request, *args, **kwargs)
|
||||||
|
|
||||||
# def get(self, request, *args, **kwargs):
|
|
||||||
# print("hello world")
|
|
||||||
# print(request.user)
|
|
||||||
# return super(UserDetailDeleteUpdateApi, self).get(request, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class UserGroupListAddApi(generics.ListCreateAPIView):
|
class UserGroupListAddApi(generics.ListCreateAPIView):
|
||||||
queryset = UserGroup.objects.all()
|
queryset = UserGroup.objects.all()
|
||||||
|
@ -48,14 +46,14 @@ class UserAttributeApi(generics.RetrieveUpdateDestroyAPIView):
|
||||||
serializer_class = UserAttributeSerializer
|
serializer_class = UserAttributeSerializer
|
||||||
|
|
||||||
|
|
||||||
class UserGroupEditApi(generics.RetrieveUpdateAPIView):
|
class GroupUserEditApi(generics.RetrieveUpdateAPIView):
|
||||||
queryset = User.objects.all()
|
queryset = User.objects.all()
|
||||||
serializer_class = UserGroupEditSerializer
|
serializer_class = GroupUserEditSerializer
|
||||||
|
|
||||||
|
|
||||||
class UserResetPasswordApi(generics.UpdateAPIView):
|
class UserResetPasswordApi(generics.UpdateAPIView):
|
||||||
queryset = User.objects.all()
|
queryset = User.objects.all()
|
||||||
serializer_class = UserGroupEditSerializer
|
serializer_class = GroupUserEditSerializer
|
||||||
|
|
||||||
def perform_update(self, serializer):
|
def perform_update(self, serializer):
|
||||||
# Note: we are not updating the user object here.
|
# Note: we are not updating the user object here.
|
||||||
|
@ -70,7 +68,7 @@ class UserResetPasswordApi(generics.UpdateAPIView):
|
||||||
|
|
||||||
class UserResetPKApi(generics.UpdateAPIView):
|
class UserResetPKApi(generics.UpdateAPIView):
|
||||||
queryset = User.objects.all()
|
queryset = User.objects.all()
|
||||||
serializer_class = UserGroupEditSerializer
|
serializer_class = GroupUserEditSerializer
|
||||||
|
|
||||||
def perform_update(self, serializer):
|
def perform_update(self, serializer):
|
||||||
user = self.get_object()
|
user = self.get_object()
|
||||||
|
@ -90,7 +88,7 @@ class UserUpdatePKApi(generics.UpdateAPIView):
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
|
|
||||||
class GroupDeleteApi(generics.DestroyAPIView):
|
class GroupEditApi(generics.RetrieveUpdateDestroyAPIView):
|
||||||
queryset = UserGroup.objects.all()
|
queryset = UserGroup.objects.all()
|
||||||
serializer_class = GroupEditSerializer
|
serializer_class = GroupEditSerializer
|
||||||
|
|
||||||
|
@ -111,3 +109,18 @@ class UserBulkUpdateApi(ListBulkCreateUpdateDestroyAPIView):
|
||||||
if isinstance(ids, list):
|
if isinstance(ids, list):
|
||||||
queryset = queryset.filter(id__in=ids)
|
queryset = queryset.filter(id__in=ids)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
class DeleteUserFromGroupApi(generics.DestroyAPIView):
|
||||||
|
queryset = UserGroup.objects.all()
|
||||||
|
serializer_class = GroupEditSerializer
|
||||||
|
|
||||||
|
def destroy(self, request, *args, **kwargs):
|
||||||
|
group = self.get_object()
|
||||||
|
self.perform_destroy(group, **kwargs)
|
||||||
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
def perform_destroy(self, instance, **kwargs):
|
||||||
|
user_id = kwargs.get('uid')
|
||||||
|
user = get_object_or_404(User, id=user_id)
|
||||||
|
instance.users.remove(user)
|
||||||
|
|
|
@ -4,14 +4,14 @@ from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.hashers import make_password
|
from django.contrib.auth.hashers import make_password
|
||||||
from django.utils import timezone
|
|
||||||
from django.db import models
|
|
||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser
|
||||||
|
from django.core import signing
|
||||||
|
from django.db import models, IntegrityError
|
||||||
from django.db.models.signals import post_save
|
from django.db.models.signals import post_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.db import IntegrityError
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.core import signing
|
from django.utils import timezone
|
||||||
|
from django.shortcuts import reverse
|
||||||
|
|
||||||
from rest_framework.authtoken.models import Token
|
from rest_framework.authtoken.models import Token
|
||||||
|
|
||||||
|
@ -102,6 +102,9 @@ class User(AbstractUser):
|
||||||
def password_raw(self, password_raw_):
|
def password_raw(self, password_raw_):
|
||||||
self.set_password(password_raw_)
|
self.set_password(password_raw_)
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('users:user-detail', args=(self.id,))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_expired(self):
|
def is_expired(self):
|
||||||
if self.date_expired > timezone.now():
|
if self.date_expired > timezone.now():
|
||||||
|
@ -162,8 +165,9 @@ class User(AbstractUser):
|
||||||
|
|
||||||
super(User, self).save(*args, **kwargs)
|
super(User, self).save(*args, **kwargs)
|
||||||
# Add the current user to the default group.
|
# Add the current user to the default group.
|
||||||
group = UserGroup.initial()
|
if not self.groups.count():
|
||||||
self.groups.add(group)
|
group = UserGroup.initial()
|
||||||
|
self.groups.add(group)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def private_token(self):
|
def private_token(self):
|
||||||
|
@ -222,7 +226,7 @@ class User(AbstractUser):
|
||||||
user.groups.add(UserGroup.initial())
|
user.groups.add(UserGroup.initial())
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
if self.is_superuser:
|
if self.pk == 1:
|
||||||
return
|
return
|
||||||
return super(User, self).delete()
|
return super(User, self).delete()
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ class GroupEditSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = UserGroup
|
model = UserGroup
|
||||||
fields = ['id', 'name', 'comment', 'date_created', 'created_by']
|
fields = ['id', 'name', 'comment', 'date_created', 'created_by', 'users']
|
||||||
|
|
||||||
|
|
||||||
class UserAttributeSerializer(serializers.ModelSerializer):
|
class UserAttributeSerializer(serializers.ModelSerializer):
|
||||||
|
@ -41,7 +41,7 @@ class UserAttributeSerializer(serializers.ModelSerializer):
|
||||||
fields = ['avatar', 'wechat', 'phone', 'enable_otp', 'comment', 'is_active', 'name']
|
fields = ['avatar', 'wechat', 'phone', 'enable_otp', 'comment', 'is_active', 'name']
|
||||||
|
|
||||||
|
|
||||||
class UserGroupEditSerializer(serializers.ModelSerializer):
|
class GroupUserEditSerializer(serializers.ModelSerializer):
|
||||||
groups = serializers.PrimaryKeyRelatedField(many=True, queryset=UserGroup.objects.all())
|
groups = serializers.PrimaryKeyRelatedField(many=True, queryset=UserGroup.objects.all())
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -73,6 +73,7 @@ class UserPKUpdateSerializer(serializers.ModelSerializer):
|
||||||
class UserBulkUpdateSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
class UserBulkUpdateSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||||
group_display = serializers.SerializerMethodField()
|
group_display = serializers.SerializerMethodField()
|
||||||
active_display = serializers.SerializerMethodField()
|
active_display = serializers.SerializerMethodField()
|
||||||
|
groups = serializers.PrimaryKeyRelatedField(many=True, queryset=UserGroup.objects.all())
|
||||||
|
|
||||||
class Meta(object):
|
class Meta(object):
|
||||||
model = User
|
model = User
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
{% extends '_modal.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block modal_class %}modal-lg{% endblock %}
|
||||||
|
{% block modal_id %}select_user_modal{% endblock %}
|
||||||
|
{% block modal_title%}{% trans "Please Select User" %}{% endblock %}
|
||||||
|
{% block modal_body %}
|
||||||
|
<table class="table table-striped table-bordered table-hover " id="select_user_table" >
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="text-center">
|
||||||
|
<div class="checkbox checkbox-default"><input id="" type="checkbox" class="ipt_check_all"><label></label></div>
|
||||||
|
</th>
|
||||||
|
<th class="text-center">{% trans 'Name' %}</a></th>
|
||||||
|
<th class="text-center">{% trans 'Username' %}</a></th>
|
||||||
|
<th class="text-center">{% trans 'Role' %}</th>
|
||||||
|
<th class="text-center">{% trans 'User group' %}</th>
|
||||||
|
<th class="text-center">{% trans 'Asset num' %}</th>
|
||||||
|
<th class="text-center">{% trans 'Active' %}</a></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
</table>
|
||||||
|
{% endblock %}
|
||||||
|
{% block modal_confirm_id %}btn_select_user{% endblock %}
|
|
@ -0,0 +1,37 @@
|
||||||
|
{% extends '_modal.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block modal_id %}user_bulk_update_modal{% endblock %}
|
||||||
|
{% block modal_title%}{% trans "Update User" %}{% endblock %}
|
||||||
|
{% block modal_body %}
|
||||||
|
<p class="text-success text-center">{% trans "Hint: only change the field you want to update." %}</p>
|
||||||
|
<form method="post" class="form-horizontal" action="" id="fm_user_bulk_update">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label col-sm-2 col-lg-2 " for="id_role">{% trans "Role" %}</label>
|
||||||
|
<div class=" col-sm-9 col-lg-9 ">
|
||||||
|
<select class=" form-control" id="id_role" name="role">
|
||||||
|
<option value="">---------</option>
|
||||||
|
<option value="Admin">{% trans "Admin" %}</option>
|
||||||
|
<option value="User">{% trans "User" %}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="groups" class="col-sm-2 control-label">{% trans 'Groups' %}</label>
|
||||||
|
<div class="col-sm-9" id="select2-container">
|
||||||
|
<select name="groups" id="select2_groups" data-placeholder="{% trans 'Select Group' %}" class="select2 form-control m-b" multiple>
|
||||||
|
{% for group in groups %}
|
||||||
|
<option value="{{ group.id }}">{{ group.name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-9 col-lg-9 col-sm-offset-2">
|
||||||
|
<div class="checkbox checkbox-success">
|
||||||
|
<input type="checkbox" name="enable_otp" checked id="id_enable_otp"><label for="id_enable_otp">{% trans 'Enable-OTP' %}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
{% block modal_confirm_id %}btn_user_bulk_update{% endblock %}
|
|
@ -222,7 +222,7 @@
|
||||||
jumpserver.selected_groups = {};
|
jumpserver.selected_groups = {};
|
||||||
|
|
||||||
function updateUserGroups(user_groups) {
|
function updateUserGroups(user_groups) {
|
||||||
var the_url = "{% url 'users:user-group-edit-api' pk=user_object.id %}";
|
var the_url = "{% url 'users:group-user-edit-api' pk=user_object.id %}";
|
||||||
var body = {
|
var body = {
|
||||||
id: {{ user_object.id }},
|
id: {{ user_object.id }},
|
||||||
groups: Object.assign([], user_groups)
|
groups: Object.assign([], user_groups)
|
||||||
|
@ -237,7 +237,7 @@ function updateUserGroups(user_groups) {
|
||||||
$('.group_edit tbody').append(
|
$('.group_edit tbody').append(
|
||||||
'<tr>' +
|
'<tr>' +
|
||||||
'<td><b class="bdg_user_group" data-gid="' + index + '">' + group_name + '</b></td>' +
|
'<td><b class="bdg_user_group" data-gid="' + index + '">' + group_name + '</b></td>' +
|
||||||
'<td><button class="btn btn-danger btn-sm pull-right btn_delete_user_group" type="button"><i class="fa fa-minus"></i></button></td>' +
|
'<td><button class="btn btn-danger btn-xs pull-right btn_delete_user_group" type="button"><i class="fa fa-minus"></i></button></td>' +
|
||||||
'</tr>'
|
'</tr>'
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,8 +5,23 @@
|
||||||
{% block custom_head_css_js %}
|
{% block custom_head_css_js %}
|
||||||
<link href="{% static "css/plugins/select2/select2.min.css" %}" rel="stylesheet">
|
<link href="{% static "css/plugins/select2/select2.min.css" %}" rel="stylesheet">
|
||||||
<link href="{% static "css/plugins/sweetalert/sweetalert.css" %}" rel="stylesheet">
|
<link href="{% static "css/plugins/sweetalert/sweetalert.css" %}" rel="stylesheet">
|
||||||
|
<link href="{% static "css/plugins/dataTables/dataTables.min.css" %}" rel="stylesheet">
|
||||||
|
<link href="{% static "css/plugins/awesome-bootstrap-checkbox/awesome-bootstrap-checkbox.css" %}" rel="stylesheet">
|
||||||
<script src="{% static "js/plugins/select2/select2.full.min.js" %}"></script>
|
<script src="{% static "js/plugins/select2/select2.full.min.js" %}"></script>
|
||||||
<script src="{% static "js/plugins/sweetalert/sweetalert.min.js" %}"></script>
|
<script src="{% static "js/plugins/sweetalert/sweetalert.min.js" %}"></script>
|
||||||
|
<script src="{% static "js/plugins/dataTables/dataTables.min.js" %}"></script>
|
||||||
|
<style>
|
||||||
|
.label {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 2.5;
|
||||||
|
}
|
||||||
|
.label .remove {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.label span {
|
||||||
|
color: #5e5e5e;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="wrapper wrapper-content animated fadeInRight">
|
<div class="wrapper wrapper-content animated fadeInRight">
|
||||||
|
@ -46,9 +61,17 @@
|
||||||
<td><b>{{ object.comment }}</b></td>
|
<td><b>{{ object.comment }}</b></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Created at:' %}:</td>
|
<td>{% trans 'Created at' %}:</td>
|
||||||
<td><b>{{ object.date_created }}</b></td>
|
<td><b>{{ object.date_created }}</b></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{% trans 'Users' %}:</td>
|
||||||
|
<td style="line-height: 2">
|
||||||
|
{% for user in object.users.all %}
|
||||||
|
<span class="label m-l-xs"><a href="{{ user.get_absolute_url }}"><span>{{ user.name }}</span></a><a data-uid="{{ user.id }}" class="btn_remove"><i class="remove fa fa-times-circle"></i></a></span>
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -61,43 +84,22 @@
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr class="no-borders-tr">
|
<tr>
|
||||||
<td width="50%">{% trans 'Active' %}:</td>
|
<td>{% trans 'Add User' %}:</td>
|
||||||
<td><span class="pull-right">
|
<td>
|
||||||
<div class="switch">
|
<span class="pull-right">
|
||||||
<div class="onoffswitch">
|
<button type="button" class="btn btn-primary btn-xs" id="btn_group_add_user" style="width: 54px" data-toggle="modal" data-target="#select_user_modal">{% trans 'Add' %}</button>
|
||||||
<input type="checkbox" {% if user_object.is_active %} checked {% endif %} class="onoffswitch-checkbox" id="is_active">
|
</span>
|
||||||
<label class="onoffswitch-label" for="is_active">
|
</td>
|
||||||
<span class="onoffswitch-inner"></span>
|
</tr>
|
||||||
<span class="onoffswitch-switch"></span>
|
<tr>
|
||||||
</label>
|
<td>{% trans 'Delete' %}:</td>
|
||||||
</div>
|
<td>
|
||||||
</div>
|
<span class="pull-right">
|
||||||
</span></td>
|
<button type="button" class="btn btn-danger btn-xs" id="btn_group_delete" style="width: 54px">{% trans 'Delete' %}</button>
|
||||||
</tr>
|
</span>
|
||||||
<tr>
|
</td>
|
||||||
<td>{% trans 'Enable OTP' %}:</td>
|
</tr>
|
||||||
<td><span class="pull-right">
|
|
||||||
<div class="switch">
|
|
||||||
<div class="onoffswitch">
|
|
||||||
<input type="checkbox" class="onoffswitch-checkbox" {% if user_object.enable_otp %} checked {% endif %}
|
|
||||||
id="enable_otp">
|
|
||||||
<label class="onoffswitch-label" for="enable_otp">
|
|
||||||
<span class="onoffswitch-inner"></span>
|
|
||||||
<span class="onoffswitch-switch"></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</span></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{% trans 'Reset password' %}:</td>
|
|
||||||
<td>
|
|
||||||
<span class="pull-right">
|
|
||||||
<button type="button" class="btn btn-primary btn-xs" id="btn_reset_password" style="width: 54px">{% trans 'Reset' %}</button>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
@ -108,4 +110,80 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% include "users/_select_user_modal.html" %}
|
||||||
|
{% endblock %}
|
||||||
|
{% block custom_foot_js %}
|
||||||
|
<script>
|
||||||
|
$(document).on('click', '.btn_remove', function(){
|
||||||
|
var $this = $(this);
|
||||||
|
var uid = $this.data('uid');
|
||||||
|
var the_url = '{% url "users:delete-user-from-group-api" pk=object.id uid=99991937 %}'.replace('99991937', uid);
|
||||||
|
var success = function(){
|
||||||
|
$this.closest('.label').remove();
|
||||||
|
};
|
||||||
|
var error = function(){};
|
||||||
|
APIUpdateAttr({url: the_url, body: "{}", method: "DELETE", success: success, error: error});
|
||||||
|
return false;
|
||||||
|
}).on('click', '#btn_group_delete', function() {
|
||||||
|
function doDelete() {
|
||||||
|
var the_url = '{% url "users:user-group-detail-api" pk=object.id %}';
|
||||||
|
var success = function() {
|
||||||
|
window.location.href = '{% url "users:user-group-list" %}';
|
||||||
|
};
|
||||||
|
APIUpdateAttr({url: the_url, body: "{}", method: "DELETE", success: success});
|
||||||
|
}
|
||||||
|
swal({
|
||||||
|
title: "{% trans 'Are you sure?' %}",
|
||||||
|
text: "{% trans 'This will delete the current group, but will not delete any user of it.' %}",
|
||||||
|
type: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonColor: "#DD6B55",
|
||||||
|
confirmButtonText: "{% trans 'Confirm' %}",
|
||||||
|
closeOnConfirm: false
|
||||||
|
}, function() {
|
||||||
|
doDelete();
|
||||||
|
});
|
||||||
|
}).on('shown.bs.modal', '#select_user_modal', function() {
|
||||||
|
if ($.fn.dataTable.isDataTable('#select_user_table')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
var options = {
|
||||||
|
ele: $('#select_user_table'),
|
||||||
|
pageLength: 10,
|
||||||
|
buttons: [],
|
||||||
|
columnDefs: [
|
||||||
|
{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>')
|
||||||
|
}
|
||||||
|
}},
|
||||||
|
],
|
||||||
|
ajax_url: '{% url "users:user-bulk-update-api" %}',
|
||||||
|
columns: [{data: function(){return ""}}, {data: "username" }, {data: "name" }, {data: "get_role_display" }, {data: "group_display" },
|
||||||
|
{data: function(){return 999}}, {data: "active_display" }],
|
||||||
|
};
|
||||||
|
jumpserver.initDataTable(options);
|
||||||
|
}).on('click', '#btn_select_user', function() {
|
||||||
|
var $data_table = $('#select_user_table').DataTable();
|
||||||
|
var id_list = [];
|
||||||
|
var plain_id_list = [];
|
||||||
|
$data_table.rows({selected: true}).every(function(){
|
||||||
|
id_list.push({id: this.data().id});
|
||||||
|
plain_id_list.push(this.data().id);
|
||||||
|
});
|
||||||
|
if (id_list === []) {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
console.log(id_list);
|
||||||
|
console.log(plain_id_list);
|
||||||
|
var body = {
|
||||||
|
id: {{ object.id }},
|
||||||
|
users: plain_id_list.map(Number)
|
||||||
|
};
|
||||||
|
console.log(body);
|
||||||
|
$('#select_user_modal').modal('hide');
|
||||||
|
})
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -65,7 +65,7 @@ $(document).on('click', '.btn_delete_user_group', function(){
|
||||||
var $this = $(this);
|
var $this = $(this);
|
||||||
function doDelete() {
|
function doDelete() {
|
||||||
var group_id = $this.data('gid');
|
var group_id = $this.data('gid');
|
||||||
var the_url = "{% url 'users:user-group-delete-api' 99991937 %}".replace('99991937', group_id);
|
var the_url = "{% url 'users:user-group-edit-api' 99991937 %}".replace('99991937', group_id);
|
||||||
var body = {};
|
var body = {};
|
||||||
var success = function() {
|
var success = function() {
|
||||||
var msg = "{% trans 'Group Deleted.' %}";
|
var msg = "{% trans 'Group Deleted.' %}";
|
||||||
|
|
|
@ -2,15 +2,28 @@
|
||||||
{% load i18n static %}
|
{% load i18n static %}
|
||||||
{% get_current_language as LANGUAGE_CODE %}
|
{% get_current_language as LANGUAGE_CODE %}
|
||||||
{% load common_tags %}
|
{% load common_tags %}
|
||||||
{% block content_left_head %}
|
{% block custom_head_css_js %}
|
||||||
<a href="{% url 'users:user-create' %}" class="btn btn-sm btn-primary "> {% trans "Create user" %} </a>
|
{{ block.super }}
|
||||||
|
<style>
|
||||||
|
div.dataTables_wrapper div.dataTables_filter,
|
||||||
|
.dataTables_length {
|
||||||
|
float: right !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.dataTables_wrapper div.dataTables_filter {
|
||||||
|
margin-left: 15px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block table_search %}{% endblock %}
|
{% block table_search %}{% endblock %}
|
||||||
{% block table_container %}
|
{% block table_container %}
|
||||||
|
<div class="uc pull-left"><a href="{% url "users:user-create" %}" class="btn btn-sm btn-primary"> {% trans "Create user" %} </a></div>
|
||||||
<table class="table table-striped table-bordered table-hover " id="user_list_table" >
|
<table class="table table-striped table-bordered table-hover " id="user_list_table" >
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th class="text-center">
|
||||||
|
<div class="checkbox checkbox-default"><input id="" type="checkbox" class="ipt_check_all"><label></label></div>
|
||||||
|
</th>
|
||||||
<th class="text-center">{% trans 'Name' %}</a></th>
|
<th class="text-center">{% trans 'Name' %}</a></th>
|
||||||
<th class="text-center">{% trans 'Username' %}</a></th>
|
<th class="text-center">{% trans 'Username' %}</a></th>
|
||||||
<th class="text-center">{% trans 'Role' %}</th>
|
<th class="text-center">{% trans 'Role' %}</th>
|
||||||
|
@ -23,93 +36,56 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<div id="actions" class="hide">
|
||||||
|
<div class="input-group">
|
||||||
|
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
|
||||||
|
<option value="delete">{% trans 'Delete selected' %}</option>
|
||||||
|
<option value="update">{% trans 'Update selected' %}</option>
|
||||||
|
<option value="deactive">{% trans 'Deactive selected' %}</option>
|
||||||
|
</select>
|
||||||
|
<div class="input-group-btn pull-left" style="padding-left: 5px;">
|
||||||
|
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
|
||||||
|
{% trans 'Submit' %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% include "users/_user_bulk_update_modal.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content_bottom_left %}
|
{% block content_bottom_left %}
|
||||||
<div class="input-group">
|
|
||||||
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
|
|
||||||
<option value="delete">{% trans 'Delete selected' %}</option>
|
|
||||||
<option value="update">{% trans 'Update selected' %}</option>
|
|
||||||
<option value="deactive">{% trans 'Deactive selected' %}</option>
|
|
||||||
</select>
|
|
||||||
<div class="input-group-btn pull-left" style="padding-left: 5px;">
|
|
||||||
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
|
|
||||||
{% trans 'Submit' %}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
$('#user_list_table').DataTable({
|
var options = {
|
||||||
dom: '<"html5buttons"B>lftip',
|
ele: $('#user_list_table'),
|
||||||
language: {
|
|
||||||
url: "{% static 'js/plugins/dataTables/i18n/language_code.json' %}".replace('language_code', '{{ LANGUAGE_CODE }}')
|
|
||||||
},
|
|
||||||
buttons: [
|
|
||||||
{extend: 'excel',
|
|
||||||
exportOptions: {
|
|
||||||
modifier: {
|
|
||||||
selected: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{extend: 'pdf',
|
|
||||||
exportOptions: {
|
|
||||||
modifier: {
|
|
||||||
selected: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{extend: 'print',
|
|
||||||
customize: function (win){
|
|
||||||
$(win.document.body).addClass('white-bg');
|
|
||||||
$(win.document.body).css('font-size', '10px');
|
|
||||||
$(win.document.body).find('table')
|
|
||||||
.addClass('compact')
|
|
||||||
.css('font-size', 'inherit');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
columnDefs: [
|
columnDefs: [
|
||||||
{orderable: false, className: 'select-checkbox', targets: 0},
|
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||||
{className: 'text-center', targets: [1, 2, 3, 4, 5, 6, 7]},
|
var detail_btn = '<a href="{% url "users:user-detail" pk=99991937 %}">' + cellData + '</a>';
|
||||||
{targets: 7,
|
$(td).html(detail_btn.replace('99991937', rowData.id));
|
||||||
createdCell: function (td, cellData, rowData) {
|
}},
|
||||||
var update_btn = '<a href="{% url "users:user-update" pk=99991937 %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', cellData);
|
{targets: 6, createdCell: function (td, cellData) {
|
||||||
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_user_delete" data-uid="99991937">{% trans "Delete" %}</a>'.replace('99991937', cellData);
|
|
||||||
if (rowData.role === 'Admin') {
|
|
||||||
$(td).html(update_btn)
|
|
||||||
} else {
|
|
||||||
$(td).html(update_btn + del_btn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{targets: 6,
|
|
||||||
createdCell: function (td, cellData) {
|
|
||||||
if (!cellData) {
|
if (!cellData) {
|
||||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||||
} else {
|
} else {
|
||||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||||
}
|
}
|
||||||
}}
|
}},
|
||||||
],
|
{targets: 7, createdCell: function (td, cellData, rowData) {
|
||||||
select: {style: 'multi'},
|
var update_btn = '<a href="{% url "users:user-update" pk=99991937 %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', cellData);
|
||||||
ajax: {
|
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_user_delete" data-uid="99991937">{% trans "Delete" %}</a>'.replace('99991937', cellData);
|
||||||
url: '{% url "users:user-bulk-update-api" %}',
|
if (rowData.id === 1) {
|
||||||
dataSrc: ""
|
$(td).html(update_btn)
|
||||||
},
|
} else {
|
||||||
columns: [
|
$(td).html(update_btn + del_btn)
|
||||||
{data: function(){return ""} },
|
}
|
||||||
{data: "name" },
|
}}],
|
||||||
{data: "username" },
|
ajax_url: '{% url "users:user-bulk-update-api" %}',
|
||||||
{data: "get_role_display" },
|
columns: [{data: function(){return ""}}, {data: "username" }, {data: "name" }, {data: "get_role_display" }, {data: "group_display" },
|
||||||
{data: "group_display" },
|
{data: function(){return 999}}, {data: "active_display" }, {data: "id" }],
|
||||||
{data: function(){return 999} },
|
op_html: $('#actions').html()
|
||||||
{data: "active_display" },
|
};
|
||||||
{data: "id" }
|
jumpserver.initDataTable(options);
|
||||||
]
|
|
||||||
});
|
|
||||||
}).on('click', '#btn_bulk_update', function(){
|
}).on('click', '#btn_bulk_update', function(){
|
||||||
var action = $('#slct_bulk_update').val();
|
var action = $('#slct_bulk_update').val();
|
||||||
var $data_table = $('#user_list_table').DataTable()
|
var $data_table = $('#user_list_table').DataTable()
|
||||||
|
@ -129,6 +105,7 @@ $(document).ready(function(){
|
||||||
});
|
});
|
||||||
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(body)});
|
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(body)});
|
||||||
$data_table.ajax.reload();
|
$data_table.ajax.reload();
|
||||||
|
jumpserver.checked = false;
|
||||||
}
|
}
|
||||||
function doDelete() {
|
function doDelete() {
|
||||||
swal({
|
swal({
|
||||||
|
@ -151,9 +128,12 @@ $(document).ready(function(){
|
||||||
};
|
};
|
||||||
var url_delete = the_url + '?id__in=' + JSON.stringify(plain_id_list);
|
var url_delete = the_url + '?id__in=' + JSON.stringify(plain_id_list);
|
||||||
APIUpdateAttr({url: url_delete, method: 'DELETE', success: success, error: fail});
|
APIUpdateAttr({url: url_delete, method: 'DELETE', success: success, error: fail});
|
||||||
|
jumpserver.checked = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function doUpdate() {}
|
function doUpdate() {
|
||||||
|
$('#user_bulk_update_modal').modal('show');
|
||||||
|
}
|
||||||
switch(action) {
|
switch(action) {
|
||||||
case 'deactive':
|
case 'deactive':
|
||||||
doDeactive();
|
doDeactive();
|
||||||
|
@ -201,6 +181,40 @@ $(document).ready(function(){
|
||||||
}, function() {
|
}, function() {
|
||||||
doDelete();
|
doDelete();
|
||||||
});
|
});
|
||||||
|
}).on('click', '#btn_user_bulk_update', function(){
|
||||||
|
var json_data = $('#fm_user_bulk_update').serializeObject();
|
||||||
|
var body = {};
|
||||||
|
body.enable_otp = (json_data.enable_otp === 'on')? true: false;
|
||||||
|
if (json_data.role != '') {
|
||||||
|
body.role = json_data.role;
|
||||||
|
}
|
||||||
|
if (json_data.groups != undefined) {
|
||||||
|
body.groups = json_data.groups;
|
||||||
|
}
|
||||||
|
if (typeof body.groups === 'string') {
|
||||||
|
body.groups = [parseInt(body.groups)]
|
||||||
|
} else if(typeof body.groups === 'array') {
|
||||||
|
new_groups = body.groups.map(Number);
|
||||||
|
body.groups = new_groups;
|
||||||
|
}
|
||||||
|
var $data_table = $('#user_list_table').DataTable()
|
||||||
|
var post_list = [];
|
||||||
|
$data_table.rows({selected: true}).every(function(){
|
||||||
|
var content = Object.assign({id: this.data().id}, body);
|
||||||
|
post_list.push(content);
|
||||||
|
});
|
||||||
|
if (post_list === []) {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
var the_url = "{% url 'users:user-bulk-update-api' %}";
|
||||||
|
var success = function() {
|
||||||
|
var msg = "{% trans 'The selected users has been updated successfully.' %}";
|
||||||
|
swal("{% trans 'User Updated' %}", msg, "success");
|
||||||
|
$('#user_list_table').DataTable().ajax.reload();
|
||||||
|
jumpserver.checked = false;
|
||||||
|
}
|
||||||
|
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(post_list), success: success});
|
||||||
|
$('#user_bulk_update_modal').modal('hide');
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -26,7 +26,6 @@ urlpatterns = [
|
||||||
url(r'^user/(?P<pk>[0-9]+)/assets-perm$', views.UserDetailView.as_view(), name='user-detail'),
|
url(r'^user/(?P<pk>[0-9]+)/assets-perm$', views.UserDetailView.as_view(), name='user-detail'),
|
||||||
url(r'^user/create$', views.UserCreateView.as_view(), name='user-create'),
|
url(r'^user/create$', views.UserCreateView.as_view(), name='user-create'),
|
||||||
url(r'^user/(?P<pk>[0-9]+)/update$', views.UserUpdateView.as_view(), name='user-update'),
|
url(r'^user/(?P<pk>[0-9]+)/update$', views.UserUpdateView.as_view(), name='user-update'),
|
||||||
url(r'^user/(?P<pk>[0-9]+)/delete$', views.UserDeleteView.as_view(), name='user-delete'),
|
|
||||||
url(r'^user-group$', views.UserGroupListView.as_view(), name='user-group-list'),
|
url(r'^user-group$', views.UserGroupListView.as_view(), name='user-group-list'),
|
||||||
url(r'^user-group/(?P<pk>[0-9]+)$', views.UserGroupDetailView.as_view(), name='user-group-detail'),
|
url(r'^user-group/(?P<pk>[0-9]+)$', views.UserGroupDetailView.as_view(), name='user-group-detail'),
|
||||||
url(r'^user-group/create$', views.UserGroupCreateView.as_view(), name='user-group-create'),
|
url(r'^user-group/create$', views.UserGroupCreateView.as_view(), name='user-group-create'),
|
||||||
|
@ -47,8 +46,10 @@ urlpatterns += [
|
||||||
url(r'^v1/user-groups$', api.UserGroupListAddApi.as_view(), name='user-group-list-api'),
|
url(r'^v1/user-groups$', api.UserGroupListAddApi.as_view(), name='user-group-list-api'),
|
||||||
url(r'^v1/user-groups/(?P<pk>[0-9]+)$',
|
url(r'^v1/user-groups/(?P<pk>[0-9]+)$',
|
||||||
api.UserGroupDetailDeleteUpdateApi.as_view(), name='user-group-detail-api'),
|
api.UserGroupDetailDeleteUpdateApi.as_view(), name='user-group-detail-api'),
|
||||||
url(r'^v1/user-groups/(?P<pk>[0-9]+)/edit$',
|
url(r'^v1/user-groups/(?P<pk>\d+)/user/(?P<uid>\d+)/$',
|
||||||
api.UserGroupEditApi.as_view(), name='user-group-edit-api'),
|
api.DeleteUserFromGroupApi.as_view(), name='delete-user-from-group-api'),
|
||||||
url(r'^v1/user-groups/(?P<pk>[0-9]+)/delete/$', api.GroupDeleteApi.as_view(),
|
url(r'^v1/user-groups/(?P<pk>[0-9]+)/users/$',
|
||||||
name='user-group-delete-api'),
|
api.GroupUserEditApi.as_view(), name='group-user-edit-api'),
|
||||||
|
url(r'^v1/user-groups/(?P<pk>[0-9]+)/edit/$', api.GroupEditApi.as_view(),
|
||||||
|
name='user-group-edit-api'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.mixins import UserPassesTestMixin
|
from django.contrib.auth.mixins import UserPassesTestMixin
|
||||||
|
|
|
@ -7,7 +7,6 @@ from django.contrib.auth import login as auth_login, logout as auth_logout
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.contrib.messages.views import SuccessMessageMixin
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.core.files.storage import default_storage
|
from django.core.files.storage import default_storage
|
||||||
from django.db.models import Q
|
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.shortcuts import reverse, redirect
|
from django.shortcuts import reverse, redirect
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
|
@ -80,27 +79,12 @@ class UserLogoutView(TemplateView):
|
||||||
return super(UserLogoutView, self).get_context_data(**kwargs)
|
return super(UserLogoutView, self).get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class UserListView(AdminUserRequiredMixin, ListView):
|
class UserListView(AdminUserRequiredMixin, TemplateView):
|
||||||
model = User
|
template_name = 'users/user_list.html'
|
||||||
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
|
|
||||||
context_object_name = 'user_list'
|
|
||||||
template_name = 'users/asset_permission_list.html'
|
|
||||||
ordering = '-date_joined'
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
self.queryset = super(UserListView, self).get_queryset()
|
|
||||||
self.keyword = keyword = self.request.GET.get('keyword', '')
|
|
||||||
self.sort = sort = self.request.GET.get('sort')
|
|
||||||
if keyword:
|
|
||||||
self.queryset = self.queryset.filter(Q(username__icontains=keyword) |
|
|
||||||
Q(name__icontains=keyword))
|
|
||||||
if sort:
|
|
||||||
self.queryset = self.queryset.order_by(sort)
|
|
||||||
return self.queryset
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(UserListView, self).get_context_data(**kwargs)
|
context = super(UserListView, self).get_context_data(**kwargs)
|
||||||
context.update({'app': _('Users'), 'action': _('User list'), 'keyword': self.keyword})
|
context.update({'app': _('Users'), 'action': _('User list'), 'groups': UserGroup.objects.all()})
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
@ -153,26 +137,6 @@ class UserUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class UserDeleteView(AdminUserRequiredMixin, DeleteView):
|
|
||||||
model = User
|
|
||||||
success_url = reverse_lazy('users:user-list')
|
|
||||||
template_name = 'users/user_delete_confirm.html'
|
|
||||||
|
|
||||||
def delete(self, request, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Calls the delete() method on the fetched object and then
|
|
||||||
redirects to the success URL.
|
|
||||||
"""
|
|
||||||
self.object = self.get_object()
|
|
||||||
success_url = self.get_success_url()
|
|
||||||
if self.object.name == "admin" or self.object.id == request.session.get('_auth_user_id'):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
self.object.delete()
|
|
||||||
|
|
||||||
return HttpResponseRedirect(success_url)
|
|
||||||
|
|
||||||
|
|
||||||
class UserDetailView(AdminUserRequiredMixin, DetailView):
|
class UserDetailView(AdminUserRequiredMixin, DetailView):
|
||||||
model = User
|
model = User
|
||||||
template_name = 'users/user_detail.html'
|
template_name = 'users/user_detail.html'
|
||||||
|
|
Loading…
Reference in New Issue