From b108ce4cfe1c193f0b9c74639cbd376ce069aed3 Mon Sep 17 00:00:00 2001 From: Darks Date: Wed, 29 Jul 2020 00:57:06 +0200 Subject: [PATCH] titles: add displayed title (#65) - with forms for user and admins --- app/forms/account.py | 16 ++++++++++ app/models/users.py | 8 ++++- app/routes/account/account.py | 5 ++++ app/routes/admin/account.py | 7 ++++- app/static/css/form.css | 4 +++ app/static/css/widgets.css | 1 + app/templates/account/account.html | 7 +++++ app/templates/admin/edit_account.html | 7 +++++ app/templates/widgets/user.html | 2 +- app/utils/validators.py | 19 ++++++++++++ ...d2eaf0413_add_displayed_title_to_member.py | 30 +++++++++++++++++++ 11 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 migrations/versions/001d2eaf0413_add_displayed_title_to_member.py diff --git a/app/forms/account.py b/app/forms/account.py index 227fafc..3115ddb 100644 --- a/app/forms/account.py +++ b/app/forms/account.py @@ -110,6 +110,14 @@ class UpdateAccountForm(FlaskForm): Optional(), ] ) + title = SelectField( + 'Titre', + coerce=int, + validators=[ + Optional(), + vd.own_title, + ] + ) newsletter = BooleanField( 'Inscription à la newsletter', description='Un mail par trimestre environ, pour être prévenu des concours, évènements et nouveautés.', @@ -226,6 +234,14 @@ class AdminUpdateAccountForm(FlaskForm): Optional(), ], ) + title = SelectField( + 'Titre', + coerce=int, + validators=[ + Optional(), + # Admin can set any title to any member! + ] + ) newsletter = BooleanField( 'Inscription à la newsletter', description='Un mail par trimestre environ, pour être prévenu des concours, évènements et nouveautés.', diff --git a/app/models/users.py b/app/models/users.py index 7b7cfed..63493c2 100644 --- a/app/models/users.py +++ b/app/models/users.py @@ -7,7 +7,7 @@ from PIL import Image from app import app, db from app.models.privs import SpecialPrivilege, Group, GroupMember, \ GroupPrivilege -from app.models.trophies import Trophy, TrophyMember +from app.models.trophies import Trophy, TrophyMember, Title from app.models.notification import Notification import app.utils.unicode_names as unicode_names from app.utils.notify import notify @@ -105,6 +105,10 @@ class Member(User): signature = db.Column(db.UnicodeText) birthday = db.Column(db.Date) + # Displayed title, if set + title_id = db.Column(db.Integer, db.ForeignKey('title.id'), nullable=True) + title = db.relationship('Title', foreign_keys=title_id) + # Settings newsletter = db.Column(db.Boolean, default=False) @@ -204,6 +208,8 @@ class Member(User): self.newsletter = data["newsletter"] if "avatar" in data: self.set_avatar(data["avatar"]) + if "title" in data: + self.title = Title.query.get(data["title"]) # For admins only if "email_confirmed" in data: diff --git a/app/routes/account/account.py b/app/routes/account/account.py index b248161..8a7e378 100644 --- a/app/routes/account/account.py +++ b/app/routes/account/account.py @@ -4,6 +4,7 @@ from app import app, db from app.forms.account import UpdateAccountForm, RegistrationForm, \ DeleteAccountForm, AskResetPasswordForm, ResetPasswordForm from app.models.users import Member +from app.models.trophies import Title from app.utils.render import render from app.utils.send_mail import send_validation_mail, send_reset_password_mail from app.utils.priv_required import guest_only @@ -16,6 +17,9 @@ from config import V5Config @login_required def edit_account(): form = UpdateAccountForm() + titles = [(t.id, t.name) for t in current_user.trophies if isinstance(t, Title)] + titles.insert(0, (-1, "Membre")) + form.title.choices = titles if form.submit.data: if form.validate_on_submit(): current_user.update( @@ -25,6 +29,7 @@ def edit_account(): birthday=form.birthday.data, signature=form.signature.data, bio=form.biography.data, + title=form.title.data, newsletter=form.newsletter.data ) db.session.merge(current_user) diff --git a/app/routes/admin/account.py b/app/routes/admin/account.py index 3859d6a..1abd8c0 100644 --- a/app/routes/admin/account.py +++ b/app/routes/admin/account.py @@ -3,7 +3,7 @@ from flask_login import current_user from wtforms import BooleanField from app.utils.priv_required import priv_required from app.models.users import Member -from app.models.trophies import Trophy +from app.models.trophies import Trophy, Title from app.models.privs import Group from app.forms.account import AdminUpdateAccountForm, AdminDeleteAccountForm, \ AdminAccountEditTrophyForm, AdminAccountEditGroupForm @@ -35,6 +35,10 @@ def adm_edit_account(user_id): setattr(GroupForm, "user_groups", [f'g{g.id}' for g in user.groups]) group_form = GroupForm(prefix="group") + titles = [(t.id, t.name) for t in Title.query.all()] + titles.insert(0, (-1, "Membre")) + form.title.choices = titles + if form.submit.data: if form.validate_on_submit(): newname = form.username.data @@ -52,6 +56,7 @@ def adm_edit_account(user_id): password=form.password.data or None, birthday=form.birthday.data, signature=form.signature.data, + title=form.title.data, bio=form.biography.data, newsletter=form.newsletter.data, xp=form.xp.data or None, diff --git a/app/static/css/form.css b/app/static/css/form.css index 6fa683f..36137a3 100644 --- a/app/static/css/form.css +++ b/app/static/css/form.css @@ -26,6 +26,7 @@ .form input[type='password'], .form input[type='search'], .form textarea, +.form select, .trophies-panel > div { display: block; width: 100%; padding: 6px 8px; @@ -49,6 +50,9 @@ max-width: 100%; resize: vertical; } +.form select { + width: auto; +} .form progress.entropy { display: none; /* display with Js enabled */ diff --git a/app/static/css/widgets.css b/app/static/css/widgets.css index 8d06ef7..399819f 100644 --- a/app/static/css/widgets.css +++ b/app/static/css/widgets.css @@ -3,6 +3,7 @@ .profile { display: flex; align-items: center; + width: 265px; } .profile-avatar { width: 128px; diff --git a/app/templates/account/account.html b/app/templates/account/account.html index 3b3bc80..03981da 100644 --- a/app/templates/account/account.html +++ b/app/templates/account/account.html @@ -51,6 +51,13 @@

À propos

+
+ {{ form.title.label }} + {{ form.title }} + {% for error in form.title.errors %} + {{ error }} + {% endfor %} +
{{ form.birthday.label }} {{ form.birthday(value=current_user.birthday) }} diff --git a/app/templates/admin/edit_account.html b/app/templates/admin/edit_account.html index 2f67edf..df17371 100644 --- a/app/templates/admin/edit_account.html +++ b/app/templates/admin/edit_account.html @@ -61,6 +61,13 @@

À propos

+
+ {{ form.title.label }} + {{ form.title }} + {% for error in form.title.errors %} + {{ error }} + {% endfor %} +
{{ form.birthday.label }} {{ form.birthday(value=user.birthday) }} diff --git a/app/templates/widgets/user.html b/app/templates/widgets/user.html index 74692bb..e0df4d6 100644 --- a/app/templates/widgets/user.html +++ b/app/templates/widgets/user.html @@ -4,7 +4,7 @@ Avatar de {{ user.name }}
-
Membre
+
{{ user.title.name if user.title else "Membre" }}
Niveau {{ user.level[0] }} ({{ user.xp }})
N{{ user.level[0] }} ({{ user.xp }})
diff --git a/app/utils/validators.py b/app/utils/validators.py index 73f2c2f..c214aac 100644 --- a/app/utils/validators.py +++ b/app/utils/validators.py @@ -2,9 +2,11 @@ from flask_login import current_user from wtforms.validators import ValidationError from PIL import Image from app.models.users import Member, User +from app.models.trophies import Title from app.utils.valid_name import valid_name from app.utils.unicode_names import normalize from math import log +from werkzeug.exceptions import NotFound import app.utils.ldap as ldap from config import V5Config @@ -117,3 +119,20 @@ def id_exists(object): def css(form, css): """Check if input is valid and sane CSS""" pass + +def own_title(form, title): + # Everyone can use "Member" + if title.data == -1: + return True + + try: + t = Title.query.get_or_404(title.data) + except NotFound: + return False + except ValueError: + return False + + if t in current_user.trophies: + return True + else: + return False diff --git a/migrations/versions/001d2eaf0413_add_displayed_title_to_member.py b/migrations/versions/001d2eaf0413_add_displayed_title_to_member.py new file mode 100644 index 0000000..68cd25f --- /dev/null +++ b/migrations/versions/001d2eaf0413_add_displayed_title_to_member.py @@ -0,0 +1,30 @@ +"""Add displayed title to Member + +Revision ID: 001d2eaf0413 +Revises: acf72cf31eea +Create Date: 2020-07-29 00:13:27.775769 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '001d2eaf0413' +down_revision = 'acf72cf31eea' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('member', sa.Column('title_id', sa.Integer(), nullable=True)) + op.create_foreign_key(None, 'member', 'title', ['title_id'], ['id']) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, 'member', type_='foreignkey') + op.drop_column('member', 'title_id') + # ### end Alembic commands ###