mirror of https://github.com/jumpserver/jumpserver
				
				
				
			Add export and import
							parent
							
								
									be99eb82e8
								
							
						
					
					
						commit
						e28f7a3bec
					
				| 
						 | 
					@ -141,4 +141,4 @@ class UserGroupPrivateAssetPermissionForm(forms.ModelForm):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class FileForm(forms.Form):
 | 
					class FileForm(forms.Form):
 | 
				
			||||||
    excel = forms.FileField()
 | 
					    users = forms.FileField()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,16 +1,14 @@
 | 
				
			||||||
{% 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">{% trans "   * CSV format should be same as export" %}</p>
 | 
					<p class="text-success">{% trans "   * CSV format should be same as export" %}</p>
 | 
				
			||||||
<form method="post" class="form-horizontal" action="{% url 'users:user-import' %}" id="fm_user_import" enctype="multipart/form-data">
 | 
					<form method="post" 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 "CSV" %}</label>
 | 
					        <label class="control-label" for="id_users">{% trans "Users csv file" %}</label>
 | 
				
			||||||
        <div class=" col-sm-9 col-lg-9 ">
 | 
					        <input id="id_users" type="file" name="users" />
 | 
				
			||||||
            <input id="id_excel" type="file" name="excel" />
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</form>
 | 
					</form>
 | 
				
			||||||
{% endblock %}
 | 
					{% endblock %}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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-csv" tabindex="0" href="#">
 | 
					            </a>
 | 
				
			||||||
                <span>CSV</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>
 | 
				
			||||||
