diff --git a/seahub/api2/endpoints/account.py b/seahub/api2/endpoints/account.py index 73fcdfb0a9..cfecfef5e4 100644 --- a/seahub/api2/endpoints/account.py +++ b/seahub/api2/endpoints/account.py @@ -3,6 +3,7 @@ import logging from dateutil.relativedelta import relativedelta from django.utils import timezone +from django.utils.translation import ugettext as _ from rest_framework import status from rest_framework.authentication import SessionAuthentication from rest_framework.permissions import IsAdminUser @@ -11,22 +12,37 @@ from rest_framework.reverse import reverse from rest_framework.views import APIView import seaserv from seaserv import seafile_api, ccnet_threaded_rpc -from pysearpc import SearpcError from seahub.api2.authentication import TokenAuthentication from seahub.api2.serializers import AccountSerializer from seahub.api2.throttling import UserRateThrottle 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.profile.models import Profile -from seahub.profile.utils import refresh_cache as refresh_profile_cache -from seahub.utils import is_valid_username +from seahub.base.templatetags.seahub_tags import email2nickname +from seahub.profile.models import Profile, DetailedProfile +from seahub.utils import is_valid_username, is_org_context +from seahub.utils.file_size import get_file_size_unit logger = logging.getLogger(__name__) json_content_type = 'application/json; charset=utf-8' +def get_account_info(email): + user = User.objects.get(email=email) + 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): """Query/Add/Delete a specific account. @@ -42,140 +58,13 @@ class Account(APIView): # query account info try: - user = User.objects.get(email=email) + User.objects.get(email=email) except User.DoesNotExist: return api_error(status.HTTP_404_NOT_FOUND, 'User %s not found.' % email) - info = {} - 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) - + info = get_account_info(email) 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): # migrate an account's repos and groups to an exist account if not is_valid_username(email): @@ -207,19 +96,208 @@ class Account(APIView): if from_user == g.creator_name: ccnet_threaded_rpc.set_group_creator(g.id, to_user) - return Response("success") + return Response({'success': True}) else: 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): + + # argument check for 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: + 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) - 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(email) + return Response(info) + 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.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(email) + resp = Response(info, status=status.HTTP_201_CREATED) + resp['Location'] = reverse('api2-account', args=[email]) + return resp def delete(self, request, email, format=None): if not is_valid_username(email): @@ -229,7 +307,7 @@ class Account(APIView): try: user = User.objects.get(email=email) user.delete() - return Response("success") + return Response({'success': True}) except User.DoesNotExist: - resp = Response("success", status=status.HTTP_202_ACCEPTED) + resp = Response({'success': True}, status=status.HTTP_202_ACCEPTED) return resp diff --git a/seahub/templates/sysadmin/sys_user_admin_ldap_imported.html b/seahub/templates/sysadmin/sys_user_admin_ldap_imported.html index e3322bb50f..c3173da50b 100644 --- a/seahub/templates/sysadmin/sys_user_admin_ldap_imported.html +++ b/seahub/templates/sysadmin/sys_user_admin_ldap_imported.html @@ -53,11 +53,13 @@ {{ user.space_usage|seahub_filesizeformat }} / - {% if user.space_quota > 0 %} - {{ user.space_quota|seahub_filesizeformat }} - {% else %} - -- - {% endif %} + + {% if user.space_quota > 0 %} + {{ user.space_quota|seahub_filesizeformat }} + {% else %} + -- + {% endif %} + diff --git a/seahub/templates/sysadmin/useradmin_js.html b/seahub/templates/sysadmin/useradmin_js.html index 699345feb1..871a2d826c 100644 --- a/seahub/templates/sysadmin/useradmin_js.html +++ b/seahub/templates/sysadmin/useradmin_js.html @@ -85,7 +85,8 @@ $('.user-status-select, .user-role-select').change(function() { // edit quota $('.quota-edit-icon').click(function() { var email = $(this).closest('tr').attr('data-userid'); - $('#set-quota-form').data('email', email).modal(); + var $spaceQuota = $(this).prev('.user-space-quota'); + $('#set-quota-form').data({'email': email, '$spaceQuota': $spaceQuota}).modal(); $('#simplemodal-container').css({'width':'auto', 'height':'auto'}); }); @@ -101,20 +102,26 @@ $('#set-quota-form').submit(function() { disable($submitBtn); var email = $(this).data('email'); + var $spaceQuota = $(this).data('$spaceQuota'); $.ajax({ - url: '{{ SITE_ROOT }}useradmin/' + encodeURIComponent(email) + '/set_quota/', - type: 'POST', + url: '{{ SITE_ROOT }}api2/accounts/' + encodeURIComponent(email) + '/', + type: 'PUT', dataType: 'json', cache: false, beforeSend: prepareCSRFToken, - data: {'space_quota': space_quota}, - success: function() { - location.reload(true); + 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; + err_msg = $.parseJSON(xhr.responseText).error_msg; } else { err_msg = "{% trans "Failed. Please check the network." %}"; } diff --git a/seahub/templates/sysadmin/useradmin_table.html b/seahub/templates/sysadmin/useradmin_table.html index 517ca92df3..4342d1ccf5 100644 --- a/seahub/templates/sysadmin/useradmin_table.html +++ b/seahub/templates/sysadmin/useradmin_table.html @@ -71,11 +71,13 @@ {{ user.space_usage|seahub_filesizeformat }} / - {% if user.space_quota > 0 %} - {{ user.space_quota|seahub_filesizeformat }} - {% else %} - -- - {% endif %} + + {% if user.space_quota > 0 %} + {{ user.space_quota|seahub_filesizeformat }} + {% else %} + -- + {% endif %} + {% if user.source == "DB" or user.source == 'LDAPImport' %} {% endif %} diff --git a/seahub/templates/sysadmin/userinfo.html b/seahub/templates/sysadmin/userinfo.html index 5953822647..54edfdd356 100644 --- a/seahub/templates/sysadmin/userinfo.html +++ b/seahub/templates/sysadmin/userinfo.html @@ -73,11 +73,13 @@
{% trans "Space Used / Quota" %}
{{ space_usage|seahub_filesizeformat }} / - {% if space_quota > 0 %} - {{ space_quota|seahub_filesizeformat }} - {% else %} - -- - {% endif %} + + {% if space_quota > 0 %} + {{ space_quota|seahub_filesizeformat }} + {% else %} + -- + {% endif %} +
@@ -307,10 +309,6 @@ $('#set-quota').click(function() { $('#set-name-form').submit(function() { var nickname = $.trim($('[name="nickname"]', $(this)).val()); var $error = $('.error', $(this)); - if (!nickname) { - $error.html("{% trans "It is required." %}").show(); - return false; - } if (nickname.indexOf('/') != -1) { $error.html("{% trans "Name should not include '/'." %}").show(); return false; @@ -320,20 +318,24 @@ $('#set-name-form').submit(function() { disable($submitBtn); $.ajax({ - url: '{% url 'user_set_nickname' email %}', - type: 'POST', + url: '{% url 'api2-account' email %}', + type: 'PUT', dataType: 'json', cache: false, beforeSend: prepareCSRFToken, - data: {'nickname': nickname}, + data: {'name': nickname}, success: function(data) { - $('#nickname').html(data.nickname); + if (nickname == '') { + $('#nickname').html('--'); + } else { + $('#nickname').html(data['name']); + } $.modal.close(); }, error: function(xhr, textStatus, errorThrown) { var err_msg; if (xhr.responseText) { - err_msg = $.parseJSON(xhr.responseText).error; + err_msg = $.parseJSON(xhr.responseText).error_msg; } else { err_msg = "{% trans "Failed. Please check the network." %}"; } @@ -348,29 +350,28 @@ $('#set-name-form').submit(function() { $('#set-dept-form').submit(function() { var department = $.trim($('[name="department"]', $(this)).val()); var $error = $('.error', $(this)); - if (!department) { - $error.html("{% trans "It is required." %}").show(); - return false; - } - var $submitBtn = $('[type="submit"]', $(this)); disable($submitBtn); $.ajax({ - url: '{% url 'user_set_department' email %}', - type: 'POST', + url: '{% url 'api2-account' email %}', + type: 'PUT', dataType: 'json', cache: false, beforeSend: prepareCSRFToken, data: {'department': department}, success: function(data) { - $('#department').html(data.department); + if (department == '') { + $('#department').html('--'); + } else { + $('#department').html(data['department']); + } $.modal.close(); }, error: function(xhr, textStatus, errorThrown) { var err_msg; if (xhr.responseText) { - err_msg = $.parseJSON(xhr.responseText).error; + err_msg = $.parseJSON(xhr.responseText).error_msg; } else { err_msg = "{% trans "Failed. Please check the network." %}"; } @@ -396,18 +397,23 @@ $('#set-quota-form').submit(function() { disable($submitBtn); $.ajax({ - url: '{% url 'user_set_quota' email %}', - type: 'POST', + url: '{% url 'api2-account' email %}', + type: 'PUT', dataType: 'json', cache: false, beforeSend: prepareCSRFToken, - data: {'space_quota': space_quota}, - success: function() { - location.reload(true); + data: {'storage': space_quota}, + success: function(data) { + if (space_quota == 0) { + $('#space_quota').html('--'); + } else { + $('#space_quota').html(parseInt(data['total'])/1000000 + ' MB'); + } + $.modal.close(); }, error: function(xhr, textStatus, errorThrown) { if (xhr.responseText) { - apply_form_error(form_id, $.parseJSON(xhr.responseText).error); + apply_form_error(form_id, $.parseJSON(xhr.responseText).error_msg); } else { apply_form_error(form_id, "{% trans "Failed. Please check the network." %}"); } diff --git a/seahub/urls.py b/seahub/urls.py index 432c34eaa0..b96a94b0c6 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -285,8 +285,6 @@ urlpatterns = patterns( url(r'^useradmin/toggle_status/(?P[^/]+)/$', user_toggle_status, name='user_toggle_status'), url(r'^useradmin/toggle_role/(?P[^/]+)/$', user_toggle_role, name='user_toggle_role'), url(r'^useradmin/(?P[^/]+)/set_quota/$', user_set_quota, name='user_set_quota'), - url(r'^useradmin/(?P[^/]+)/set_nickname/$', user_set_nickname, name='user_set_nickname'), - url(r'^useradmin/(?P[^/]+)/set_department/$', user_set_department, name='user_set_department'), url(r'^sys/termsadmin/$', sys_terms_admin, name='sys_terms_admin'), url(r'^sys/termsadmin/delete/(?P[^/]+)/$', sys_delete_terms, name='sys_delete_terms'), url(r'^useradmin/password/reset/(?P[^/]+)/$', user_reset, name='user_reset'), diff --git a/seahub/views/sysadmin.py b/seahub/views/sysadmin.py index adb1fdc1ee..88334e1f05 100644 --- a/seahub/views/sysadmin.py +++ b/seahub/views/sysadmin.py @@ -672,40 +672,6 @@ def user_set_quota(request, email): result['error'] = str(f.errors.values()[0]) return HttpResponse(json.dumps(result), status=400, content_type=content_type) -@login_required_ajax -@sys_staff_required -def user_set_nickname(request, email): - if request.method != 'POST': - raise Http404 - - content_type = 'application/json; charset=utf-8' - result = {} - - form = ProfileForm(request.POST) - if form.is_valid(): - form.save(username=email) - return HttpResponse(json.dumps({'nickname': form.cleaned_data["nickname"]}), content_type=content_type) - else: - result['error'] = str(form.errors.values()[0]) - return HttpResponse(json.dumps(result), status=400, content_type=content_type) - -@login_required_ajax -@sys_staff_required -def user_set_department(request, email): - if request.method != 'POST': - raise Http404 - - content_type = 'application/json; charset=utf-8' - result = {} - - form = DetailedProfileForm(request.POST) - if form.is_valid(): - form.save(username=email) - return HttpResponse(json.dumps({'department': form.cleaned_data["department"]}), content_type=content_type) - else: - result['error'] = str(form.errors.values()[0]) - return HttpResponse(json.dumps(result), status=400, content_type=content_type) - @login_required_ajax @sys_staff_required def sys_org_set_quota(request, org_id): diff --git a/tests/api/endpoints/test_account.py b/tests/api/endpoints/test_account.py index 8d179411be..68e0b8a445 100644 --- a/tests/api/endpoints/test_account.py +++ b/tests/api/endpoints/test_account.py @@ -77,7 +77,7 @@ class AccountTest(BaseTestCase): resp = self._do_get_info() 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['is_staff'] is False assert json_resp['is_active'] is True @@ -104,7 +104,7 @@ class AccountTest(BaseTestCase): self.assertEqual(Profile.objects.get_profile_by_user( self.user1.username).intro, 'this_is_user1') self.assertEqual(seafile_api.get_user_quota( - self.user1.username), 102400) + self.user1.username), 102400000000) def test_refresh_profile_cache_after_update(self): self.login_as(self.admin) diff --git a/tests/api/test_accounts.py b/tests/api/test_accounts.py index 859f378a36..60ddee506c 100644 --- a/tests/api/test_accounts.py +++ b/tests/api/test_accounts.py @@ -35,8 +35,7 @@ class AccountsApiTest(ApiTestBase): # non-admin user can not create new user self.put(test_account_url, data=data, expected=403) - res = self.admin_put(test_account_url, data=data, expected=201) - self.assertEqual(res.text, u'"success"') + self.admin_put(test_account_url, data=data, expected=201) # non-admin user can not delete a user self.delete(test_account_url, expected=403) @@ -99,10 +98,10 @@ class AccountsApiTest(ApiTestBase): def test_update_account_storage_quota(self): 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.assertEqual(self.admin_get(user.user_url).json()['total'], - 1024) + 1024000000) # def test_update_account_sharing_quota(self): # with self.get_tmp_user() as user: