diff --git a/admin/mailu/admin/forms.py b/admin/mailu/admin/forms.py index c17128a0..015a6df1 100644 --- a/admin/mailu/admin/forms.py +++ b/admin/mailu/admin/forms.py @@ -46,6 +46,7 @@ class DomainForm(flask_wtf.FlaskForm): name = fields.StringField(_('Domain name'), [validators.DataRequired()]) max_users = fields_.IntegerField(_('Maximum user 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')) submit = fields.SubmitField(_('Create')) diff --git a/admin/mailu/admin/models.py b/admin/mailu/admin/models.py index d7d46e65..ff46b9f9 100644 --- a/admin/mailu/admin/models.py +++ b/admin/mailu/admin/models.py @@ -57,6 +57,7 @@ class Domain(Base): backref=db.backref('manager_of'), lazy='dynamic') max_users = 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 def dkim_key(self): diff --git a/admin/mailu/admin/templates/domain/create.html b/admin/mailu/admin/templates/domain/create.html index 29a4f431..301d91ac 100644 --- a/admin/mailu/admin/templates/domain/create.html +++ b/admin/mailu/admin/templates/domain/create.html @@ -9,6 +9,9 @@ {{ form.hidden_tag() }} {{ macros.form_field(form.name) }} {{ macros.form_fields((form.max_users, form.max_aliases)) }} + {{ macros.form_field(form.max_quota_bytes, step=1000000000, max=50000000000, + prepend=''+((form.max_quota_bytes.data//1000000000).__str__() if form.max_quota_bytes.data else '∞')+' GiB', + oninput='$("#quota").text(this.value == 0 ? "∞" : this.value/1000000000);') }} {{ macros.form_field(form.comment) }} {{ macros.form_field(form.submit) }} diff --git a/admin/mailu/admin/templates/user/create.html b/admin/mailu/admin/templates/user/create.html index d0907ed1..829b6c80 100644 --- a/admin/mailu/admin/templates/user/create.html +++ b/admin/mailu/admin/templates/user/create.html @@ -13,7 +13,7 @@ {{ form.hidden_tag() }} {{ macros.form_field(form.localpart, append='@'+domain.name+'') }} {{ 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=''+(form.quota_bytes.data//1000000000).__str__()+' GiB', oninput='$("#quota").text(this.value/1000000000);') }} {{ macros.form_field(form.enable_imap) }} diff --git a/admin/mailu/admin/views/users.py b/admin/mailu/admin/views/users.py index 9fa7bb1b..b7647138 100644 --- a/admin/mailu/admin/views/users.py +++ b/admin/mailu/admin/views/users.py @@ -2,6 +2,7 @@ from mailu.admin import app, db, models, forms, access import flask import flask_login +import wtforms import wtforms_components @@ -21,6 +22,8 @@ def user_create(domain_name): return flask.redirect( flask.url_for('.user_list', domain_name=domain.name)) form = forms.UserForm() + form.quota_bytes.validators = [ + wtforms.validators.NumberRange(max=domain.max_quota_bytes)] if form.validate_on_submit(): if domain.has_email(form.localpart.data): flask.flash('Email is already used', 'error') @@ -41,10 +44,17 @@ def user_create(domain_name): @access.domain_admin(models.User, 'user_email') def user_edit(user_email): 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) wtforms_components.read_only(form.localpart) form.pw.validators = [] form.localpart.validators = [] + form.quota_bytes.validators = [ + wtforms.validators.NumberRange(max=max_quota_bytes)] if form.validate_on_submit(): form.populate_obj(user) if form.pw.data: @@ -53,7 +63,8 @@ def user_edit(user_email): flask.flash('User %s updated' % user) return flask.redirect( 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/', methods=['GET', 'POST']) diff --git a/admin/migrations/versions/2335c80a6bc3_.py b/admin/migrations/versions/2335c80a6bc3_.py new file mode 100644 index 00000000..ac5c1e83 --- /dev/null +++ b/admin/migrations/versions/2335c80a6bc3_.py @@ -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')