user bulk import through Excel and close #20

parent 05e961f2
# coding: utf-8 # coding: utf-8
from django.db import models from django.db import models
from django.http import JsonResponse
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
...@@ -36,3 +37,11 @@ class NoDeleteModelMixin(models.Model): ...@@ -36,3 +37,11 @@ class NoDeleteModelMixin(models.Model):
self.is_discard = True self.is_discard = True
self.discard_time = now() self.discard_time = now()
return self.save() return self.save()
class JSONResponseMixin(object):
"""JSON mixin"""
def render_json_response(self, context):
return JsonResponse(context)
This diff is collapsed.
...@@ -34,6 +34,13 @@ class UserCreateForm(forms.ModelForm): ...@@ -34,6 +34,13 @@ class UserCreateForm(forms.ModelForm):
} }
class UserBulkImportForm(forms.ModelForm):
class Meta:
model = User
fields = ['username', 'email', 'enable_otp', 'role']
class UserUpdateForm(forms.ModelForm): class UserUpdateForm(forms.ModelForm):
class Meta: class Meta:
......
{% extends '_modal.html' %}
{% load i18n %}
{% block modal_id %}user_import_modal{% endblock %}
{% block modal_title%}{% trans "Import User" %}{% endblock %}
{% block modal_body %}
<p class="text-success text-center">{% trans "Hint: your excel should organized in the following format." %}</p>
<p class="text-success text-center">{% trans "* You should have a very worksheet named `users`." %}</p>
<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 %}
<div class="form-group">
<label class="control-label col-sm-2 col-lg-2 " for="id_excel">{% trans "Excel" %}</label>
<div class=" col-sm-9 col-lg-9 ">
<input id="id_excel" type="file" name="excel" />
</div>
</div>
</form>
{% endblock %}
{% block modal_confirm_id %}btn_user_import{% endblock %}
...@@ -17,7 +17,8 @@ div.dataTables_wrapper div.dataTables_filter { ...@@ -17,7 +17,8 @@ div.dataTables_wrapper div.dataTables_filter {
{% endblock %} {% endblock %}
{% block table_search %}{% endblock %} {% block table_search %}{% endblock %}
{% block table_container %} {% block table_container %}
<div class="uc pull-left"><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 m-l-5 m-r-5"><a href="{% url "users:user-create" %}" class="btn btn-sm btn-primary"> {% trans "Create 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>
...@@ -51,10 +52,12 @@ div.dataTables_wrapper div.dataTables_filter { ...@@ -51,10 +52,12 @@ div.dataTables_wrapper div.dataTables_filter {
</div> </div>
</div> </div>
{% include "users/_user_bulk_update_modal.html" %} {% include "users/_user_bulk_update_modal.html" %}
{% include "users/_user_import_modal.html" %}
{% endblock %} {% endblock %}
{% block content_bottom_left %} {% block content_bottom_left %}
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
<script src="{% static 'js/jquery.form.min.js' %}"></script>
<script> <script>
$(document).ready(function(){ $(document).ready(function(){
var options = { var options = {
...@@ -219,6 +222,23 @@ $(document).ready(function(){ ...@@ -219,6 +222,23 @@ $(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});
}).on('change', '#id_excel', function() {
$(this).siblings('.help-block').remove();
}) })
</script> </script>
{% endblock %} {% endblock %}
......
...@@ -23,6 +23,7 @@ urlpatterns = [ ...@@ -23,6 +23,7 @@ urlpatterns = [
url(r'^user/(?P<pk>[0-9]+)/granted-asset', views.UserGrantedAssetView.as_view(), name='user-granted-asset'), url(r'^user/(?P<pk>[0-9]+)/granted-asset', 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/(?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'),
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django import forms
from django.conf import settings from django.conf import settings
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
...@@ -23,10 +24,11 @@ from django.views.generic.detail import DetailView ...@@ -23,10 +24,11 @@ from django.views.generic.detail import DetailView
from formtools.wizard.views import SessionWizardView from formtools.wizard.views import SessionWizardView
from common.mixins import JSONResponseMixin
from common.utils import get_object_or_none, get_logger from common.utils import get_object_or_none, get_logger
from .models import User, UserGroup from .models import User, UserGroup
from .forms import UserCreateForm, UserUpdateForm, UserGroupForm, UserLoginForm, UserInfoForm, UserKeyForm, \ from .forms import UserCreateForm, UserUpdateForm, UserGroupForm, UserLoginForm, UserInfoForm, UserKeyForm, \
UserPrivateAssetPermissionForm UserPrivateAssetPermissionForm, UserBulkImportForm
from .utils import AdminUserRequiredMixin, user_add_success_next, send_reset_password_mail from .utils import AdminUserRequiredMixin, user_add_success_next, send_reset_password_mail
from .hands import AssetPermission, get_user_granted_asset_groups, get_user_granted_assets from .hands import AssetPermission, get_user_granted_asset_groups, get_user_granted_assets
...@@ -443,3 +445,66 @@ class UserGrantedAssetView(AdminUserRequiredMixin, SingleObjectMixin, ListView): ...@@ -443,3 +445,66 @@ class UserGrantedAssetView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
} }
kwargs.update(context) kwargs.update(context)
return super(UserGrantedAssetView, self).get_context_data(**kwargs) return super(UserGrantedAssetView, self).get_context_data(**kwargs)
class FileForm(forms.Form):
excel = forms.FileField()
class BulkImportUserView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
form_class = FileForm
def form_invalid(self, form):
try:
error = form.errors.values()[-1][-1]
except Exception as e:
print e
error = _('Invalid file.')
data = {
'success': False,
'msg': error
}
return self.render_json_response(data)
def form_valid(self, form):
from openpyxl import load_workbook
try:
wb = load_workbook(form.cleaned_data['excel'])
ws = wb['users']
except Exception as e:
print e
error = _('Not a valid Excel file.')
data = {
'success': False,
'msg': error
}
return self.render_json_response(data)
errors = []
for index, row in enumerate(ws.rows):
user_data = [cell.value for cell in row]
if len(user_data) != 4:
errors.append("Row {}: invalid user data format.".format(index))
continue
username, email, enable_otp, role = user_data
data = {
'username': username,
'email': email,
'enable_otp': True if enable_otp in ['T', '1', 1, True] else False,
'role': role
}
form = 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)
...@@ -13,6 +13,7 @@ wcwidth==0.1.7 ...@@ -13,6 +13,7 @@ wcwidth==0.1.7
websocket-client==0.37.0 websocket-client==0.37.0
djangorestframework==3.4.5 djangorestframework==3.4.5
ForgeryPy==0.1 ForgeryPy==0.1
openpyxl==2.4.0
paramiko==2.0.2 paramiko==2.0.2
celery==3.1.23 celery==3.1.23
ansible==2.1.1.0 ansible==2.1.1.0
......
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