Commit 0e4804b5 authored by ibuler's avatar ibuler

Merge branch 'audits'

parents c7437154 69767e97
...@@ -6,17 +6,18 @@ from rest_framework.views import APIView ...@@ -6,17 +6,18 @@ from rest_framework.views import APIView
from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin, ListBulkCreateUpdateDestroyAPIView from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin, ListBulkCreateUpdateDestroyAPIView
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from common.mixins import BulkDeleteApiMixin from common.mixins import IDInFilterMixin
from common.utils import get_object_or_none, signer from common.utils import get_object_or_none, signer
from .hands import IsSuperUserOrTerminalUser, IsSuperUser from .hands import IsSuperUserOrTerminalUser, IsSuperUser
from .models import AssetGroup, Asset, IDC, SystemUser, AdminUser from .models import AssetGroup, Asset, IDC, SystemUser, AdminUser
from . import serializers from . import serializers
class AssetViewSet(viewsets.ModelViewSet): class AssetViewSet(IDInFilterMixin, viewsets.ModelViewSet):
"""API endpoint that allows Asset to be viewed or edited.""" """API endpoint that allows Asset to be viewed or edited."""
queryset = Asset.objects.all() queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer serializer_class = serializers.AssetSerializer
filter_fields = ('id', 'ip', 'hostname')
def get_queryset(self): def get_queryset(self):
queryset = super(AssetViewSet, self).get_queryset() queryset = super(AssetViewSet, self).get_queryset()
...@@ -27,7 +28,6 @@ class AssetViewSet(viewsets.ModelViewSet): ...@@ -27,7 +28,6 @@ class AssetViewSet(viewsets.ModelViewSet):
if asset_group_id: if asset_group_id:
queryset = queryset.filter(groups__id=asset_group_id) queryset = queryset.filter(groups__id=asset_group_id)
return queryset return queryset
...@@ -71,7 +71,7 @@ class SystemUserViewSet(viewsets.ModelViewSet): ...@@ -71,7 +71,7 @@ class SystemUserViewSet(viewsets.ModelViewSet):
# return self.object.assets.all() # return self.object.assets.all()
class AssetListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView): class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
queryset = Asset.objects.all() queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer serializer_class = serializers.AssetSerializer
permission_classes = (IsSuperUser,) permission_classes = (IsSuperUser,)
......
...@@ -309,4 +309,8 @@ class AssetTagForm(forms.ModelForm): ...@@ -309,4 +309,8 @@ class AssetTagForm(forms.ModelForm):
} }
help_texts = { help_texts = {
'name': '* required', 'name': '* required',
} }
\ No newline at end of file
class FileForm(forms.Form):
file = forms.FileField()
\ No newline at end of file
...@@ -60,38 +60,6 @@ class IDC(models.Model): ...@@ -60,38 +60,6 @@ class IDC(models.Model):
continue continue
class AssetExtend(models.Model):
key = models.CharField(max_length=64, verbose_name=_('KEY'))
value = models.CharField(max_length=64, verbose_name=_('VALUE'))
created_by = models.CharField(max_length=32, blank=True, verbose_name=_("Created by"))
date_created = models.DateTimeField(auto_now_add=True, null=True)
comment = models.TextField(blank=True, verbose_name=_('Comment'))
def __unicode__(self):
return '%(key)s: %(value)s' % {'key': self.key, 'value': self.value}
@classmethod
def initial(cls):
for k, v in (
(_('status'), _('In use')),
(_('status'), _('Out of use')),
(_('type'), _('Server')),
(_('type'), _('VM')),
(_('type'), _('Switch')),
(_('type'), _('Router')),
(_('type'), _('Firewall')),
(_('type'), _('Storage')),
(_('env'), _('Production')),
(_('env'), _('Development')),
(_('env'), _('Testing')),
):
cls.objects.create(key=k, value=v, created_by='System')
class Meta:
db_table = 'asset_extend'
unique_together = ('key', 'value')
def private_key_validator(value): def private_key_validator(value):
if not validate_ssh_private_key(value): if not validate_ssh_private_key(value):
raise ValidationError( raise ValidationError(
...@@ -233,6 +201,10 @@ class SystemUser(models.Model): ...@@ -233,6 +201,10 @@ class SystemUser(models.Model):
def assets_amount(self): def assets_amount(self):
return self.assets.count() return self.assets.count()
@property
def asset_group_amount(self):
return self.asset_groups.count()
class Meta: class Meta:
db_table = 'system_user' db_table = 'system_user'
...@@ -294,18 +266,29 @@ class AssetGroup(models.Model): ...@@ -294,18 +266,29 @@ class AssetGroup(models.Model):
continue continue
def get_default_extend(key, value):
try:
return AssetExtend.objects.get_or_create(key=key, value=value)[0]
except:
return None
def get_default_idc(): def get_default_idc():
return IDC.initial() return IDC.initial()
class Asset(models.Model): class Asset(models.Model):
STATUS_CHOICES = (
('In use', _('In use')),
('Out of use', _('Out of use')),
)
TYPE_CHOICES = (
('Server', _('Server')),
('VM', _('VM')),
('Switch', _('Switch')),
('Router', _('Router')),
('Firewall', _('Firewall')),
('Storage', _("Storage")),
)
ENV_CHOICES = (
('Prod', 'Production'),
('Dev', 'Development'),
('Test', 'Testing'),
)
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True) ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
other_ip = models.CharField(max_length=255, null=True, blank=True, verbose_name=_('Other IP')) other_ip = models.CharField(max_length=255, null=True, blank=True, verbose_name=_('Other IP'))
remote_card_ip = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('Remote card IP')) remote_card_ip = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('Remote card IP'))
...@@ -326,15 +309,12 @@ class Asset(models.Model): ...@@ -326,15 +309,12 @@ class Asset(models.Model):
cabinet_no = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Cabinet number')) cabinet_no = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Cabinet number'))
cabinet_pos = models.IntegerField(null=True, blank=True, verbose_name=_('Cabinet position')) cabinet_pos = models.IntegerField(null=True, blank=True, verbose_name=_('Cabinet position'))
number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number')) number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number'))
status = models.ForeignKey(AssetExtend, null=True, blank=True, status = models.CharField(choices=STATUS_CHOICES, max_length=8, null=True, blank=True,
related_name="status_asset", verbose_name=_('Asset status'),) default='In use', verbose_name=_('Asset status'))
# default=functools.partial(get_default_extend, 'status', 'In use')) type = models.CharField(choices=TYPE_CHOICES, max_length=16, blank=True, null=True,
type = models.ForeignKey(AssetExtend, blank=True,null=True, limit_choices_to={'key': 'type'}, default='Server', verbose_name=_('Asset type'),)
related_name="type_asset", verbose_name=_('Asset type'),) env = models.CharField(choices=ENV_CHOICES, max_length=8, blank=True, null=True,
# default=functools.partial(get_default_extend, 'type','Server')) default='Prod', verbose_name=_('Asset environment'),)
env = models.ForeignKey(AssetExtend, blank=True, null=True, limit_choices_to={'key': 'env'},
related_name="env_asset", verbose_name=_('Asset environment'),)
# default=functools.partial(get_default_extend, 'env', 'Production'))
sn = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Serial number')) sn = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Serial number'))
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by')) created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
is_active = models.BooleanField(default=True, verbose_name=_('Is active')) is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
...@@ -400,7 +380,7 @@ class Tag(models.Model): ...@@ -400,7 +380,7 @@ class Tag(models.Model):
def init_all_models(): def init_all_models():
for cls in (AssetExtend, AssetGroup): for cls in (AssetGroup,):
cls.initial() cls.initial()
...@@ -410,5 +390,5 @@ def generate_fake(): ...@@ -410,5 +390,5 @@ def generate_fake():
def flush_all(): def flush_all():
for cls in (AssetGroup, AssetExtend, IDC, AdminUser, SystemUser, Asset): for cls in (AssetGroup, IDC, AdminUser, SystemUser, Asset):
cls.objects.all().delete() cls.objects.all().delete()
# -*- 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 viewsets, serializers,generics from rest_framework import viewsets, serializers,generics
from .models import AssetGroup, Asset, IDC, AssetExtend, AdminUser, SystemUser from .models import AssetGroup, Asset, IDC, AdminUser, SystemUser
from common.mixins import BulkDeleteApiMixin from common.mixins import IDInFilterMixin
from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin
...@@ -35,7 +35,7 @@ class SystemUserSerializer(serializers.ModelSerializer): ...@@ -35,7 +35,7 @@ class SystemUserSerializer(serializers.ModelSerializer):
def get_field_names(self, declared_fields, info): def get_field_names(self, declared_fields, info):
fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info) fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info)
fields.append('assets_amount') fields.extend(['assets_amount'])
return fields return fields
...@@ -43,7 +43,6 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer): ...@@ -43,7 +43,6 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
# system_users = SystemUserSerializer(many=True, read_only=True) # system_users = SystemUserSerializer(many=True, read_only=True)
# admin_user = AdminUserSerializer(many=False, read_only=True) # admin_user = AdminUserSerializer(many=False, read_only=True)
hardware = serializers.SerializerMethodField() hardware = serializers.SerializerMethodField()
type_display = serializers.SerializerMethodField()
class Meta(object): class Meta(object):
model = Asset model = Asset
...@@ -51,15 +50,16 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer): ...@@ -51,15 +50,16 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
@staticmethod @staticmethod
def get_hardware(obj): def get_hardware(obj):
return '%s %s %s' % (obj.cpu, obj.memory, obj.disk) if obj.cpu:
return '%s %s %s' % (obj.cpu, obj.memory, obj.disk)
@staticmethod
def get_type_display(obj):
if obj.type:
return obj.type.value
else: else:
return '' return ''
def get_field_names(self, declared_fields, info):
fields = super(AssetSerializer, self).get_field_names(declared_fields, info)
fields.extend(['get_type_display', 'get_env_display'])
return fields
class AssetGrantedSerializer(serializers.ModelSerializer): class AssetGrantedSerializer(serializers.ModelSerializer):
system_users = SystemUserSerializer(many=True, read_only=True) system_users = SystemUserSerializer(many=True, read_only=True)
......
{% extends '_modal.html' %}
{% load i18n %}
{% block modal_id %}asset_import_modal{% endblock %}
{% block modal_title%}{% trans "Import asset" %}{% endblock %}
{% block modal_body %}
<p class="text-success">{% trans "Download template or use export excel format" %}</p>
<form method="post" action="{% url 'assets:asset-import' %}" id="fm_asset_import" enctype="multipart/form-data">
{% csrf_token %}
<div class="form-group">
<label class="control-label" for="id_assets">{% trans "Template" %}</label>
<a href="{{ MEDIA_URL }}files/asset_import_template.xlsx" style="display: block">{% trans 'Download' %}</a>
</div>
<div class="form-group">
<label class="control-label" for="id_users">{% trans "Asset excel file" %}</label>
<input id="id_assets" type="file" name="file" />
</div>
</form>
<p>
<p class="text-success" id="id_created"></p>
<p id="id_created_detail"></p>
<p class="text-warning" id="id_updated"></p>
<p id="id_updated_detail"></p>
<p class="text-danger" id="id_failed"></p>
<p id="id_failed_detail"></p>
</p>
{% endblock %}
{% block modal_confirm_id %}btn_asset_import{% endblock %}
...@@ -39,13 +39,6 @@ $(document).ready(function(){ ...@@ -39,13 +39,6 @@ $(document).ready(function(){
var innerHtml = cellData.length > 8 ? cellData.substring(0, 24) + '...': cellData; var innerHtml = cellData.length > 8 ? cellData.substring(0, 24) + '...': cellData;
$(td).html('<a href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</a>'); $(td).html('<a href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</a>');
}}, }},
{# {targets: 6, createdCell: function (td, cellData) {#}
{# if (!cellData) {#}
{# $(td).html('<i class="fa fa-times text-danger"></i>')#}
{# } else {#}
{# $(td).html('<i class="fa fa-check text-navy"></i>')#}
{# }#}
{# }},#}
{targets: 6, createdCell: function (td, cellData, rowData) { {targets: 6, createdCell: function (td, cellData, rowData) {
var script_btn = '<a href="{% url "assets:admin-user-update" pk=99991937 %}" class="btn btn-xs btn-primary">{% trans "Script" %}</a>'.replace('99991937', cellData); var script_btn = '<a href="{% url "assets:admin-user-update" pk=99991937 %}" class="btn btn-xs btn-primary">{% trans "Script" %}</a>'.replace('99991937', cellData);
var update_btn = '<a href="{% url "assets:admin-user-update" pk=99991937 %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', cellData); var update_btn = '<a href="{% url "assets:admin-user-update" pk=99991937 %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', cellData);
...@@ -55,7 +48,6 @@ $(document).ready(function(){ ...@@ -55,7 +48,6 @@ $(document).ready(function(){
ajax_url: '{% url "api-assets:admin-user-list" %}', ajax_url: '{% url "api-assets:admin-user-list" %}',
columns: [{data: function(){return ""}}, {data: "name" }, {data: "username" }, {data: "assets_amount" }, {data: function () {return 'lost'} }, columns: [{data: function(){return ""}}, {data: "name" }, {data: "username" }, {data: "assets_amount" }, {data: function () {return 'lost'} },
{data: "comment" }, {data: "id" }], {data: "comment" }, {data: "id" }],
op_html: $('#actions').html()
}; };
jumpserver.initDataTable(options); jumpserver.initDataTable(options);
}); });
......
...@@ -96,7 +96,7 @@ ...@@ -96,7 +96,7 @@
</tr> </tr>
<tr> <tr>
<td>{% trans 'Asset status' %}:</td> <td>{% trans 'Asset status' %}:</td>
<td><b>{{ asset.status }}</b></td> <td><b>{{ asset.get_status_display() }}</b></td>
</tr> </tr>
<tr> <tr>
<td>{% trans 'Is active' %}:</td> <td>{% trans 'Is active' %}:</td>
......
...@@ -18,10 +18,11 @@ ...@@ -18,10 +18,11 @@
{% block table_search %} {% block table_search %}
<div class="html5buttons"> <div class="html5buttons">
<div class="dt-buttons btn-group"> <div class="dt-buttons btn-group">
<a class="btn btn-default buttons-pdf" tabindex="0" href="#"> <a class="btn btn-default btn_import" data-toggle="modal" data-target="#asset_import_modal" tabindex="0">
<span>PDF</span></a> <span>{% trans "Import" %}</span>
<a class="btn btn-default buttons-excel" tabindex="0" href="#"> </a>
<span>Excel</span> <a class="btn btn-default btn_export" tabindex="0">
<span>{% trans "Export" %}</span>
</a> </a>
</div> </div>
</div> </div>
...@@ -32,7 +33,7 @@ ...@@ -32,7 +33,7 @@
<div class="tagBtnList"> <div class="tagBtnList">
{% for tag in tag_list %} {% for tag in tag_list %}
<a href="{% url 'assets:asset-tags' tag_id=tag.0 %}" <a href="{% url 'assets:asset-tags' tag_id=tag.0 %}"
{% if tag.0|IntToStr == tag_id %} {% if tag.0|int_to_str == tag_id %}
class="tagBtn2 label label-warning" name="tag_on"> class="tagBtn2 label label-warning" name="tag_on">
{% else %} {% else %}
class="tagBtn2 label label-default"> class="tagBtn2 label label-default">
...@@ -47,7 +48,6 @@ ...@@ -47,7 +48,6 @@
{% block table_container %} {% block table_container %}
<div class="uc pull-left m-l-5 m-r-5"><a href="{% url "assets:asset-create" %}" class="btn btn-sm btn-primary"> {% trans "Create asset" %} </a></div> <div class="uc pull-left m-l-5 m-r-5"><a href="{% url "assets:asset-create" %}" class="btn btn-sm btn-primary"> {% trans "Create asset" %} </a></div>
<div class="uc pull-left"><a href="javascript:void(0);" class="btn btn-sm btn-primary" data-toggle="modal" data-target="#asset_import_modal"> {% trans "Import asset" %} </a></div>
<table class="table table-striped table-bordered table-hover " id="asset_list_table" > <table class="table table-striped table-bordered table-hover " id="asset_list_table" >
<thead> <thead>
<tr> <tr>
...@@ -80,11 +80,13 @@ ...@@ -80,11 +80,13 @@
</div> </div>
</div> </div>
</div> </div>
{% include 'assets/_asset_import_modal.html' %}
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
<script src="{% static 'js/jquery.form.min.js' %}"></script>
<script type="text/javascript"> <script type="text/javascript">
window.onload=function (){ window.onload = function (){
var tag_on = document.getElementsByName("tag_on"); var tag_on = document.getElementsByName("tag_on");
var oDiv = document.getElementById("ydxbd"); var oDiv = document.getElementById("ydxbd");
if(tag_on.length > 0){ if(tag_on.length > 0){
...@@ -99,7 +101,7 @@ ...@@ -99,7 +101,7 @@
}else{ }else{
oDiv.style.display = "none"; oDiv.style.display = "none";
} }
}; //onload; } //onload;
$(document).ready(function(){ $(document).ready(function(){
var options = { var options = {
...@@ -131,11 +133,52 @@ ...@@ -131,11 +133,52 @@
], ],
ajax_url: '{% url "api-assets:asset-list" %}', ajax_url: '{% url "api-assets:asset-list" %}',
columns: [{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" }, columns: [{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" },
{data: "type_display" }, {data: "env"}, {data: "hardware"}, {data: "is_active" }, {data: "get_type_display" }, {data: "get_env_display"}, {data: "hardware"},
{data: "is_active"}, {data: "id" }], {data: "is_active" }, {data: "is_active"}, {data: "id" }],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
var table = jumpserver.initDataTable(options); var table = jumpserver.initDataTable(options);
$('.btn_export').click(function () {
var assets = [];
var rows = table.rows('.selected').data();
$.each(rows, function (index, obj) {
assets.push(obj.id)
});
$.ajax({
url: "{% url "assets:asset-export" %}",
method: 'POST',
data: JSON.stringify({assets_id: assets}),
dataType: "json",
success: function (data, textStatus) {
window.open(data.redirect)
},
error: function () {
toastr.error('Export failed');
}
})
});
$('#btn_asset_import').click(function() {
var $form = $('#fm_asset_import');
$form.find('.help-block').remove();
function success (data) {
if (data.valid === false) {
$('<span />', {class: 'help-block text-danger'}).html(data.msg).insertAfter($('#id_assets'));
} else {
$('#id_created').html(data.created_info);
$('#id_created_detail').html(data.created.join(', '));
$('#id_updated').html(data.updated_info);
$('#id_updated_detail').html(data.updated.join(', '));
$('#id_failed').html(data.failed_info);
$('#id_failed_detail').html(data.failed.join(', '));
var $data_table = $('#asset_list_table').DataTable();
$data_table.ajax.reload();
}
}
$form.ajaxSubmit({success: success});
})
}); });
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
{% extends '_base_list.html' %} {% extends '_base_list.html' %}
{% load i18n %} {% load i18n %}
{% load common_tags %} {% load common_tags %}
{% block content_left_head %}
<a href="{% url 'assets:system-user-create' %}" class="btn btn-sm btn-primary "> {% trans "Create system user" %} </a>
{% endblock %}
{% block table_head %} {% block table_search %}
<th class="text-center">{% trans 'ID' %}</th>
<th class="text-center"><a href="{% url 'assets:system-user-list' %}?sort=name">{% trans 'Name' %}</a></th>
<th class="text-center"><a href="{% url 'assets:system-user-list' %}?sort=username">{% trans 'Username' %}</a></th>
<th class="text-center">{% trans 'Asset num' %}</th>
<th class="text-center">{% trans 'Asset group num' %}</th>
<th class="text-center">{% trans 'Unreachable' %}</th>
<th class="text-center">{% trans 'Comment' %}</th>
<th class="text-center"></th>
{% endblock %} {% endblock %}
{% block table_body %} {% block table_container %}
{% for system_user in system_user_list %} <div class="uc pull-left m-l-5 m-r-5">
<tr class="gradeX"> <a href="{% url 'assets:system-user-create' %}" class="btn btn-sm btn-primary "> {% trans "Create system user" %} </a>
<td class="text-center">{{ system_user.id }}</td> </div>
<td> <table class="table table-striped table-bordered table-hover " id="system_user_list_table" >
<a href="{% url 'assets:system-user-detail' pk=system_user.id %}"> <thead>
{{ system_user.name }} <tr>
</a> <th class="text-center">
</td> <input type="checkbox" id="check_all" class="ipt_check_all" >
<td class="text-center">{{ system_user.username }}</td> </th>
<td class="text-center">{{ system_user.get_assets|length }}</td> <th class="text-center">{% trans 'Name' %}</th>
<td class="text-center">{{ system_user.asset_groups.count }}</td> <th class="text-center">{% trans 'Username' %}</th>
<td class="text-center">{{ system_user.assets.count }}</td> <th class="text-center">{% trans 'Asset' %}</th>
<td class="text-center">{{ system_user.comment|truncatewords:4 }}</td> <th class="text-center">{% trans 'Unreachable' %}</th>
<td class="text-center"> <th class="text-center">{% trans 'Comment' %}</th>
<!-- Todo: Click script button will paste a url to clipboard like: curl http://url/system_user_create.sh | bash --> <th class="text-center">{% trans 'Action' %}</th>
<a href="{% url 'assets:system-user-update' pk=system_user.id %}" class="btn btn-xs btn-primary">{% trans 'Script' %}</a> </tr>
<!-- Todo: Click refresh button will run a task to test admin user could connect asset or not immediately --> </thead>
<a href="{% url 'assets:system-user-update' pk=system_user.id %}" class="btn btn-xs btn-warning">{% trans 'Refresh' %}</a> <tbody>
<a href="{% url 'assets:system-user-update' pk=system_user.id %}" class="btn btn-xs btn-info">{% trans 'Update' %}</a> </tbody>
<a onclick="obj_del(this,'{{ system_user.name }}','{% url 'assets:system-user-delete' system_user.id %}')" class="btn btn-xs btn-danger del">{% trans 'Delete' %}</a> </table>
</td> {% endblock %}
</tr> {% block custom_foot_js %}
{% endfor %} <script>
$(document).ready(function(){
var options = {
ele: $('#system_user_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
var detail_btn = '<a href="{% url "assets:system-user-detail" pk=99991937 %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('99991937', rowData.id));
}},
{targets: 5, createdCell: function (td, cellData) {
var innerHtml = cellData.length > 30 ? cellData.substring(0, 30) + '...': cellData;
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
}},
{targets: 6, createdCell: function (td, cellData, rowData) {
var script_btn = '<a href="{% url "assets:system-user-update" pk=99991937 %}" class="btn btn-xs btn-primary">{% trans "Script" %}</a>'.replace('99991937', cellData);
var update_btn = '<a href="{% url "assets:system-user-update" pk=99991937 %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_delete" data-uid="99991937">{% trans "Delete" %}</a>'.replace('99991937', cellData);
$(td).html(script_btn + update_btn + del_btn)
}}],
ajax_url: '{% url "api-assets:system-user-list" %}',
columns: [{data: "id" }, {data: "name" }, {data: "username" }, {data: "assets_amount" }, {data: function () { return "3"}},
{data: "comment" }, {data: "id" }],
};
jumpserver.initDataTable(options);
});
</script>
{% endblock %} {% endblock %}
...@@ -9,6 +9,8 @@ urlpatterns = [ ...@@ -9,6 +9,8 @@ urlpatterns = [
url(r'^$', views.AssetListView.as_view(), name='asset-index'), url(r'^$', views.AssetListView.as_view(), name='asset-index'),
url(r'^asset/$', views.AssetListView.as_view(), name='asset-list'), url(r'^asset/$', views.AssetListView.as_view(), name='asset-list'),
url(r'^asset/create/$', views.AssetCreateView.as_view(), name='asset-create'), url(r'^asset/create/$', views.AssetCreateView.as_view(), name='asset-create'),
url(r'^asset/export/$', views.AssetExportView.as_view(), name='asset-export'),
url(r'^asset/import/$', views.BulkImportAssetView.as_view(), name='asset-import'),
url(r'^asset/(?P<pk>[0-9]+)/$', views.AssetDetailView.as_view(), name='asset-detail'), url(r'^asset/(?P<pk>[0-9]+)/$', views.AssetDetailView.as_view(), name='asset-detail'),
url(r'^asset/(?P<pk>[0-9]+)/update/$', views.AssetUpdateView.as_view(), name='asset-update'), url(r'^asset/(?P<pk>[0-9]+)/update/$', views.AssetUpdateView.as_view(), name='asset-update'),
url(r'^asset/(?P<pk>[0-9]+)/delete/$', views.AssetDeleteView.as_view(), name='asset-delete'), url(r'^asset/(?P<pk>[0-9]+)/delete/$', views.AssetDeleteView.as_view(), name='asset-delete'),
......
This diff is collapsed.
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
{% load common_tags %} {% load common_tags %}
{% block content_left_head %} {% block content_left_head %}
<link href="{% static "css/plugins/footable/footable.core.css" %}" rel="stylesheet"> <link href="{% static "css/plugins/footable/footable.core.css" %}" rel="stylesheet">
<link href="{% static 'css/plugins/datepicker/datepicker3.css' %}" rel="stylesheet">
<style> <style>
#search_btn { #search_btn {
margin-bottom: 0; margin-bottom: 0;
...@@ -91,6 +92,7 @@ ...@@ -91,6 +92,7 @@
{% block custom_foot_js %} {% block custom_foot_js %}
<script src="{% static "js/plugins/footable/footable.all.min.js" %}"></script> <script src="{% static "js/plugins/footable/footable.all.min.js" %}"></script>
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
<script> <script>
$(document).ready(function () { $(document).ready(function () {
$('.footable').footable(); $('.footable').footable();
......
...@@ -40,14 +40,13 @@ class NoDeleteModelMixin(models.Model): ...@@ -40,14 +40,13 @@ class NoDeleteModelMixin(models.Model):
class JSONResponseMixin(object): class JSONResponseMixin(object):
"""JSON mixin""" """JSON mixin"""
@staticmethod
def render_json_response(self, context): def render_json_response(context):
return JsonResponse(context) return JsonResponse(context)
class BulkDeleteApiMixin(object): class IDInFilterMixin(object):
def filter_queryset(self, queryset): def filter_queryset(self, queryset):
id_list = self.request.query_params.get('id__in') id_list = self.request.query_params.get('id__in')
......
...@@ -44,6 +44,7 @@ def join_attr(seq, attr=None, sep=None): ...@@ -44,6 +44,7 @@ def join_attr(seq, attr=None, sep=None):
print(seq) print(seq)
return sep.join(seq) return sep.join(seq)
@register.filter @register.filter
def IntToStr(value): def int_to_str(value):
return str(value) return str(value)
\ No newline at end of file
This diff is collapsed.
[{"model": "users.usergroup", "pk": 1, "fields": {"is_discard": false, "discard_time": null, "name": "Default", "comment": "Default user group for all user", "date_created": "2016-11-02T14:49:50Z", "created_by": "System"}}, {"model": "assets.assetextend", "pk": 1, "fields": {"key": "status", "value": "In use", "created_by": "System", "date_created": "2016-11-02T14:51:53Z", "comment": ""}}, {"model": "assets.assetextend", "pk": 2, "fields": {"key": "status", "value": "Out of use", "created_by": "System", "date_created": "2016-11-02T14:51:53Z", "comment": ""}}, {"model": "assets.assetextend", "pk": 3, "fields": {"key": "type", "value": "Server", "created_by": "System", "date_created": "2016-11-02T14:51:53Z", "comment": ""}}, {"model": "assets.assetextend", "pk": 4, "fields": {"key": "type", "value": "VM", "created_by": "System", "date_created": "2016-11-02T14:51:53Z", "comment": ""}}, {"model": "assets.assetextend", "pk": 5, "fields": {"key": "type", "value": "Switch", "created_by": "System", "date_created": "2016-11-02T14:51:53Z", "comment": ""}}, {"model": "assets.assetextend", "pk": 6, "fields": {"key": "type", "value": "Router", "created_by": "System", "date_created": "2016-11-02T14:51:53Z", "comment": ""}}, {"model": "assets.assetextend", "pk": 7, "fields": {"key": "type", "value": "Firewall", "created_by": "System", "date_created": "2016-11-02T14:51:53Z", "comment": ""}}, {"model": "assets.assetextend", "pk": 8, "fields": {"key": "type", "value": "Storage", "created_by": "System", "date_created": "2016-11-02T14:51:53Z", "comment": ""}}, {"model": "assets.assetextend", "pk": 9, "fields": {"key": "env", "value": "Production", "created_by": "System", "date_created": "2016-11-02T14:51:53Z", "comment": ""}}, {"model": "assets.assetextend", "pk": 10, "fields": {"key": "env", "value": "Development", "created_by": "System", "date_created": "2016-11-02T14:51:53Z", "comment": ""}}, {"model": "assets.assetextend", "pk": 11, "fields": {"key": "env", "value": "Testing", "created_by": "System", "date_created": "2016-11-02T14:51:53Z", "comment": ""}}, {"model": "assets.assetgroup", "pk": 1, "fields": {"name": "Default", "created_by": "", "date_created": "2016-11-02T14:51:53Z", "comment": "Default asset group", "system_users": []}}, {"model": "users.user", "pk": 2, "fields": {"password": "pbkdf2_sha256$30000$y45nsj5IDfVi$KUXoECb9rZJZ2ZosQSxi9anmj2oY5LAr1MdJby/xEzU=", "last_login": null, "first_name": "", "last_name": "", "is_active": true, "date_joined": "2016-11-02T14:51:45Z", "username": "admin", "name": "Administrator", "email": "admin@jumpserver.org", "role": "Admin", "avatar": "", "wechat": "", "phone": "", "enable_otp": false, "secret_key_otp": "", "_private_key": "", "_public_key": "", "comment": "Administrator is the super user of system", "is_first_login": false, "date_expired": "2086-10-16T14:51:45Z", "created_by": "System", "user_permissions": [], "groups": [1]}}] [{"model": "users.usergroup", "pk": 1, "fields": {"is_discard": false, "discard_time": null, "name": "Default", "comment": "Default user group for all user", "date_created": "2016-11-25T06:50:28.410Z", "created_by": "System"}}, {"model": "assets.assetgroup", "pk": 1, "fields": {"name": "Default", "created_by": "", "date_created": "2016-11-25T06:50:28.627Z", "comment": "Default asset group", "system_users": []}}, {"model": "users.user", "pk": 1, "fields": {"password": "pbkdf2_sha256$30000$RwSpXYAYQGbQ$PADpsQmM+cO5Y/R1CVSx3qNV4EbGIm2k+iMBXUtwvNc=", "last_login": null, "first_name": "", "last_name": "", "is_active": true, "date_joined": "2016-11-25T06:50:28.412Z", "username": "admin", "name": "Administrator", "email": "admin@jumpserver.org", "role": "Admin", "avatar": "", "wechat": "", "phone": null, "enable_otp": false, "secret_key_otp": "", "_private_key": "", "_public_key": "", "comment": "Administrator is the super user of system", "is_first_login": false, "date_expired": "2086-11-08T06:50:28.412Z", "created_by": "System", "user_permissions": [], "groups": [1]}}]
\ No newline at end of file
...@@ -98,6 +98,7 @@ TEMPLATES = [ ...@@ -98,6 +98,7 @@ TEMPLATES = [
'django.contrib.messages.context_processors.messages', 'django.contrib.messages.context_processors.messages',
'django.template.context_processors.static', 'django.template.context_processors.static',
'django.template.context_processors.request', 'django.template.context_processors.request',
'django.template.context_processors.media',
], ],
}, },
}, },
...@@ -262,7 +263,6 @@ REST_FRAMEWORK = { ...@@ -262,7 +263,6 @@ REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions, # Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users. # or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': ( 'DEFAULT_PERMISSION_CLASSES': (
# 'rest_framework.permissions.IsAuthenticated',
'users.backends.IsValidUser', 'users.backends.IsValidUser',
), ),
'DEFAULT_AUTHENTICATION_CLASSES': ( 'DEFAULT_AUTHENTICATION_CLASSES': (
...@@ -272,6 +272,7 @@ REST_FRAMEWORK = { ...@@ -272,6 +272,7 @@ REST_FRAMEWORK = {
'rest_framework.authentication.BasicAuthentication', 'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.SessionAuthentication',
), ),
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',),
} }
# Custom User Auth model # Custom User Auth model
......
...@@ -214,9 +214,7 @@ function APIUpdateAttr(props) { ...@@ -214,9 +214,7 @@ function APIUpdateAttr(props) {
// Sweet Alert for Delete // Sweet Alert for Delete
function objectDelete(obj, name, url) { function objectDelete(obj, name, url) {
var $this = $(this);
function doDelete() { function doDelete() {
var uid = $this.data('uid');
var body = {}; var body = {};
var success = function() { var success = function() {
swal('Deleted!', "[ "+name+"]"+" has been deleted ", "success"); swal('Deleted!', "[ "+name+"]"+" has been deleted ", "success");
...@@ -292,7 +290,7 @@ jumpserver.initDataTable = function (options) { ...@@ -292,7 +290,7 @@ jumpserver.initDataTable = function (options) {
columnDefs = options.columnDefs ? options.columnDefs.concat(columnDefs) : columnDefs; columnDefs = options.columnDefs ? options.columnDefs.concat(columnDefs) : columnDefs;
var table = ele.DataTable({ var table = ele.DataTable({
pageLength: options.pageLength || 15, pageLength: options.pageLength || 15,
dom: options.dom || '<"#uc.pull-left"><"html5buttons"B>flti<"row m-t"<"#op.col-md-6"><"col-md-6"p>>', dom: options.dom || '<"#uc.pull-left">flt<"row m-t"<"col-md-8"<"#op.col-md-6"><"col-md-6 text-center"i>><"col-md-4"p>>',
language: { language: {
url: options.i18n_url || "/static/js/plugins/dataTables/i18n/zh-hans.json" url: options.i18n_url || "/static/js/plugins/dataTables/i18n/zh-hans.json"
}, },
......
...@@ -24,10 +24,10 @@ class TerminalSerializer(serializers.ModelSerializer): ...@@ -24,10 +24,10 @@ class TerminalSerializer(serializers.ModelSerializer):
@staticmethod @staticmethod
def get_is_alive(obj): def get_is_alive(obj):
log = obj.terminalheatbeat_set.last() log = obj.terminalheatbeat_set.last()
if timezone.now() - log.date_created > timezone.timedelta(seconds=600): if log and timezone.now() - log.date_created < timezone.timedelta(seconds=600):
return False
else:
return True return True
else:
return False
class TerminalHeatbeatSerializer(serializers.ModelSerializer): class TerminalHeatbeatSerializer(serializers.ModelSerializer):
......
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
# #
import base64
from django.shortcuts import get_object_or_404
from django.core.cache import cache from django.core.cache import cache
from django.conf import settings from django.conf import settings
from rest_framework import generics, status, viewsets from rest_framework import generics, viewsets
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView, BulkModelViewSet from rest_framework_bulk import BulkModelViewSet
from rest_framework import authentication from django_filters.rest_framework import DjangoFilterBackend
from common.mixins import BulkDeleteApiMixin from common.mixins import IDInFilterMixin
from common.utils import get_logger from common.utils import get_logger
from .utils import check_user_valid, token_gen from .utils import check_user_valid, token_gen
from .models import User, UserGroup from .models import User, UserGroup
...@@ -24,10 +22,12 @@ from . import serializers ...@@ -24,10 +22,12 @@ from . import serializers
logger = get_logger(__name__) logger = get_logger(__name__)
class UserViewSet(BulkModelViewSet): class UserViewSet(IDInFilterMixin, BulkModelViewSet):
queryset = User.objects.all() queryset = User.objects.all()
serializer_class = serializers.UserSerializer serializer_class = serializers.UserSerializer
permission_classes = (IsSuperUser,) permission_classes = (IsSuperUser,)
filter_backends = (DjangoFilterBackend,)
filter_fields = ('username', 'email', 'name', 'id')
class UserUpdateGroupApi(generics.RetrieveUpdateAPIView): class UserUpdateGroupApi(generics.RetrieveUpdateAPIView):
...@@ -73,7 +73,7 @@ class UserUpdatePKApi(generics.UpdateAPIView): ...@@ -73,7 +73,7 @@ class UserUpdatePKApi(generics.UpdateAPIView):
user.save() user.save()
class UserGroupViewSet(viewsets.ModelViewSet): class UserGroupViewSet(IDInFilterMixin, BulkModelViewSet):
queryset = UserGroup.objects.all() queryset = UserGroup.objects.all()
serializer_class = serializers.UserGroupSerializer serializer_class = serializers.UserGroupSerializer
...@@ -83,52 +83,7 @@ class UserGroupUpdateUserApi(generics.RetrieveUpdateAPIView): ...@@ -83,52 +83,7 @@ class UserGroupUpdateUserApi(generics.RetrieveUpdateAPIView):
serializer_class = serializers.UserGroupUpdateMemeberSerializer serializer_class = serializers.UserGroupUpdateMemeberSerializer
permission_classes = (IsSuperUser,) permission_classes = (IsSuperUser,)
# class GroupDetailApi(generics.RetrieveUpdateDestroyAPIView):
# queryset = UserGroup.objects.all()
# serializer_class = serializers.GroupDetailSerializer
#
# def perform_update(self, serializer):
# users = serializer.validated_data.get('users')
# if users:
# group = self.get_object()
# Note: use `list` method to force hitting the db.
# group_users = list(group.users.all())
# serializer.save()
# group.users.set(users + group_users)
# group.save()
# return
# serializer.save()
# class UserListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView):
# queryset = User.objects.all()
# serializer_class = serializers.UserBulkUpdateSerializer
# permission_classes = (IsSuperUserOrTerminalUser,)
#
# def get(self, request, *args, **kwargs):
# return super(UserListUpdateApi, self).get(request, *args, **kwargs)
#
# class GroupListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView):
# queryset = UserGroup.objects.all()
# serializer_class = serializers.GroupBulkUpdateSerializer
#
# class DeleteUserFromGroupApi(generics.DestroyAPIView):
# queryset = UserGroup.objects.all()
# serializer_class = serializers.GroupDetailSerializer
#
# 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)
#
#
class UserAuthApi(APIView): class UserAuthApi(APIView):
permission_classes = () permission_classes = ()
expiration = settings.CONFIG.TOKEN_EXPIRATION or 3600 expiration = settings.CONFIG.TOKEN_EXPIRATION or 3600
......
...@@ -141,4 +141,4 @@ class UserGroupPrivateAssetPermissionForm(forms.ModelForm): ...@@ -141,4 +141,4 @@ class UserGroupPrivateAssetPermissionForm(forms.ModelForm):
class FileForm(forms.Form): class FileForm(forms.Form):
excel = forms.FileField() file = forms.FileField()
...@@ -79,7 +79,7 @@ class User(AbstractUser): ...@@ -79,7 +79,7 @@ class User(AbstractUser):
role = models.CharField(choices=ROLE_CHOICES, default='User', max_length=10, blank=True, verbose_name=_('Role')) role = models.CharField(choices=ROLE_CHOICES, default='User', max_length=10, blank=True, verbose_name=_('Role'))
avatar = models.ImageField(upload_to="avatar", verbose_name=_('Avatar')) avatar = models.ImageField(upload_to="avatar", verbose_name=_('Avatar'))
wechat = models.CharField(max_length=30, blank=True, verbose_name=_('Wechat')) wechat = models.CharField(max_length=30, blank=True, verbose_name=_('Wechat'))
phone = models.CharField(max_length=20, blank=True, verbose_name=_('Phone')) phone = models.CharField(max_length=20, blank=True, null=True, verbose_name=_('Phone'))
enable_otp = models.BooleanField(default=False, verbose_name=_('Enable OTP')) enable_otp = models.BooleanField(default=False, verbose_name=_('Enable OTP'))
secret_key_otp = models.CharField(max_length=16, blank=True) secret_key_otp = models.CharField(max_length=16, blank=True)
_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'))
......
...@@ -9,12 +9,6 @@ from common.utils import signer, validate_ssh_public_key ...@@ -9,12 +9,6 @@ from common.utils import signer, validate_ssh_public_key
from .models import User, UserGroup from .models import User, UserGroup
# class UserDetailSerializer(BulkSerializerMixin, serializers.ModelSerializer):
# class Meta:
# model = User
# fields = ['avatar', 'wechat', 'phone', 'enable_otp', 'comment', 'is_active', 'name']
class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
groups_display = serializers.SerializerMethodField() groups_display = serializers.SerializerMethodField()
groups = serializers.PrimaryKeyRelatedField(many=True, queryset=UserGroup.objects.all()) groups = serializers.PrimaryKeyRelatedField(many=True, queryset=UserGroup.objects.all())
...@@ -33,10 +27,6 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): ...@@ -33,10 +27,6 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
def get_groups_display(obj): def get_groups_display(obj):
return " ".join([group.name for group in obj.groups.all()]) return " ".join([group.name for group in obj.groups.all()])
# @staticmethod
# def get_active_display(obj):
# return not (obj.is_expired and obj.is_active)
class UserPKUpdateSerializer(serializers.ModelSerializer): class UserPKUpdateSerializer(serializers.ModelSerializer):
class Meta: class Meta:
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="col-sm-9 col-lg-9 col-sm-offset-2"> <div class="col-sm-9 col-lg-9 col-sm-offset-2">
<div class="checkbox checkbox-success"> <div class="checkbox">
<input type="checkbox" name="enable_otp" checked id="id_enable_otp"><label for="id_enable_otp">{% trans 'Enable-OTP' %}</label> <input type="checkbox" name="enable_otp" checked id="id_enable_otp"><label for="id_enable_otp">{% trans 'Enable-OTP' %}</label>
</div> </div>
</div> </div>
......
{% extends '_modal.html' %} {% extends '_modal.html' %}
{% load i18n %} {% load i18n %}
{% block modal_id %}user_import_modal{% endblock %} {% block modal_id %}user_import_modal{% endblock %}
{% block modal_title%}{% trans "Import User" %}{% endblock %} {% block modal_title%}{% trans "Import user" %}{% endblock %}
{% block modal_body %} {% block modal_body %}
<p class="text-success text-center">{% trans "Hint: your excel should organized in the following format." %}</p> <p class="text-success">{% trans "Download template or use export excel format" %}</p>
<p class="text-success text-center">{% trans "* You should have a very worksheet named `users`." %}</p> <form method="post" action="{% url 'users:user-import' %}" id="fm_user_import" enctype="multipart/form-data">
<p class="text-success text-center">{% trans "* Rows in this worksheet: username, email, enable_opt(0, 1), role(one of ['Admin', 'User'])" %}</p>
<form method="post" class="form-horizontal" action="{% url 'users:user-import' %}" id="fm_user_import" enctype="multipart/form-data">
{% csrf_token %} {% csrf_token %}
<div class="form-group"> <div class="form-group">
<label class="control-label col-sm-2 col-lg-2 " for="id_excel">{% trans "Excel" %}</label> <label class="control-label" for="id_users">{% trans "Template" %}</label>
<div class=" col-sm-9 col-lg-9 "> <a href="{{ MEDIA_URL }}files/user_import_template.xlsx" style="display: block">{% trans 'Download' %}</a>
<input id="id_excel" type="file" name="excel" /> </div>
</div> <div class="form-group">
<label class="control-label" for="id_users">{% trans "Users excel file" %}</label>
<input id="id_users" type="file" name="file" />
</div> </div>
</form> </form>
<p>
<p class="text-success" id="id_created"></p>
<p id="id_created_detail"></p>
<p class="text-warning" id="id_updated"></p>
<p id="id_updated_detail"></p>
<p class="text-danger" id="id_failed"></p>
<p id="id_failed_detail"></p>
</p>
{% endblock %} {% endblock %}
{% block modal_confirm_id %}btn_user_import{% endblock %} {% block modal_confirm_id %}btn_user_import{% endblock %}
...@@ -169,7 +169,7 @@ ...@@ -169,7 +169,7 @@
id: $this.attr('id'), id: $this.attr('id'),
user_id: {{ user.id }} user_id: {{ user.id }}
}; };
var the_url = "{% url 'perms:revoke-user-asset-permission' %}"; var the_url = "{% url 'api-perms:revoke-user-asset-permission' %}";
var success = function () { var success = function () {
$this.closest('tr').remove(); $this.closest('tr').remove();
}; };
......
...@@ -165,7 +165,7 @@ ...@@ -165,7 +165,7 @@
id: $this.attr('id'), id: $this.attr('id'),
user_group_id: {{ user_group.id }} user_group_id: {{ user_group.id }}
}; };
var the_url = "{% url 'perms:revoke-user-group-asset-permission' %}"; var the_url = "{% url 'api-perms:revoke-user-group-asset-permission' %}";
var success = function () { var success = function () {
$this.closest('tr').remove(); $this.closest('tr').remove();
}; };
......
...@@ -133,7 +133,7 @@ ...@@ -133,7 +133,7 @@
} }
}} }}
], ],
ajax_url: '{% url "perms:api-user-group-assets" pk=user_group.id %}', ajax_url: '{% url "api-perms:user-group-assets" pk=user_group.id %}',
columns: [{data: function(){return ""}}, {data: "hostname" }, {data: "ip" }, {data: "port"}, columns: [{data: function(){return ""}}, {data: "hostname" }, {data: "ip" }, {data: "port"},
{data: "system_users_join"}, {data: "is_active"}] {data: "system_users_join"}, {data: "is_active"}]
}; };
......
...@@ -47,8 +47,11 @@ $(document).ready(function() { ...@@ -47,8 +47,11 @@ $(document).ready(function() {
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>'); $(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
}}, }},
{targets: 4, createdCell: function (td, cellData, rowData) { {targets: 4, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "users:user-group-update" pk=99991937 %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', cellData); var update_btn = '<a href="{% url "users:user-group-update" pk=99991937 %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_delete_user_group" data-uid="99991937">{% trans "Delete" %}</a>'.replace('99991937', cellData); .replace('99991937', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_delete_user_group" data-gid="99991937" data-name="99991938">{% trans "Delete" %}</a>'
.replace('99991937', cellData)
.replace('99991938', rowData.name);
if (rowData.id === 1) { if (rowData.id === 1) {
$(td).html(update_btn) $(td).html(update_btn)
} else { } else {
...@@ -63,38 +66,10 @@ $(document).ready(function() { ...@@ -63,38 +66,10 @@ $(document).ready(function() {
jumpserver.initDataTable(options); jumpserver.initDataTable(options);
}).on('click', '.btn_delete_user_group', function(){ }).on('click', '.btn_delete_user_group', function(){
var $this = $(this); var $this = $(this);
function doDelete() { var group_id = $this.data('gid');
var group_id = $this.data('gid'); var name = $this.data('name');
var the_url = "{% url 'api-users:user-group-detail' pk=99991937 %}".replace('99991937', group_id); var the_url = "{% url 'api-users:user-group-detail' pk=99991937 %}".replace('99991937', group_id);
var body = {}; objectDelete($this, name, the_url)
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();
});
}).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 = $('#group_list_table').DataTable() var $data_table = $('#group_list_table').DataTable()
......
...@@ -3,17 +3,18 @@ ...@@ -3,17 +3,18 @@
{% block table_search %} {% block table_search %}
<div class="html5buttons"> <div class="html5buttons">
<div class="dt-buttons btn-group"> <div class="dt-buttons btn-group">
<a class="btn btn-default buttons-pdf" tabindex="0" href="#"> <a class="btn btn-default btn_import" data-toggle="modal" data-target="#user_import_modal" tabindex="0">
<span>PDF</span></a> <span>{% trans "Import" %}</span>
<a class="btn btn-default buttons-excel" tabindex="0" href="#"> </a>
<span>Excel</span> <a class="btn btn-default btn_export" tabindex="0">
<span>{% trans "Export" %}</span>
</a> </a>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% block table_container %} {% block table_container %}
<div class="uc pull-left m-l-5 m-r-5"><a href="{% url "users:user-create" %}" class="btn btn-sm btn-primary"> {% trans "Create user" %} </a></div> <div class="uc pull-left m-l-5 m-r-5"><a href="{% url "users:user-create" %}" class="btn btn-sm btn-primary"> {% trans "Create user" %} </a></div>
<div class="uc pull-left"><a href="javascript:void(0);" class="btn btn-sm btn-primary" data-toggle="modal" data-target="#user_import_modal"> {% trans "Import user" %} </a></div> {#<div class="uc pull-left"><a href="javascript:void(0);" class="btn btn-sm btn-primary" data-toggle="modal" data-target="#user_import_modal"> {% trans "Import 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>
...@@ -38,6 +39,7 @@ ...@@ -38,6 +39,7 @@
<option value="delete">{% trans 'Delete selected' %}</option> <option value="delete">{% trans 'Delete selected' %}</option>
<option value="update">{% trans 'Update selected' %}</option> <option value="update">{% trans 'Update selected' %}</option>
<option value="deactive">{% trans 'Deactive selected' %}</option> <option value="deactive">{% trans 'Deactive selected' %}</option>
<option value="active">{% trans 'Active selected' %}</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='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary"> <button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
...@@ -74,7 +76,9 @@ $(document).ready(function(){ ...@@ -74,7 +76,9 @@ $(document).ready(function(){
}}, }},
{targets: 6, createdCell: function (td, cellData, rowData) { {targets: 6, 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); var update_btn = '<a href="{% url "users:user-update" pk=99991937 %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('99991937', 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); var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_user_delete" data-uid="99991937" data-name="99991938">{% trans "Delete" %}</a>'
.replace('99991937', cellData)
.replace('99991938', rowData.name);
if (rowData.id === 1 || rowData.username == "admin") { if (rowData.id === 1 || rowData.username == "admin") {
$(td).html(update_btn) $(td).html(update_btn)
} else { } else {
...@@ -82,20 +86,52 @@ $(document).ready(function(){ ...@@ -82,20 +86,52 @@ $(document).ready(function(){
} }
}}], }}],
ajax_url: '{% url "api-users:user-list" %}', ajax_url: '{% url "api-users:user-list" %}',
columns: [{data: "id"}, {data: "username" }, {data: "name" }, {data: "get_role_display" }, columns: [{data: "id"}, {data: "name" }, {data: "username" }, {data: "get_role_display" },
{data: "groups_display" }, {data: "is_valid" }, {data: "id" }], {data: "groups_display" }, {data: "is_valid" }, {data: "id" }],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
var table = jumpserver.initDataTable(options); var table = jumpserver.initDataTable(options);
$('.buttons-pdf').click(function () { $('.btn_export').click(function () {
var users = []; var users = [];
var rows = table.rows('.selected').data(); var rows = table.rows('.selected').data();
$.each(rows, function (index, obj) { $.each(rows, function (index, obj) {
users.push(obj.id) users.push(obj.id)
});
$.ajax({
url: "{% url 'users:user-export' %}",
method: 'POST',
data: JSON.stringify({users_id: users}),
dataType: "json",
success: function (data, textStatus) {
window.open(data.redirect)
},
error: function () {
toastr.error('Export failed');
}
}) })
}); });
$('#btn_user_import').click(function() {
var $form = $('#fm_user_import');
$form.find('.help-block').remove();
function success (data) {
if (data.valid === false) {
$('<span />', {class: 'help-block text-danger'}).html(data.msg).insertAfter($('#id_users'));
} else {
$('#id_created').html(data.created_info);
$('#id_created_detail').html(data.created.join(', '));
$('#id_updated').html(data.updated_info);
$('#id_updated_detail').html(data.updated.join(', '));
$('#id_failed').html(data.failed_info);
$('#id_failed_detail').html(data.failed.join(', '));
var $data_table = $('#user_list_table').DataTable();
$data_table.ajax.reload();
}
}
$form.ajaxSubmit({success: success});
})
}).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();
...@@ -117,6 +153,14 @@ $(document).ready(function(){ ...@@ -117,6 +153,14 @@ $(document).ready(function(){
$data_table.ajax.reload(); $data_table.ajax.reload();
jumpserver.checked = false; jumpserver.checked = false;
} }
function doActive() {
var body = $.each(id_list, function(index, user_object) {
user_object['is_active'] = true;
});
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(body)});
$data_table.ajax.reload();
jumpserver.checked = false;
}
function doDelete() { function doDelete() {
swal({ swal({
title: "{% trans 'Are you sure?' %}", title: "{% trans 'Are you sure?' %}",
...@@ -154,43 +198,18 @@ $(document).ready(function(){ ...@@ -154,43 +198,18 @@ $(document).ready(function(){
case 'update': case 'update':
doUpdate(); doUpdate();
break; break;
case 'active':
doActive();
break;
default: default:
break; break;
} }
}).on('click', '.btn_user_delete', function(){ }).on('click', '.btn_user_delete', function(){
var $this = $(this); var $this = $(this);
function doDelete() { var name = $this.data('name');
var uid = $this.data('uid'); var uid = $this.data('uid');
var the_url = '{% url "api-users:user-detail" pk=99991937 %}'.replace('99991937', uid); var the_url = '{% url "api-users:user-detail" pk=99991937 %}'.replace('99991937', uid);
var body = {}; objectDelete($this, name, the_url);
var success = function() {
var msg = "{% trans 'User Deleted.' %}";
swal("{% trans 'User Delete' %}", msg, "success");
$('#user_list_table').DataTable().ajax.reload();
};
var fail = function() {
var msg = "{% trans 'User Deleting failed.' %}";
swal("{% trans 'User 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 user.' %}",
type: "warning",
showCancelButton: true,
confirmButtonColor: "#DD6B55",
confirmButtonText: "{% trans 'Confirm' %}",
closeOnConfirm: false
}, function() {
doDelete();
});
}).on('click', '#btn_user_bulk_update', function(){ }).on('click', '#btn_user_bulk_update', function(){
var json_data = $('#fm_user_bulk_update').serializeObject(); var json_data = $('#fm_user_bulk_update').serializeObject();
var body = {}; var body = {};
...@@ -204,10 +223,10 @@ $(document).ready(function(){ ...@@ -204,10 +223,10 @@ $(document).ready(function(){
if (typeof body.groups === 'string') { if (typeof body.groups === 'string') {
body.groups = [parseInt(body.groups)] body.groups = [parseInt(body.groups)]
} else if(typeof body.groups === 'array') { } else if(typeof body.groups === 'array') {
new_groups = body.groups.map(Number); var new_groups = body.groups.map(Number);
body.groups = new_groups; body.groups = new_groups;
} }
var $data_table = $('#user_list_table').DataTable() var $data_table = $('#user_list_table').DataTable();
var post_list = []; var post_list = [];
$data_table.rows({selected: true}).every(function(){ $data_table.rows({selected: true}).every(function(){
var content = Object.assign({id: this.data().id}, body); var content = Object.assign({id: this.data().id}, body);
...@@ -225,22 +244,7 @@ $(document).ready(function(){ ...@@ -225,22 +244,7 @@ $(document).ready(function(){
}; };
APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(post_list), success: success}); APIUpdateAttr({url: the_url, method: 'PATCH', body: JSON.stringify(post_list), success: success});
$('#user_bulk_update_modal').modal('hide'); $('#user_bulk_update_modal').modal('hide');
}).on('click', '#btn_user_import', function() { });
var $form = $('#fm_user_import');
$form.find('.help-block').remove();
function success (data) {
if (data.success === false) {
var $help = $form.find('.help-block');
$('<span />', {class: 'help-block text-danger'}).html(data.msg).insertAfter($('#id_excel'));
} else {
$('#user_import_modal').modal('hide');
var $data_table = $('#user_list_table').DataTable();
toastr.success("{% trans 'Import User Success.' %}");
$data_table.ajax.reload();
}
}
$form.ajaxSubmit({success: success});
})
</script> </script>
{% endblock %} {% endblock %}
...@@ -23,8 +23,9 @@ urlpatterns = [ ...@@ -23,8 +23,9 @@ urlpatterns = [
name='user-asset-permission-create'), name='user-asset-permission-create'),
url(r'^user/(?P<pk>[0-9]+)/assets', views.UserGrantedAssetView.as_view(), name='user-granted-asset'), url(r'^user/(?P<pk>[0-9]+)/assets', views.UserGrantedAssetView.as_view(), name='user-granted-asset'),
url(r'^user/(?P<pk>[0-9]+)/login-history', views.UserDetailView.as_view(), name='user-login-history'), url(r'^user/(?P<pk>[0-9]+)/login-history', views.UserDetailView.as_view(), name='user-login-history'),
url(r'^user/export/', views.UserExportView.as_view(), name='user-export'),
url(r'^first-login/$', views.UserFirstLoginView.as_view(), name='user-first-login'), url(r'^first-login/$', views.UserFirstLoginView.as_view(), name='user-first-login'),
url(r'^import/$', views.BulkImportUserView.as_view(), name='user-import'), url(r'^user/import/$', views.BulkImportUserView.as_view(), name='user-import'),
# 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'),
......
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals from __future__ import unicode_literals
import json
import uuid
import csv from openpyxl import Workbook
from openpyxl.writer.excel import save_virtual_workbook
from openpyxl import load_workbook
from django import forms from django import forms
from django.conf import settings from django.utils import timezone
from django.core.cache import cache
from django.db import IntegrityError
from django.contrib.auth import login as auth_login, logout as auth_logout 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.http import HttpResponseRedirect, HttpResponse from django.http import HttpResponseRedirect, HttpResponse, JsonResponse
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
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.views import View
from django.views.decorators.cache import never_cache from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect from django.views.decorators.csrf import csrf_protect, csrf_exempt
from django.views.decorators.debug import sensitive_post_parameters 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
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
...@@ -32,7 +38,6 @@ from .utils import AdminUserRequiredMixin, user_add_success_next, send_reset_pas ...@@ -32,7 +38,6 @@ from .utils import AdminUserRequiredMixin, user_add_success_next, send_reset_pas
from .hands import write_login_log_async from .hands import write_login_log_async
from . import forms from . import forms
logger = get_logger(__name__) logger = get_logger(__name__)
...@@ -90,7 +95,11 @@ class UserListView(AdminUserRequiredMixin, TemplateView): ...@@ -90,7 +95,11 @@ class UserListView(AdminUserRequiredMixin, TemplateView):
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'), 'groups': UserGroup.objects.all()}) context.update({
'app': _('Users'),
'action': _('User list'),
'groups': UserGroup.objects.all()
})
return context return context
...@@ -231,10 +240,6 @@ class UserGroupDetailView(AdminUserRequiredMixin, DetailView): ...@@ -231,10 +240,6 @@ class UserGroupDetailView(AdminUserRequiredMixin, DetailView):
return super(UserGroupDetailView, self).get_context_data(**kwargs) return super(UserGroupDetailView, self).get_context_data(**kwargs)
class UserGroupDeleteView(DeleteView):
pass
class UserForgotPasswordView(TemplateView): class UserForgotPasswordView(TemplateView):
template_name = 'users/forgot_password.html' template_name = 'users/forgot_password.html'
...@@ -490,55 +495,99 @@ class BulkImportUserView(AdminUserRequiredMixin, JSONResponseMixin, FormView): ...@@ -490,55 +495,99 @@ class BulkImportUserView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
return self.render_json_response(data) return self.render_json_response(data)
def form_valid(self, form): def form_valid(self, form):
from openpyxl import load_workbook
try: try:
wb = load_workbook(form.cleaned_data['excel']) wb = load_workbook(form.cleaned_data['file'])
ws = wb['users'] ws = wb.get_active_sheet()
except Exception as e: except Exception as e:
print e print(e)
error = _('Not a valid Excel file.') data = {'valid': False, 'msg': 'Not a valid Excel file'}
data = {
'success': False,
'msg': error
}
return self.render_json_response(data) return self.render_json_response(data)
errors = [] rows = ws.rows
for index, row in enumerate(ws.rows): header_need = ["name", 'username', 'email', 'groups', "role", "phone", "wechat", "comment"]
user_data = [cell.value for cell in row] header = [col.value for col in next(rows)]
if len(user_data) != 4: print(header)
errors.append("Row {}: invalid user data format.".format(index)) if header != header_need:
continue data = {'valid': False, 'msg': 'Must be same format as template or export file'}
username, email, enable_otp, role = user_data return self.render_json_response(data)
data = {
'username': username, created = []
'email': email, updated = []
'enable_otp': True if enable_otp in ['T', '1', 1, True] else False, failed = []
'role': role for row in rows:
} user_dict = dict(zip(header, [col.value for col in row]))
form = forms.UserBulkImportForm(data, auto_id=False) groups_name = user_dict.pop('groups')
if form.is_valid(): if groups_name:
form.save() groups_name = groups_name.split(',')
groups = UserGroup.objects.filter(name__in=groups_name)
else: else:
form_errors = form.errors.as_data() groups = None
for key, err_list in form_errors.iteritems(): try:
error_line = "{} :".format(key) user = User.objects.create(**user_dict)
for errs in err_list: user_add_success_next(user)
error_line = "{}{}".format(error_line, ";".join([err for err in errs.messages])) created.append(user_dict['username'])
errors.append("Row {}: {}".format(index, error_line)) except IntegrityError as e:
user = User.objects.filter(username=user_dict['username'])
if not user:
failed.append(user_dict['username'])
continue
user.update(**user_dict)
user = user[0]
updated.append(user_dict['username'])
except TypeError as e:
print(e)
failed.append(user_dict['username'])
user = None
if user and groups:
user.groups.add(*tuple(groups))
user.save()
data = { data = {
'success': True if not errors else False, 'created': created,
'msg': 'ok' if not errors else '<br />'.join(errors) 'created_info': 'Created {}'.format(len(created)),
'updated': updated,
'updated_info': 'Updated {}'.format(len(updated)),
'failed': failed,
'failed_info': 'Failed {}'.format(len(failed)),
'valid': True,
'msg': 'Created: {}. Updated: {}, Error: {}'.format(len(created), len(updated), len(failed))
} }
return self.render_json_response(data) return self.render_json_response(data)
def down_csv(request, xx): @method_decorator(csrf_exempt, name='dispatch')
print(xx) class UserExportView(View):
response = HttpResponse(content_type='application/csv') def get(self, request, *args, **kwargs):
response['Content-Disposition'] = 'attachment; filename="somefile.csv"' spm = request.GET.get('spm', '')
writer = csv.writer(response) users_id = cache.get(spm)
writer.writerow(['First row', 'Foo', 'Bar', 'Baz']) if not users_id and not isinstance(users_id, list):
writer.writerow(['Second row', 'A', 'B', 'C', '"Testing"', "Here's a quote"]) return HttpResponse('May be expired', status=404)
return response
users = User.objects.filter(id__in=users_id)
wb = Workbook()
ws = wb.active
ws.title = 'User'
header = ["name", 'username', 'email', 'groups', "role", "phone", "wechat", "comment"]
ws.append(header)
for user in users:
ws.append([user.name, user.username, user.email,
','.join([group.name for group in user.groups.all()]),
user.role, user.phone, user.wechat, user.comment])
filename = 'users-{}.xlsx'.format(timezone.localtime(timezone.now()).strftime('%Y-%m-%d_%H-%M-%S'))
response = HttpResponse(save_virtual_workbook(wb), content_type='application/vnd.ms-excel')
response['Content-Disposition'] = 'attachment; filename="%s"' % filename
return response
def post(self, request, *args, **kwargs):
try:
users_id = json.loads(request.body).get('users_id', [])
except ValueError:
return HttpResponse('Json object not valid', status=400)
spm = uuid.uuid4().get_hex()
cache.set(spm, users_id, 300)
url = reverse('users:user-export') + '?spm=%s' % spm
return JsonResponse({'redirect': url})
...@@ -14,4 +14,6 @@ djangorestframework-bulk==0.2.1 ...@@ -14,4 +14,6 @@ djangorestframework-bulk==0.2.1
paramiko==2.0.2 paramiko==2.0.2
django-redis-cache==1.7.1 django-redis-cache==1.7.1
requests==2.11.1 requests==2.11.1
itsdangerous==0.24 itsdangerous==0.24
\ No newline at end of file unicodecsv==0.14.1
django-filter==1.0.0
#!/bin/bash
#
for app in users assets perms audits teminal ops;do
rm -f ../apps/$app/migrations/000*
done
#!/bin/bash
#
python ../apps/manage.py loaddata init
#!/bin/bash
#
python ../apps/manage.py shell << EOF
from users.models import *
generate_fake()
from assets.models import *
generate_fake()
EOF
python ../apps/manage.py dbshell << EOF
delete from django_content_type;
delete from auth_permission;
EOF
python ../apps/manage.py dumpdata > ../apps/fixtures/fake.json
#!/bin/bash
#
python ../apps/manage.py shell << EOF
from users.models import *
init_all_models()
from assets.models import *
init_all_models()
EOF
python ../apps/manage.py dbshell << EOF
delete from django_content_type;
delete from auth_permission;
EOF
python ../apps/manage.py dumpdata > ../apps/fixtures/init.json
#!/bin/bash
#
python ../apps/manage.py makemigrations
python ../apps/manage.py migrate
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