1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-08-28 11:41:18 +00:00

Merge pull request #1429 from haiwen/useradmin

Useradmin
This commit is contained in:
xiez 2017-01-06 14:01:59 +08:00 committed by GitHub
commit 9a9b6eb9e1
11 changed files with 538 additions and 183 deletions

View File

@ -3,6 +3,7 @@ import logging
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext as _
from rest_framework import status from rest_framework import status
from rest_framework.authentication import SessionAuthentication from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAdminUser from rest_framework.permissions import IsAdminUser
@ -11,22 +12,37 @@ from rest_framework.reverse import reverse
from rest_framework.views import APIView from rest_framework.views import APIView
import seaserv import seaserv
from seaserv import seafile_api, ccnet_threaded_rpc from seaserv import seafile_api, ccnet_threaded_rpc
from pysearpc import SearpcError
from seahub.api2.authentication import TokenAuthentication from seahub.api2.authentication import TokenAuthentication
from seahub.api2.serializers import AccountSerializer from seahub.api2.serializers import AccountSerializer
from seahub.api2.throttling import UserRateThrottle from seahub.api2.throttling import UserRateThrottle
from seahub.api2.utils import api_error, to_python_boolean from seahub.api2.utils import api_error, to_python_boolean
from seahub.api2.status import HTTP_520_OPERATION_FAILED
from seahub.base.accounts import User from seahub.base.accounts import User
from seahub.profile.models import Profile from seahub.base.templatetags.seahub_tags import email2nickname
from seahub.profile.utils import refresh_cache as refresh_profile_cache from seahub.profile.models import Profile, DetailedProfile
from seahub.utils import is_valid_username from seahub.utils import is_valid_username, is_org_context
from seahub.utils.file_size import get_file_size_unit
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
json_content_type = 'application/json; charset=utf-8' json_content_type = 'application/json; charset=utf-8'
def get_account_info(user):
email = user.username
d_profile = DetailedProfile.objects.get_detailed_profile_by_user(email)
info = {}
info['email'] = email
info['name'] = email2nickname(email)
info['department'] = d_profile.department if d_profile else ''
info['id'] = user.id
info['is_staff'] = user.is_staff
info['is_active'] = user.is_active
info['create_time'] = user.ctime
info['total'] = seafile_api.get_user_quota(email)
info['usage'] = seafile_api.get_user_self_usage(email)
return info
class Account(APIView): class Account(APIView):
"""Query/Add/Delete a specific account. """Query/Add/Delete a specific account.
@ -46,136 +62,9 @@ class Account(APIView):
except User.DoesNotExist: except User.DoesNotExist:
return api_error(status.HTTP_404_NOT_FOUND, 'User %s not found.' % email) return api_error(status.HTTP_404_NOT_FOUND, 'User %s not found.' % email)
info = {} info = get_account_info(user)
info['email'] = user.email
info['id'] = user.id
info['is_staff'] = user.is_staff
info['is_active'] = user.is_active
info['create_time'] = user.ctime
info['total'] = seafile_api.get_user_quota(email)
info['usage'] = seafile_api.get_user_self_usage(email)
return Response(info) return Response(info)
def _update_account_profile(self, request, email):
name = request.data.get("name", None)
note = request.data.get("note", None)
if name is None and note is None:
return
profile = Profile.objects.get_profile_by_user(email)
if profile is None:
profile = Profile(user=email)
if name is not None:
# if '/' in name:
# return api_error(status.HTTP_400_BAD_REQUEST, "Nickname should not include '/'")
profile.nickname = name
if note is not None:
profile.intro = note
profile.save()
def _update_account_quota(self, request, email):
storage = request.data.get("storage", None)
sharing = request.data.get("sharing", None)
if storage is None and sharing is None:
return
if storage is not None:
seafile_api.set_user_quota(email, int(storage))
# if sharing is not None:
# seafile_api.set_user_share_quota(email, int(sharing))
def _create_account(self, request, email):
copy = request.data.copy()
copy['email'] = email
serializer = AccountSerializer(data=copy)
if serializer.is_valid():
try:
user = User.objects.create_user(serializer.data['email'],
serializer.data['password'],
serializer.data['is_staff'],
serializer.data['is_active'])
except User.DoesNotExist as e:
logger.error(e)
return api_error(status.HTTP_520_OPERATION_FAILED,
'Failed to add user.')
self._update_account_profile(request, user.username)
resp = Response('success', status=status.HTTP_201_CREATED)
resp['Location'] = reverse('api2-account', args=[email])
return resp
else:
return api_error(status.HTTP_400_BAD_REQUEST, serializer.errors)
def _update_account(self, request, user):
password = request.data.get("password", None)
is_staff = request.data.get("is_staff", None)
if is_staff is not None:
try:
is_staff = to_python_boolean(is_staff)
except ValueError:
return api_error(status.HTTP_400_BAD_REQUEST,
'is_staff invalid.')
is_active = request.data.get("is_active", None)
if is_active is not None:
try:
is_active = to_python_boolean(is_active)
except ValueError:
return api_error(status.HTTP_400_BAD_REQUEST,
'is_active invalid.')
if password is not None:
user.set_password(password)
if is_staff is not None:
user.is_staff = is_staff
if is_active is not None:
user.is_active = is_active
result_code = user.save()
if result_code == -1:
return api_error(status.HTTP_520_OPERATION_FAILED,
'Failed to update user.')
self._update_account_profile(request, user.username)
try:
self._update_account_quota(request, user.username)
except SearpcError as e:
logger.error(e)
return api_error(HTTP_520_OPERATION_FAILED, 'Failed to set user quota.')
is_trial = request.data.get("is_trial", None)
if is_trial is not None:
try:
from seahub_extra.trialaccount.models import TrialAccount
except ImportError:
pass
else:
try:
is_trial = to_python_boolean(is_trial)
except ValueError:
return api_error(status.HTTP_400_BAD_REQUEST,
'is_trial invalid')
if is_trial is True:
expire_date = timezone.now() + relativedelta(days=7)
TrialAccount.object.create_or_update(user.username,
expire_date)
else:
TrialAccount.objects.filter(user_or_org=user.username).delete()
return Response('success')
def post(self, request, email, format=None): def post(self, request, email, format=None):
# migrate an account's repos and groups to an exist account # migrate an account's repos and groups to an exist account
if not is_valid_username(email): if not is_valid_username(email):
@ -207,19 +96,208 @@ class Account(APIView):
if from_user == g.creator_name: if from_user == g.creator_name:
ccnet_threaded_rpc.set_group_creator(g.id, to_user) ccnet_threaded_rpc.set_group_creator(g.id, to_user)
return Response("success") return Response({'success': True})
else: else:
return api_error(status.HTTP_400_BAD_REQUEST, 'op can only be migrate.') return api_error(status.HTTP_400_BAD_REQUEST, 'op can only be migrate.')
def _update_account_additional_info(self, request, email):
# update account profile
name = request.data.get("name", None)
note = request.data.get("note", None)
if name is not None or note is not None:
profile = Profile.objects.get_profile_by_user(email)
if profile is None:
profile = Profile(user=email)
if name is not None:
profile.nickname = name
if note is not None:
profile.intro = note
profile.save()
# update account detailed profile
department = request.data.get("department", None)
if department is not None:
d_profile = DetailedProfile.objects.get_detailed_profile_by_user(email)
if d_profile is None:
d_profile = DetailedProfile(user=email)
d_profile.department = department
d_profile.save()
# update user quota
space_quota_mb = request.data.get("storage", None)
if space_quota_mb is not None:
space_quota = int(space_quota_mb) * get_file_size_unit('MB')
if is_org_context(request):
org_id = request.user.org.org_id
seaserv.seafserv_threaded_rpc.set_org_user_quota(org_id,
email, space_quota)
else:
seafile_api.set_user_quota(email, space_quota)
# update is_trial
is_trial = request.data.get("is_trial", None)
if is_trial is not None:
try:
from seahub_extra.trialaccount.models import TrialAccount
except ImportError:
pass
else:
if is_trial is True:
expire_date = timezone.now() + relativedelta(days=7)
TrialAccount.object.create_or_update(email, expire_date)
else:
TrialAccount.objects.filter(user_or_org=email).delete()
def put(self, request, email, format=None): def put(self, request, email, format=None):
# argument check for email
if not is_valid_username(email): if not is_valid_username(email):
return api_error(status.HTTP_400_BAD_REQUEST, 'Email %s invalid.' % email) return api_error(status.HTTP_400_BAD_REQUEST,
'Email %s invalid.' % email)
# argument check for name
name = request.data.get("name", None)
if name is not None:
if len(name) > 64:
return api_error(status.HTTP_400_BAD_REQUEST,
_(u'Name is too long (maximum is 64 characters)'))
if "/" in name:
return api_error(status.HTTP_400_BAD_REQUEST,
_(u"Name should not include ' / '"))
# argument check for note
note = request.data.get("note", None)
if note is not None:
if len(note) > 256:
return api_error(status.HTTP_400_BAD_REQUEST,
_(u'Note is too long (maximum is 256 characters)'))
# argument check for department
department = request.data.get("department", None)
if department is not None:
if len(department) > 512:
return api_error(status.HTTP_400_BAD_REQUEST,
_(u'Department is too long (maximum is 512 characters)'))
# argument check for storage
space_quota_mb = request.data.get("storage", None)
if space_quota_mb is not None:
if space_quota_mb == '':
return api_error(status.HTTP_400_BAD_REQUEST,
_('Space quota can\'t be empty'))
try: try:
space_quota_mb = int(space_quota_mb)
except ValueError:
return api_error(status.HTTP_400_BAD_REQUEST,
'storage invalid.')
if space_quota_mb < 0:
return api_error(status.HTTP_400_BAD_REQUEST,
_('Space quota is too low (minimum value is 0)'))
if is_org_context(request):
org_id = request.user.org.org_id
org_quota_mb = seaserv.seafserv_threaded_rpc.get_org_quota(org_id) / \
get_file_size_unit('MB')
if space_quota_mb > org_quota_mb:
return api_error(status.HTTP_400_BAD_REQUEST, \
_(u'Failed to set quota: maximum quota is %d MB' % org_quota_mb))
# argument check for is_trial
is_trial = request.data.get("is_trial", None)
if is_trial is not None:
try:
is_trial = to_python_boolean(is_trial)
except ValueError:
return api_error(status.HTTP_400_BAD_REQUEST,
'is_trial invalid')
try:
# update account basic info
user = User.objects.get(email=email) user = User.objects.get(email=email)
return self._update_account(request, user) # argument check for is_staff
is_staff = request.data.get("is_staff", None)
if is_staff is not None:
try:
is_staff = to_python_boolean(is_staff)
except ValueError:
return api_error(status.HTTP_400_BAD_REQUEST,
'is_staff invalid.')
user.is_staff = is_staff
# argument check for is_active
is_active = request.data.get("is_active", None)
if is_active is not None:
try:
is_active = to_python_boolean(is_active)
except ValueError:
return api_error(status.HTTP_400_BAD_REQUEST,
'is_active invalid.')
user.is_active = is_active
# update password
password = request.data.get("password", None)
if password is not None:
user.set_password(password)
# save user
result_code = user.save()
if result_code == -1:
return api_error(status.HTTP_520_OPERATION_FAILED,
'Failed to update user.')
try:
# update account additional info
self._update_account_additional_info(request, email)
except Exception as e:
logger.error(e)
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR,
'Internal Server Error')
# get account info and return
info = get_account_info(user)
return Response(info)
except User.DoesNotExist: except User.DoesNotExist:
return self._create_account(request, email) # create user account
copy = request.data.copy()
copy['email'] = email
serializer = AccountSerializer(data=copy)
if not serializer.is_valid():
return api_error(status.HTTP_400_BAD_REQUEST, serializer.errors)
try:
user = User.objects.create_user(serializer.data['email'],
serializer.data['password'],
serializer.data['is_staff'],
serializer.data['is_active'])
except User.DoesNotExist as e:
logger.error(e)
return api_error(status.HTTP_520_OPERATION_FAILED,
'Failed to add user.')
try:
# update account additional info
self._update_account_additional_info(request, email)
except Exception as e:
logger.error(e)
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR,
'Internal Server Error')
# get account info and return
info = get_account_info(user)
resp = Response(info, status=status.HTTP_201_CREATED)
resp['Location'] = reverse('api2-account', args=[email])
return resp
def delete(self, request, email, format=None): def delete(self, request, email, format=None):
if not is_valid_username(email): if not is_valid_username(email):
@ -229,7 +307,7 @@ class Account(APIView):
try: try:
user = User.objects.get(email=email) user = User.objects.get(email=email)
user.delete() user.delete()
return Response("success") return Response({'success': True})
except User.DoesNotExist: except User.DoesNotExist:
resp = Response("success", status=status.HTTP_202_ACCEPTED) resp = Response({'success': True}, status=status.HTTP_202_ACCEPTED)
return resp return resp

