Commit 7cebc4ef authored by liuzheng712's avatar liuzheng712

Merge branch 'master' of code.simcu.com:jumpserver/jumpserver

parents 91f0800e a99a6b69
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
if __name__ == '__main__':
pass
This diff is collapsed.
This diff is collapsed.
...@@ -969,17 +969,43 @@ button.dim:active:before { ...@@ -969,17 +969,43 @@ button.dim:active:before {
position: relative; position: relative;
width: 100%; width: 100%;
} }
.select2-container--default .select2-results__option--highlighted[aria-selected] {
background-color: #1ab394;
color: white;
}
.select2-selection--multiple {
border: 1px solid #e5e6e7 !important;
cursor: text !important;
}
/*.select2-container--classic .select2-selection--multiple:focus {*/
/*border: 1px solid #1ab394;*/
/*}*/
.select2-container--forcus {
border: 1px solid #1AB394 !important;
}
.select2-selection__choice,
.chosen-container-multi .chosen-choices li.search-choice { .chosen-container-multi .chosen-choices li.search-choice {
background: #f1f1f1; background: #f1f1f1 !important;
border: 1px solid #ededed; border: 1px solid #e5e6e7 !important;
border-radius: 2px; /*border: 1px solid #ededed;*/
box-shadow: none; border-radius: 2px !important;
color: #333333; box-shadow: none !important;
cursor: default; color: #333333 !important;
line-height: 13px; cursor: default !important;
margin: 3px 0 3px 5px; line-height: 13px !important;
padding: 3px 20px 3px 5px; /*margin: 3px 0 3px 5px !important;*/
position: relative; padding: 3px 20px 3px 5px !important;
position: relative !important;
}
.select2-container--default.select2-container--focus .select2-selection--multiple {
border: 1px solid #1ab394 !important;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.3) !important;
} }
.chosen-container .chosen-results li.highlighted { .chosen-container .chosen-results li.highlighted {
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
{% load static %} {% load static %}
<!-- Mainly scripts --> <!-- Mainly scripts -->
<script src="{% static "js/plugins/metisMenu/jquery.metisMenu.js" %}"></script> <script src="{% static "js/plugins/metisMenu/jquery.metisMenu.js" %}"></script>
{#<script src="{% static "js/plugins/slimscroll/jquery.slimscroll.min.js" %}"></script>#}
{#<script src="{% static "js/bootstrap-dialog.js" %}"></script>#}
{#<script src="{% static "js/mindmup-editabletable.js" %}"></script>#}
{#<script src="{% static "js/plugins/fullcalendar/moment.min.js" %}"></script>#}
{#<script src="{% static "js/plugins/fullcalendar/fullcalendar.min.js" %}"></script>#}
<!-- Custom and plugin javascript --> <!-- Custom and plugin javascript -->
<script src="{% static "js/inspinia.js" %}"></script> <script src="{% static "js/inspinia.js" %}"></script>
{#<script src="{% static "js/plugins/pace/pace.min.js" %}"></script>#}
<!-- Peity -->
{#<script src="{% static "js/plugins/peity/jquery.peity.min.js" %}"></script>#}
{#<script src="{% static "js/demo/peity-demo.js" %}"></script>#}
<script src="{% static "js/base.js" %}"></script> <script src="{% static "js/base.js" %}"></script>
<!-- pop windows layer-->
{#<script src="{% static "js/layer/layer.js" %}"></script>#}
<!-- highcharts -->
{#<script src="{% static "js/highcharts/highcharts.js" %}"></script>#}
{#<script src="{% static "js/dropzone/dropzone.js" %}"></script>#}
<!-- active menu --> <!-- active menu -->
<script> <script>
var url_array = document.location.pathname.split("/"); var url_array = document.location.pathname.split("/");
......
<div class="row border-bottom"> <div class="row border-bottom">
<nav class="navbar navbar-static-top" role="navigation" style="margin-bottom: 0"> <nav class="navbar navbar-static-top white-bg" role="navigation" style="margin-bottom: 0">
<div class="navbar-header"> <div class="navbar-header">
<a class="navbar-minimalize minimalize-styl-2 btn btn-primary " href="#"><i class="fa fa-bars"></i> </a> <a class="navbar-minimalize minimalize-styl-2 btn btn-primary " href="#"><i class="fa fa-bars"></i> </a>
<form role="search" class="navbar-form-custom" method="get" action=""> <form role="search" class="navbar-form-custom" method="get" action="">
......
{% extends 'base.html' %}
{% load common_tags %}
{% 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="ibox-title">
<h5> {{ path2 }} </h5>
<div class="ibox-tools">
<a class="collapise-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<div class="">
{# left button add #}
{% block content_left_head %} {% endblock %}
<form id="search_form" method="get" action="" class="pull-right mail-search">
<div class="input-group">
<input type="text" class="form-control input-sm" name="keyword" placeholder="Search" value="{{ keyword }}">
<div class="input-group-btn">
<button id='search_btn' type="submit" class="btn btn-sm btn-primary">
搜索
</button>
</div>
</div>
</form>
</div>
<table class="table table-striped table-bordered table-hover " id="editable" >
<thead>
<tr>
{% block table_head %} {% endblock %}
</tr>
</thead>
<tbody>
{% block table_body %} {% endblock %}
</tbody>
</table>
<div class="row">
<div class="col-sm-4">
{# Update batch #}
{% block content_bottom_left %} {% endblock %}
</div>
{% include '_pagination.html' %}
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }}" style="margin: 20px auto 0px">
{{ message|safe }}
</div>
{% endfor %}
{% endif %}
<li id=""> <li id="index">
<a href=""> <a href="">
<i class="fa fa-dashboard"></i> <span class="nav-label">仪表盘</span><span class="label label-info pull-right"></span> <i class="fa fa-dashboard"></i> <span class="nav-label">仪表盘</span><span class="label label-info pull-right"></span>
</a> </a>
</li> </li>
<li id=""> <li id="users">
<a href="#"> <a href="#">
<i class="fa fa-group"></i> <span class="nav-label">用户管理</span><span class="fa arrow"></span> <i class="fa fa-group"></i> <span class="nav-label">用户管理</span><span class="fa arrow"></span>
</a> </a>
<ul class="nav nav-second-level"> <ul class="nav nav-second-level active">
<li class="group"><a href="{% url 'users:user-list' %}">用户列表</a></li> <li class="user"><a href="{% url 'users:user-list' %}">用户列表</a></li>
<li class="user"><a href="{% url 'users:usergroup-list' %}">用户组列表</a></li> <li class="usergroup"><a href="{% url 'users:usergroup-list' %}">用户组列表</a></li>
</ul> </ul>
</li> </li>
<li id=""> <li id="">
......
{% load common_tags %} {% load common_tags %}
{% if is_paginated %} {% if is_paginated %}
<div class="row"> <div class="col-sm-4">
<div class="col-sm-6"> <div class="dataTables_info text-center" id="editable_info" role="status" aria-live="polite">
<div class="dataTables_info" id="editable_info" role="status" aria-live="polite"> Showing {{ page_obj.start_index }} to {{ page_obj.end_index }} of {{ paginator.count }} entries
Showing {{ page_obj.start_index }} to {{ page_obj.end_index }} of {{ paginator.count }} entries </div>
</div> </div>
</div> <div class="col-sm-4">
<div class="col-sm-6"> <div class="dataTables_paginate paging_simple_numbers" id="editable_paginate">
<div class="dataTables_paginate paging_simple_numbers" id="editable_paginate"> <ul class="pagination" style="margin-top: 0; float: right">
<ul class="pagination" style="margin-top: 0; float: right"> {% if page_obj.has_previous %}
{% if page_obj.has_previous %} <li class="paginate_button previous" aria-controls="editable" tabindex="0" id="previous">
<li class="paginate_button previous" aria-controls="editable" tabindex="0" id="previous"> <a data-page="next" href="?page={{ page_obj.previous_page_number}}"></a>
<a data-page="next" href="?page={{ page_obj.previous_page_number}}"></a> </li>
</li> {% endif %}
{% endif %}
{% for page in paginator.num_pages|pagination_range:page_obj.number %} {% for page in paginator.num_pages|pagination_range:page_obj.number %}
{% if page == page_obj.number %} {% if page == page_obj.number %}
<li class="paginate_button active" aria-controls="editable" tabindex="0"> <li class="paginate_button active" aria-controls="editable" tabindex="0">
{% else %} {% else %}
<li class="paginate_button" aria-controls="editable" tabindex="0"> <li class="paginate_button" aria-controls="editable" tabindex="0">
{% endif %} {% endif %}
<a class="page" href="?page={{ page }}" title="第{{ page }}页">{{ page }}</a> <a class="page" href="?page={{ page }}" title="第{{ page }}页">{{ page }}</a>
</li> </li>
{% endfor %} {% endfor %}
{% if page_obj.has_next %} {% if page_obj.has_next %}
<li class="paginate_button next" aria-controls="editable" tabindex="0" id="next"> <li class="paginate_button next" aria-controls="editable" tabindex="0" id="next">
<a data-page="next" href="?page={{ page_obj.next_page_number }}"></a> <a data-page="next" href="?page={{ page_obj.next_page_number }}"></a>
</li> </li>
{% endif %} {% endif %}
</ul> </ul>
</div> </div>
</div> </div>
</div> {% endif %}
{% endif %}
<script> <script>
function sleep(n) { //n表示的毫秒数 function sleep(n) { //n表示的毫秒数
var start = new Date().getTime(); var start = new Date().getTime();
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
{% include '_left_side_bar.html' %} {% include '_left_side_bar.html' %}
<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' %}
{% block content %}{% endblock %} {% block content %}{% endblock %}
{% include '_footer.html' %} {% include '_footer.html' %}
</div> </div>
......
...@@ -21,7 +21,7 @@ class UserAddForm(ModelForm): ...@@ -21,7 +21,7 @@ class UserAddForm(ModelForm):
} }
widgets = { widgets = {
'groups': forms.SelectMultiple(attrs={'class': 'chosen-select', 'data-placeholder': '请选择用户组'}), 'groups': forms.SelectMultiple(attrs={'class': 'select2', 'data-placeholder': '请选择用户组'}),
} }
...@@ -29,19 +29,18 @@ class UserUpdateForm(ModelForm): ...@@ -29,19 +29,18 @@ class UserUpdateForm(ModelForm):
class Meta: class Meta:
model = User model = User
fields = [ fields = [
'name', 'email', 'groups', 'wechat', 'avatar', 'name', 'email', 'groups', 'wechat',
'phone', 'enable_otp', 'role', 'date_expired', 'comment', 'phone', 'enable_otp', 'role', 'date_expired', 'comment',
] ]
help_texts = { help_texts = {
'username': '* required', 'username': '* required',
'name': '* required',
'email': '* required', 'email': '* required',
'groups': '* required' 'groups': '* required'
} }
widgets = { widgets = {
'groups': forms.SelectMultiple(attrs={'class': 'chosen-select', 'data-placeholder': '请选择用户组'}), 'groups': forms.SelectMultiple(attrs={'class': 'select2', 'data-placeholder': '请选择用户组'}),
} }
......
...@@ -7,6 +7,7 @@ from django.contrib.auth.hashers import make_password ...@@ -7,6 +7,7 @@ from django.contrib.auth.hashers import make_password
from django.utils import timezone from django.utils import timezone
from django.db import models from django.db import models
from django.contrib.auth.models import AbstractUser, Permission from django.contrib.auth.models import AbstractUser, Permission
from django.db import OperationalError
class Role(models.Model): class Role(models.Model):
...@@ -23,6 +24,12 @@ class Role(models.Model): ...@@ -23,6 +24,12 @@ class Role(models.Model):
def __unicode__(self): def __unicode__(self):
return self.name return self.name
def delete(self, using=None, keep_parents=False):
if self.user_set.all().count() > 0:
raise OperationalError('Role %s has some member, should not be delete.' % self.name)
else:
return super(Role, self).delete(using=using, keep_parents=keep_parents)
class Meta: class Meta:
db_table = 'role' db_table = 'role'
...@@ -56,7 +63,7 @@ class UserGroup(models.Model): ...@@ -56,7 +63,7 @@ class UserGroup(models.Model):
@classmethod @classmethod
def initial(cls): def initial(cls):
group_or_create = cls.objects.get_or_create(name='All', comment='Default user group for all user', group_or_create = 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_or_create[0]
...@@ -93,7 +100,7 @@ class User(AbstractUser): ...@@ -93,7 +100,7 @@ class User(AbstractUser):
phone = models.CharField(max_length=20, blank=True, verbose_name='手机号') phone = models.CharField(max_length=20, blank=True, verbose_name='手机号')
enable_otp = models.BooleanField(default=False, verbose_name='启用二次验证') enable_otp = models.BooleanField(default=False, verbose_name='启用二次验证')
secret_key_otp = models.CharField(max_length=16, blank=True) secret_key_otp = models.CharField(max_length=16, blank=True)
role = models.ForeignKey(Role, on_delete=models.PROTECT, verbose_name='角色') role = models.ForeignKey(Role, on_delete=models.SET('None'), verbose_name='角色')
private_key = models.CharField(max_length=5000, blank=True, verbose_name='ssh私钥') # ssh key max length 4096 bit private_key = models.CharField(max_length=5000, blank=True, verbose_name='ssh私钥') # ssh key max length 4096 bit
public_key = models.CharField(max_length=1000, blank=True, verbose_name='公钥') public_key = models.CharField(max_length=1000, blank=True, verbose_name='公钥')
comment = models.TextField(max_length=200, blank=True, verbose_name='描述') comment = models.TextField(max_length=200, blank=True, verbose_name='描述')
...@@ -123,11 +130,13 @@ class User(AbstractUser): ...@@ -123,11 +130,13 @@ class User(AbstractUser):
# If user not set name, it's default equal username # 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' # Set user default group 'All'
# Todo: It's have bug
group = UserGroup.initial() group = UserGroup.initial()
self.groups.add(group) if group not in self.groups.all():
self.groups.add(group)
# super(User, self).save(*args, **kwargs)
class Meta: class Meta:
db_table = 'user' db_table = 'user'
......
...@@ -2,9 +2,9 @@ ...@@ -2,9 +2,9 @@
{% load static %} {% load static %}
{% load bootstrap %} {% load bootstrap %}
{% block custom_head_css_js %} {% block custom_head_css_js %}
<link href="{% static "css/plugins/chosen/chosen.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>
<link href="{% static "css/plugins/datepicker/datepicker3.css" %}" rel="stylesheet"> <link href="{% static "css/plugins/datepicker/datepicker3.css" %}" rel="stylesheet">
<script src="{% static "js/plugins/chosen/chosen.jquery.min.js" %}"></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
...@@ -41,13 +41,14 @@ ...@@ -41,13 +41,14 @@
<div class="hr-line-dashed"></div> <div class="hr-line-dashed"></div>
<h3>角色安全</h3> <h3>角色安全</h3>
{{ form.role|bootstrap_horizontal }} {{ form.role|bootstrap_horizontal }}
<div class="form-group" id="date_5"> <div class="form-group {% if form.date_expired.errors %} has-error {% endif %}" id="date_5">
<label for="{{ form.date_expired.id_for_label }}" class="col-sm-2 control-label">{{ form.date_expired.label }}</label> <label for="{{ form.date_expired.id_for_label }}" class="col-sm-2 control-label">{{ form.date_expired.label }}</label>
<div class="col-sm-9"> <div class="col-sm-9">
<div class="input-group date"> <div class="input-group date">
<span class="input-group-addon"><i class="fa fa-calendar"></i></span> <span class="input-group-addon"><i class="fa fa-calendar"></i></span>
<input id="{{ form.date_expired.id_for_label }}" name="{{ form.date_expired.html_name }}" type="text" class="form-control" value="{{ form.date_expired.value|date:'m/d/Y' }}"> <input id="{{ form.date_expired.id_for_label }}" name="{{ form.date_expired.html_name }}" type="text" class="form-control" value="{{ form.date_expired.value|date:'m/d/Y' }}">
</div> </div>
<span class="help-block ">{{ form.date_expired.errors }}</span>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
...@@ -79,16 +80,7 @@ ...@@ -79,16 +80,7 @@
<script src="{% static 'js/plugins/datapicker/bootstrap-datepicker.js' %}"></script> <script src="{% static 'js/plugins/datapicker/bootstrap-datepicker.js' %}"></script>
<script> <script>
$(document).ready(function () { $(document).ready(function () {
var config = { $('.select2').select2();
'.chosen-select' : {},
'.chosen-select-deselect' : {allow_single_deselect:true},
'.chosen-select-no-single' : {disable_search_threshold:10},
'.chosen-select-no-results': {no_results_text:'Oops, nothing found!'},
'.chosen-select-width' : {width:"95%"}
};
for (var selector in config) {
$(selector).chosen(config[selector]);
}
$('.input-group.date').datepicker({ $('.input-group.date').datepicker({
todayBtn: "linked", todayBtn: "linked",
......
...@@ -4,8 +4,8 @@ ...@@ -4,8 +4,8 @@
{% load static %} {% load static %}
{% block custom_head_css_js %} {% block custom_head_css_js %}
<link href="{% static "css/plugins/chosen/chosen.css" %}" rel="stylesheet"> <link href="{% static "css/plugins/select2/select2.min.css" %}" rel="stylesheet">
<script src="{% static "js/plugins/chosen/chosen.jquery.min.js" %}"></script> <script src="{% static "js/plugins/select2/select2.full.min.js" %}"></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="wrapper wrapper-content animated fadeInRight"> <div class="wrapper wrapper-content animated fadeInRight">
...@@ -192,7 +192,7 @@ ...@@ -192,7 +192,7 @@
<form> <form>
<tr> <tr>
<td colspan="2" class="no-borders"> <td colspan="2" class="no-borders">
<select data-placeholder="选择用户组" class="chosen-select" style="width: 100%" multiple="" tabindex="4"> <select data-placeholder="选择用户组" class="select2" style="width: 100%" multiple="" tabindex="4">
{% for group in groups %} {% for group in groups %}
<option value="{{ group.id }}">{{ group.name }}</option> <option value="{{ group.id }}">{{ group.name }}</option>
{% endfor %} {% endfor %}
...@@ -208,7 +208,7 @@ ...@@ -208,7 +208,7 @@
{% for group in user.groups.all %} {% for group in user.groups.all %}
<tr> <tr>
<td width="40%"><b style="font-size: medium">{{ group.name }}</b></td> <td ><b>{{ group.name }}</b></td>
<td> <td>
<button class="btn btn-danger btn-sm" type="button" style="float: right;"><i class="fa fa-minus"></i></button> <button class="btn btn-danger btn-sm" type="button" style="float: right;"><i class="fa fa-minus"></i></button>
</td> </td>
...@@ -230,16 +230,7 @@ ...@@ -230,16 +230,7 @@
{% block custom_foot_js %} {% block custom_foot_js %}
<script> <script>
$(document).ready(function () { $(document).ready(function () {
var config = { $('.select2').select2();
'.chosen-select' : {},
'.chosen-select-deselect' : {allow_single_deselect:true},
'.chosen-select-no-single' : {disable_search_threshold:10},
'.chosen-select-no-results': {no_results_text:'Oops, nothing found!'},
'.chosen-select-width' : {width:"95%"}
};
for (var selector in config) {
$(selector).chosen(config[selector]);
}
}) })
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
{% extends '_list_base.html' %}
{% load common_tags %}
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5> 查看用户 </h5>
<div class="ibox-tools">
<a class="collapise-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<div class="">
<a href="{% url 'users:user-add' %}" class="btn btn-sm btn-primary "> 添加用户 </a>
<a id="del_btn" class="btn btn-sm btn-danger "> 删除所选 </a>
<form id="search_form" method="get" action="" class="pull-right mail-search">
<div class="input-group">
<input type="text" class="form-control input-sm" name="keyword" placeholder="用户名或姓名" value="{{ keyword }}">
<div class="input-group-btn">
<button id='search_btn' type="submit" class="btn btn-sm btn-primary">
搜索
</button>
</div>
</div>
</form>
</div>
<table class="table table-striped table-bordered table-hover " id="editable" >
<thead>
<tr>
<th class="text-center">
<input type="checkbox" id="check_all" onclick="checkAll('check_all', 'checked')">
</th>
<th class="text-center"><a href="{% url 'users:user-list' %}?sort=name">姓名</a></th>
<th class="text-center"><a href="{% url 'users:user-list' %}?sort=username">用户名</a></th>
<th class="text-center">角色</th>
<th class="text-center">用户组</th>
<th class="text-center">资产数量</th>
<th class="text-center"><a href="{% url 'users:user-list' %}?sort=date_expired">有效</a></th>
<th class="text-center"></th>
</tr>
</thead>
<tbody>
{% for user in user_list %}
<tr class="gradeX">
<td class="text-center">
<input type="checkbox" name="checked" value="{{ user.id }}">
</td>
<td class="text-center">
<a href="{% url 'users:user-detail' pk=user.id %}">
{{ user.name }}
</a>
</td>
<td class="text-center">{{ user.username }}</td>
<td class="text-center">{{ user.role.name }}</td>
<td class="text-center" title="{% for user_group in user.group.all %} {{ user_group.name }} {% endfor %}"> {{ user.groups.all|join_queryset_attr:"name" }} </td>
<th class="text-center">{{ user.name }}</th>
<td class="text-center">
{% if user.is_expired %}
<i class="fa fa-times text-danger"></i>
{% else %}
<i class="fa fa-check text-navy"></i>
{% endif %}
</td>
<td class="text-center">
<a href="{% url 'users:user-edit' pk=user.id %}" class="btn btn-xs btn-info">编辑</a>
<a href="{% url 'users:user-delete' pk=user.id %}" class="btn btn-xs btn-danger del {% if user.username == 'admin' %} disabled {% endif %}">删除</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="row">
<div class="col-sm-6">
</div>
{% include '_pagination.html' %}
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% extends 'base.html' %} {% extends '_list_base.html' %}
{% load common_tags %} {% load common_tags %}
{% block content %} {% block content_left_head %}
<div class="wrapper wrapper-content animated fadeInRight"> <a href="{% url 'users:user-add' %}" class="btn btn-sm btn-primary "> 添加用户 </a>
<div class="row"> {# <a id="del_btn" class="btn btn-sm btn-danger "> 删除所选 </a>#}
<div class="col-sm-12"> {% endblock %}
<div class="ibox float-e-margins">
<div class="ibox-title"> {% block table_head %}
<h5> 查看用户 </h5> <th class="text-center">
<div class="ibox-tools"> <input type="checkbox" id="check_all" onclick="checkAll('check_all', 'checked')">
<a class="collapise-link"> </th>
<i class="fa fa-chevron-up"></i> <th class="text-center"><a href="{% url 'users:user-list' %}?sort=name">姓名</a></th>
</a> <th class="text-center"><a href="{% url 'users:user-list' %}?sort=username">用户名</a></th>
<a class="dropdown-toggle" data-toggle="dropdown" href="#"> <th class="text-center">角色</th>
<i class="fa fa-wrench"></i> <th class="text-center">用户组</th>
</a> <th class="text-center">资产数量</th>
<a class="close-link"> <th class="text-center"><a href="{% url 'users:user-list' %}?sort=date_expired">有效</a></th>
<i class="fa fa-times"></i> <th class="text-center"></th>
</a> {% endblock %}
</div>
</div> {% block table_body %}
{% for user in user_list %}
<tr class="gradeX">
<td class="text-center">
<input type="checkbox" name="checked" value="{{ user.id }}">
</td>
<td class="text-center">
<a href="{% url 'users:user-detail' pk=user.id %}">
{{ user.name }}
</a>
</td>
<td class="text-center">{{ user.username }}</td>
<td class="text-center">{{ user.role.name }}</td>
<td class="text-center" title="{% for user_group in user.group.all %} {{ user_group.name }} {% endfor %}"> {{ user.groups.all|join_queryset_attr:"name" }} </td>
<th class="text-center">{{ user.name }}</th>
<td class="text-center">
{% if user.is_expired %}
<i class="fa fa-times text-danger"></i>
{% else %}
<i class="fa fa-check text-navy"></i>
{% endif %}
</td>
<td class="text-center">
<a href="{% url 'users:user-edit' pk=user.id %}" class="btn btn-xs btn-info">编辑</a>
<a href="{% url 'users:user-delete' pk=user.id %}" class="btn btn-xs btn-danger del {% if user.username == 'admin' %} disabled {% endif %}">删除</a>
</td>
</tr>
{% endfor %}
{% endblock %}
<div class="ibox-content"> {% block content_bottom_left %}
<div class=""> <form id="" method="get" action="" class=" mail-search">
<a href="{% url 'users:user-add' %}" class="btn btn-sm btn-primary "> 添加用户 </a> <div class="input-group">
<a id="del_btn" class="btn btn-sm btn-danger "> 删除所选 </a> <select class="form-control m-b" style="width: auto">
<form id="search_form" method="get" action="" class="pull-right mail-search"> <option>批量删除</option>
<div class="input-group"> <option>批量更新</option>
<input type="text" class="form-control input-sm" name="keyword" placeholder="用户名或姓名" value="{{ keyword }}"> <option>批量禁用</option>
<div class="input-group-btn"> <option>批量导出</option>
<button id='search_btn' type="submit" class="btn btn-sm btn-primary"> </select>
搜索
</button>
</div>
</div>
</form>
</div>
<table class="table table-striped table-bordered table-hover " id="editable" > <div class="input-group-btn pull-left" style="padding-left: 5px;">
<thead> <button id='search_btn' type="submit" style="height: 32px;" class="btn btn-sm btn-primary">
<tr> 确认
<th class="text-center"> </button>
<input type="checkbox" id="check_all" onclick="checkAll('check_all', 'checked')">
</th>
<th class="text-center"><a href="{% url 'users:user-list' %}?sort=name">姓名</a></th>
<th class="text-center"><a href="{% url 'users:user-list' %}?sort=username">用户名</a></th>
<th class="text-center">角色</th>
<th class="text-center">用户组</th>
<th class="text-center">资产数量</th>
<th class="text-center"><a href="{% url 'users:user-list' %}?sort=date_expired">有效</a></th>
<th class="text-center"></th>
</tr>
</thead>
<tbody>
{% for user in user_list %}
<tr class="gradeX">
<td class="text-center">
<input type="checkbox" name="checked" value="{{ user.id }}">
</td>
<td class="text-center">
<a href="{% url 'users:user-detail' pk=user.id %}">
{{ user.name }}
</a>
</td>
<td class="text-center">{{ user.username }}</td>
<td class="text-center">{{ user.role.name }}</td>
<td class="text-center" title="{% for user_group in user.group.all %} {{ user_group.name }} {% endfor %}"> {{ user.groups.all|join_queryset_attr:"name" }} </td>
<th class="text-center">{{ user.name }}</th>
<td class="text-center">
{% if user.is_expired %}
<i class="fa fa-times text-danger"></i>
{% else %}
<i class="fa fa-check text-navy"></i>
{% endif %}
</td>
<td class="text-center">
<a href="{% url 'users:user-edit' pk=user.id %}" class="btn btn-xs btn-info">编辑</a>
<a href="{% url 'users:user-delete' pk=user.id %}" class="btn btn-xs btn-danger del {% if user.username == 'admin' %} disabled {% endif %}">删除</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% include '_pagination.html' %}
</div>
</div> </div>
</div>
</div>
</div>
</div>
</form>
{% endblock %} {% endblock %}
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
{% load static %} {% load static %}
{% load bootstrap %} {% load bootstrap %}
{% block custom_head_css_js %} {% block custom_head_css_js %}
<link href="{% static "css/plugins/chosen/chosen.css" %}" rel="stylesheet"> <link href="{% static "css/plugins/select2/select2.min.css" %}" rel="stylesheet">
<script src="{% static "js/plugins/chosen/chosen.jquery.min.js" %}"></script> <script src="{% static "js/plugins/select2/select2.full.min.js" %}"></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
...@@ -32,8 +32,8 @@ ...@@ -32,8 +32,8 @@
<div class="form-group"> <div class="form-group">
<label for="users" class="col-sm-2 control-label">用户</label> <label for="users" class="col-sm-2 control-label">用户</label>
<div class="col-sm-8"> <div class="col-sm-9">
<select name="users" id="users" data-placeholder="选择用户" class="chosen-select form-control m-b" multiple tabindex="2"> <select name="users" id="users" data-placeholder="选择用户" class="select2 form-control m-b" multiple tabindex="2">
{% for user in users %} {% for user in users %}
<option value="{{ user.id }}">{{ user.name }}</option> <option value="{{ user.id }}">{{ user.name }}</option>
{% endfor %} {% endfor %}
...@@ -59,16 +59,7 @@ ...@@ -59,16 +59,7 @@
{% block custom_foot_js %} {% block custom_foot_js %}
<script> <script>
$(document).ready(function () { $(document).ready(function () {
var config = { $('.select2').select2();
'.chosen-select' : {},
'.chosen-select-deselect' : {allow_single_deselect:true},
'.chosen-select-no-single' : {disable_search_threshold:10},
'.chosen-select-no-results': {no_results_text:'Oops, nothing found!'},
'.chosen-select-width' : {width:"95%"}
};
for (var selector in config) {
$(selector).chosen(config[selector]);
}
}) })
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
{% extends 'base.html' %} {% extends '_list_base.html' %}
{% load common_tags %} {% load common_tags %}
{% block content %} {% block content_left_head %}
<div class="wrapper wrapper-content animated fadeInRight"> <a href="{% url 'users:usergroup-add' %}" class="btn btn-sm btn-primary "> 添加用户组 </a>
<div class="row"> {% endblock %}
<div class="col-sm-12">
<div class="ibox float-e-margins"> {% block table_head %}
<div class="ibox-title"> <th class="text-center">
<h5> 查看用户组 </h5> <input type="checkbox" id="check_all" onclick="checkAll('check_all', 'checked')">
<div class="ibox-tools"> </th>
<a class="collapise-link"> <th class="text-center"><a href="{% url 'users:usergroup-list' %}?sort=name">名称</a></th>
<i class="fa fa-chevron-up"></i> <th class="text-center">用户数量</th>
</a> <th class="text-center">资产数量</th>
<a class="dropdown-toggle" data-toggle="dropdown" href="#"> <th class="text-center">描述</th>
<i class="fa fa-wrench"></i> <th class="text-center"></th>
</a> {% endblock %}
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content"> {% block table_body %}
<div class=""> {% for usergroup in usergroup_list %}
<a href="{% url 'users:usergroup-add' %}" class="btn btn-sm btn-primary "> 添加用户组 </a> <tr class="gradeX">
<a id="del_btn" class="btn btn-sm btn-danger "> 删除所选 </a> <td class="text-center">
<form id="search_form" method="get" action="" class="pull-right mail-search"> <input type="checkbox" name="checked" value="{{ usergroup.id }}">
<div class="input-group"> </td>
<input type="text" class="form-control input-sm" name="keyword" placeholder="名称" value="{{ keyword }}"> <td class="text-center">
<div class="input-group-btn"> <a href="{% url 'users:usergroup-detail' pk=usergroup.id %}">
<button id='search_btn' type="submit" class="btn btn-sm btn-primary"> {{ usergroup.name }}
搜索 </a>
</button> </td>
</div> <td class="text-center">{{ usergroup.user_set.all|length }}</td>
</div> <td class="text-center">数量</td>
</form> <th class="text-center">{{ usergroup.comment|truncatewords:8 }}</th>
</div> <td class="text-center">
<a href="{% url 'users:usergroup-edit' pk=usergroup.id %}" class="btn btn-xs btn-info">编辑</a>
<a href="{% url 'users:usergroup-delete' pk=usergroup.id %}" class="btn btn-xs btn-danger del">删除</a>
</td>
</tr>
{% endfor %}
{% endblock %}
<table class="table table-striped table-bordered table-hover " id="editable" > {% block content_bottom_left %}
<thead> <form id="" method="get" action="" class=" mail-search">
<tr> <div class="input-group">
<th class="text-center"> <select class="form-control m-b" style="width: auto">
<input type="checkbox" id="check_all" onclick="checkAll('check_all', 'checked')"> <option>批量删除</option>
</th> <option>批量更新</option>
<th class="text-center"><a href="{% url 'users:usergroup-list' %}?sort=name">名称</a></th> <option>批量禁用</option>
<th class="text-center">用户数量</th> <option>批量导出</option>
<th class="text-center">资产数量</th> </select>
<th class="text-center">描述</th>
<th class="text-center"></th> <div class="input-group-btn pull-left" style="padding-left: 5px;">
</tr> <button id='search_btn' type="submit" style="height: 32px;" class="btn btn-sm btn-primary">
</thead> 确认
<tbody> </button>
{% for usergroup in usergroup_list %}
<tr class="gradeX">
<td class="text-center">
<input type="checkbox" name="checked" value="{{ usergroup.id }}">
</td>
<td class="text-center">
<a href="{% url 'users:usergroup-detail' pk=usergroup.id %}">
{{ usergroup.name }}
</a>
</td>
<td class="text-center">{{ usergroup.user_set.all|length }}</td>
<td class="text-center">数量</td>
<th class="text-center">{{ usergroup.comment|truncatewords:8 }}</th>
<td class="text-center">
<a href="{% url 'users:usergroup-edit' pk=usergroup.id %}" class="btn btn-xs btn-info">编辑</a>
<a href="{% url 'users:usergroup-delete' pk=usergroup.id %}" class="btn btn-xs btn-danger del">删除</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% include '_pagination.html' %}
</div>
</div> </div>
</div> </div>
</div> </form>
</div>
{% endblock %} {% endblock %}
# ~*~ coding: utf-8 ~*~
from random import choice
import forgery_py
from users.models import User, UserGroup, Role, init_all_models
def gen_username():
return forgery_py.internet.user_name(True)
def gen_email():
return forgery_py.internet.email_address()
def gen_name():
return forgery_py.name.full_name()
def get_role():
role = choice(Role.objects.all())
return role
\ No newline at end of file
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
from random import choice
import forgery_py
from django.utils import timezone from django.utils import timezone
from django.shortcuts import reverse
from django.test import TestCase, Client, TransactionTestCase from django.test import TestCase, Client, TransactionTestCase
from django.test.utils import setup_test_environment from django.db import IntegrityError
from django.db import IntegrityError, transaction from users.models import User, UserGroup, Role, init_all_models
from .models import User, UserGroup, Role, init_all_models from django.contrib.auth.models import Permission
from .base import gen_name, gen_username, gen_email, get_role
setup_test_environment()
client = Client()
def create_usergroup(name):
pass
def get_random_usergroup():
pass
def create_user(username, name, email, groups):
pass
def gen_username():
return forgery_py.internet.user_name(True)
def gen_email():
return forgery_py.internet.email_address()
def gen_name():
return forgery_py.name.full_name()
class UserModelTest(TransactionTestCase): class UserModelTest(TransactionTestCase):
def setUp(self): def setUp(self):
init_all_models() init_all_models()
def test_user_duplicate(self): # 创建一个用户用于测试
# 创建一个基准测试用户 role = get_role()
role = choice(Role.objects.all())
user = User(name='test', username='test', email='test@email.org', role=role) user = User(name='test', username='test', email='test@email.org', role=role)
user.save() user.save()
# 创建一个姓名一致的用户, 应该创建成功 def test_initial(self):
self.assertEqual(User.objects.all().count(), 2)
self.assertEqual(Role.objects.all().count(), 3)
self.assertEqual(UserGroup.objects.all().count(), 1)
@property
def role(self):
return get_role()
# 创建一个姓名一致的用户, 应该创建成功
def test_user_name_duplicate(self):
user1 = User(name='test', username=gen_username(), password_raw=gen_username(), user1 = User(name='test', username=gen_username(), password_raw=gen_username(),
email=gen_email(), role=role) email=gen_email(), role=self.role)
try: try:
user1.save() user1.save()
user1.delete() user1.delete()
except IntegrityError: except IntegrityError:
self.assertTrue(0, 'Duplicate <name> not allowed.') self.assertTrue(0, 'Duplicate <name> not allowed.')
# 创建一个用户名一致的用户, 应该创建不成功 # 创建一个用户名一致的用户, 应该创建不成功
user2 = User(username='test', email=gen_email(), role=role) def test_user_username_duplicate(self):
user2 = User(username='test', email=gen_email(), role=self.role)
try: with self.assertRaises(IntegrityError):
user2.save() user2.save()
self.assertTrue(0, 'Duplicate <username> allowed.')
except IntegrityError:
pass
# 创建一个Email一致的用户,应该创建不成功 # 创建一个Email一致的用户,应该创建不成功
user3 = User(username=gen_username(), email='test@email.org', role=role) def test_user_email_duplicate(self):
try: user3 = User(username=gen_username(), email='test@email.org', role=self.role)
with self.assertRaises(IntegrityError):
user3.save() user3.save()
self.assertTrue(0, 'Duplicate <email> allowed.')
except IntegrityError:
pass
# 用户过期测试
def test_user_was_expired(self): def test_user_was_expired(self):
role = choice(Role.objects.all())
date = timezone.now() - timezone.timedelta(days=1) date = timezone.now() - timezone.timedelta(days=1)
user = User(name=gen_name(), username=gen_username(), user = User(name=gen_name(), username=gen_username(),
email=gen_email(), role=role, date_expired=date) email=gen_email(), role=self.role, date_expired=date)
self.assertTrue(user.is_expired()) self.assertTrue(user.is_expired())
# 测试用户默认会输入All用户组
def test_user_with_default_group(self): def test_user_with_default_group(self):
role = choice(Role.objects.all()) role = get_role()
user = User(username=gen_username(), email=gen_email(), role=role) user = User(username=gen_username(), email=gen_email(), role=role)
user.save() user.save()
self.assertEqual(user.groups.count(), 1) self.assertEqual(user.groups.count(), 1)
self.assertEqual(user.groups.first().name, 'All') self.assertEqual(user.groups.first().name, 'Default')
def test_user_password_authenticated(self):
password = gen_username() * 3
user = User(username=gen_username(), password_raw=password, role=self.role)
user.save()
self.assertTrue(user.check_password(password))
self.assertFalse(user.check_password(password*2))
def tearDown(self):
User.objects.all().delete()
UserGroup.objects.all().delete()
Role.objects.all().delete()
class UserListViewTests(TestCase):
class RoleModelTestCase(TransactionTestCase):
def setUp(self):
Role.objects.all().delete()
Role.initial()
def test_role_initial(self):
self.assertEqual(Role.objects.all().count(), 3)
def test_create_new_role(self):
role = Role(name=gen_name(), comment=gen_name()*3)
role.save()
role.permissions = Permission.objects.all()
role.save()
self.assertEqual(Role.objects.count(), 4)
role = Role.objects.last()
self.assertEqual(role.permissions.all().count(), Permission.objects.all().count())
class UserGroupModelTestCase(TransactionTestCase):
pass pass
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
from users.models import User, UserGroup, Role, init_all_models
from django.shortcuts import reverse
from django.test import TestCase, Client, TransactionTestCase
from .base import gen_username, gen_name, gen_email, get_role
class UserListViewTests(TransactionTestCase):
def setUp(self):
init_all_models()
def test_a_new_user_in_list(self):
username = gen_username()
user = User(username=username, email=gen_email(), role=get_role())
user.save()
response = self.client.get(reverse('users:user-list'))
self.assertContains(response, username)
def test_list_view_with_admin_user(self):
response = self.client.get(reverse('users:user-list'))
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Admin')
self.assertEqual(response.context['user_list'].count(), User.objects.all().count())
def test_pagination(self):
User.generate_fake(count=20)
response = self.client.get(reverse('users:user-list'))
self.assertEqual(response.context['is_paginated'], True)
class UserAddTests(TestCase):
def setUp(self):
init_all_models()
def test_add_a_new_user(self):
username = gen_username()
data = {
'username': username,
'comment': '',
'name': gen_name(),
'email': gen_email(),
'groups': [UserGroup.objects.first().id, ],
'role': get_role().id,
'date_expired': '2086-08-06 19:12:22',
}
response = self.client.post(reverse('users:user-add'), data)
self.assertEqual(response.status_code, 302)
self.assertEqual(response['location'], reverse('users:user-list'))
response = self.client.get(reverse('users:user-list'))
self.assertContains(response, username)
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
from django.shortcuts import get_object_or_404 from __future__ import unicode_literals
from django.shortcuts import get_object_or_404, reverse
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.db.models import Q from django.db.models import Q
from django.views.generic.list import ListView from django.views.generic.list import ListView
from django.views.generic.edit import CreateView, DeleteView, UpdateView from django.views.generic.edit import CreateView, DeleteView, UpdateView
from django.views.generic.detail import DetailView from django.views.generic.detail import DetailView
from django.contrib.messages.views import SuccessMessageMixin
from django.conf import settings from django.conf import settings
from .models import User, UserGroup, Role from .models import User, UserGroup, Role
...@@ -37,11 +40,12 @@ class UserListView(ListView): ...@@ -37,11 +40,12 @@ class UserListView(ListView):
return context return context
class UserAddView(CreateView): class UserAddView(SuccessMessageMixin, CreateView):
model = User model = User
form_class = UserAddForm form_class = UserAddForm
template_name = 'users/user_add.html' template_name = 'users/user_add.html'
success_url = reverse_lazy('users:user-list') success_url = reverse_lazy('users:user-list')
success_message = '添加用户 <a href="%s">%s</a> 成功 .'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(UserAddView, self).get_context_data(**kwargs) context = super(UserAddView, self).get_context_data(**kwargs)
...@@ -54,6 +58,12 @@ class UserAddView(CreateView): ...@@ -54,6 +58,12 @@ class UserAddView(CreateView):
user.save() user.save()
return super(UserAddView, self).form_valid(form) return super(UserAddView, self).form_valid(form)
def get_success_message(self, cleaned_data):
return self.success_message % (
reverse_lazy('users:user-detail', kwargs={'pk': self.object.pk}),
self.object.name,
)
class UserUpdateView(UpdateView): class UserUpdateView(UpdateView):
model = User model = User
...@@ -72,6 +82,10 @@ class UserUpdateView(UpdateView): ...@@ -72,6 +82,10 @@ class UserUpdateView(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)
class UserDeleteView(DeleteView): class UserDeleteView(DeleteView):
model = User model = User
...@@ -93,7 +107,7 @@ class UserDetailView(DetailView): ...@@ -93,7 +107,7 @@ class UserDetailView(DetailView):
class UserGroupListView(ListView): class UserGroupListView(ListView):
model = UserGroup model = UserGroup
paginate_by = 20 paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
context_object_name = 'usergroup_list' context_object_name = 'usergroup_list'
template_name = 'users/usergroup_list.html' template_name = 'users/usergroup_list.html'
ordering = '-date_added' ordering = '-date_added'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment