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:
22
seahub/institutions/migrations/0002_institutionquota.py
Normal file
22
seahub/institutions/migrations/0002_institutionquota.py
Normal 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')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
@@ -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()
|
||||||
|
@@ -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 %}
|
||||||
|
@@ -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'),
|
||||||
)
|
)
|
||||||
|
25
seahub/institutions/utils.py
Normal file
25
seahub/institutions/utils.py
Normal 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
|
@@ -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)
|
||||||
|
|
||||||
|
@@ -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'), {
|
||||||
|
@@ -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 %}
|
||||||
|
@@ -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'), {
|
||||||
|
@@ -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'),
|
||||||
|
@@ -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):
|
||||||
|
Reference in New Issue
Block a user