View File

@ -16,6 +16,9 @@ class AddUserForm(forms.Form):
Form for adding a user. Form for adding a user.
""" """
email = forms.EmailField() email = forms.EmailField()
name = forms.CharField(max_length=64, required=False)
department = forms.CharField(max_length=512, required=False)
role = forms.ChoiceField(choices=[(DEFAULT_USER, DEFAULT_USER), role = forms.ChoiceField(choices=[(DEFAULT_USER, DEFAULT_USER),
(GUEST_USER, GUEST_USER)]) (GUEST_USER, GUEST_USER)])
password1 = forms.CharField(widget=forms.PasswordInput()) password1 = forms.CharField(widget=forms.PasswordInput())
@ -32,6 +35,16 @@ class AddUserForm(forms.Form):
except User.DoesNotExist: except User.DoesNotExist:
return self.cleaned_data['email'] return self.cleaned_data['email']
def clean_name(self):
"""
should not include '/'
"""
if "/" in self.cleaned_data["name"]:
raise forms.ValidationError(_(u"Name should not include ' / '"))
return self.cleaned_data["name"]
def clean(self): def clean(self):
""" """
Verifiy that the values entered into the two password fields Verifiy that the values entered into the two password fields
@ -137,7 +150,6 @@ class SetUserQuotaForm(forms.Form):
""" """
Form for setting user quota. Form for setting user quota.
""" """
email = forms.CharField(error_messages={'required': _('Email is required')})
space_quota = forms.IntegerField(min_value=0, space_quota = forms.IntegerField(min_value=0,
error_messages={'required': _('Space quota can\'t be empty'), error_messages={'required': _('Space quota can\'t be empty'),
'min_value': _('Space quota is too low (minimum value is 0)')}) 'min_value': _('Space quota is too low (minimum value is 0)')})

