Merge pull request #252 from Mailu/feature-alternative-domains

Implement alternative domains
master
kaiyou 7 years ago committed by GitHub
commit 4463a4eade

@ -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).
""" """

@ -0,0 +1,9 @@
{% extends "form.html" %}
{% block title %}
{% trans %}Create alternative domain{% endtrans %}
{% endblock %}
{% block subtitle %}
{{ domain }}
{% endblock %}

@ -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>&nbsp; <a href="{{ url_for('.user_list', domain_name=domain.name) }}" title="{% trans %}Users{% endtrans %}"><i class="fa fa-envelope-o"></i></a>&nbsp;
<a href="{{ url_for('.alias_list', domain_name=domain.name) }}" title="{% trans %}Aliases{% endtrans %}"><i class="fa fa-at"></i></a>&nbsp; <a href="{{ url_for('.alias_list', domain_name=domain.name) }}" title="{% trans %}Aliases{% endtrans %}"><i class="fa fa-at"></i></a>&nbsp;
<a href="{{ url_for('.manager_list', domain_name=domain.name) }}" title="{% trans %}Managers{% endtrans %}"><i class="fa fa-user"></i></a>&nbsp; <a href="{{ url_for('.manager_list', domain_name=domain.name) }}" title="{% trans %}Managers{% endtrans %}"><i class="fa fa-user"></i></a>&nbsp;
{% 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>&nbsp;
{% 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>

@ -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()

@ -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')

@ -4,7 +4,9 @@ query =
FROM FROM
(SELECT destination, email, wildcard, localpart FROM alias (SELECT destination, email, wildcard, localpart FROM alias
UNION UNION
SELECT (CASE WHEN forward_enabled=1 THEN (CASE WHEN forward_keep=1 THEN email||',' ELSE '' END)||forward_destination ELSE email END) AS destination, email, 0 as wildcard, localpart FROM user) SELECT (CASE WHEN forward_enabled=1 THEN (CASE WHEN forward_keep=1 THEN email||',' ELSE '' END)||forward_destination ELSE email END) AS destination, email, 0 as wildcard, localpart FROM user
UNION
SELECT '@'||domain_name as destination, '@'||name as email, 0 as wildcard, '' as localpart FROM alternative)
WHERE WHERE
( (
wildcard = 0 wildcard = 0

@ -1,2 +1,5 @@
dbpath = /data/main.db dbpath = /data/main.db
query = SELECT name FROM domain WHERE name='%s' query =
SELECT name FROM domain WHERE name='%s'
UNION
SELECT name FROM alternative WHERE name='%s'

Loading…
Cancel
Save