You've already forked Mailu
							
							
				mirror of
				https://github.com/Mailu/Mailu.git
				synced 2025-10-30 23:37:43 +02:00 
			
		
		
		
	Add the ability to configure alternative domains
This commit is contained in:
		| @@ -28,4 +28,5 @@ from mailu.admin.views import \ | ||||
|     aliases, \ | ||||
|     users, \ | ||||
|     domains, \ | ||||
|     alternatives, \ | ||||
|     fetches | ||||
|   | ||||
| @@ -51,6 +51,11 @@ class DomainForm(flask_wtf.FlaskForm): | ||||
|     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): | ||||
|     localpart = fields.StringField(_('E-mail'), [validators.DataRequired()]) | ||||
|     pw = fields.PasswordField(_('Password'), [validators.DataRequired()]) | ||||
|   | ||||
| @@ -100,6 +100,22 @@ class Domain(Base): | ||||
|             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): | ||||
|     """ 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('.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>  | ||||
|         {% 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>{{ domain.name }}</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(): | ||||
|     form = forms.DomainForm() | ||||
|     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') | ||||
|         else: | ||||
|             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') | ||||
		Reference in New Issue
	
	Block a user