View File

@ -26,9 +26,9 @@
<tr> <tr>
<th width="36%">{% trans "Email" %}</th> <th width="36%">{% trans "Email" %}</th>
<th width="12%">{% trans "Status" %}</th> <th width="12%">{% trans "Status" %}</th>
<th width="16%">{% trans "Space Used" %}</th> <th width="18%">{% trans "Space Used / Quota" %}</th>
<th width="18%">{% trans "Last Login" %}</th> <th width="18%">{% trans "Last Login" %}</th>
<th width="18%">{% trans "Operations" %}</th> <th width="16%">{% trans "Operations" %}</th>
</tr> </tr>
{% for user in users %} {% for user in users %}
@ -53,7 +53,15 @@
</select> </select>
</td> </td>
<td style="font-size:11px;"> <td style="font-size:11px;">
<p> {{ user.space_usage|seahub_filesizeformat }} {% if user.space_quota > 0 %} / {{ user.space_quota|seahub_filesizeformat }} {% endif %} </p> {{ user.space_usage|seahub_filesizeformat }} /
<span class="user-space-quota">
{% if user.space_quota > 0 %}
{{ user.space_quota|seahub_filesizeformat }}
{% else %}
--
{% endif %}
</span>
<span title="{% trans "Edit Quota" %}" class="quota-edit-icon sf2-icon-edit op-icon vh"></span>
</td> </td>
<td> <td>
{% if user.last_login %}{{user.last_login|translate_seahub_time}} {% else %} -- {% endif %} {% if user.last_login %}{{user.last_login|translate_seahub_time}} {% else %} -- {% endif %}
@ -67,6 +75,13 @@
{% endfor %} {% endfor %}
</table> </table>
{% include "snippets/admin_paginator.html" %} {% include "snippets/admin_paginator.html" %}
<form id="set-quota-form" method="post" action="" class="hide">{% csrf_token %}
<h3>{% trans "Set user storage limit" %}</h3>
<input type="text" name="space_quota" /> MB
<p class="tip">{% trans "Tip: 0 means default limit" %}</p>
<p class="error hide"></p>
<input type="submit" value="{% trans "Submit" %}" class="submit" />
</form>
{% else %} {% else %}
<div class="empty-tips"> <div class="empty-tips">
<h2 class="alc">{% trans "No LDAP users have been imported" %}</h2> <h2 class="alc">{% trans "No LDAP users have been imported" %}</h2>

