Implement a maximum quota per domain, fixes #106
This commit is contained in:
@@ -46,6 +46,7 @@ class DomainForm(flask_wtf.FlaskForm):
|
|||||||
name = fields.StringField(_('Domain name'), [validators.DataRequired()])
|
name = fields.StringField(_('Domain name'), [validators.DataRequired()])
|
||||||
max_users = fields_.IntegerField(_('Maximum user count'), default=10)
|
max_users = fields_.IntegerField(_('Maximum user count'), default=10)
|
||||||
max_aliases = fields_.IntegerField(_('Maximum alias count'), default=10)
|
max_aliases = fields_.IntegerField(_('Maximum alias count'), default=10)
|
||||||
|
max_quota_bytes = fields_.IntegerSliderField(_('Maximum user quota'), default=0)
|
||||||
comment = fields.StringField(_('Comment'))
|
comment = fields.StringField(_('Comment'))
|
||||||
submit = fields.SubmitField(_('Create'))
|
submit = fields.SubmitField(_('Create'))
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ class Domain(Base):
|
|||||||
backref=db.backref('manager_of'), lazy='dynamic')
|
backref=db.backref('manager_of'), lazy='dynamic')
|
||||||
max_users = db.Column(db.Integer, nullable=False, default=0)
|
max_users = db.Column(db.Integer, nullable=False, default=0)
|
||||||
max_aliases = db.Column(db.Integer, nullable=False, default=0)
|
max_aliases = db.Column(db.Integer, nullable=False, default=0)
|
||||||
|
max_quota_bytes = db.Column(db.Integer(), nullable=False, default=0)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def dkim_key(self):
|
def dkim_key(self):
|
||||||
|
|||||||
@@ -9,6 +9,9 @@
|
|||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
{{ macros.form_field(form.name) }}
|
{{ macros.form_field(form.name) }}
|
||||||
{{ macros.form_fields((form.max_users, form.max_aliases)) }}
|
{{ macros.form_fields((form.max_users, form.max_aliases)) }}
|
||||||
|
{{ macros.form_field(form.max_quota_bytes, step=1000000000, max=50000000000,
|
||||||
|
prepend='<span class="input-group-addon"><span id="quota">'+((form.max_quota_bytes.data//1000000000).__str__() if form.max_quota_bytes.data else '∞')+'</span> GiB</span>',
|
||||||
|
oninput='$("#quota").text(this.value == 0 ? "∞" : this.value/1000000000);') }}
|
||||||
{{ macros.form_field(form.comment) }}
|
{{ macros.form_field(form.comment) }}
|
||||||
{{ macros.form_field(form.submit) }}
|
{{ macros.form_field(form.submit) }}
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
{{ macros.form_field(form.localpart, append='<span class="input-group-addon">@'+domain.name+'</span>') }}
|
{{ macros.form_field(form.localpart, append='<span class="input-group-addon">@'+domain.name+'</span>') }}
|
||||||
{{ macros.form_fields((form.pw, form.pw2)) }}
|
{{ macros.form_fields((form.pw, form.pw2)) }}
|
||||||
{{ macros.form_field(form.quota_bytes, step=1000000000, max=50000000000,
|
{{ macros.form_field(form.quota_bytes, step=1000000000, max=(max_quota_bytes or domain.max_quota_bytes or 50000000000),
|
||||||
prepend='<span class="input-group-addon"><span id="quota">'+(form.quota_bytes.data//1000000000).__str__()+'</span> GiB</span>',
|
prepend='<span class="input-group-addon"><span id="quota">'+(form.quota_bytes.data//1000000000).__str__()+'</span> GiB</span>',
|
||||||
oninput='$("#quota").text(this.value/1000000000);') }}
|
oninput='$("#quota").text(this.value/1000000000);') }}
|
||||||
{{ macros.form_field(form.enable_imap) }}
|
{{ macros.form_field(form.enable_imap) }}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from mailu.admin import app, db, models, forms, access
|
|||||||
|
|
||||||
import flask
|
import flask
|
||||||
import flask_login
|
import flask_login
|
||||||
|
import wtforms
|
||||||
import wtforms_components
|
import wtforms_components
|
||||||
|
|
||||||
|
|
||||||
@@ -21,6 +22,8 @@ def user_create(domain_name):
|
|||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
flask.url_for('.user_list', domain_name=domain.name))
|
flask.url_for('.user_list', domain_name=domain.name))
|
||||||
form = forms.UserForm()
|
form = forms.UserForm()
|
||||||
|
form.quota_bytes.validators = [
|
||||||
|
wtforms.validators.NumberRange(max=domain.max_quota_bytes)]
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
if domain.has_email(form.localpart.data):
|
if domain.has_email(form.localpart.data):
|
||||||
flask.flash('Email is already used', 'error')
|
flask.flash('Email is already used', 'error')
|
||||||
@@ -41,10 +44,17 @@ def user_create(domain_name):
|
|||||||
@access.domain_admin(models.User, 'user_email')
|
@access.domain_admin(models.User, 'user_email')
|
||||||
def user_edit(user_email):
|
def user_edit(user_email):
|
||||||
user = models.User.query.get(user_email) or flask.abort(404)
|
user = models.User.query.get(user_email) or flask.abort(404)
|
||||||
|
# Handle the case where user quota is more than allowed
|
||||||
|
max_quota_bytes = user.domain.max_quota_bytes
|
||||||
|
if max_quota_bytes and user.quota_bytes > max_quota_bytes:
|
||||||
|
max_quota_bytes = user.quota_bytes
|
||||||
|
# Create the form
|
||||||
form = forms.UserForm(obj=user)
|
form = forms.UserForm(obj=user)
|
||||||
wtforms_components.read_only(form.localpart)
|
wtforms_components.read_only(form.localpart)
|
||||||
form.pw.validators = []
|
form.pw.validators = []
|
||||||
form.localpart.validators = []
|
form.localpart.validators = []
|
||||||
|
form.quota_bytes.validators = [
|
||||||
|
wtforms.validators.NumberRange(max=max_quota_bytes)]
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
form.populate_obj(user)
|
form.populate_obj(user)
|
||||||
if form.pw.data:
|
if form.pw.data:
|
||||||
@@ -53,7 +63,8 @@ def user_edit(user_email):
|
|||||||
flask.flash('User %s updated' % user)
|
flask.flash('User %s updated' % user)
|
||||||
return flask.redirect(
|
return flask.redirect(
|
||||||
flask.url_for('.user_list', domain_name=user.domain.name))
|
flask.url_for('.user_list', domain_name=user.domain.name))
|
||||||
return flask.render_template('user/edit.html', form=form, user=user, domain=user.domain)
|
return flask.render_template('user/edit.html', form=form, user=user,
|
||||||
|
domain=user.domain, max_quota_bytes=max_quota_bytes)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/user/delete/<user_email>', methods=['GET', 'POST'])
|
@app.route('/user/delete/<user_email>', methods=['GET', 'POST'])
|
||||||
|
|||||||
23
admin/migrations/versions/2335c80a6bc3_.py
Normal file
23
admin/migrations/versions/2335c80a6bc3_.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
""" Add a maximum quota per domain
|
||||||
|
|
||||||
|
Revision ID: 2335c80a6bc3
|
||||||
|
Revises: 12e9a4f6ed73
|
||||||
|
Create Date: 2016-12-04 12:57:37.576622
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '2335c80a6bc3'
|
||||||
|
down_revision = '12e9a4f6ed73'
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.add_column('domain', sa.Column('max_quota_bytes', sa.Integer(), nullable=False, server_default='0'))
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
with op.batch_alter_table('domain') as batch:
|
||||||
|
batch.drop_column('max_quota_bytes')
|
||||||
Reference in New Issue
Block a user