diff --git a/app/admin/routes.py b/app/admin/routes.py
index 9e70f422..0e2ca625 100644
--- a/app/admin/routes.py
+++ b/app/admin/routes.py
@@ -1,7 +1,7 @@
from datetime import datetime, timedelta
from time import sleep
-from flask import request, flash, json, url_for, current_app, redirect
+from flask import request, flash, json, url_for, current_app, redirect, g
from flask_login import login_required, current_user
from flask_babel import _
from sqlalchemy import text, desc
@@ -15,9 +15,9 @@ from app.admin.forms import FederationForm, SiteMiscForm, SiteProfileForm, EditC
from app.admin.util import unsubscribe_from_everything_then_delete, unsubscribe_from_community
from app.community.util import save_icon_file, save_banner_file
from app.models import AllowedInstances, BannedInstances, ActivityPubLog, utcnow, Site, Community, CommunityMember, \
- User, Instance, File, Report, Topic
+ User, Instance, File, Report, Topic, UserRegistration
from app.utils import render_template, permission_required, set_setting, get_setting, gibberish, markdown_to_html, \
- moderating_communities, joined_communities
+ moderating_communities, joined_communities, finalize_user_setup
from app.admin import bp
@@ -26,7 +26,8 @@ from app.admin import bp
@permission_required('change instance settings')
def admin_home():
return render_template('admin/home.html', title=_('Admin'), moderating_communities=moderating_communities(current_user.get_id()),
- joined_communities=joined_communities(current_user.get_id()))
+ joined_communities=joined_communities(current_user.get_id()),
+ site=g.site)
@bp.route('/site', methods=['GET', 'POST'])
@@ -54,7 +55,8 @@ def admin_site():
form.legal_information.data = site.legal_information
return render_template('admin/site.html', title=_('Site profile'), form=form,
moderating_communities=moderating_communities(current_user.get_id()),
- joined_communities=joined_communities(current_user.get_id())
+ joined_communities=joined_communities(current_user.get_id()),
+ site=g.site
)
@@ -95,7 +97,8 @@ def admin_misc():
form.log_activitypub_json.data = site.log_activitypub_json
return render_template('admin/misc.html', title=_('Misc settings'), form=form,
moderating_communities=moderating_communities(current_user.get_id()),
- joined_communities=joined_communities(current_user.get_id())
+ joined_communities=joined_communities(current_user.get_id()),
+ site=g.site
)
@@ -135,7 +138,8 @@ def admin_federation():
return render_template('admin/federation.html', title=_('Federation settings'), form=form,
moderating_communities=moderating_communities(current_user.get_id()),
- joined_communities=joined_communities(current_user.get_id())
+ joined_communities=joined_communities(current_user.get_id()),
+ site=g.site
)
@@ -155,7 +159,8 @@ def admin_activities():
prev_url = url_for('admin.admin_activities', page=activities.prev_num) if activities.has_prev and page != 1 else None
return render_template('admin/activities.html', title=_('ActivityPub Log'), next_url=next_url, prev_url=prev_url,
- activities=activities)
+ activities=activities,
+ site=g.site)
@bp.route('/activity_json/')
@@ -164,7 +169,9 @@ def admin_activities():
def activity_json(activity_id):
activity = ActivityPubLog.query.get_or_404(activity_id)
return render_template('admin/activity_json.html', title=_('Activity JSON'),
- activity_json_data=json.loads(activity.activity_json), activity=activity, current_app=current_app)
+ activity_json_data=json.loads(activity.activity_json), activity=activity,
+ current_app=current_app,
+ site=g.site)
@bp.route('/activity_json//replay')
@@ -198,7 +205,8 @@ def admin_communities():
return render_template('admin/communities.html', title=_('Communities'), next_url=next_url, prev_url=prev_url,
communities=communities, moderating_communities=moderating_communities(current_user.get_id()),
- joined_communities=joined_communities(current_user.get_id()))
+ joined_communities=joined_communities(current_user.get_id()),
+ site=g.site)
def topics_for_form():
@@ -280,7 +288,8 @@ def admin_community_edit(community_id):
form.default_layout.data = community.default_layout
return render_template('admin/edit_community.html', title=_('Edit community'), form=form, community=community,
moderating_communities=moderating_communities(current_user.get_id()),
- joined_communities=joined_communities(current_user.get_id())
+ joined_communities=joined_communities(current_user.get_id()),
+ site=g.site
)
@@ -332,7 +341,8 @@ def admin_topics():
topics = Topic.query.order_by(Topic.name).all()
return render_template('admin/topics.html', title=_('Topics'), topics=topics,
moderating_communities=moderating_communities(current_user.get_id()),
- joined_communities=joined_communities(current_user.get_id())
+ joined_communities=joined_communities(current_user.get_id()),
+ site=g.site
)
@@ -356,7 +366,8 @@ def admin_topic_add():
return render_template('admin/edit_topic.html', title=_('Add topic'), form=form,
moderating_communities=moderating_communities(current_user.get_id()),
- joined_communities=joined_communities(current_user.get_id())
+ joined_communities=joined_communities(current_user.get_id()),
+ site=g.site
)
@bp.route('/topic//edit', methods=['GET', 'POST'])
@@ -381,7 +392,8 @@ def admin_topic_edit(topic_id):
form.machine_name.data = topic.machine_name
return render_template('admin/edit_topic.html', title=_('Edit topic'), form=form, topic=topic,
moderating_communities=moderating_communities(current_user.get_id()),
- joined_communities=joined_communities(current_user.get_id())
+ joined_communities=joined_communities(current_user.get_id()),
+ site=g.site
)
@@ -425,10 +437,42 @@ def admin_users():
return render_template('admin/users.html', title=_('Users'), next_url=next_url, prev_url=prev_url, users=users,
local_remote=local_remote, search=search,
moderating_communities=moderating_communities(current_user.get_id()),
- joined_communities=joined_communities(current_user.get_id())
+ joined_communities=joined_communities(current_user.get_id()),
+ site=g.site
)
+@bp.route('/approve_registrations', methods=['GET'])
+@login_required
+@permission_required('approve registrations')
+def admin_approve_registrations():
+ registrations = UserRegistration.query.filter_by(status=0).order_by(UserRegistration.created_at).all()
+ recently_approved = UserRegistration.query.filter_by(status=1).order_by(desc(UserRegistration.approved_at)).limit(30)
+ return render_template('admin/approve_registrations.html',
+ registrations=registrations,
+ recently_approved=recently_approved,
+ site=g.site)
+
+
+@bp.route('/approve_registrations//approve', methods=['GET'])
+@login_required
+@permission_required('approve registrations')
+def admin_approve_registrations_approve(user_id):
+ user = User.query.get_or_404(user_id)
+ registration = UserRegistration.query.filter_by(status=0, user_id=user_id).first()
+ if registration:
+ registration.status = 1
+ registration.approved_at = utcnow()
+ registration.approved_by = current_user.id
+ db.session.commit()
+ if user.verified:
+ finalize_user_setup(user, True)
+
+ flash(_('Registration approved.'))
+
+ return redirect(url_for('admin.admin_approve_registrations'))
+
+
@bp.route('/user//edit', methods=['GET', 'POST'])
@login_required
@permission_required('administer all users')
@@ -493,7 +537,8 @@ def admin_user_edit(user_id):
return render_template('admin/edit_user.html', title=_('Edit user'), form=form, user=user,
moderating_communities=moderating_communities(current_user.get_id()),
- joined_communities=joined_communities(current_user.get_id())
+ joined_communities=joined_communities(current_user.get_id()),
+ site=g.site
)
@@ -540,5 +585,6 @@ def admin_reports():
return render_template('admin/reports.html', title=_('Reports'), next_url=next_url, prev_url=prev_url, reports=reports,
local_remote=local_remote, search=search,
moderating_communities=moderating_communities(current_user.get_id()),
- joined_communities=joined_communities(current_user.get_id())
+ joined_communities=joined_communities(current_user.get_id()),
+ site=g.site
)
\ No newline at end of file
diff --git a/app/auth/email.py b/app/auth/email.py
deleted file mode 100644
index ab718db8..00000000
--- a/app/auth/email.py
+++ /dev/null
@@ -1,30 +0,0 @@
-from flask import render_template, current_app
-from flask_babel import _
-from app.email import send_email
-
-
-def send_password_reset_email(user):
- token = user.get_reset_password_token()
- send_email(_('[PieFed] Reset Your Password'),
- sender='PieFed ',
- recipients=[user.email],
- text_body=render_template('email/reset_password.txt',
- user=user, token=token),
- html_body=render_template('email/reset_password.html',
- user=user, token=token))
-
-
-def send_welcome_email(user):
- send_email(_('Welcome to PieFed'),
- sender='PieFed ',
- recipients=[user.email],
- text_body=render_template('email/welcome.txt', user=user),
- html_body=render_template('email/welcome.html', user=user))
-
-
-def send_verification_email(user):
- send_email(_('Please verify your email address'),
- sender='PieFed ',
- recipients=[user.email],
- text_body=render_template('email/verification.txt', user=user),
- html_body=render_template('email/verification.html', user=user))
\ No newline at end of file
diff --git a/app/auth/forms.py b/app/auth/forms.py
index 7bd1899a..eeddf077 100644
--- a/app/auth/forms.py
+++ b/app/auth/forms.py
@@ -20,6 +20,7 @@ class RegistrationForm(FlaskForm):
password2 = PasswordField(
_l('Repeat password'), validators=[DataRequired(),
EqualTo('password')])
+ question = StringField(_('Why would you like to join this site?'), validators=[DataRequired(), Length(min=1, max=512)])
recaptcha = RecaptchaField()
submit = SubmitField(_l('Register'))
diff --git a/app/auth/routes.py b/app/auth/routes.py
index 6a704c98..62591803 100644
--- a/app/auth/routes.py
+++ b/app/auth/routes.py
@@ -3,14 +3,16 @@ from flask import redirect, url_for, flash, request, make_response, session, Mar
from werkzeug.urls import url_parse
from flask_login import login_user, logout_user, current_user
from flask_babel import _
+from wtforms import Label
+
from app import db, cache
from app.auth import bp
from app.auth.forms import LoginForm, RegistrationForm, ResetPasswordRequestForm, ResetPasswordForm
from app.auth.util import random_token, normalize_utf
-from app.models import User, utcnow, IpBan
-from app.auth.email import send_password_reset_email, send_welcome_email, send_verification_email
-from app.activitypub.signature import RsaKeys
-from app.utils import render_template, ip_address, user_ip_banned, user_cookie_banned, banned_ip_addresses
+from app.email import send_verification_email, send_password_reset_email
+from app.models import User, utcnow, IpBan, UserRegistration
+from app.utils import render_template, ip_address, user_ip_banned, user_cookie_banned, banned_ip_addresses, \
+ finalize_user_setup
@bp.route('/login', methods=['GET', 'POST'])
@@ -51,6 +53,8 @@ def login():
# Set a cookie so we have another way to track banned people
response.set_cookie('sesion', '17489047567495', expires=datetime(year=2099, month=12, day=30))
return response
+ if user.waiting_for_approval():
+ return redirect(url_for('auth.please_wait'))
login_user(user, remember=True)
current_user.last_seen = utcnow()
current_user.ip_address = ip_address()
@@ -84,6 +88,8 @@ def register():
if current_user.is_authenticated:
return redirect(url_for('main.index'))
form = RegistrationForm()
+ if g.site.registration_mode != 'RequireApplication':
+ form.question.validators = ()
if form.validate_on_submit():
if form.email.data == '': # ignore any registration where the email field is filled out. spam prevention
if form.real_email.data.lower().startswith('postmaster@') or form.real_email.data.lower().startswith('abuse@') or \
@@ -104,20 +110,37 @@ def register():
user.set_password(form.password.data)
db.session.add(user)
db.session.commit()
- login_user(user, remember=True)
- send_welcome_email(user)
send_verification_email(user)
-
if current_app.config['MODE'] == 'development':
current_app.logger.info('Verify account:' + url_for('auth.verify_email', token=user.verification_token, _external=True))
-
- flash(_('Great, you are now a registered user!'))
+ if g.site.registration_mode == 'RequireApplication':
+ application = UserRegistration(user_id=user.id, answer=form.question.data)
+ db.session.add(application)
+ db.session.commit()
+ return redirect(url_for('auth.please_wait'))
+ else:
+ return redirect(url_for('auth.check_email'))
resp = make_response(redirect(url_for('topic.choose_topics')))
if user_ip_banned():
resp.set_cookie('sesion', '17489047567495', expires=datetime(year=2099, month=12, day=30))
return resp
- return render_template('auth/register.html', title=_('Register'), form=form, site=g.site)
+ else:
+ if g.site.registration_mode == 'RequireApplication' and g.site.application_question != '':
+ form.question.label = Label('question', g.site.application_question)
+ if g.site.registration_mode != 'RequireApplication':
+ del form.question
+ return render_template('auth/register.html', title=_('Register'), form=form, site=g.site)
+
+
+@bp.route('/please_wait', methods=['GET'])
+def please_wait():
+ return render_template('auth/please_wait.html', title=_('Account under review'), site=g.site)
+
+
+@bp.route('/check_email', methods=['GET'])
+def check_email():
+ return render_template('auth/check_email.html', title=_('Check your email'), site=g.site)
@bp.route('/reset_password_request', methods=['GET', 'POST'])
@@ -168,21 +191,21 @@ def verify_email(token):
if user.verified: # guard against users double-clicking the link in the email
return redirect(url_for('main.index'))
user.verified = True
- user.last_seen = utcnow()
- private_key, public_key = RsaKeys.generate_keypair()
- user.private_key = private_key
- user.public_key = public_key
- user.ap_profile_id = f"https://{current_app.config['SERVER_NAME']}/u/{user.user_name}"
- user.ap_public_url = f"https://{current_app.config['SERVER_NAME']}/u/{user.user_name}"
- user.ap_inbox_url = f"https://{current_app.config['SERVER_NAME']}/u/{user.user_name}/inbox"
db.session.commit()
- flash(_('Thank you for verifying your email address.'))
+ if not user.waiting_for_approval():
+ finalize_user_setup(user)
+ else:
+ flash(_('Thank you for verifying your email address.'))
else:
flash(_('Email address validation failed.'), 'error')
- if len(user.communities()) == 0:
- return redirect(url_for('topic.choose_topics'))
+ if user.waiting_for_approval():
+ return redirect(url_for('auth.please_wait'))
else:
- return redirect(url_for('main.index'))
+ login_user(user, remember=True)
+ if len(user.communities()) == 0:
+ return redirect(url_for('topic.choose_topics'))
+ else:
+ return redirect(url_for('main.index'))
@bp.route('/validation_required')
diff --git a/app/cli.py b/app/cli.py
index 257d4724..dba51034 100644
--- a/app/cli.py
+++ b/app/cli.py
@@ -9,8 +9,8 @@ import click
import os
from app.activitypub.signature import RsaKeys
-from app.auth.email import send_verification_email
from app.auth.util import random_token
+from app.email import send_verification_email
from app.models import Settings, BannedInstances, Interest, Role, User, RolePermission, Domain, ActivityPubLog, \
utcnow, Site, Instance
from app.utils import file_get_contents, retrieve_block_list
diff --git a/app/email.py b/app/email.py
index d43bb935..59884139 100644
--- a/app/email.py
+++ b/app/email.py
@@ -1,6 +1,6 @@
from flask import current_app, render_template, escape
from app import db, celery
-from flask_babel import _, lazy_gettext as _l # todo: set the locale based on account_id so that _() works
+from flask_babel import _, lazy_gettext as _l # todo: set the locale based on account_id so that _() works
import boto3
from botocore.exceptions import ClientError
from typing import List
@@ -9,6 +9,34 @@ AWS_REGION = "ap-southeast-2"
CHARSET = "UTF-8"
+def send_password_reset_email(user):
+ token = user.get_reset_password_token()
+ send_email(_('[PieFed] Reset Your Password'),
+ sender='PieFed ',
+ recipients=[user.email],
+ text_body=render_template('email/reset_password.txt',
+ user=user, token=token),
+ html_body=render_template('email/reset_password.html',
+ user=user, token=token))
+
+
+def send_verification_email(user):
+ send_email(_('Please verify your email address'),
+ sender='PieFed ',
+ recipients=[user.email],
+ text_body=render_template('email/verification.txt', user=user),
+ html_body=render_template('email/verification.html', user=user))
+
+
+def send_welcome_email(user, application_required):
+ subject = _('Your application has been approved - welcome to PieFed') if application_required else _('Welcome to PieFed')
+ send_email(subject,
+ sender='PieFed ',
+ recipients=[user.email],
+ text_body=render_template('email/welcome.txt', user=user, application_required=application_required),
+ html_body=render_template('email/welcome.html', user=user, application_required=application_required))
+
+
@celery.task
def send_async_email(subject, sender, recipients, text_body, html_body, reply_to):
if type(recipients) == str:
diff --git a/app/models.py b/app/models.py
index 838b90e2..a336c25a 100644
--- a/app/models.py
+++ b/app/models.py
@@ -447,6 +447,10 @@ class User(UserMixin, db.Model):
def is_local(self):
return self.ap_id is None or self.ap_profile_id.startswith('https://' + current_app.config['SERVER_NAME'])
+ def waiting_for_approval(self):
+ application = UserRegistration.query.filter_by(user_id=self.id, status=0).first()
+ return application is not None
+
@cache.memoize(timeout=30)
def is_admin(self):
for role in self.roles:
@@ -581,6 +585,8 @@ class User(UserMixin, db.Model):
file.delete_from_disk()
self.avatar_id = None
db.session.delete(file)
+ if self.waiting_for_approval():
+ db.session.query(UserRegistration).filter(UserRegistration.user_id == self.id).delete()
def purge_content(self):
files = File.query.join(Post).filter(Post.user_id == self.id).all()
@@ -861,6 +867,17 @@ class UserFollowRequest(db.Model):
follow_id = db.Column(db.Integer, db.ForeignKey('user.id'))
+class UserRegistration(db.Model):
+ id = db.Column(db.Integer, primary_key=True)
+ user_id = db.Column(db.Integer, db.ForeignKey('user.id'), index=True)
+ answer = db.Column(db.String(512))
+ status = db.Column(db.Integer, default=0, index=True) # 0 = unapproved, 1 = approved
+ created_at = db.Column(db.DateTime, default=utcnow)
+ approved_at = db.Column(db.DateTime)
+ approved_by = db.Column(db.Integer, db.ForeignKey('user.id'))
+ user = db.relationship('User', foreign_keys=[user_id], lazy='joined')
+
+
class PostVote(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), index=True)
@@ -980,7 +997,7 @@ class Site(db.Model):
enable_nsfl = db.Column(db.Boolean, default=False)
community_creation_admin_only = db.Column(db.Boolean, default=False)
reports_email_admins = db.Column(db.Boolean, default=True)
- registration_mode = db.Column(db.String(20), default='Closed')
+ registration_mode = db.Column(db.String(20), default='Closed') # possible values: Open, RequireApplication, Closed
application_question = db.Column(db.Text, default='')
allow_or_block_list = db.Column(db.Integer, default=2) # 1 = allow list, 2 = block list
allowlist = db.Column(db.Text, default='')
diff --git a/app/static/structure.css b/app/static/structure.css
index bc67fda1..3648d938 100644
--- a/app/static/structure.css
+++ b/app/static/structure.css
@@ -598,7 +598,7 @@ fieldset legend {
}
.post_list .post_teaser .utilities_row a {
display: inline-block;
- width: 44px;
+ min-width: 44px;
}
.post_list .post_teaser .utilities_row .preview_image, .post_list .post_teaser .utilities_row .post_options {
text-align: center;
diff --git a/app/static/structure.scss b/app/static/structure.scss
index ce1a8545..f1c3f7da 100644
--- a/app/static/structure.scss
+++ b/app/static/structure.scss
@@ -221,7 +221,7 @@ nav, etc which are used site-wide */
.utilities_row {
a {
display: inline-block;
- width: 44px;
+ min-width: 44px;
}
.preview_image, .post_options {
text-align: center;
diff --git a/app/templates/admin/_nav.html b/app/templates/admin/_nav.html
index eb18c5a2..0df1acfe 100644
--- a/app/templates/admin/_nav.html
+++ b/app/templates/admin/_nav.html
@@ -4,7 +4,10 @@
{{ _('Misc settings') }} |
{{ _('Communities') }} |
{{ _('Topics') }} |
- {{ _('Users') }} |
+ {{ _('Users') }} |
+ {% if site.registration_mode == 'RequireApplication' %}
+ {{ _('Registration applications') }} |
+ {% endif %}
{{ _('Moderation') }} |
{{ _('Federation') }} |
{{ _('Activities') }}
diff --git a/app/templates/admin/approve_registrations.html b/app/templates/admin/approve_registrations.html
new file mode 100644
index 00000000..0b86f2eb
--- /dev/null
+++ b/app/templates/admin/approve_registrations.html
@@ -0,0 +1,49 @@
+{% extends "base.html" %}
+{% from 'bootstrap/form.html' import render_form %}
+
+{% block app_content %}
+
+
+ {% include 'admin/_nav.html' %}
+
+
+
+
+
+ {% if registrations %}
+
{{ _('When registering, people are asked "%(question)s".', question=site.application_question) }}
+
+
+
+ Name |
+ Email |
+ Email verifed |
+ Answer |
+ Applied |
+ IP |
+ Actions |
+
+ {% for registration in registrations %}
+
+
+ {{ registration.user.display_name() }} |
+ {{ registration.user.email }} |
+ {{ '✓'|safe if registration.user.verified else '✗'|safe }} |
+ {{ registration.answer }} |
+ {{ moment(registration.created_at).fromNow() }} |
+ {{ registration.user.ip_address if registration.user.ip_address }} |
+ {{ _('Approve') }}
+ {{ _('View') }} |
+ {{ _('Delete') }}
+ |
+
+ {% endfor %}
+
+ {% else %}
+
{{ _('No one is waiting to be approved.') }}
+ {% endif %}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/app/templates/admin/users.html b/app/templates/admin/users.html
index 627d8221..f2a52d34 100644
--- a/app/templates/admin/users.html
+++ b/app/templates/admin/users.html
@@ -33,7 +33,12 @@
{{ user.display_name() }} |
{{ 'Local' if user.is_local() else 'Remote' }} |
- {{ user.last_seen }} |
+ {% if request.args.get('local_remote', '') == 'local' %}
+ {{ moment(user.last_seen).fromNow() }}
+ {% else %}
+ {{ user.last_seen }}
+ {% endif %}
+ |
{{ user.attitude * 100 if user.attitude != 1 }} |
{{ 'R ' + str(user.reputation) if user.reputation }} |
{{ 'Banned'|safe if user.banned }} |
diff --git a/app/templates/auth/check_email.html b/app/templates/auth/check_email.html
new file mode 100644
index 00000000..a8ab001a
--- /dev/null
+++ b/app/templates/auth/check_email.html
@@ -0,0 +1,15 @@
+{% extends 'base.html' %}
+
+{% block app_content %}
+
+
+
+
+
{{ _('Check your email') }}
+
{{ _('We sent you an email containing a link that you need to click to enable your account.') }}
+
+
+
+
+
+{% endblock %}
diff --git a/app/templates/auth/please_wait.html b/app/templates/auth/please_wait.html
new file mode 100644
index 00000000..04e96144
--- /dev/null
+++ b/app/templates/auth/please_wait.html
@@ -0,0 +1,15 @@
+{% extends 'base.html' %}
+
+{% block app_content %}
+
+
+
+
+
{{ _('Thanks for registering') }}
+
{{ _('We are reviewing your application and will email you once it has been accepted.') }}
+
+
+
+
+
+{% endblock %}
diff --git a/app/templates/email/welcome.html b/app/templates/email/welcome.html
index 990ba21a..43382828 100644
--- a/app/templates/email/welcome.html
+++ b/app/templates/email/welcome.html
@@ -3,7 +3,7 @@
I'm Rimu, the founder of PieFed, and I'm super grateful for your sign-up.
As PieFed is still in an early development phase, your thoughts and ideas mean the world to me. You might stumble upon a
few hiccups or notice some missing bits here and there — that's totally expected. Just hit reply to this email and
- let me know what's up. Screenshots, if you've got 'em, will make a world of difference!
+ let me know what's up. Screenshots, if you've got 'em, will be very helpful!
If you are technically-minded, reporting issues on the Codeberg
issue tracker would be awesome. And hey, if Python's
your jam and you're keen to pitch in, check out the project site or
diff --git a/app/templates/user/edit_profile.html b/app/templates/user/edit_profile.html
index ba8640b8..34c04570 100644
--- a/app/templates/user/edit_profile.html
+++ b/app/templates/user/edit_profile.html
@@ -12,14 +12,14 @@
{{ _('Edit profile of %(name)s', name=user.user_name) }}
-