| 
						 | 
					@ -88,7 +89,7 @@ $(document).ready(function(){
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    var table = jumpserver.initDataTable(options);
 | 
					    var table = jumpserver.initDataTable(options);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    $('.buttons-csv').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) {
 | 
				
			||||||
| 
						 | 
					@ -108,6 +109,26 @@ $(document).ready(function(){
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $('#btn_user_import').click(function() {
 | 
				
			||||||
 | 
					        var $form = $('#fm_user_import');
 | 
				
			||||||
 | 
					        $form.find('.help-block').remove();
 | 
				
			||||||
 | 
					        function success (data) {
 | 
				
			||||||
 | 
					            if (data.valid === false) {
 | 
				
			||||||
 | 
					                var $help = $form.find('.help-block');
 | 
				
			||||||
 | 
					                $('<span />', {class: 'help-block text-danger'}).html(data.msg).insertAfter($('#id_users'));
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					{#                $('#user_import_modal').modal('hide');#}
 | 
				
			||||||
 | 
					{#                var $data_table = $('#user_list_table').DataTable();#}
 | 
				
			||||||
 | 
					{#                toastr.success("{% trans 'Import User Success.' %}");#}
 | 
				
			||||||
 | 
					                $('<span />', {class: 'help-block text-danger'}).html(data.errors.join(',')).insertAfter($('#id_users'));
 | 
				
			||||||
 | 
					                $('<span />', {class: 'help-block text-warning'}).html(data.updated.join(',')).insertAfter($('#id_users'));
 | 
				
			||||||
 | 
					                $('<span />', {class: 'help-block text-primary'}).html(data.created.join(',')).insertAfter($('#id_users'));
 | 
				
			||||||
 | 
					{#                $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();
 | 
				
			||||||
| 
						 | 
					@ -219,7 +240,7 @@ $(document).ready(function(){
 | 
				
			||||||
        new_groups = body.groups.map(Number);
 | 
					        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);
 | 
				
			||||||
| 
						 | 
					@ -237,22 +258,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 %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,7 +24,7 @@ urlpatterns = [
 | 
				
			||||||
    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'^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'),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,11 +5,11 @@ import json
 | 
				
			||||||
import uuid
 | 
					import uuid
 | 
				
			||||||
from io import BytesIO
 | 
					from io import BytesIO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from reportlab.pdfgen import canvas
 | 
					 | 
				
			||||||
import unicodecsv as csv
 | 
					import unicodecsv as csv
 | 
				
			||||||
from django import forms
 | 
					from django import forms
 | 
				
			||||||
from django.utils import timezone
 | 
					from django.utils import timezone
 | 
				
			||||||
from django.core.cache import cache
 | 
					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
 | 
				
			||||||
| 
						 | 
					@ -496,45 +496,41 @@ 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
 | 
					        users_csv = form.cleaned_data['users']
 | 
				
			||||||
        try:
 | 
					        users_csv_f = csv.reader(users_csv, encoding='utf-8')
 | 
				
			||||||
            wb = load_workbook(form.cleaned_data['excel'])
 | 
					        header_need = ["name", 'username', 'email', 'groups', "role", "phone", "wechat", "comment"]
 | 
				
			||||||
            ws = wb['users']
 | 
					        header = next(users_csv_f)
 | 
				
			||||||
        except Exception as e:
 | 
					        print(header)
 | 
				
			||||||
            print e
 | 
					        if header != header_need:
 | 
				
			||||||
            error = _('Not a valid Excel file.')
 | 
					            data = {'valid': False, 'msg': 'Must be same format as export csv: name, ...'}
 | 
				
			||||||
            data = {
 | 
					 | 
				
			||||||
                'success': False,
 | 
					 | 
				
			||||||
                'msg': error
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return self.render_json_response(data)
 | 
					            return self.render_json_response(data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        created = []
 | 
				
			||||||
 | 
					        updated = []
 | 
				
			||||||
        errors = []
 | 
					        errors = []
 | 
				
			||||||
        for index, row in enumerate(ws.rows):
 | 
					        for row in users_csv_f:
 | 
				
			||||||
            user_data = [cell.value for cell in row]
 | 
					            user_dict = dict(zip(header, row))
 | 
				
			||||||
            if len(user_data) != 4:
 | 
					            groups_name = user_dict.pop('groups').split(',')
 | 
				
			||||||
                errors.append("Row {}: invalid user data format.".format(index))
 | 
					            groups = UserGroup.objects.filter(name__in=groups_name)
 | 
				
			||||||
                continue
 | 
					            try:
 | 
				
			||||||
            username, email, enable_otp, role = user_data
 | 
					                user = User.objects.create(**user_dict)
 | 
				
			||||||
 | 
					                user.groups.add(*tuple(groups))
 | 
				
			||||||
 | 
					                user.save()
 | 
				
			||||||
 | 
					                created.append(user_dict['username'])
 | 
				
			||||||
 | 
					            except IntegrityError:
 | 
				
			||||||
 | 
					                user = User.objects.filter(username=user_dict['username'])
 | 
				
			||||||
 | 
					                user.update(**user_dict)
 | 
				
			||||||
 | 
					                user[0].groups.add(*tuple(groups))
 | 
				
			||||||
 | 
					                updated.append(user_dict['username'])
 | 
				
			||||||
 | 
					            except TypeError:
 | 
				
			||||||
 | 
					                errors.append(user_dict['username'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        data = {
 | 
					        data = {
 | 
				
			||||||
                'username': username,
 | 
					            'created': created,
 | 
				
			||||||
                'email': email,
 | 
					            'updated': updated,
 | 
				
			||||||
                'enable_otp': True if enable_otp in ['T', '1', 1, True] else False,
 | 
					            'errors': errors,
 | 
				
			||||||
                'role': role
 | 
					            'valid': True,
 | 
				
			||||||
            }
 | 
					            'msg': 'Created: {}. Updated: {}, Error: {}'.format(len(created), len(updated), len(errors))
 | 
				
			||||||
            form = forms.UserBulkImportForm(data, auto_id=False)
 | 
					 | 
				
			||||||
            if form.is_valid():
 | 
					 | 
				
			||||||
                form.save()
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                form_errors = form.errors.as_data()
 | 
					 | 
				
			||||||
                for key, err_list in form_errors.iteritems():
 | 
					 | 
				
			||||||
                    error_line = "{} :".format(key)
 | 
					 | 
				
			||||||
                    for errs in err_list:
 | 
					 | 
				
			||||||
                        error_line = "{}{}".format(error_line, ";".join([err for err in errs.messages]))
 | 
					 | 
				
			||||||
                    errors.append("Row {}: {}".format(index, error_line))
 | 
					 | 
				
			||||||
        data = {
 | 
					 | 
				
			||||||
            'success': True if not errors else False,
 | 
					 | 
				
			||||||
            'msg': 'ok' if not errors else '<br />'.join(errors)
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return self.render_json_response(data)
 | 
					        return self.render_json_response(data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -543,22 +539,21 @@ class BulkImportUserView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
 | 
				
			||||||
class ExportUserCsvView(View):
 | 
					class ExportUserCsvView(View):
 | 
				
			||||||
    def get(self, request, *args, **kwargs):
 | 
					    def get(self, request, *args, **kwargs):
 | 
				
			||||||
        spm = request.GET.get('spm', '')
 | 
					        spm = request.GET.get('spm', '')
 | 
				
			||||||
        print(spm)
 | 
					 | 
				
			||||||
        users_id = cache.get(spm)
 | 
					        users_id = cache.get(spm)
 | 
				
			||||||
        if not users_id and not isinstance(users_id, list):
 | 
					        if not users_id and not isinstance(users_id, list):
 | 
				
			||||||
            return HttpResponse('May be expired', status=404)
 | 
					            return HttpResponse('May be expired', status=404)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        users = User.objects.filter(id__in=users_id)
 | 
					        users = User.objects.filter(id__in=users_id)
 | 
				
			||||||
        filename = 'users-%s.csv' % timezone.localtime(timezone.now()).strftime('%Y-%m-%d_%H:%M:%D')
 | 
					        filename = 'users-%s.csv' % timezone.localtime(timezone.now()).strftime('%Y-%m-%d_%H-%M-%S')
 | 
				
			||||||
        response = HttpResponse(content_type='application/csv')
 | 
					        response = HttpResponse(content_type='application/csv')
 | 
				
			||||||
        response['Content-Disposition'] = 'attachment; filename="%s"' % filename
 | 
					        response['Content-Disposition'] = 'attachment; filename="%s"' % filename
 | 
				
			||||||
        writer = csv.writer(response, delimiter=str(","), lineterminator='\n',
 | 
					        writer = csv.writer(response, delimiter=str(","), lineterminator='\n',
 | 
				
			||||||
                            quoting=csv.QUOTE_ALL, dialect='excel')
 | 
					                            quoting=csv.QUOTE_ALL, dialect='excel')
 | 
				
			||||||
        header = [_("name"), _('username'), _('email'), _('user group'),
 | 
					        header = ["name", 'username', 'email', 'groups', "role", "phone", "wechat", "comment"]
 | 
				
			||||||
                  _('role'), _('phone'), _('wechat'), _('comment')]
 | 
					 | 
				
			||||||
        writer.writerow(header)
 | 
					        writer.writerow(header)
 | 
				
			||||||
        for user in users:
 | 
					        for user in users:
 | 
				
			||||||
            writer.writerow([user.name, user.username, user.email, ','.join([group.name for group in user.groups.all()]),
 | 
					            writer.writerow([user.name, user.username, user.email,
 | 
				
			||||||
 | 
					                             ','.join([group.name for group in user.groups.all()]),
 | 
				
			||||||
                             user.role, user.phone, user.wechat, user.comment])
 | 
					                             user.role, user.phone, user.wechat, user.comment])
 | 
				
			||||||
        return response
 | 
					        return response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue