mirror of https://github.com/jumpserver/jumpserver
				
				
				
			Update user import and export
							parent
							
								
									e28f7a3bec
								
							
						
					
					
						commit
						eae580e51f
					
				| 
						 | 
					@ -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)
 | 
				
			||||||
| 
						 | 
					@ -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',
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
										
											Binary file not shown.
										
									
								
							| 
						 | 
					@ -141,4 +141,4 @@ class UserGroupPrivateAssetPermissionForm(forms.ModelForm):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class FileForm(forms.Form):
 | 
					class FileForm(forms.Form):
 | 
				
			||||||
    users = forms.FileField()
 | 
					    file = forms.FileField()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,13 +3,25 @@
 | 
				
			||||||
{% 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 "Download template or use export excel format" %}</p>
 | 
				
			||||||
<form method="post" 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" for="id_users">{% trans "Users csv file" %}</label>
 | 
					        <label class="control-label" for="id_users">{% trans "Template" %}</label>
 | 
				
			||||||
        <input id="id_users" type="file" name="users" />
 | 
					        <a href="{{ MEDIA_URL }}files/user_import_template.xlsx" style="display: block">{% trans 'Download' %}</a>
 | 
				
			||||||
 | 
					    </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 %}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -114,16 +114,16 @@ $(document).ready(function(){
 | 
				
			||||||
        $form.find('.help-block').remove();
 | 
					        $form.find('.help-block').remove();
 | 
				
			||||||
        function success (data) {
 | 
					        function success (data) {
 | 
				
			||||||
            if (data.valid === false) {
 | 
					            if (data.valid === false) {
 | 
				
			||||||
                var $help = $form.find('.help-block');
 | 
					 | 
				
			||||||
                $('<span />', {class: 'help-block text-danger'}).html(data.msg).insertAfter($('#id_users'));
 | 
					                $('<span />', {class: 'help-block text-danger'}).html(data.msg).insertAfter($('#id_users'));
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
{#                $('#user_import_modal').modal('hide');#}
 | 
					                $('#id_created').html(data.created_info);
 | 
				
			||||||
{#                var $data_table = $('#user_list_table').DataTable();#}
 | 
					                $('#id_created_detail').html(data.created.join(','));
 | 
				
			||||||
{#                toastr.success("{% trans 'Import User Success.' %}");#}
 | 
					                $('#id_updated').html(data.updated_info);
 | 
				
			||||||
                $('<span />', {class: 'help-block text-danger'}).html(data.errors.join(',')).insertAfter($('#id_users'));
 | 
					                $('#id_updated_detail').html(data.updated.join(','));
 | 
				
			||||||
                $('<span />', {class: 'help-block text-warning'}).html(data.updated.join(',')).insertAfter($('#id_users'));
 | 
					                $('#id_failed').html(data.failed_info);
 | 
				
			||||||
                $('<span />', {class: 'help-block text-primary'}).html(data.created.join(',')).insertAfter($('#id_users'));
 | 
					                $('#id_failed_detail').html(data.failed.join(','));
 | 
				
			||||||
{#                $data_table.ajax.reload();#}
 | 
					                var $data_table = $('#user_list_table').DataTable();
 | 
				
			||||||
 | 
					                $data_table.ajax.reload();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        $form.ajaxSubmit({success: success});
 | 
					        $form.ajaxSubmit({success: success});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,8 +3,11 @@
 | 
				
			||||||
from __future__ import unicode_literals
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
import json
 | 
					import json
 | 
				
			||||||
import uuid
 | 
					import uuid
 | 
				
			||||||
from io import BytesIO
 | 
					import codecs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from openpyxl import Workbook
 | 
				
			||||||
 | 
					from openpyxl.writer.excel import save_virtual_workbook
 | 
				
			||||||
 | 
					from openpyxl import load_workbook
 | 
				
			||||||
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
 | 
				
			||||||
| 
						 | 
					@ -37,8 +40,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__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -96,7 +97,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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -496,41 +501,61 @@ 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):
 | 
				
			||||||
        users_csv = form.cleaned_data['users']
 | 
					        try:
 | 
				
			||||||
        users_csv_f = csv.reader(users_csv, encoding='utf-8')
 | 
					            wb = load_workbook(form.cleaned_data['file'])
 | 
				
			||||||
 | 
					            ws = wb.get_active_sheet()
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            print(e)
 | 
				
			||||||
 | 
					            data = {'valid': False, 'msg': 'Not a valid Excel file'}
 | 
				
			||||||
 | 
					            return self.render_json_response(data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        rows = ws.rows
 | 
				
			||||||
        header_need = ["name", 'username', 'email', 'groups', "role", "phone", "wechat", "comment"]
 | 
					        header_need = ["name", 'username', 'email', 'groups', "role", "phone", "wechat", "comment"]
 | 
				
			||||||
        header = next(users_csv_f)
 | 
					        header = [col.value for col in next(rows)]
 | 
				
			||||||
        print(header)
 | 
					        print(header)
 | 
				
			||||||
        if header != header_need:
 | 
					        if header != header_need:
 | 
				
			||||||
            data = {'valid': False, 'msg': 'Must be same format as export csv: name, ...'}
 | 
					            data = {'valid': False, 'msg': 'Must be same format as template or export file'}
 | 
				
			||||||
            return self.render_json_response(data)
 | 
					            return self.render_json_response(data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        created = []
 | 
					        created = []
 | 
				
			||||||
        updated = []
 | 
					        updated = []
 | 
				
			||||||
        errors = []
 | 
					        failed = []
 | 
				
			||||||
        for row in users_csv_f:
 | 
					        for row in rows:
 | 
				
			||||||
            user_dict = dict(zip(header, row))
 | 
					            user_dict = dict(zip(header, [col.value for col in row]))
 | 
				
			||||||
            groups_name = user_dict.pop('groups').split(',')
 | 
					            groups_name = user_dict.pop('groups')
 | 
				
			||||||
            groups = UserGroup.objects.filter(name__in=groups_name)
 | 
					            if groups_name:
 | 
				
			||||||
 | 
					                groups_name = groups_name.split(',')
 | 
				
			||||||
 | 
					                groups = UserGroup.objects.filter(name__in=groups_name)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                groups = None
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                user = User.objects.create(**user_dict)
 | 
					                user = User.objects.create(**user_dict)
 | 
				
			||||||
                user.groups.add(*tuple(groups))
 | 
					 | 
				
			||||||
                user.save()
 | 
					 | 
				
			||||||
                created.append(user_dict['username'])
 | 
					                created.append(user_dict['username'])
 | 
				
			||||||
            except IntegrityError:
 | 
					            except IntegrityError:
 | 
				
			||||||
                user = User.objects.filter(username=user_dict['username'])
 | 
					                user = User.objects.filter(username=user_dict['username'])
 | 
				
			||||||
 | 
					                if not user:
 | 
				
			||||||
 | 
					                    failed.append(user_dict['username'])
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
                user.update(**user_dict)
 | 
					                user.update(**user_dict)
 | 
				
			||||||
                user[0].groups.add(*tuple(groups))
 | 
					                user = user[0]
 | 
				
			||||||
                updated.append(user_dict['username'])
 | 
					                updated.append(user_dict['username'])
 | 
				
			||||||
            except TypeError:
 | 
					            except TypeError:
 | 
				
			||||||
                errors.append(user_dict['username'])
 | 
					                failed.append(user_dict['username'])
 | 
				
			||||||
 | 
					                user = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if user and groups:
 | 
				
			||||||
 | 
					                user.groups.add(*tuple(groups))
 | 
				
			||||||
 | 
					                user.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        data = {
 | 
					        data = {
 | 
				
			||||||
            'created': created,
 | 
					            'created': created,
 | 
				
			||||||
 | 
					            'created_info': 'Created {}'.format(len(created)),
 | 
				
			||||||
            'updated': updated,
 | 
					            'updated': updated,
 | 
				
			||||||
            'errors': errors,
 | 
					            'updated_info': 'Updated {}'.format(len(updated)),
 | 
				
			||||||
 | 
					            'failed': failed,
 | 
				
			||||||
 | 
					            'failed_info': 'Failed {}'.format(len(failed)),
 | 
				
			||||||
            'valid': True,
 | 
					            'valid': True,
 | 
				
			||||||
            'msg': 'Created: {}. Updated: {}, Error: {}'.format(len(created), len(updated), len(errors))
 | 
					            'msg': 'Created: {}. Updated: {}, Error: {}'.format(len(created), len(updated), len(failed))
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return self.render_json_response(data)
 | 
					        return self.render_json_response(data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -544,22 +569,24 @@ class ExportUserCsvView(View):
 | 
				
			||||||
            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-%S')
 | 
					        wb = Workbook()
 | 
				
			||||||
        response = HttpResponse(content_type='application/csv')
 | 
					        ws = wb.active
 | 
				
			||||||
        response['Content-Disposition'] = 'attachment; filename="%s"' % filename
 | 
					        ws.title = 'User'
 | 
				
			||||||
        writer = csv.writer(response, delimiter=str(","), lineterminator='\n',
 | 
					 | 
				
			||||||
                            quoting=csv.QUOTE_ALL, dialect='excel')
 | 
					 | 
				
			||||||
        header = ["name", 'username', 'email', 'groups', "role", "phone", "wechat", "comment"]
 | 
					        header = ["name", 'username', 'email', 'groups', "role", "phone", "wechat", "comment"]
 | 
				
			||||||
        writer.writerow(header)
 | 
					        ws.append(header)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for user in users:
 | 
					        for user in users:
 | 
				
			||||||
            writer.writerow([user.name, user.username, user.email,
 | 
					            ws.append([user.name, user.username, user.email,
 | 
				
			||||||
                             ','.join([group.name for group in user.groups.all()]),
 | 
					                       ','.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])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        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
 | 
					        return response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def post(self, request, *args, **kwargs):
 | 
					    def post(self, request, *args, **kwargs):
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            print(request.body)
 | 
					 | 
				
			||||||
            users_id = json.loads(request.body).get('users_id', [])
 | 
					            users_id = json.loads(request.body).get('users_id', [])
 | 
				
			||||||
        except ValueError:
 | 
					        except ValueError:
 | 
				
			||||||
            return HttpResponse('Json object not valid', status=400)
 | 
					            return HttpResponse('Json object not valid', status=400)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue