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

[institution] Add institution quota

This commit is contained in:
zhengxie
2016-12-21 17:34:17 +08:00
parent 0c61a22100
commit 81db6a7a0a
11 changed files with 225 additions and 9 deletions

View File

@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('institutions', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='InstitutionQuota',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('quota', models.BigIntegerField()),
('institution', models.ForeignKey(to='institutions.Institution')),
],
),
]

View File

@@ -11,3 +11,17 @@ class Institution(models.Model):
class InstitutionAdmin(models.Model): class InstitutionAdmin(models.Model):
institution = models.ForeignKey(Institution) institution = models.ForeignKey(Institution)
user = models.EmailField() user = models.EmailField()
class InstitutionQuotaManager(models.Manager):
def get_or_none(self, *args, **kwargs):
try:
return self.get(*args, **kwargs).quota
except self.model.DoesNotExist:
return None
class InstitutionQuota(models.Model):
institution = models.ForeignKey(Institution)
quota = models.BigIntegerField()
objects = InstitutionQuotaManager()

View File

@@ -45,17 +45,14 @@
{% endif %} {% endif %}
<dt>{% trans "Space Used" %}</dt> <dt>{% trans "Space Used" %}</dt>
<dd>{{ space_usage|seahub_filesizeformat }} {% if space_quota > 0 %} / {{ space_quota|seahub_filesizeformat }} {% endif %}</dd> <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>
</dl> </dl>
<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="hidden" name="email" value="{{ email }}" />
<input type="text" name="space_quota" class="input" /> MB <input type="text" name="space_quota" class="input" /> MB
<p class="tip"> <p class="tip">{% trans "Available quota:" %} {{ available_quota|seahub_filesizeformat}}</p>
<span>{% trans "An integer that is greater than or equal to 0." %}</span><br />
<span>{% trans "Tip: 0 means default limit" %}</span>
</p>
<p class="error hide"></p> <p class="error hide"></p>
<input type="submit" value="{% trans "Submit" %}" class="submit" /> <input type="submit" value="{% trans "Submit" %}" class="submit" />
</form> </form>
@@ -140,5 +137,46 @@
{% block extra_script %} {% block extra_script %}
<script type="text/javascript"> <script type="text/javascript">
$('#set-quota').click(function() {
$("#set-quota-form").modal({appendTo: "#main"});
return false;
});
$('#set-quota-form .submit').click(function() {
var form = $('#set-quota-form'),
form_id = form.attr('id'),
space_quota = $('input[name="space_quota"]', form).val();
if (!$.trim(space_quota)) {
apply_form_error(form_id, "{% trans "Space Quota can't be empty" %}");
return false;
}
data = { 'email': $('input[name="email"]', form).val(), 'space_quota': space_quota };
var sb_btn = $(this);
disable(sb_btn);
$.ajax({
url: '{% url 'institutions:user_set_quota' email %}',
type: 'POST',
dataType: 'json',
cache: false,
beforeSend: prepareCSRFToken,
data: data,
success: function(data) {
location.reload(true);
},
error: function(xhr, textStatus, errorThrown) {
if (xhr.responseText) {
apply_form_error(form_id, $.parseJSON(xhr.responseText).error);
} else {
apply_form_error(form_id, "{% trans "Failed. Please check the network." %}");
}
enable(sb_btn);
}
});
return false;
});
</script> </script>
{% endblock %} {% endblock %}

View File

@@ -2,7 +2,7 @@
from django.conf.urls import patterns, url from django.conf.urls import patterns, url
from .views import (info, useradmin, user_info, user_remove, useradmin_search, from .views import (info, useradmin, user_info, user_remove, useradmin_search,
user_toggle_status) user_toggle_status, user_set_quota)
urlpatterns = patterns( urlpatterns = patterns(
'', '',
@@ -11,5 +11,6 @@ urlpatterns = patterns(
url(r'^useradmin/info/(?P<email>[^/]+)/$', user_info, name='user_info'), url(r'^useradmin/info/(?P<email>[^/]+)/$', user_info, name='user_info'),
url(r'^useradmin/remove/(?P<email>[^/]+)/$', user_remove, name='user_remove'), url(r'^useradmin/remove/(?P<email>[^/]+)/$', user_remove, name='user_remove'),
url('^useradmin/search/$', useradmin_search, name="useradmin_search"), url('^useradmin/search/$', useradmin_search, name="useradmin_search"),
url(r'^useradmin/set_quota/(?P<email>[^/]+)/$', user_set_quota, name='user_set_quota'),
url(r'^useradmin/toggle_status/(?P<email>[^/]+)/$', user_toggle_status, name='user_toggle_status'), url(r'^useradmin/toggle_status/(?P<email>[^/]+)/$', user_toggle_status, name='user_toggle_status'),
) )

View File

@@ -0,0 +1,25 @@
# Copyright (c) 2012-2016 Seafile Ltd.
from seaserv import seafile_api
from seahub.profile.models import Profile
from seahub.institutions.models import InstitutionQuota
def get_institution_space_usage(inst):
# TODO: need to refactor
usernames = [x.user for x in Profile.objects.filter(institution=inst.name)]
total = 0
for user in usernames:
total += seafile_api.get_user_self_usage(user)
return total
def get_institution_available_quota(inst):
inst_quota = InstitutionQuota.objects.get_or_none(institution=inst)
if inst_quota is None:
return None
usernames = [x.user for x in Profile.objects.filter(institution=inst.name)]
allocated = 0
for user in usernames:
allocated += seafile_api.get_user_quota(user)
return 0 if allocated >= inst_quota else inst_quota - allocated

View File

@@ -18,13 +18,16 @@ from seahub.base.decorators import require_POST
from seahub.base.models import UserLastLogin from seahub.base.models import UserLastLogin
from seahub.institutions.decorators import (inst_admin_required, from seahub.institutions.decorators import (inst_admin_required,
inst_admin_can_manage_user) inst_admin_can_manage_user)
from seahub.institutions.utils import get_institution_available_quota
from seahub.profile.models import Profile, DetailedProfile from seahub.profile.models import Profile, DetailedProfile
from seahub.utils import is_valid_username, clear_token from seahub.utils import is_valid_username, clear_token
from seahub.utils.rpc import mute_seafile_api from seahub.utils.rpc import mute_seafile_api
from seahub.utils.file_size import get_file_size_unit
from seahub.views.sysadmin import email_user_on_activation, populate_user_info from seahub.views.sysadmin import email_user_on_activation, populate_user_info
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def _populate_user_quota_usage(user): def _populate_user_quota_usage(user):
"""Populate space/share quota to user. """Populate space/share quota to user.
@@ -162,6 +165,8 @@ def user_info(request, email):
else: else:
g.role = _('Member') g.role = _('Member')
available_quota = get_institution_available_quota(request.user.institution)
return render_to_response( return render_to_response(
'institutions/user_info.html', { 'institutions/user_info.html', {
'owned_repos': owned_repos, 'owned_repos': owned_repos,
@@ -172,6 +177,7 @@ def user_info(request, email):
'profile': profile, 'profile': profile,
'd_profile': d_profile, 'd_profile': d_profile,
'personal_groups': personal_groups, 'personal_groups': personal_groups,
'available_quota': available_quota,
}, context_instance=RequestContext(request)) }, context_instance=RequestContext(request))
@require_POST @require_POST
@@ -192,6 +198,26 @@ def user_remove(request, email):
return HttpResponseRedirect(next) return HttpResponseRedirect(next)
@login_required_ajax
@require_POST
@inst_admin_required
@inst_admin_can_manage_user
def user_set_quota(request, email):
content_type = 'application/json; charset=utf-8'
quota_mb = int(request.POST.get('space_quota', 0))
quota = quota_mb * get_file_size_unit('MB')
available_quota = get_institution_available_quota(request.user.institution)
if available_quota < quota:
result = {}
result['error'] = _(u'Failed to set quota: maximum quota is %d MB' % \
(available_quota / 10 ** 6))
return HttpResponse(json.dumps(result), status=400, content_type=content_type)
seafile_api.set_user_quota(email, quota)
return HttpResponse(json.dumps({'success': True}), content_type=content_type)
@login_required_ajax @login_required_ajax
@require_POST @require_POST
@inst_admin_required @inst_admin_required
@@ -234,3 +260,4 @@ def user_toggle_status(request, email):
except User.DoesNotExist: except User.DoesNotExist:
return HttpResponse(json.dumps({'success': False}), status=500, return HttpResponse(json.dumps({'success': False}), status=500,
content_type=content_type) content_type=content_type)

View File

@@ -49,7 +49,7 @@
{% endblock %} {% endblock %}
{% block extra_script %} {% block extra_script %}{{ block.super }}
<script type="text/javascript"> <script type="text/javascript">
addConfirmTo($('.js-toggle-admin'), { addConfirmTo($('.js-toggle-admin'), {

View File

@@ -19,11 +19,68 @@
<dl> <dl>
<dt>{% trans "Number of members" %}</dt> <dt>{% trans "Number of members" %}</dt>
<dd>{{ users_count }}</dd> <dd>{{ users_count }}</dd>
<dt>{% trans "Space Used" %}</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>
</dl> </dl>
<form id="set-quota-form" method="post" class="hide">{% csrf_token %}
<h3>{% trans "Set 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>
<form action="{% url 'sys_inst_search_user' inst.pk %}" method="get" class="side-search-form"> <form action="{% url 'sys_inst_search_user' inst.pk %}" method="get" class="side-search-form">
<input type="text" name="q" class="input" value="{{q}}" placeholder="{% trans "Search users..." %}" /> <input type="text" name="q" class="input" value="{{q}}" placeholder="{% trans "Search users..." %}" />
</form> </form>
</div> </div>
{% endblock %} {% endblock %}
{% block extra_script %}
<script type="text/javascript">
$('#set-quota').click(function() {
$("#set-quota-form").modal({appendTo: "#main"});
return false;
});
$('#set-quota-form .submit').click(function() {
var form = $('#set-quota-form'),
form_id = form.attr('id'),
space_quota = $('input[name="space_quota"]', form).val();
if (!$.trim(space_quota)) {
apply_form_error(form_id, "{% trans "Space Quota can't be empty" %}");
return false;
}
data = { 'space_quota': space_quota };
var sb_btn = $(this);
disable(sb_btn);
$.ajax({
url: '{% url 'sys_inst_set_quota' inst.pk %}',
type: 'POST',
dataType: 'json',
cache: false,
beforeSend: prepareCSRFToken,
data: data,
success: function(data) {
location.reload(true);
},
error: function(xhr, textStatus, errorThrown) {
if (xhr.responseText) {
apply_form_error(form_id, $.parseJSON(xhr.responseText).error);
} else {
apply_form_error(form_id, "{% trans "Failed. Please check the network." %}");
}
enable(sb_btn);
}
});
return false;
});
</script>
{% endblock %}

View File

@@ -54,7 +54,7 @@
{% endblock %} {% endblock %}
{% block extra_script %} {% block extra_script %}{{ block.super }}
<script type="text/javascript"> <script type="text/javascript">
addConfirmTo($('.js-toggle-admin'), { addConfirmTo($('.js-toggle-admin'), {

View File

@@ -307,6 +307,7 @@ urlpatterns = patterns(
url(r'^sys/instadmin/(?P<inst_id>\d+)/users/search/$', sys_inst_search_user, name='sys_inst_search_user'), url(r'^sys/instadmin/(?P<inst_id>\d+)/users/search/$', sys_inst_search_user, name='sys_inst_search_user'),
url(r'^sys/instadmin/(?P<inst_id>\d+)/admins/$', sys_inst_info_admins, name='sys_inst_info_admins'), url(r'^sys/instadmin/(?P<inst_id>\d+)/admins/$', sys_inst_info_admins, name='sys_inst_info_admins'),
url(r'^sys/instadmin/(?P<inst_id>\d+)/toggleadmin/(?P<email>[^/]+)/$', sys_inst_toggle_admin, name='sys_inst_toggle_admin'), url(r'^sys/instadmin/(?P<inst_id>\d+)/toggleadmin/(?P<email>[^/]+)/$', sys_inst_toggle_admin, name='sys_inst_toggle_admin'),
url(r'^sys/instadmin/(?P<inst_id>\d+)/set_quota/$', sys_inst_set_quota, name='sys_inst_set_quota'),
url(r'^sys/publinkadmin/$', sys_publink_admin, name='sys_publink_admin'), url(r'^sys/publinkadmin/$', sys_publink_admin, name='sys_publink_admin'),
url(r'^sys/publink/remove/$', sys_publink_remove, name='sys_publink_remove'), url(r'^sys/publink/remove/$', sys_publink_remove, name='sys_publink_remove'),
url(r'^sys/uploadlink/remove/$', sys_upload_link_remove, name='sys_upload_link_remove'), url(r'^sys/uploadlink/remove/$', sys_upload_link_remove, name='sys_upload_link_remove'),

View File

@@ -35,7 +35,9 @@ from seahub.base.templatetags.seahub_tags import tsstr_sec, email2nickname
from seahub.auth import authenticate from seahub.auth import authenticate
from seahub.auth.decorators import login_required, login_required_ajax from seahub.auth.decorators import login_required, login_required_ajax
from seahub.constants import GUEST_USER, DEFAULT_USER from seahub.constants import GUEST_USER, DEFAULT_USER
from seahub.institutions.models import Institution, InstitutionAdmin from seahub.institutions.models import (Institution, InstitutionAdmin,
InstitutionQuota)
from seahub.institutions.utils import get_institution_space_usage
from seahub.invitations.models import Invitation from seahub.invitations.models import Invitation
from seahub.role_permissions.utils import get_available_roles from seahub.role_permissions.utils import get_available_roles
from seahub.utils import IS_EMAIL_CONFIGURED, string2list, is_valid_username, \ from seahub.utils import IS_EMAIL_CONFIGURED, string2list, is_valid_username, \
@@ -2127,6 +2129,8 @@ def sys_inst_info_user(request, inst_id):
u.last_login = last_login.last_login u.last_login = last_login.last_login
users_count = Profile.objects.filter(institution=inst.name).count() users_count = Profile.objects.filter(institution=inst.name).count()
space_quota = InstitutionQuota.objects.get_or_none(institution=inst)
space_usage = get_institution_space_usage(inst)
return render_to_response('sysadmin/sys_inst_info_user.html', { return render_to_response('sysadmin/sys_inst_info_user.html', {
'inst': inst, 'inst': inst,
@@ -2137,6 +2141,8 @@ def sys_inst_info_user(request, inst_id):
'next_page': current_page + 1, 'next_page': current_page + 1,
'per_page': per_page, 'per_page': per_page,
'page_next': page_next, 'page_next': page_next,
'space_usage': space_usage,
'space_quota': space_quota,
}, context_instance=RequestContext(request)) }, context_instance=RequestContext(request))
@login_required @login_required
@@ -2250,6 +2256,31 @@ def sys_inst_toggle_admin(request, inst_id, email):
messages.success(request, _('Success')) messages.success(request, _('Success'))
return HttpResponseRedirect(next) return HttpResponseRedirect(next)
@login_required
@sys_staff_required
@require_POST
def sys_inst_set_quota(request, inst_id):
"""Set institution quota"""
try:
inst = Institution.objects.get(pk=inst_id)
except Institution.DoesNotExist:
raise Http404
next = request.META.get('HTTP_REFERER', None)
if not next:
next = reverse('sys_inst_info_users', args=[inst.pk])
quota_mb = int(request.POST.get('space_quota', ''))
quota = quota_mb * get_file_size_unit('MB')
obj, created = InstitutionQuota.objects.update_or_create(
institution=inst,
defaults={'quota': quota},
)
content_type = 'application/json; charset=utf-8'
return HttpResponse(json.dumps({'success': True}), status=200,
content_type=content_type)
@login_required @login_required
@sys_staff_required @sys_staff_required
def sys_invitation_admin(request): def sys_invitation_admin(request):