View File

@ -32,6 +32,12 @@
<h3>{% trans "Add user" %}</h3> <h3>{% trans "Add user" %}</h3>
<label for="id_email">{% trans "Email" %}</label><br /> <label for="id_email">{% trans "Email" %}</label><br />
<input type="text" name="email" id="id_email" class="input" /><br /> <input type="text" name="email" id="id_email" class="input" /><br />
<label for="id_name">{% trans "Name(optional)" %}</label><br />
<input type="text" name="name" id="id_name" class="input" /><br />
<label for="id_dept">{% trans "Department(optional)" %}</label><br />
<input type="text" name="department" id="id_dept" class="input" /><br />
{% if is_pro %} {% if is_pro %}
<label>{% trans "Role"%}</label><span class="icon-question-sign" title="{% trans "You can also add a user as a guest, who will not be allowed to create libraries and groups." %}" style="color:#666; margin-left:3px;"></span> <label>{% trans "Role"%}</label><span class="icon-question-sign" title="{% trans "You can also add a user as a guest, who will not be allowed to create libraries and groups." %}" style="color:#666; margin-left:3px;"></span>
<select name="role" class="w100"> <select name="role" class="w100">
@ -54,7 +60,10 @@
<form id="upload-csv-form" class="hide" enctype="multipart/form-data" method="post" action="{% url 'batch_add_user' %}">{% csrf_token %} <form id="upload-csv-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 CSV file" %}</h3>
<input type="file" name="file" /> <input type="file" name="file" />
<p class="tip">{% trans "File format: user@mail.com,password"%}</p> <p class="tip">
{% trans "File format: user@mail.com,password,name,department"%}<br />
{% trans "Name and department are optional." %}
</p>
<p class="error hide">{% trans "Please choose a CSV file" %}</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>
@ -143,6 +152,8 @@ $('#add-user-form').submit(function() {
var form = $(this), var form = $(this),
form_id = $(this).attr('id'), form_id = $(this).attr('id'),
email = $.trim(form.children('[name="email"]').val()), email = $.trim(form.children('[name="email"]').val()),
name = $.trim($('[name="name"]', form).val()),
department = $.trim($('[name="department"]', form).val()),
{% if is_pro %} {% if is_pro %}
role = $('select[name="role"]', form).val(), role = $('select[name="role"]', form).val(),
{% endif %} {% endif %}
@ -176,6 +187,8 @@ $('#add-user-form').submit(function() {
beforeSend: prepareCSRFToken, beforeSend: prepareCSRFToken,
data: { data: {
'email': email, 'email': email,
'name': name,
'department': department,
{% if is_pro %} {% if is_pro %}
'role': role, 'role': role,
{% endif %} {% endif %}

View File

@ -22,18 +22,22 @@
<table> <table>
<tr> <tr>
<th width="40%">{% trans "Email" %}</th> <th width="40%">{% trans "Email" %}</th>
<th width="25%">{% trans "Space Used / Quota" %}</th>
<th width="35%">{% trans "Create At / Last Login" %}</th> <th width="35%">{% trans "Create At / Last Login" %}</th>
<th width="25%">{% trans "Space Used" %}</th>
</tr> </tr>
{% for user in users %} {% for user in users %}
<tr> <tr>
<td data="{{user.id}}"><a href="{{ SITE_ROOT }}useradmin/info/{{ user.props.email }}/">{{ user.email }}</a></td> <td data="{{user.id}}"><a href="{{ SITE_ROOT }}useradmin/info/{{ user.props.email }}/">{{ user.email }}</a></td>
<td style="font-size:11px;"> -- / {% if user.last_login %}{{user.last_login|translate_seahub_time}} {% else %} -- {% endif %}
</td>
<td style="font-size:11px;"> <td style="font-size:11px;">
<p> {{ user.space_usage|seahub_filesizeformat }} {% if user.space_quota > 0 %} / {{ user.space_quota|seahub_filesizeformat }} {% endif %} </p> {{ user.space_usage|seahub_filesizeformat }} /
{% if user.space_quota > 0 %}
{{ user.space_quota|seahub_filesizeformat }}
{% else %}
--
{% endif %}
</td> </td>
<td> -- / {% if user.last_login %}{{user.last_login|translate_seahub_time}} {% else %} -- {% endif %}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>

View File

@ -80,6 +80,60 @@ $('.user-status-select, .user-role-select').change(function() {
} }
}); });
}); });
{% if user.source == "DB" or user.source == 'LDAPImport' %}
// edit quota
$('.quota-edit-icon').click(function() {
var email = $(this).closest('tr').attr('data-userid');
var $spaceQuota = $(this).prev('.user-space-quota');
$('#set-quota-form').data({'email': email, '$spaceQuota': $spaceQuota}).modal();
$('#simplemodal-container').css({'width':'auto', 'height':'auto'});
});
$('#set-quota-form').submit(function() {
var space_quota = $.trim($('[name="space_quota"]', $(this)).val());
var $error = $('.error', $(this));
if (!space_quota) {
$error.html("{% trans "It is required." %}").show();
return false;
}
var $submitBtn = $('[type="submit"]', $(this));
disable($submitBtn);
var email = $(this).data('email');
var $spaceQuota = $(this).data('$spaceQuota');
$.ajax({
url: '{{ SITE_ROOT }}api2/accounts/' + encodeURIComponent(email) + '/',
type: 'PUT',
dataType: 'json',
cache: false,
beforeSend: prepareCSRFToken,
data: {'storage': space_quota},
success: function(data) {
if (space_quota == 0) {
$spaceQuota.html('--');
} else {
$spaceQuota.html(parseInt(data['total'])/1000000 + ' MB');
}
$.modal.close();
},
error: function(xhr, textStatus, errorThrown) {
var err_msg;
if (xhr.responseText) {
err_msg = $.parseJSON(xhr.responseText).error_msg;
} else {
err_msg = "{% trans "Failed. Please check the network." %}";
}
$error.html(err_msg).show();
enable($submitBtn);
}
});
return false;
});
{% endif %}
// select shows, but the user doesn't select a value, or doesn't change the permission, click other place to hide the select // select shows, but the user doesn't select a value, or doesn't change the permission, click other place to hide the select
$(document).click(function(e) { $(document).click(function(e) {
var target = e.target || event.srcElement; var target = e.target || event.srcElement;

View File

@ -9,7 +9,7 @@
<th width="36%">{% trans "Email" %} / {% trans "Name" %} / {% trans "Contact Email" %}</th> <th width="36%">{% trans "Email" %} / {% trans "Name" %} / {% trans "Contact Email" %}</th>
<th width="12%">{% trans "Status" %}</th> <th width="12%">{% trans "Status" %}</th>
{% endif %} {% endif %}
<th width="16%">{% trans "Space Used" %}</th> <th width="16%">{% trans "Space Used / Quota" %}</th>
<th width="22%">{% trans "Create At / Last Login" %}</th> <th width="22%">{% trans "Create At / Last Login" %}</th>
<th width="14%">{% trans "Operations" %}</th> <th width="14%">{% trans "Operations" %}</th>
</tr> </tr>
@ -70,7 +70,17 @@
{% endif %} {% endif %}
<td style="font-size:11px;"> <td style="font-size:11px;">
<p> {{ user.space_usage|seahub_filesizeformat }} {% if user.space_quota > 0 %} / {{ user.space_quota|seahub_filesizeformat }} {% endif %} </p> {{ user.space_usage|seahub_filesizeformat }} /
<span class="user-space-quota">
{% if user.space_quota > 0 %}
{{ user.space_quota|seahub_filesizeformat }}
{% else %}
--
{% endif %}
</span>
{% if user.source == "DB" or user.source == 'LDAPImport' %}
<span title="{% trans "Edit Quota" %}" class="quota-edit-icon sf2-icon-edit op-icon vh"></span>
{% endif %}
</td> </td>
<td> <td>
{% if user.source == "DB" %} {% if user.source == "DB" %}
@ -94,3 +104,13 @@
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>
{% if user.source == "DB" or user.source == 'LDAPImport' %}
<form id="set-quota-form" method="post" action="" class="hide">{% csrf_token %}
<h3>{% trans "Set user storage limit" %}</h3>
<input type="text" name="space_quota" /> MB
<p class="tip">{% trans "Tip: 0 means default limit" %}</p>
<p class="error hide"></p>
<input type="submit" value="{% trans "Submit" %}" class="submit" />
</form>
{% endif %}

View File

@ -41,26 +41,65 @@
<dd>{{ org_name }}</dd> <dd>{{ org_name }}</dd>
{% endif %} {% endif %}
{% if profile %}
<dt>{% trans "Name" context "true name" %}</dt> <dt>{% trans "Name" context "true name" %}</dt>
<dd>{{ profile.nickname }}</dd> <dd>
<span id="nickname">
{% if profile and profile.nickname %}
{{ profile.nickname }}
{% else %}
--
{% endif %} {% endif %}
</span>
<span id="set-name" title="{% trans "Edit"%}" class="sf2-icon-edit op-icon"></span>
</dd>
{% if d_profile %}
<dt>{% trans "Department" %}</dt> <dt>{% trans "Department" %}</dt>
<dd>{{ d_profile.department }}</dd> <dd>
<span id="department">
{% if d_profile and d_profile.department %}
{{ d_profile.department }}
{% else %}
--
{% endif %}
</span>
<span id="set-dept" title="{% trans "Edit"%}" class="sf2-icon-edit op-icon"></span>
</dd>
{% if d_profile and d_profile.telephone %}
<dt>{% trans "Telephone" %}</dt> <dt>{% trans "Telephone" %}</dt>
<dd>{{ d_profile.telephone }}</dd> <dd>{{ d_profile.telephone }}</dd>
{% endif %} {% endif %}
<dt>{% trans "Space Used" %}</dt> <dt>{% trans "Space Used / Quota" %}</dt>
<dd>{{ space_usage|seahub_filesizeformat }} {% if space_quota > 0 %} / {{ space_quota|seahub_filesizeformat }} {% endif %} <a href="#" class="sf-btn-link" style="margin-left:20px;" id="set-quota">{% trans "Set Quota" %}</a></dd> <dd>
{{ space_usage|seahub_filesizeformat }} /
<span id="quota">
{% if space_quota > 0 %}
{{ space_quota|seahub_filesizeformat }}
{% else %}
--
{% endif %}
</span>
<span id="set-quota" title="{% trans "Edit Quota" %}" class="sf2-icon-edit op-icon"></span>
</dd>
</dl> </dl>
<form id="set-name-form" method="post" action="" class="hide">{% csrf_token %}
<h3>{% trans "Set user name" %}</h3>
<input type="text" name="nickname" class="input" value="" /><br />
<p class="error hide"></p>
<input type="submit" value="{% trans "Submit" %}" class="submit" />
</form>
<form id="set-dept-form" method="post" action="" class="hide">{% csrf_token %}
<h3>{% trans "Set user department" %}</h3>
<input type="text" name="department" class="input" value="" /><br />
<p class="error hide"></p>
<input type="submit" value="{% trans "Submit" %}" class="submit" />
</form>
<form id="set-quota-form" method="post" class="hide">{% csrf_token %} <form id="set-quota-form" method="post" class="hide">{% csrf_token %}
<h3>{% trans "Set user storage limit" %}</h3> <h3>{% trans "Set user storage limit" %}</h3>
<input type="hidden" name="email" value="{{ email }}" />
<input type="text" name="space_quota" /> MB <input type="text" name="space_quota" /> MB
<p class="tip">{% trans "Tip: 0 means default limit" %}</p> <p class="tip">{% trans "Tip: 0 means default limit" %}</p>
<p class="error hide"></p> <p class="error hide"></p>
@ -254,43 +293,134 @@ $('.rm-link').click(function() {
}); });
return false; return false;
}); });
$('#set-name').click(function() {
$("#set-name-form").modal({appendTo: "#main"});
$('#simplemodal-container').css({'width':'auto', 'height':'auto'});
});
$('#set-dept').click(function() {
$("#set-dept-form").modal({appendTo: "#main"});
$('#simplemodal-container').css({'width':'auto', 'height':'auto'});
});
$('#set-quota').click(function() { $('#set-quota').click(function() {
$("#set-quota-form").modal({appendTo: "#main"}); $("#set-quota-form").modal({appendTo: "#main"});
return false; $('#simplemodal-container').css({'width':'auto', 'height':'auto'});
}); });
$('#set-quota-form .submit').click(function() { $('#set-name-form').submit(function() {
var form = $('#set-quota-form'), var nickname = $.trim($('[name="nickname"]', $(this)).val());
form_id = form.attr('id'), var $name = $('#nickname');
space_quota = $('input[name="space_quota"]', form).val(); var $error = $('.error', $(this));
if (nickname.indexOf('/') != -1) {
if (!$.trim(space_quota)) { $error.html("{% trans "Name should not include '/'." %}").show();
apply_form_error(form_id, "{% trans "Space Quota can't be empty" %}");
return false; return false;
} }
data = { 'email': $('input[name="email"]', form).val(), 'space_quota': space_quota }; var $submitBtn = $('[type="submit"]', $(this));
disable($submitBtn);
var sb_btn = $(this);
disable(sb_btn);
$.ajax({ $.ajax({
url: '{% url 'user_set_quota' email %}', url: '{% url 'api2-account' email %}',
type: 'POST', type: 'PUT',
dataType: 'json', dataType: 'json',
cache: false, cache: false,
beforeSend: prepareCSRFToken, beforeSend: prepareCSRFToken,
data: data, data: {'name': nickname},
success: function(data) { success: function(data) {
location.reload(true); if (nickname == '') {
$name.html('--');
} else {
$name.html(HTMLescape(data['name']));
}
$.modal.close();
},
error: function(xhr, textStatus, errorThrown) {
var err_msg;
if (xhr.responseText) {
err_msg = $.parseJSON(xhr.responseText).error_msg;
} else {
err_msg = "{% trans "Failed. Please check the network." %}";
}
$error.html(err_msg).show();
enable($submitBtn);
}
});
return false;
});
$('#set-dept-form').submit(function() {
var department = $.trim($('[name="department"]', $(this)).val());
var $department = $('#department');
var $error = $('.error', $(this));
var $submitBtn = $('[type="submit"]', $(this));
disable($submitBtn);
$.ajax({
url: '{% url 'api2-account' email %}',
type: 'PUT',
dataType: 'json',
cache: false,
beforeSend: prepareCSRFToken,
data: {'department': department},
success: function(data) {
if (department == '') {
$department.html('--');
} else {
$department.html(HTMLescape(data['department']));
}
$.modal.close();
},
error: function(xhr, textStatus, errorThrown) {
var err_msg;
if (xhr.responseText) {
err_msg = $.parseJSON(xhr.responseText).error_msg;
} else {
err_msg = "{% trans "Failed. Please check the network." %}";
}
$error.html(err_msg).show();
enable($submitBtn);
}
});
return false;
});
$('#set-quota-form').submit(function() {
var form = $(this),
form_id = form.attr('id'),
space_quota = $.trim($('input[name="space_quota"]', form).val());
var $quota = $('#quota');
if (!space_quota) {
apply_form_error(form_id, "{% trans "It is required." %}");
return false;
}
var $submitBtn = $('[type="submit"]', $(this));
disable($submitBtn);
$.ajax({
url: '{% url 'api2-account' email %}',
type: 'PUT',
dataType: 'json',
cache: false,
beforeSend: prepareCSRFToken,
data: {'storage': space_quota},
success: function(data) {
if (space_quota == 0) {
$quota.html('--');
} else {
$quota.html(parseInt(data['total'])/1000000 + ' MB');
}
$.modal.close();
}, },
error: function(xhr, textStatus, errorThrown) { error: function(xhr, textStatus, errorThrown) {
if (xhr.responseText) { if (xhr.responseText) {
apply_form_error(form_id, $.parseJSON(xhr.responseText).error); apply_form_error(form_id, $.parseJSON(xhr.responseText).error_msg);
} else { } else {
apply_form_error(form_id, "{% trans "Failed. Please check the network." %}"); apply_form_error(form_id, "{% trans "Failed. Please check the network." %}");
} }
enable(sb_btn); enable($submitBtn);
} }
}); });
return false; return false;

View File

@ -54,6 +54,7 @@ from seahub.views.ajax import (get_related_users_by_org_repo,
get_related_users_by_repo) get_related_users_by_repo)
from seahub.forms import SetUserQuotaForm, AddUserForm, BatchAddUserForm, \ from seahub.forms import SetUserQuotaForm, AddUserForm, BatchAddUserForm, \
TermsAndConditionsForm TermsAndConditionsForm
from seahub.profile.forms import ProfileForm, DetailedProfileForm
from seahub.options.models import UserOptions from seahub.options.models import UserOptions
from seahub.profile.models import Profile, DetailedProfile from seahub.profile.models import Profile, DetailedProfile
from seahub.signals import repo_deleted from seahub.signals import repo_deleted
@ -645,7 +646,6 @@ def user_set_quota(request, email):
f = SetUserQuotaForm(request.POST) f = SetUserQuotaForm(request.POST)
if f.is_valid(): if f.is_valid():
email = f.cleaned_data['email']
space_quota_mb = f.cleaned_data['space_quota'] space_quota_mb = f.cleaned_data['space_quota']
space_quota = space_quota_mb * get_file_size_unit('MB') space_quota = space_quota_mb * get_file_size_unit('MB')
@ -980,6 +980,8 @@ def user_add(request):
form = AddUserForm(post_values) form = AddUserForm(post_values)
if form.is_valid(): if form.is_valid():
email = form.cleaned_data['email'] email = form.cleaned_data['email']
name = form.cleaned_data['name']
department = form.cleaned_data['department']
role = form.cleaned_data['role'] role = form.cleaned_data['role']
password = form.cleaned_data['password1'] password = form.cleaned_data['password1']
@ -995,6 +997,10 @@ def user_add(request):
User.objects.update_role(email, role) User.objects.update_role(email, role)
if config.FORCE_PASSWORD_CHANGE: if config.FORCE_PASSWORD_CHANGE:
UserOptions.objects.set_force_passwd_change(email) UserOptions.objects.set_force_passwd_change(email)
if name:
Profile.objects.add_or_update(email, name, '')
if department:
DetailedProfile.objects.add_or_update(email, department, '')
if request.user.org: if request.user.org:
org_id = request.user.org.org_id org_id = request.user.org.org_id
@ -1834,12 +1840,31 @@ def batch_add_user(request):
username = row[0].strip() username = row[0].strip()
password = row[1].strip() password = row[1].strip()
# nickname & department are optional
try:
nickname = row[2].strip()
except IndexError:
nickname = ''
try:
department = row[3].strip()
except IndexError:
department = ''
if not is_valid_username(username): if not is_valid_username(username):
continue continue
if password == '': if password == '':
continue continue
if nickname:
if len(nickname) > 64 or '/' in nickname:
continue
if department:
if len(department) > 512:
continue
try: try:
User.objects.get(email=username) User.objects.get(email=username)
continue continue
@ -1847,6 +1872,11 @@ def batch_add_user(request):
User.objects.create_user(username, password, is_staff=False, User.objects.create_user(username, password, is_staff=False,
is_active=True) is_active=True)
if nickname:
Profile.objects.add_or_update(username, nickname, '')
if department:
DetailedProfile.objects.add_or_update(username, department, '')
send_html_email_with_dj_template( send_html_email_with_dj_template(
username, dj_template='sysadmin/user_batch_add_email.html', username, dj_template='sysadmin/user_batch_add_email.html',
subject=_(u'You are invited to join %s') % SITE_NAME, subject=_(u'You are invited to join %s') % SITE_NAME,

View File

@ -77,7 +77,7 @@ class AccountTest(BaseTestCase):
resp = self._do_get_info() resp = self._do_get_info()
json_resp = json.loads(resp.content) json_resp = json.loads(resp.content)
assert len(json_resp) == 7 assert len(json_resp) == 9
assert json_resp['email'] == self.user1.username assert json_resp['email'] == self.user1.username
assert json_resp['is_staff'] is False assert json_resp['is_staff'] is False
assert json_resp['is_active'] is True assert json_resp['is_active'] is True
@ -104,7 +104,7 @@ class AccountTest(BaseTestCase):
self.assertEqual(Profile.objects.get_profile_by_user( self.assertEqual(Profile.objects.get_profile_by_user(
self.user1.username).intro, 'this_is_user1') self.user1.username).intro, 'this_is_user1')
self.assertEqual(seafile_api.get_user_quota( self.assertEqual(seafile_api.get_user_quota(
self.user1.username), 102400) self.user1.username), 102400000000)
def test_refresh_profile_cache_after_update(self): def test_refresh_profile_cache_after_update(self):
self.login_as(self.admin) self.login_as(self.admin)

View File

@ -35,8 +35,7 @@ class AccountsApiTest(ApiTestBase):
# non-admin user can not create new user # non-admin user can not create new user
self.put(test_account_url, data=data, expected=403) self.put(test_account_url, data=data, expected=403)
res = self.admin_put(test_account_url, data=data, expected=201) self.admin_put(test_account_url, data=data, expected=201)
self.assertEqual(res.text, u'"success"')
# non-admin user can not delete a user # non-admin user can not delete a user
self.delete(test_account_url, expected=403) self.delete(test_account_url, expected=403)
@ -99,10 +98,10 @@ class AccountsApiTest(ApiTestBase):
def test_update_account_storage_quota(self): def test_update_account_storage_quota(self):
with self.get_tmp_user() as user: with self.get_tmp_user() as user:
data = {'storage': 1024} # 1KB data = {'storage': 1024} # 1 Mb
self.admin_put(user.user_url, data=data, expected=200) self.admin_put(user.user_url, data=data, expected=200)
self.assertEqual(self.admin_get(user.user_url).json()['total'], self.assertEqual(self.admin_get(user.user_url).json()['total'],
1024) 1024000000)
# def test_update_account_sharing_quota(self): # def test_update_account_sharing_quota(self):
# with self.get_tmp_user() as user: # with self.get_tmp_user() as user: