mirror of
https://github.com/Mailu/Mailu.git
synced 2025-02-21 19:19:39 +02:00
Add the ability to configure alternative domains
This commit is contained in:
parent
07c789fc8b
commit
f823f1e8a5
@ -28,4 +28,5 @@ from mailu.admin.views import \
|
|||||||
aliases, \
|
aliases, \
|
||||||
users, \
|
users, \
|
||||||
domains, \
|
domains, \
|
||||||
|
alternatives, \
|
||||||
fetches
|
fetches
|
||||||
|
@ -51,6 +51,11 @@ class DomainForm(flask_wtf.FlaskForm):
|
|||||||
submit = fields.SubmitField(_('Create'))
|
submit = fields.SubmitField(_('Create'))
|
||||||
|
|
||||||
|
|
||||||
|
class AlternativeForm(flask_wtf.FlaskForm):
|
||||||
|
name = fields.StringField(_('Alternative name'), [validators.DataRequired()])
|
||||||
|
submit = fields.SubmitField(_('Create'))
|
||||||
|
|
||||||
|
|
||||||
class UserForm(flask_wtf.FlaskForm):
|
class UserForm(flask_wtf.FlaskForm):
|
||||||
localpart = fields.StringField(_('E-mail'), [validators.DataRequired()])
|
localpart = fields.StringField(_('E-mail'), [validators.DataRequired()])
|
||||||
pw = fields.PasswordField(_('Password'), [validators.DataRequired()])
|
pw = fields.PasswordField(_('Password'), [validators.DataRequired()])
|
||||||
|
@ -100,6 +100,22 @@ class Domain(Base):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class Alternative(Base):
|
||||||
|
""" Alternative name for a served domain.
|
||||||
|
The name "domain alias" was avoided to prevent some confusion.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__tablename__ = "alternative"
|
||||||
|
|
||||||
|
name = db.Column(db.String(80), primary_key=True, nullable=False)
|
||||||
|
domain_name = db.Column(db.String(80), db.ForeignKey(Domain.name))
|
||||||
|
domain = db.relationship(Domain,
|
||||||
|
backref=db.backref('alternatives', cascade='all, delete-orphan'))
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class Email(object):
|
class Email(object):
|
||||||
""" Abstraction for an email address (localpart and domain).
|
""" Abstraction for an email address (localpart and domain).
|
||||||
"""
|
"""
|
||||||
|
9
admin/mailu/admin/templates/alternative/create.html
Normal file
9
admin/mailu/admin/templates/alternative/create.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{% extends "form.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{% trans %}Create alternative domain{% endtrans %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block subtitle %}
|
||||||
|
{{ domain }}
|
||||||
|
{% endblock %}
|
34
admin/mailu/admin/templates/alternative/list.html
Normal file
34
admin/mailu/admin/templates/alternative/list.html
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{% trans %}Alternative domain list{% endtrans %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block subtitle %}
|
||||||
|
{{ domain.name }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main_action %}
|
||||||
|
<a class="btn btn-primary" href="{{ url_for('.alternative_create', domain_name=domain.name) }}">{% trans %}Add alternative{% endtrans %}</a>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block box %}
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans %}Actions{% endtrans %}</th>
|
||||||
|
<th>{% trans %}Name{% endtrans %}</th>
|
||||||
|
<th>{% trans %}Created{% endtrans %}</th>
|
||||||
|
</tr>
|
||||||
|
{% for alternative in domain.alternatives %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="{{ url_for('.alternative_delete', alternative=alternative.name) }}" title="{% trans %}Delete{% endtrans %}"><i class="fa fa-trash"></i></a>
|
||||||
|
</td>
|
||||||
|
<td>{{ alternative }}</td>
|
||||||
|
<td>{{ alternative.created_at }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endblock %}
|
@ -36,6 +36,9 @@
|
|||||||
<a href="{{ url_for('.user_list', domain_name=domain.name) }}" title="{% trans %}Users{% endtrans %}"><i class="fa fa-envelope-o"></i></a>
|
<a href="{{ url_for('.user_list', domain_name=domain.name) }}" title="{% trans %}Users{% endtrans %}"><i class="fa fa-envelope-o"></i></a>
|
||||||
<a href="{{ url_for('.alias_list', domain_name=domain.name) }}" title="{% trans %}Aliases{% endtrans %}"><i class="fa fa-at"></i></a>
|
<a href="{{ url_for('.alias_list', domain_name=domain.name) }}" title="{% trans %}Aliases{% endtrans %}"><i class="fa fa-at"></i></a>
|
||||||
<a href="{{ url_for('.manager_list', domain_name=domain.name) }}" title="{% trans %}Managers{% endtrans %}"><i class="fa fa-user"></i></a>
|
<a href="{{ url_for('.manager_list', domain_name=domain.name) }}" title="{% trans %}Managers{% endtrans %}"><i class="fa fa-user"></i></a>
|
||||||
|
{% if current_user.global_admin %}
|
||||||
|
<a href="{{ url_for('.alternative_list', domain_name=domain.name) }}" title="{% trans %}Alternatives{% endtrans %}"><i class="fa fa-asterisk"></i></a>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ domain.name }}</td>
|
<td>{{ domain.name }}</td>
|
||||||
<td>{{ domain.users | count }} / {{ domain.max_users or '∞' }}</td>
|
<td>{{ domain.users | count }} / {{ domain.max_users or '∞' }}</td>
|
||||||
|
46
admin/mailu/admin/views/alternatives.py
Normal file
46
admin/mailu/admin/views/alternatives.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
from mailu.admin import app, db, models, forms, access
|
||||||
|
|
||||||
|
import flask
|
||||||
|
import wtforms_components
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/alternative/list/<domain_name>', methods=['GET'])
|
||||||
|
@access.global_admin
|
||||||
|
def alternative_list(domain_name):
|
||||||
|
domain = models.Domain.query.get(domain_name) or flask.abort(404)
|
||||||
|
return flask.render_template('alternative/list.html', domain=domain)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/alternative/create/<domain_name>', methods=['GET', 'POST'])
|
||||||
|
@access.global_admin
|
||||||
|
def alternative_create(domain_name):
|
||||||
|
domain = models.Domain.query.get(domain_name) or flask.abort(404)
|
||||||
|
form = forms.AlternativeForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
conflicting_domain = models.Domain.query.get(form.name.data)
|
||||||
|
conflicting_alternative = models.Alternative.query.get(form.name.data)
|
||||||
|
if conflicting_domain or conflicting_alternative:
|
||||||
|
flask.flash('Domain %s is already used' % form.name.data, 'error')
|
||||||
|
else:
|
||||||
|
alternative = models.Alternative(domain=domain)
|
||||||
|
form.populate_obj(alternative)
|
||||||
|
db.session.add(alternative)
|
||||||
|
db.session.commit()
|
||||||
|
flask.flash('Alternative domain %s created' % alternative)
|
||||||
|
return flask.redirect(
|
||||||
|
flask.url_for('.alternative_list', domain_name=domain.name))
|
||||||
|
return flask.render_template('alternative/create.html',
|
||||||
|
domain=domain, form=form)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/alternative/delete/<alternative>', methods=['GET', 'POST'])
|
||||||
|
@access.global_admin
|
||||||
|
@access.confirmation_required("delete {alternative}")
|
||||||
|
def alternative_delete(alternative):
|
||||||
|
alternative = models.Alternative.query.get(alternative) or flask.abort(404)
|
||||||
|
domain = alternative.domain
|
||||||
|
db.session.delete(alternative)
|
||||||
|
db.session.commit()
|
||||||
|
flask.flash('Alternative %s deleted' % alternative)
|
||||||
|
return flask.redirect(
|
||||||
|
flask.url_for('.alternative_list', domain_name=domain.name))
|
@ -16,7 +16,9 @@ def domain_list():
|
|||||||
def domain_create():
|
def domain_create():
|
||||||
form = forms.DomainForm()
|
form = forms.DomainForm()
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
if models.Domain.query.get(form.name.data):
|
conflicting_domain = models.Domain.query.get(form.name.data)
|
||||||
|
conflicting_alternative = models.Alternative.query.get(form.name.data)
|
||||||
|
if conflicting_domain or conflicting_alternative:
|
||||||
flask.flash('Domain %s is already used' % form.name.data, 'error')
|
flask.flash('Domain %s is already used' % form.name.data, 'error')
|
||||||
else:
|
else:
|
||||||
domain = models.Domain()
|
domain = models.Domain()
|
||||||
|
30
admin/migrations/versions/c9a0b4e653cf_.py
Normal file
30
admin/migrations/versions/c9a0b4e653cf_.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
""" Add alternative domains
|
||||||
|
|
||||||
|
Revision ID: c9a0b4e653cf
|
||||||
|
Revises: 73e56bad5ec5
|
||||||
|
Create Date: 2017-09-03 18:23:36.356527
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'c9a0b4e653cf'
|
||||||
|
down_revision = '73e56bad5ec5'
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.create_table('alternative',
|
||||||
|
sa.Column('created_at', sa.Date(), nullable=False),
|
||||||
|
sa.Column('updated_at', sa.Date(), nullable=True),
|
||||||
|
sa.Column('comment', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('name', sa.String(length=80), nullable=False),
|
||||||
|
sa.Column('domain_name', sa.String(length=80), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['domain_name'], ['domain.name'], ),
|
||||||
|
sa.PrimaryKeyConstraint('name')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
op.drop_table('alternative')
|
Loading…
x
Reference in New Issue
Block a user