mirror of https://github.com/jumpserver/jumpserver
update assets_group
commit
6c7e51041f
|
@ -0,0 +1,38 @@
|
||||||
|
# coding: utf-8
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.timezone import now
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class NoDeleteQuerySet(models.query.QuerySet):
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
return self.update(is_discard=True, discard_time=now())
|
||||||
|
|
||||||
|
|
||||||
|
class NoDeleteManager(models.Manager):
|
||||||
|
|
||||||
|
def get_all(self):
|
||||||
|
return NoDeleteQuerySet(self.model, using=self._db)
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return NoDeleteQuerySet(self.model, using=self._db).filter(is_discard=False)
|
||||||
|
|
||||||
|
def get_deleted(self):
|
||||||
|
return NoDeleteQuerySet(self.model, using=self._db).filter(is_discard=True)
|
||||||
|
|
||||||
|
|
||||||
|
class NoDeleteModelMixin(models.Model):
|
||||||
|
is_discard = models.BooleanField(verbose_name=_("is discard"), default=False)
|
||||||
|
discard_time = models.DateTimeField(verbose_name=_("discard time"), null=True, blank=True)
|
||||||
|
|
||||||
|
objects = NoDeleteManager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
self.is_discard = True
|
||||||
|
self.discard_time = now()
|
||||||
|
return self.save()
|
|
@ -1,6 +1,5 @@
|
||||||
/*
|
@import url("https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700");
|
||||||
@import url("https://fonts.useso.com/css?family=Open+Sans:300,400,600,700");
|
@import url("https://fonts.googleapis.com/css?family=Roboto:400,300,500,700");
|
||||||
@import url("https://fonts.useso.com/css?family=Roboto:400,300,500,700");
|
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* INSPINIA - Responsive Admin Theme
|
* INSPINIA - Responsive Admin Theme
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,4 +1,4 @@
|
||||||
{% load static %}
|
{% load static i18n %}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
@ -20,6 +20,25 @@
|
||||||
<div id="page-wrapper" class="gray-bg">
|
<div id="page-wrapper" class="gray-bg">
|
||||||
{% include '_header_bar.html' %}
|
{% include '_header_bar.html' %}
|
||||||
{% include '_message.html' %}
|
{% include '_message.html' %}
|
||||||
|
{% block first_login_message %}
|
||||||
|
{% if user.is_authenticated and user.is_first_login %}
|
||||||
|
<div class="alert alert-danger" style="margin: 20px auto 0px">
|
||||||
|
{% url 'users:user-first-login' as the_url %}
|
||||||
|
{% blocktrans %}
|
||||||
|
Your information was incomplete. Please click <a href="{{ the_url }}"> this link </a>to complete your information.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
{% block update_public_key_message %}
|
||||||
|
{% if user.is_authenticated and not user.is_public_key_valid %}
|
||||||
|
<div class="alert alert-danger" style="margin: 20px auto 0px">
|
||||||
|
{% blocktrans %}
|
||||||
|
Your ssh-public-key has been expired. Please click <a href="#"> this link </a>to update your ssh-public-key.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
{% include '_footer.html' %}
|
{% include '_footer.html' %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -5,7 +5,8 @@ import logging
|
||||||
|
|
||||||
from rest_framework import generics
|
from rest_framework import generics
|
||||||
|
|
||||||
from .serializers import UserSerializer, UserGroupSerializer, UserAttributeSerializer, UserGroupEditSerializer
|
from .serializers import UserSerializer, UserGroupSerializer, UserAttributeSerializer, UserGroupEditSerializer, \
|
||||||
|
GroupEditSerializer, UserPKUpdateSerializer
|
||||||
from .models import User, UserGroup
|
from .models import User, UserGroup
|
||||||
|
|
||||||
|
|
||||||
|
@ -72,7 +73,22 @@ class UserResetPKApi(generics.UpdateAPIView):
|
||||||
|
|
||||||
def perform_update(self, serializer):
|
def perform_update(self, serializer):
|
||||||
user = self.get_object()
|
user = self.get_object()
|
||||||
user._public_key = ''
|
user.is_public_key_valid = False
|
||||||
user.save()
|
user.save()
|
||||||
from .utils import send_reset_ssh_key_mail
|
from .utils import send_reset_ssh_key_mail
|
||||||
send_reset_ssh_key_mail(user)
|
send_reset_ssh_key_mail(user)
|
||||||
|
|
||||||
|
|
||||||
|
class UserUpdatePKApi(generics.UpdateAPIView):
|
||||||
|
queryset = User.objects.all()
|
||||||
|
serializer_class = UserPKUpdateSerializer
|
||||||
|
|
||||||
|
def perform_update(self, serializer):
|
||||||
|
user = self.get_object()
|
||||||
|
user.private_key = serializer.validated_data['_public_key']
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
|
||||||
|
class GroupDeleteApi(generics.DestroyAPIView):
|
||||||
|
queryset = UserGroup.objects.all()
|
||||||
|
serializer_class = GroupEditSerializer
|
||||||
|
|
|
@ -79,9 +79,11 @@ class UserKeyForm(forms.Form):
|
||||||
help_text=_('Paste your id_ras.pub here.'))
|
help_text=_('Paste your id_ras.pub here.'))
|
||||||
|
|
||||||
def clean_public_key(self):
|
def clean_public_key(self):
|
||||||
|
public_key = self.cleaned_data['public_key']
|
||||||
|
if self.user._public_key and public_key == self.user.public_key:
|
||||||
|
raise forms.ValidationError(_('Public key should not be the same as your old one.'))
|
||||||
from sshpubkeys import SSHKey
|
from sshpubkeys import SSHKey
|
||||||
from sshpubkeys.exceptions import InvalidKeyException
|
from sshpubkeys.exceptions import InvalidKeyException
|
||||||
public_key = self.cleaned_data['public_key']
|
|
||||||
ssh = SSHKey(public_key)
|
ssh = SSHKey(public_key)
|
||||||
try:
|
try:
|
||||||
ssh.parse()
|
ssh.parse()
|
||||||
|
|
|
@ -4,19 +4,22 @@ 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.utils import timezone
|
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 rest_framework.authtoken.models import Token
|
from rest_framework.authtoken.models import Token
|
||||||
|
|
||||||
from common.utils import encrypt, decrypt, date_expired_default
|
from common.utils import encrypt, decrypt, date_expired_default
|
||||||
|
from common.mixins import NoDeleteModelMixin
|
||||||
|
|
||||||
|
|
||||||
class UserGroup(models.Model):
|
class UserGroup(NoDeleteModelMixin):
|
||||||
name = models.CharField(max_length=100, unique=True, verbose_name=_('Name'))
|
name = models.CharField(max_length=100, unique=True, verbose_name=_('Name'))
|
||||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||||
date_created = models.DateTimeField(auto_now_add=True)
|
date_created = models.DateTimeField(auto_now_add=True)
|
||||||
|
@ -30,14 +33,20 @@ class UserGroup(models.Model):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
if self.name != 'Default':
|
||||||
|
self.users.clear()
|
||||||
|
return super(UserGroup, self).delete()
|
||||||
|
return True
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = 'user_group'
|
db_table = 'user-group'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def initial(cls):
|
def initial(cls):
|
||||||
group_or_create = cls.objects.get_or_create(name='Default', comment='Default user group for all user',
|
group, created = cls.objects.get_or_create(name='Default', comment='Default user group for all user',
|
||||||
created_by='System')
|
created_by='System')
|
||||||
return group_or_create[0]
|
return group
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def generate_fake(cls, count=100):
|
def generate_fake(cls, count=100):
|
||||||
|
@ -48,8 +57,7 @@ class UserGroup(models.Model):
|
||||||
for i in range(count):
|
for i in range(count):
|
||||||
group = cls(name=forgery_py.name.full_name(),
|
group = cls(name=forgery_py.name.full_name(),
|
||||||
comment=forgery_py.lorem_ipsum.sentence(),
|
comment=forgery_py.lorem_ipsum.sentence(),
|
||||||
created_by=choice(User.objects.all()).username
|
created_by=choice(User.objects.all()).username)
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
group.save()
|
group.save()
|
||||||
except IntegrityError:
|
except IntegrityError:
|
||||||
|
@ -76,7 +84,7 @@ class User(AbstractUser):
|
||||||
_private_key = models.CharField(max_length=5000, blank=True, verbose_name=_('ssh private key'))
|
_private_key = models.CharField(max_length=5000, blank=True, verbose_name=_('ssh private key'))
|
||||||
_public_key = models.CharField(max_length=1000, blank=True, verbose_name=_('ssh public key'))
|
_public_key = models.CharField(max_length=1000, blank=True, verbose_name=_('ssh public key'))
|
||||||
comment = models.TextField(max_length=200, blank=True, verbose_name=_('Comment'))
|
comment = models.TextField(max_length=200, blank=True, verbose_name=_('Comment'))
|
||||||
is_first_login = models.BooleanField(default=True)
|
is_first_login = models.BooleanField(default=False)
|
||||||
date_expired = models.DateTimeField(default=date_expired_default, blank=True, null=True,
|
date_expired = models.DateTimeField(default=date_expired_default, blank=True, null=True,
|
||||||
verbose_name=_('Date expired'))
|
verbose_name=_('Date expired'))
|
||||||
created_by = models.CharField(max_length=30, default='', verbose_name=_('Created by'))
|
created_by = models.CharField(max_length=30, default='', verbose_name=_('Created by'))
|
||||||
|
@ -143,17 +151,13 @@ class User(AbstractUser):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
# If user not set name, it's default equal username
|
|
||||||
if not self.name:
|
if not self.name:
|
||||||
self.name = self.username
|
self.name = self.username
|
||||||
|
|
||||||
super(User, self).save(*args, **kwargs)
|
super(User, self).save(*args, **kwargs)
|
||||||
# Set user default group 'All'
|
# Add the current user to the default group.
|
||||||
# Todo: It's have bug
|
|
||||||
group = UserGroup.initial()
|
group = UserGroup.initial()
|
||||||
if group not in self.groups.all():
|
self.groups.add(group)
|
||||||
self.groups.add(group)
|
|
||||||
# super(User, self).save(*args, **kwargs)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def private_token(self):
|
def private_token(self):
|
||||||
|
@ -226,8 +230,7 @@ class User(AbstractUser):
|
||||||
role=choice(dict(User.ROLE_CHOICES).keys()),
|
role=choice(dict(User.ROLE_CHOICES).keys()),
|
||||||
wechat=forgery_py.internet.user_name(True),
|
wechat=forgery_py.internet.user_name(True),
|
||||||
comment=forgery_py.lorem_ipsum.sentence(),
|
comment=forgery_py.lorem_ipsum.sentence(),
|
||||||
created_by=choice(cls.objects.all()).username,
|
created_by=choice(cls.objects.all()).username)
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
user.save()
|
user.save()
|
||||||
except IntegrityError:
|
except IntegrityError:
|
||||||
|
@ -239,13 +242,13 @@ class User(AbstractUser):
|
||||||
|
|
||||||
def init_all_models():
|
def init_all_models():
|
||||||
for model in (UserGroup, User):
|
for model in (UserGroup, User):
|
||||||
if hasattr(model, b'initial'):
|
if hasattr(model, 'initial'):
|
||||||
model.initial()
|
model.initial()
|
||||||
|
|
||||||
|
|
||||||
def generate_fake():
|
def generate_fake():
|
||||||
for model in (UserGroup, User):
|
for model in (UserGroup, User):
|
||||||
if hasattr(model, b'generate_fake'):
|
if hasattr(model, 'generate_fake'):
|
||||||
model.generate_fake()
|
model.generate_fake()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
@ -27,6 +26,13 @@ class UserGroupSerializer(serializers.ModelSerializer):
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class GroupEditSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = UserGroup
|
||||||
|
fields = ['id', 'name', 'comment', 'date_created', 'created_by']
|
||||||
|
|
||||||
|
|
||||||
class UserAttributeSerializer(serializers.ModelSerializer):
|
class UserAttributeSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -46,11 +52,18 @@ class UserPKUpdateSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ['id', '_private_key']
|
fields = ['id', '_public_key']
|
||||||
|
|
||||||
def validate__private_key(self, value):
|
def validate__public_key(self, value):
|
||||||
from users.utils import validate_ssh_pk
|
from sshpubkeys import SSHKey
|
||||||
checked, reason = validate_ssh_pk(value)
|
from sshpubkeys.exceptions import InvalidKeyException
|
||||||
if not checked:
|
ssh = SSHKey(value)
|
||||||
raise serializers.ValidationError(_('Not a valid ssh private key.'))
|
try:
|
||||||
|
ssh.parse()
|
||||||
|
except InvalidKeyException as e:
|
||||||
|
print e
|
||||||
|
raise serializers.ValidationError(_('Not a valid ssh public key'))
|
||||||
|
except NotImplementedError as e:
|
||||||
|
print e
|
||||||
|
raise serializers.ValidationError(_('Not a valid ssh public key'))
|
||||||
return value
|
return value
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
{% extends '_modal.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block modal_id %}user_reset_pk_modal{% endblock %}
|
|
||||||
{% block modal_title%}{% trans 'Reset User SSH Private Key' %}{% endblock %}
|
|
||||||
{% block modal_body %}
|
|
||||||
<textarea id="txt_pk" class="form-control" cols="30" rows="10" placeholder="-----BEGIN RSA PRIVATE KEY-----"></textarea>
|
|
||||||
{% endblock %}
|
|
||||||
{% block modal_confirm_id %}btn_user_reset_pk{% endblock %}
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
{% extends '_modal.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block modal_id %}user_update_pk_modal{% endblock %}
|
||||||
|
{% block modal_title%}{% trans "Update User SSH Public Key" %}{% endblock %}
|
||||||
|
{% block modal_body %}
|
||||||
|
<textarea id="txt_pk" class="form-control" cols="30" rows="10" placeholder="ssh-rsa AAAAB3NzaC1yc2EAA....."></textarea>
|
||||||
|
{% endblock %}
|
||||||
|
{% block modal_confirm_id %}btn_user_update_pk{% endblock %}
|
|
@ -3,10 +3,12 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load bootstrap %}
|
{% load bootstrap %}
|
||||||
|
|
||||||
|
|
||||||
{% block custom_head_css_js %}
|
{% block custom_head_css_js %}
|
||||||
{{ wizard.form.media }}
|
{{ wizard.form.media }}
|
||||||
<link href="{% static 'css/plugins/steps/jquery.steps.css' %}" rel="stylesheet">
|
<link href="{% static 'css/plugins/steps/jquery.steps.css' %}" rel="stylesheet">
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
{% block first_login_message %}{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="wrapper wrapper-content animated fadeInRight">
|
<div class="wrapper wrapper-content animated fadeInRight">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
|
@ -160,6 +160,14 @@
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{% trans 'Update ssh key' %}:</td>
|
||||||
|
<td>
|
||||||
|
<span class="pull-right">
|
||||||
|
<button type="button" class="btn btn-primary btn-xs" id="btn_update_pk" style="width: 54px;" data-toggle="modal" data-target="#user_update_pk_modal">{% trans 'Update' %}</button>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
@ -207,6 +215,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% include 'users/_user_update_pk_modal.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script>
|
<script>
|
||||||
|
@ -352,6 +361,33 @@ $(document).ready(function() {
|
||||||
}, function() {
|
}, function() {
|
||||||
doReset();
|
doReset();
|
||||||
});
|
});
|
||||||
|
}).on('click', '#btn_user_update_pk', function(){
|
||||||
|
var $this = $(this);
|
||||||
|
var pk = $('#txt_pk').val();
|
||||||
|
var the_url = '{% url "users:user-update-pk-api" pk=user_object.id %}';
|
||||||
|
var body = {'_public_key': pk};
|
||||||
|
var success = function() {
|
||||||
|
$('#txt_pk').val('');
|
||||||
|
$this.closest('.modal').modal('hide');
|
||||||
|
var msg = "{% trans 'Successfully updated the SSH public key.' %}";
|
||||||
|
swal("{% trans 'User SSH Public Key Update' %}", msg, "success");
|
||||||
|
};
|
||||||
|
var fail = function() {
|
||||||
|
var msg = "{% trans 'Failed to update the user\'s SSH public key.' %}";
|
||||||
|
swal({
|
||||||
|
title: "{% trans 'User SSH Public Key Update' %}",
|
||||||
|
text: msg,
|
||||||
|
type: "error",
|
||||||
|
showCancelButton: false,
|
||||||
|
confirmButtonColor: "#DD6B55",
|
||||||
|
confirmButtonText: "{% trans 'Confirm' %}",
|
||||||
|
closeOnConfirm: true
|
||||||
|
}, function () {
|
||||||
|
$('#txt_pk').focus();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
APIUpdateAttr({ url: the_url, body: JSON.stringify(body), success: success, error: fail});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block custom_head_css_js %}
|
||||||
|
<link href="{% static "css/plugins/select2/select2.min.css" %}" rel="stylesheet">
|
||||||
|
<link href="{% static "css/plugins/sweetalert/sweetalert.css" %}" rel="stylesheet">
|
||||||
|
<script src="{% static "js/plugins/select2/select2.full.min.js" %}"></script>
|
||||||
|
<script src="{% static "js/plugins/sweetalert/sweetalert.min.js" %}"></script>
|
||||||
|
{% endblock %}
|
||||||
|
{% 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">
|
||||||
|
<li class="active">
|
||||||
|
<a href="{% url 'users:user-group-detail' pk=object.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'User Group Detail' %} </a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="tab-content">
|
||||||
|
<div class="col-sm-7" 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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ibox-content">
|
||||||
|
<table class="table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td width="20%">{% trans 'Name' %}:</td>
|
||||||
|
<td><b>{{ object.name }}</b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{% trans 'Comment' %}:</td>
|
||||||
|
<td><b>{{ object.comment }}</b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{% trans 'Created at:' %}:</td>
|
||||||
|
<td><b>{{ object.date_created }}</b></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-5" style="padding-left: 0;padding-right: 0">
|
||||||
|
<div class="panel panel-primary">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<i class="fa fa-info-circle"></i> {% trans 'Quick modify' %}
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<table class="table">
|
||||||
|
<tbody>
|
||||||
|
<tr class="no-borders-tr">
|
||||||
|
<td width="50%">{% trans 'Active' %}:</td>
|
||||||
|
<td><span class="pull-right">
|
||||||
|
<div class="switch">
|
||||||
|
<div class="onoffswitch">
|
||||||
|
<input type="checkbox" {% if user_object.is_active %} checked {% endif %} class="onoffswitch-checkbox" id="is_active">
|
||||||
|
<label class="onoffswitch-label" for="is_active">
|
||||||
|
<span class="onoffswitch-inner"></span>
|
||||||
|
<span class="onoffswitch-switch"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{% trans 'Enable OTP' %}:</td>
|
||||||
|
<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>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -1,18 +1,22 @@
|
||||||
{% extends '_list_base.html' %}
|
{% extends '_list_base.html' %}
|
||||||
{% load i18n %}
|
{% load i18n static %}
|
||||||
{% load common_tags %}
|
{% 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_left_head %}
|
{% block content_left_head %}
|
||||||
<a href="{% url 'users:user-group-create' %}" class="btn btn-sm btn-primary "> 添加用户组 </a>
|
<a href="{% url 'users:user-group-create' %}" class="btn btn-sm btn-primary ">{% trans "Add User Group" %}</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block table_head %}
|
{% block table_head %}
|
||||||
<th class="text-center">
|
<th class="text-center">
|
||||||
<input type="checkbox" id="check_all" onclick="checkAll('check_all', 'checked')">
|
<input type="checkbox" id="check_all" onclick="checkAll('check_all', 'checked')">
|
||||||
</th>
|
</th>
|
||||||
<th class="text-center"><a href="{% url 'users:user-group-list' %}?sort=name">名称</a></th>
|
<th class="text-center"><a href="{% url 'users:user-group-list' %}?sort=name">{% trans "Name" %}</a></th>
|
||||||
<th class="text-center">用户数量</th>
|
<th class="text-center">{% trans "User Amount" %}</th>
|
||||||
<th class="text-center">资产数量</th>
|
<th class="text-center">{% trans "Asset Amount" %}</th>
|
||||||
<th class="text-center">描述</th>
|
<th class="text-center">{% trans "Comment" %}</th>
|
||||||
<th class="text-center"></th>
|
<th class="text-center"></th>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
@ -27,12 +31,13 @@
|
||||||
{{ user_group.name }}
|
{{ user_group.name }}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">{{ user_group.users.all|length }}</td>
|
<td class="text-center">{{ user_group.users.count }}</td>
|
||||||
<td class="text-center">数量</td>
|
<td class="text-center">999</td>
|
||||||
<th class="text-center">{{ user_group.comment|truncatewords:8 }}</th>
|
<th class="text-center">{{ user_group.comment|truncatewords:8 }}</th>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<a href="{% url 'users:user-group-update' pk=user_group.id %}" class="btn btn-xs btn-info">编辑</a>
|
<a href="{% url 'users:user-group-update' pk=user_group.id %}" class="btn btn-xs btn-info">{% trans "Edit" %}</a>
|
||||||
<a href="{% url 'users:user-group-delete' pk=user_group.id %}" class="btn btn-xs btn-danger del">删除</a>
|
<a href="javascript:void(0)" data-gid="{{ user_group.id }}"
|
||||||
|
class="btn btn-xs btn-danger del {% ifequal user_group.name 'Default' %}disabled{% else %}btn_delete_user_group{% endifequal %}">{% trans "Delete" %}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -42,18 +47,53 @@
|
||||||
<form id="" method="get" action="" class=" mail-search">
|
<form id="" method="get" action="" class=" mail-search">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<select class="form-control m-b" style="width: auto">
|
<select class="form-control m-b" style="width: auto">
|
||||||
<option>批量删除</option>
|
<option>{% trans "Bulk Update" %}</option>
|
||||||
<option>批量更新</option>
|
<option>{% trans "Bulk Export" %}</option>
|
||||||
<option>批量禁用</option>
|
<option>{% trans "Bulk Update" %}</option>
|
||||||
<option>批量导出</option>
|
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<div class="input-group-btn pull-left" style="padding-left: 5px;">
|
<div class="input-group-btn pull-left" style="padding-left: 5px;">
|
||||||
<button id='search_btn' type="submit" style="height: 32px;" class="btn btn-sm btn-primary">
|
<button id='search_btn' type="submit" style="height: 32px;" class="btn btn-sm btn-primary">{% trans "Confirm" %}</button>
|
||||||
确认
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block custom_foot_js %}
|
||||||
|
<script>
|
||||||
|
$(document).on('click', '.btn_delete_user_group', function(){
|
||||||
|
var $this = $(this);
|
||||||
|
function doDelete() {
|
||||||
|
var group_id = $this.data('gid');
|
||||||
|
var the_url = "{% url 'users:user-group-delete-api' 99991937 %}".replace('99991937', group_id);
|
||||||
|
var body = {};
|
||||||
|
var success = function() {
|
||||||
|
var msg = "{% trans 'Group Deleted.' %}";
|
||||||
|
swal("{% trans 'Group Delete' %}", msg, "success");
|
||||||
|
$this.closest('tr.gradeX').remove();
|
||||||
|
};
|
||||||
|
var fail = function() {
|
||||||
|
var msg = "{% trans 'Group Deleting failed.' %}";
|
||||||
|
swal("{% trans 'Group Delete' %}", msg, "error");
|
||||||
|
}
|
||||||
|
APIUpdateAttr({
|
||||||
|
url: the_url,
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
method: 'DELETE',
|
||||||
|
success: success,
|
||||||
|
error: fail
|
||||||
|
});
|
||||||
|
}
|
||||||
|
swal({
|
||||||
|
title: "{% trans 'Are you sure?' %}",
|
||||||
|
text: "{% trans 'This will delete the selected group, but will not remove any user in this group.' %}",
|
||||||
|
type: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonColor: "#DD6B55",
|
||||||
|
confirmButtonText: "{% trans 'Confirm' %}",
|
||||||
|
closeOnConfirm: false
|
||||||
|
}, function() {
|
||||||
|
doDelete();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="{{ form.username.id_for_label }}" class="col-sm-2 control-label">{% trans 'Username' %}</label>
|
<label for="{{ form.username.id_for_label }}" class="col-sm-2 control-label">{% trans 'Username' %}</label>
|
||||||
<div class="col-sm-9 controls" >
|
<div class="col-sm-9 controls" >
|
||||||
<input id="{{ form.username.id_for_label }}" name="{{ form.username.html_name }}" type="text" value="{{ user.username }}" readonly class="form-control">
|
<input id="{{ form.username.id_for_label }}" name="{{ form.username.html_name }}" type="text" value="{{ user_object.username }}" readonly class="form-control">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -31,7 +31,6 @@ urlpatterns = [
|
||||||
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'),
|
||||||
url(r'^user-group/(?P<pk>[0-9]+)/update$', views.UserGroupUpdateView.as_view(), name='user-group-update'),
|
url(r'^user-group/(?P<pk>[0-9]+)/update$', views.UserGroupUpdateView.as_view(), name='user-group-update'),
|
||||||
url(r'^user-group/(?P<pk>[0-9]+)/delete$', views.UserGroupDeleteView.as_view(), name='user-group-delete'),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -43,9 +42,12 @@ urlpatterns += [
|
||||||
api.UserAttributeApi.as_view(), name='user-patch-api'),
|
api.UserAttributeApi.as_view(), name='user-patch-api'),
|
||||||
url(r'^v1/users/(?P<pk>\d+)/reset-password/$', api.UserResetPasswordApi.as_view(), name='user-reset-password-api'),
|
url(r'^v1/users/(?P<pk>\d+)/reset-password/$', api.UserResetPasswordApi.as_view(), name='user-reset-password-api'),
|
||||||
url(r'^v1/users/(?P<pk>\d+)/reset-pk/$', api.UserResetPKApi.as_view(), name='user-reset-pk-api'),
|
url(r'^v1/users/(?P<pk>\d+)/reset-pk/$', api.UserResetPKApi.as_view(), name='user-reset-pk-api'),
|
||||||
|
url(r'^v1/users/(?P<pk>\d+)/update-pk/$', api.UserUpdatePKApi.as_view(), name='user-update-pk-api'),
|
||||||
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>[0-9]+)/edit$',
|
||||||
api.UserGroupEditApi.as_view(), name='user-group-edit-api'),
|
api.UserGroupEditApi.as_view(), name='user-group-edit-api'),
|
||||||
|
url(r'^v1/user-groups/(?P<pk>[0-9]+)/delete/$', api.GroupDeleteApi.as_view(),
|
||||||
|
name='user-group-delete-api'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -19,7 +19,7 @@ from django.views.decorators.debug import sensitive_post_parameters
|
||||||
from django.views.generic.base import TemplateView
|
from django.views.generic.base import TemplateView
|
||||||
from django.views.generic.list import ListView
|
from django.views.generic.list import ListView
|
||||||
from django.views.generic.edit import CreateView, DeleteView, UpdateView, FormView, SingleObjectMixin, \
|
from django.views.generic.edit import CreateView, DeleteView, UpdateView, FormView, SingleObjectMixin, \
|
||||||
FormMixin, ModelFormMixin, ProcessFormView, BaseCreateView
|
FormMixin
|
||||||
from django.views.generic.detail import DetailView
|
from django.views.generic.detail import DetailView
|
||||||
from formtools.wizard.views import SessionWizardView
|
from formtools.wizard.views import SessionWizardView
|
||||||
|
|
||||||
|
@ -133,7 +133,7 @@ class UserUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||||
model = User
|
model = User
|
||||||
form_class = UserUpdateForm
|
form_class = UserUpdateForm
|
||||||
template_name = 'users/user_update.html'
|
template_name = 'users/user_update.html'
|
||||||
context_object_name = 'user'
|
context_object_name = 'user_object'
|
||||||
success_url = reverse_lazy('users:user-list')
|
success_url = reverse_lazy('users:user-list')
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
|
@ -146,10 +146,6 @@ class UserUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||||
user.set_password(password)
|
user.set_password(password)
|
||||||
return super(UserUpdateView, self).form_valid(form)
|
return super(UserUpdateView, self).form_valid(form)
|
||||||
|
|
||||||
def form_invalid(self, form):
|
|
||||||
print(form.errors)
|
|
||||||
return super(UserUpdateView, self).form_invalid(form)
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(UserUpdateView, self).get_context_data(**kwargs)
|
context = super(UserUpdateView, self).get_context_data(**kwargs)
|
||||||
context.update({'app': _('Users'), 'action': _('Update user')})
|
context.update({'app': _('Users'), 'action': _('Update user')})
|
||||||
|
@ -238,8 +234,14 @@ class UserGroupUpdateView(UpdateView):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class UserGroupDetailView(DetailView):
|
class UserGroupDetailView(AdminUserRequiredMixin, DetailView):
|
||||||
pass
|
model = UserGroup
|
||||||
|
template_name = 'users/user_group_detail.html'
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = {'app': _('Users'), 'action': _('User Group Detail')}
|
||||||
|
kwargs.update(context)
|
||||||
|
return super(UserGroupDetailView, self).get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class UserGroupDeleteView(DeleteView):
|
class UserGroupDeleteView(DeleteView):
|
||||||
|
@ -332,6 +334,7 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView):
|
||||||
if field.name == 'enable_otp':
|
if field.name == 'enable_otp':
|
||||||
user.enable_otp = field.value()
|
user.enable_otp = field.value()
|
||||||
user.is_first_login = False
|
user.is_first_login = False
|
||||||
|
user.is_public_key_valid = True
|
||||||
user.save()
|
user.save()
|
||||||
return redirect(reverse('index'))
|
return redirect(reverse('index'))
|
||||||
|
|
||||||
|
@ -351,6 +354,16 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView):
|
||||||
}
|
}
|
||||||
return super(UserFirstLoginView, self).get_form_initial(step)
|
return super(UserFirstLoginView, self).get_form_initial(step)
|
||||||
|
|
||||||
|
def get_form(self, step=None, data=None, files=None):
|
||||||
|
form = super(UserFirstLoginView, self).get_form(step, data, files)
|
||||||
|
|
||||||
|
if step is None:
|
||||||
|
step = self.steps.current
|
||||||
|
|
||||||
|
if step == '1':
|
||||||
|
form.user = self.request.user
|
||||||
|
return form
|
||||||
|
|
||||||
|
|
||||||
class UserAssetPermissionView(AdminUserRequiredMixin, FormMixin, SingleObjectMixin, ListView):
|
class UserAssetPermissionView(AdminUserRequiredMixin, FormMixin, SingleObjectMixin, ListView):
|
||||||
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
|
paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
|
||||||
|
@ -376,7 +389,7 @@ class UserAssetPermissionView(AdminUserRequiredMixin, FormMixin, SingleObjectMix
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
asset_permissions = set(self.object.asset_permissions.all()) \
|
asset_permissions = set(self.object.asset_permissions.all()) \
|
||||||
| self.get_asset_permission_inherit_from_user_group()
|
| self.get_asset_permission_inherit_from_user_group()
|
||||||
return list(asset_permissions)
|
return list(asset_permissions)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
|
Loading…
Reference in New Issue