Enable self-service account signup
This commit is contained in:
@@ -60,6 +60,7 @@ class Domain(Base):
|
|||||||
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)
|
max_quota_bytes = db.Column(db.Integer(), nullable=False, default=0)
|
||||||
|
signup_enabled = db.Column(db.Boolean(), nullable=False, default=False)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def dkim_key(self):
|
def dkim_key(self):
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ class DomainForm(flask_wtf.FlaskForm):
|
|||||||
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)
|
max_quota_bytes = fields_.IntegerSliderField(_('Maximum user quota'), default=0)
|
||||||
|
signup_enabled = fields.BooleanField(_('Enable sign-up'), default=False)
|
||||||
comment = fields.StringField(_('Comment'))
|
comment = fields.StringField(_('Comment'))
|
||||||
submit = fields.SubmitField(_('Create'))
|
submit = fields.SubmitField(_('Create'))
|
||||||
|
|
||||||
@@ -74,6 +75,13 @@ class UserForm(flask_wtf.FlaskForm):
|
|||||||
submit = fields.SubmitField(_('Save'))
|
submit = fields.SubmitField(_('Save'))
|
||||||
|
|
||||||
|
|
||||||
|
class UserSignupForm(flask_wtf.FlaskForm):
|
||||||
|
localpart = fields.StringField(_('Email address'))
|
||||||
|
pw = fields.PasswordField(_('Password'), [validators.DataRequired()])
|
||||||
|
pw2 = fields.PasswordField(_('Confirm password'), [validators.EqualTo('pw')])
|
||||||
|
submit = fields.SubmitField(_('Sign up'))
|
||||||
|
|
||||||
|
|
||||||
class UserSettingsForm(flask_wtf.FlaskForm):
|
class UserSettingsForm(flask_wtf.FlaskForm):
|
||||||
displayed_name = fields.StringField(_('Displayed name'))
|
displayed_name = fields.StringField(_('Displayed name'))
|
||||||
spam_enabled = fields.BooleanField(_('Enable spam filter'))
|
spam_enabled = fields.BooleanField(_('Enable spam filter'))
|
||||||
|
|||||||
@@ -100,10 +100,17 @@
|
|||||||
</li>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li>
|
<li>
|
||||||
<a href="#">
|
<a href="{{ url_for('.login') }}">
|
||||||
<i class="fa fa-sign-in"></i> <span>{% trans %}Sign in{% endtrans %}</span>
|
<i class="fa fa-sign-in"></i> <span>{% trans %}Sign in{% endtrans %}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% if signup_domains %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ url_for('.user_signup') }}">
|
||||||
|
<i class="fa fa-user-plus"></i> <span>{% trans %}Sign up{% endtrans %}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
20
core/admin/mailu/ui/templates/user/signup.html
Normal file
20
core/admin/mailu/ui/templates/user/signup.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{% trans %}Sign up{% endtrans %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block subtitle %}
|
||||||
|
{{ domain }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form class="form" method="post" role="form">
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
{% call macros.box() %}
|
||||||
|
{{ macros.form_field(form.localpart, append='<span class="input-group-addon">@'+domain.name+'</span>') }}
|
||||||
|
{{ macros.form_fields((form.pw, form.pw2)) }}
|
||||||
|
{{ macros.form_field(form.submit) }}
|
||||||
|
{% endcall %}
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
26
core/admin/mailu/ui/templates/user/signup_domain.html
Normal file
26
core/admin/mailu/ui/templates/user/signup_domain.html
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{% trans %}Sign up{% endtrans %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block subtitle %}
|
||||||
|
{% trans %}pick a domain for the new account{% endtrans %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% call macros.table() %}
|
||||||
|
<tr>
|
||||||
|
<th>{% trans %}Domain{% endtrans %}</th>
|
||||||
|
<th>{% trans %}Available slots{% endtrans %}</th>
|
||||||
|
<th>{% trans %}Quota{% endtrans %}</th>
|
||||||
|
</tr>
|
||||||
|
{% for domain_name, domain in available_domains.items() %}
|
||||||
|
<tr>
|
||||||
|
<td><a href="{{ url_for('.user_signup', domain_name=domain_name) }}">{{ domain_name }}</a></td>
|
||||||
|
<td>{{ domain.max_users - domain.users if domain.max_users else '∞' }}</td>
|
||||||
|
<td>{{ domain.max_quota_bytes or config['DEFAULT_QUOTA'] | filesizeformat }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
{% endcall %}
|
||||||
|
{% endblock %}
|
||||||
@@ -153,3 +153,35 @@ def user_reply(user_email):
|
|||||||
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/reply.html', form=form, user=user)
|
return flask.render_template('user/reply.html', form=form, user=user)
|
||||||
|
|
||||||
|
|
||||||
|
@ui.route('/user/signup', methods=['GET', 'POST'])
|
||||||
|
@ui.route('/user/signup/<domain_name>', methods=['GET', 'POST'])
|
||||||
|
def user_signup(domain_name=None):
|
||||||
|
available_domains = {
|
||||||
|
domain.name: domain
|
||||||
|
for domain in models.Domain.query.filter_by(signup_enabled=True).all()
|
||||||
|
if not domain.max_users or len(domain.users) < domain.max_users
|
||||||
|
}
|
||||||
|
if not available_domains:
|
||||||
|
flask.flash('No domain available for registration')
|
||||||
|
if not domain_name:
|
||||||
|
return flask.render_template('user/signup_domain.html',
|
||||||
|
available_domains=available_domains)
|
||||||
|
domain = available_domains.get(domain_name) or flask.abort(404)
|
||||||
|
quota_bytes = min(config['DEFAULT_QUOTA'], domain.max_quota_bytes)
|
||||||
|
form = forms.UserSignupForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
if domain.has_email(form.localpart.data):
|
||||||
|
flask.flash('Email is already used', 'error')
|
||||||
|
else:
|
||||||
|
user = models.User(domain=domain)
|
||||||
|
form.populate_obj(user)
|
||||||
|
user.set_password(form.pw.data)
|
||||||
|
user.quota_bytes = quota_bytes
|
||||||
|
db.session.add(user)
|
||||||
|
db.session.commit()
|
||||||
|
user.send_welcome()
|
||||||
|
flask.flash('Successfully signed up %s' % user)
|
||||||
|
return flask.redirect(flask.url_for('.index'))
|
||||||
|
return flask.render_template('user/signup.html', domain=domain, form=form)
|
||||||
|
|||||||
Reference in New Issue
Block a user