1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-21 19:37:28 +00:00

[api] Add users in bulk from excel, export batch add user help file, add (#1792)

* [api] Add users in bulk from excel, export batch add user help file, add
test

* remove csv feature

* review

* column width, del tip, add head

* del write_xls_sample, modify head

* review

* download text

* option->optional, trans
This commit is contained in:
zMingGit
2017-09-26 10:33:36 +08:00
committed by xiez
parent 249e4c5b70
commit 2feb8ae575
6 changed files with 234 additions and 36 deletions

View File

@@ -173,7 +173,7 @@ class RepoSettingForm(forms.Form):
class BatchAddUserForm(forms.Form): class BatchAddUserForm(forms.Form):
""" """
Form for importing users from CSV file. Form for importing users from XLSX file.
""" """
file = forms.FileField() file = forms.FileField()

View File

@@ -25,7 +25,7 @@
</ul> </ul>
<div class="js-op-for-all fright"> <div class="js-op-for-all fright">
<button id="import-users-btn">{% trans "Import users" %}</button> <button id="import-users-from-excel-btn">{% trans "Import users" %}</button>
<button id="add-user-btn">{% trans "Add user" %}</button> <button id="add-user-btn">{% trans "Add user" %}</button>
<button id="export-excel">{% trans "Export Excel" %}</button> <button id="export-excel">{% trans "Export Excel" %}</button>
</div> </div>
@@ -70,14 +70,13 @@
<button type="submit" class="submit">{% trans "Submit" %}</button> <button type="submit" class="submit">{% trans "Submit" %}</button>
</form> </form>
<form id="upload-csv-form" class="hide" enctype="multipart/form-data" method="post" action="{% url 'batch_add_user' %}">{% csrf_token %} <form id="upload-excel-form" class="hide" enctype="multipart/form-data" method="post" action="{% url 'batch_add_user' %}">{% csrf_token %}
<h3>{% trans "Import users from a CSV file" %}</h3> <h3>{% trans "Import users from a .xlsx file" %}</h3>
<a href="#" id="excel-example">{% trans "Download an example file" %}</a>
<br />
<input type="file" name="file" /> <input type="file" name="file" />
<p class="tip"> <br />
{% trans "File format: user@mail.com,password,name,department,role,quota"%}<br /> <p class="error hide">{% trans "Please choose a .xlsx file." %}</p>
{% trans "Name, department, role and quota are optional." %}
</p>
<p class="error hide">{% trans "Please choose a CSV file" %}</p>
<button type="submit" class="submit">{% trans "Submit" %}</button> <button type="submit" class="submit">{% trans "Submit" %}</button>
</form> </form>
@@ -145,7 +144,6 @@ $(function(){
}); });
{% endif %} {% endif %}
}); });
$('#add-user-btn').click(function() { $('#add-user-btn').click(function() {
$('#add-user-form').modal(); $('#add-user-form').modal();
$('#simplemodal-container').css({'width':'auto', 'height':'auto'}); $('#simplemodal-container').css({'width':'auto', 'height':'auto'});
@@ -237,11 +235,15 @@ $('#add-user-form').submit(function() {
}); });
return false; return false;
}); });
$('#import-users-btn').click(function () { $('#import-users-from-excel-btn').click(function () {
$('#upload-csv-form').modal(); $('#upload-excel-form').modal();
$('#simplemodal-container').css({'width':'auto', 'height':'auto'}); $('#simplemodal-container').css({'width':'auto', 'height':'auto'});
}); });
$('#upload-csv-form').submit(function() { $("#excel-example").click(function() {
location.href = "{% url 'batch_add_user_example' %}";
return false;
});
$('#upload-excel-form').submit(function() {
var form = $(this); var form = $(this);
if (!$('[name=file]', form).val()) { if (!$('[name=file]', form).val()) {
$('.error', form).removeClass('hide'); $('.error', form).removeClass('hide');

View File

@@ -435,6 +435,7 @@ urlpatterns = patterns(
url(r'^useradmin/password/reset/(?P<email>[^/]+)/$', user_reset, name='user_reset'), url(r'^useradmin/password/reset/(?P<email>[^/]+)/$', user_reset, name='user_reset'),
url(r'^useradmin/batchmakeadmin/$', batch_user_make_admin, name='batch_user_make_admin'), url(r'^useradmin/batchmakeadmin/$', batch_user_make_admin, name='batch_user_make_admin'),
url(r'^useradmin/batchadduser/$', batch_add_user, name='batch_add_user'), url(r'^useradmin/batchadduser/$', batch_add_user, name='batch_add_user'),
url(r'^useradmin/batchadduser/example/$', batch_add_user_example, name='batch_add_user_example'),
url(r'^client-login/$', client_token_login, name='client_token_login'), url(r'^client-login/$', client_token_login, name='client_token_login'),
) )

View File

@@ -2,14 +2,15 @@
# encoding: utf-8 # encoding: utf-8
import os import os
from io import BytesIO
from types import FunctionType from types import FunctionType
import logging import logging
import json import json
import re import re
import datetime import datetime
import csv, chardet, StringIO
import time import time
from constance import config from constance import config
from openpyxl import load_workbook
from django.db.models import Q from django.db.models import Q
from django.conf import settings as dj_settings from django.conf import settings as dj_settings
@@ -1905,10 +1906,41 @@ def batch_user_make_admin(request):
return HttpResponse(json.dumps({'success': True,}), content_type=content_type) return HttpResponse(json.dumps({'success': True,}), content_type=content_type)
@login_required
@sys_staff_required
def batch_add_user_example(request):
""" get example file.
"""
next = request.META.get('HTTP_REFERER', None)
if not next:
next = SITE_ROOT
data_list = []
head = [_('email'), _('password'), _('name')+ '(' + _('optional') + ')',
_('department')+ '(' + _('optional') + ')', _('role')+
'(' + _('optional') + ')', _('quota') + '(MB, ' + _('optional') + ')']
for i in xrange(5):
username = "test" + str(i) +"@example.com"
password = "123456"
name = "test" + str(i)
department = "department" + str(i)
role = "default"
quota = "1000"
data_list.append([username, password, name, department, role, quota])
wb = write_xls('sample', head, data_list)
if not wb:
messages.error(request, _(u'Failed to export Excel'))
return HttpResponseRedirect(next)
response = HttpResponse(content_type='application/ms-excel')
response['Content-Disposition'] = 'attachment; filename=users.xlsx'
wb.save(response)
return response
@login_required @login_required
@sys_staff_required @sys_staff_required
def batch_add_user(request): def batch_add_user(request):
"""Batch add users. Import users from CSV file. """ Batch add users. Import users from XLSX file.
""" """
if request.method != 'POST': if request.method != 'POST':
raise Http404 raise Http404
@@ -1918,25 +1950,30 @@ def batch_add_user(request):
form = BatchAddUserForm(request.POST, request.FILES) form = BatchAddUserForm(request.POST, request.FILES)
if form.is_valid(): if form.is_valid():
content = request.FILES['file'].read() content = request.FILES['file'].read()
encoding = chardet.detect(content)['encoding'] if str(request.FILES['file']).split('.')[-1].lower() != 'xlsx':
if encoding != 'utf-8': messages.error(request, _(u'Please choose a .xlsx file.'))
content = content.decode(encoding, 'replace').encode('utf-8') return HttpResponseRedirect(next)
filestream = StringIO.StringIO(content) try:
reader = csv.reader(filestream) fs = BytesIO(content)
new_users_count = len(list(reader)) wb = load_workbook(filename=fs, read_only=True)
if user_number_over_limit(new_users=new_users_count): except Exception as e:
logger.error(e)
messages.error(request, _('Internal Server Error'))
return HttpResponseRedirect(next)
rows = wb.worksheets[0].rows
records = []
# remove first row(head field).
next(rows)
for row in rows:
records.append([c.value for c in row])
if user_number_over_limit(new_users=len(records)):
messages.error(request, _(u'The number of users exceeds the limit.')) messages.error(request, _(u'The number of users exceeds the limit.'))
return HttpResponseRedirect(next) return HttpResponseRedirect(next)
# return to the top of the file for row in records:
filestream.seek(0)
reader = csv.reader(filestream)
for row in reader:
if not row:
continue
try: try:
username = row[0].strip() username = row[0].strip()
password = row[1].strip() password = row[1].strip()
@@ -1978,8 +2015,7 @@ def batch_add_user(request):
logger.error(e) logger.error(e)
try: try:
space_quota_mb = row[5].strip() space_quota_mb = int(row[5])
space_quota_mb = int(space_quota_mb)
if space_quota_mb >= 0: if space_quota_mb >= 0:
space_quota = int(space_quota_mb) * get_file_size_unit('MB') space_quota = int(space_quota_mb) * get_file_size_unit('MB')
seafile_api.set_user_quota(username, space_quota) seafile_api.set_user_quota(username, space_quota)
@@ -2001,10 +2037,9 @@ def batch_add_user(request):
} }
admin_operation.send(sender=None, admin_name=request.user.username, admin_operation.send(sender=None, admin_name=request.user.username,
operation=USER_ADD, detail=admin_op_detail) operation=USER_ADD, detail=admin_op_detail)
messages.success(request, _('Import succeeded')) messages.success(request, _('Import succeeded'))
else: else:
messages.error(request, _(u'Please select a csv file first.')) messages.error(request, _(u'Please choose a .xlsx file.'))
return HttpResponseRedirect(next) return HttpResponseRedirect(next)

Binary file not shown.

View File

@@ -1,11 +1,12 @@
import os import os
import openpyxl
from io import BytesIO
from mock import patch from mock import patch
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from post_office.models import Email from post_office.models import Email
from seahub.base.accounts import User from seahub.base.accounts import User
from seahub.options.models import (UserOptions, KEY_FORCE_PASSWD_CHANGE, from seahub.options.models import (UserOptions, KEY_FORCE_PASSWD_CHANGE)
VAL_FORCE_PASSWD_CHANGE)
from seahub.test_utils import BaseTestCase from seahub.test_utils import BaseTestCase
from seahub.utils.ms_excel import write_xls as real_write_xls from seahub.utils.ms_excel import write_xls as real_write_xls
@@ -278,3 +279,162 @@ class BatchAddUserTest(BaseTestCase):
assert self.new_users[0] == email.to[0] assert self.new_users[0] == email.to[0]
assert "Email: %s" % self.new_users[0] in email.html_message assert "Email: %s" % self.new_users[0] in email.html_message
assert email.status == 2 assert email.status == 2
class BatchAddUserUsingExcelTest(BaseTestCase):
def setUp(self):
self.clear_cache()
self.login_as(self.admin)
self.new_users = []
self.excel_file = os.path.join(os.getcwd(), 'tests/seahub/views/sysadmin/batch_add_user.xlsx')
data_list = []
for i in xrange(20):
username = "username@test" + str(i) +".com"
password = "password"
name = "name_test" + str(i)
department = "department_test" + str(i)
if i < 10:
role = "guest"
else:
role = "default"
quota = "999"
data_list.append([username, password, name, department, role, quota])
self.new_users.append(username)
wb = real_write_xls('test', data_list[0], data_list[1:])
wb.save(self.excel_file)
def tearDown(self):
for u in self.new_users:
self.remove_user(u)
def test_can_batch_add(self):
for e in self.new_users:
try:
r = User.objects.get(e)
except User.DoesNotExist:
r = None
assert r is None
with open(self.excel_file) as f:
resp = self.client.post(reverse('batch_add_user_using_excel'), {
'file': f
})
self.assertEqual(302, resp.status_code)
assert 'Import succeeded' in resp.cookies['messages'].value
for e in self.new_users:
assert User.objects.get(e) is not None
def test_can_batch_add_when_pwd_change_required(self):
config.FORCE_PASSWORD_CHANGE = 1
for e in self.new_users:
assert len(UserOptions.objects.filter(
email=e, option_key=KEY_FORCE_PASSWD_CHANGE)) == 0
for e in self.new_users:
try:
r = User.objects.get(e)
except User.DoesNotExist:
r = None
assert r is None
with open(self.excel_file) as f:
resp = self.client.post(reverse('batch_add_user_using_excel'), {
'file': f
})
self.assertEqual(302, resp.status_code)
assert 'Import succeeded' in resp.cookies['messages'].value
for e in self.new_users:
assert User.objects.get(e) is not None
assert UserOptions.objects.passwd_change_required(e)
def test_can_batch_add_when_pwd_change_not_required(self):
config.FORCE_PASSWORD_CHANGE = 0
for e in self.new_users:
assert len(UserOptions.objects.filter(
email=e, option_key=KEY_FORCE_PASSWD_CHANGE)) == 0
for e in self.new_users:
try:
r = User.objects.get(e)
except User.DoesNotExist:
r = None
assert r is None
with open(self.excel_file) as f:
resp = self.client.post(reverse('batch_add_user_using_excel'), {
'file': f
})
self.assertEqual(302, resp.status_code)
assert 'Import succeeded' in resp.cookies['messages'].value
for e in self.new_users:
assert User.objects.get(e) is not None
assert not UserOptions.objects.passwd_change_required(e)
@patch('seahub.views.sysadmin.user_number_over_limit')
def test_can_not_batch_add_if_user_over_limit(self, mock_user_number_over_limit):
mock_user_number_over_limit.return_value = True
for e in self.new_users:
try:
r = User.objects.get(e)
except User.DoesNotExist:
r = None
assert r is None
with open(self.excel_file) as f:
resp = self.client.post(reverse('batch_add_user_using_excel'), {
'file': f
})
self.assertEqual(302, resp.status_code)
assert 'users exceeds the limit' in resp.cookies['messages'].value
def test_can_send_email(self):
self.assertEqual(0, len(Email.objects.all()))
with open(self.excel_file) as f:
resp = self.client.post(reverse('batch_add_user_using_excel'), {
'file': f
})
self.assertEqual(302, resp.status_code)
self.assertNotEqual(0, len(Email.objects.all()))
email = Email.objects.all()[0]
assert self.new_users[0] == email.to[0]
assert "Email: %s" % self.new_users[0] in email.html_message
assert email.status == 2
class BatchAddUserUsingExcelHelpTest(BaseTestCase):
def setUp(self):
self.login_as(self.admin)
def test_can_get_excel(self):
resp = self.client.get(reverse('batch_add_user_example')+"?type=xlsx")
assert resp.status_code == 200
def test_validate_excel(self):
resp = self.client.get(reverse('batch_add_user_example')+"?type=xlsx")
wb = openpyxl.load_workbook(filename=BytesIO(resp.content), read_only=True)
assert wb.sheetnames[0] == 'sample'
rows = wb.worksheets[0].rows
i = 0
for r in rows:
assert r[0].value == 'username@test' + str(i) + '.com'
assert r[1].value == 'password'
assert r[2].value == 'name_test' + str(i)
assert r[3].value == 'department_test' + str(i)
if i < 10:
assert r[4].value == 'guest'
else:
assert r[4].value == 'default'
assert r[5].value == '999'
i += 1