diff --git a/app/__init__.py b/app/__init__.py index 47625cc..b101104 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -16,5 +16,5 @@ login.login_message = "Veuillez vous authentifier avant de continuer." from app import models # IDK why this is here, but it works from app.routes import index, search, users # To load routes at initialization from app.routes.account import login, account -from app.routes.admin import index, groups, account +from app.routes.admin import index, groups, account, trophies from app.utils import pluralize # To use pluralize into the templates diff --git a/app/forms/account.py b/app/forms/account.py index fcfb0df..aff6f92 100644 --- a/app/forms/account.py +++ b/app/forms/account.py @@ -36,20 +36,11 @@ class DeleteAccountForm(FlaskForm): class AdminUpdateAccountForm(FlaskForm): - username = StringField('Pseudonyme', - validators=[Optional(), vd.name_valid]) - avatar = FileField('Avatar', - validators=[Optional(), vd.avatar]) - email = StringField('Adresse email', - validators=[Optional(), Email(), vd.email]) - email_validate = BooleanField("""Envoyer un email de validation à la - nouvelle adresse""", - description="""Si décoché, l'utilisateur devra demander explicitement - un email de validation, ou faire valider son adresse email par un - administrateur.""") - password = PasswordField('Mot de passe', - description="L'ancien mot de passe ne pourra pas être récupéré !", - validators=[Optional(), vd.password]) + username = StringField('Pseudonyme', validators=[Optional(), vd.name_valid]) + avatar = FileField('Avatar', validators=[Optional(), vd.avatar]) + email = StringField('Adresse email', validators=[Optional(), Email(), vd.email]) + email_validate = BooleanField("Envoyer un email de validation à la nouvelle adresse", description="Si décoché, l'utilisateur devra demander explicitement un email de validation, ou faire valider son adresse email par un administrateur.") + password = PasswordField('Mot de passe', description="L'ancien mot de passe ne pourra pas être récupéré !", validators=[Optional(), vd.password]) xp = DecimalField('XP', validators=[Optional()]) birthday = DateField('Anniversaire', validators=[Optional()]) signature = TextAreaField('Signature', validators=[Optional()]) diff --git a/app/forms/trophies.py b/app/forms/trophies.py new file mode 100644 index 0000000..626be35 --- /dev/null +++ b/app/forms/trophies.py @@ -0,0 +1,12 @@ +from flask_wtf import FlaskForm +from wtforms import StringField, BooleanField, SubmitField +from wtforms.validators import DataRequired, Optional +from flask_wtf.file import FileField # Cuz' wtforms' FileField is shitty + + +class CreateTrophyForm(FlaskForm): + name = StringField('Nom', validators=[DataRequired()]) + icon = FileField('Icone') + is_title = BooleanField('Le trophée est aussi un titre') + title = StringField('Titre', description='Titre affiché dans le cas échéant', validators=[Optional()]) + submit = SubmitField('Créer le trophée') diff --git a/app/models/trophies.py b/app/models/trophies.py new file mode 100644 index 0000000..104855a --- /dev/null +++ b/app/models/trophies.py @@ -0,0 +1,37 @@ +from app import db + + +class Trophy(db.Model): + __tablename__ = 'trophy' + id = db.Column(db.Integer, primary_key=True) + # Trophy type (polymorphic discriminator) + type = db.Column(db.String(20)) + __mapper_args__ = { + 'polymorphic_identity': __tablename__, + 'polymorphic_on': type + } + # Standalone properties + name = db.Column(db.Text(convert_unicode=True)) + + owners = db.relationship('Member', secondary=lambda: TrophyMember, + back_populates='trophies') + + def __init__(self, name): + self.name = name + +class Title(Trophy): + __tablename__ = 'title' + __mapper_args__ = {'polymorphic_identity': __tablename__} + + id = db.Column(db.Integer, db.ForeignKey('trophy.id'), primary_key=True) + title = db.Column(db.Text(convert_unicode=True)) + + def __init__(self, name, title): + self.name = name + self.title = title + + +# Many-to-many relation for users earning trophies +TrophyMember = db.Table('trophy_member', db.Model.metadata, + db.Column('tid', db.Integer, db.ForeignKey('trophy.id')), + db.Column('uid', db.Integer, db.ForeignKey('member.id'))) diff --git a/app/models/users.py b/app/models/users.py index b1096fa..6dfbbc4 100644 --- a/app/models/users.py +++ b/app/models/users.py @@ -4,6 +4,7 @@ from flask_login import UserMixin from app.models.contents import Content from app.models.privs import SpecialPrivilege, Group, GroupMember, \ GroupPrivilege +from app.models.trophies import Trophy, TrophyMember import app.utils.unicode_names as unicode_names from config import V5Config @@ -90,7 +91,8 @@ class Member(User, db.Model): newsletter = db.Column(db.Boolean, default=False) # Relations - # trophies = db.relationship('Trophy', back_populates='member') + trophies = db.relationship('Trophy', secondary=TrophyMember, + back_populates='owners') # tests = db.relationship('Test', back_populates='author') def __init__(self, name, email, password): diff --git a/app/routes/admin/account.py b/app/routes/admin/account.py index 4311686..53d1bbb 100644 --- a/app/routes/admin/account.py +++ b/app/routes/admin/account.py @@ -7,7 +7,7 @@ from app import app, db @app.route('/admin/edit-account/', methods=['GET', 'POST']) -@priv_required('edit-account') +@priv_required('access-admin-panel', 'edit-account') def adm_edit_account(user_id): user = Member.query.filter_by(id=user_id).first_or_404() @@ -43,7 +43,7 @@ def adm_edit_account(user_id): @app.route('/admin/edit-account//delete', methods=['GET', 'POST']) -@priv_required('delete-account') +@priv_required('access-admin-panel', 'delete-account') def adm_delete_account(user_id): user = Member.query.filter_by(id=user_id).first_or_404() diff --git a/app/routes/admin/index.py b/app/routes/admin/index.py index 5ef458e..bd0c7cc 100644 --- a/app/routes/admin/index.py +++ b/app/routes/admin/index.py @@ -3,7 +3,7 @@ from app.utils.render import render from app import app -@app.route('/admin', methods=['GET', 'POST']) +@app.route('/admin', methods=['GET']) @priv_required('access-admin-panel') def adm(): return render('admin/index.html') diff --git a/app/routes/admin/trophies.py b/app/routes/admin/trophies.py new file mode 100644 index 0000000..42b76c9 --- /dev/null +++ b/app/routes/admin/trophies.py @@ -0,0 +1,29 @@ +from flask import request, flash +from app.utils.priv_required import priv_required +from app.models.trophies import Trophy, Title +from app.forms.trophies import CreateTrophyForm +from app.utils.render import render +from app import app, db + + +@app.route('/admin/trophies', methods=['GET', 'POST']) +@priv_required('access-admin-panel', ) +def adm_trophies(): + form = CreateTrophyForm() + if request.method == "POST": + if form.validate_on_submit(): + if form.is_title.data: + trophy = Title(form.name.data, form.title.data) + type = 'titre' + else: + trophy = Trophy(form.name.data) + type = 'trophée' + db.session.add(trophy) + db.session.commit() + flash(f'Nouveau {type} ajoutée', 'ok') + else: + flash('Erreur lors de la création du trophée', 'error') + + trophies = Trophy.query.all() + return render('admin/trophies.html', trophies=trophies, + form=form) diff --git a/app/templates/admin/index.html b/app/templates/admin/index.html index 7ea09d0..f4f37ee 100644 --- a/app/templates/admin/index.html +++ b/app/templates/admin/index.html @@ -9,6 +9,7 @@

Pages générales du panneau d'administration :

{% endblock %} diff --git a/app/templates/admin/trophies.html b/app/templates/admin/trophies.html new file mode 100644 index 0000000..5c5434b --- /dev/null +++ b/app/templates/admin/trophies.html @@ -0,0 +1,56 @@ +{% extends "base/base.html" %} + +{% block title %} +Panneau d'administration »

Titres et trophées

+{% endblock %} + +{% block content %} +
+

Cette page présente une vue d'ensemble des titres et trophées.

+ +

Titres et trophées

+ + + + + {% for trophy in trophies %} + + + + + + {% endfor %} +
IcôneNomTitreModifier
{{ trophy.name }}{{ trophy.name }}{{ trophy.title }}Modifier
+
+ +
+
+ {{ form.hidden_tag() }} +

Nouveau trophée

+ +
+ {{ form.name.label }} + {{ form.name }} + {% for error in form.name.errors %} + {{ error }} + {% endfor %} +
+
+ {{ form.is_title.label }} + {{ form.is_title }} +
{{ form.is_title.description }}
+ {% for error in form.is_title.errors %} + {{ error }} + {% endfor %} +
+
+ {{ form.title.label }} + {{ form.title }} +
{{ form.title.description }}
+ {% for error in form.title.errors %} + {{ error }} + {% endfor %} +
+
{{ form.submit(class_="bg-green") }}
+
+{% endblock %} diff --git a/config.py b/config.py index bf71675..85eb3ba 100644 --- a/config.py +++ b/config.py @@ -7,6 +7,7 @@ class Config(object): 'postgresql+psycopg2://' + os.environ.get('USER') + ':@/pcv5' SQLALCHEMY_TRACK_MODIFICATIONS = False UPLOAD_FOLDER = './app/static/avatars' + LOGIN_DISABLED = True class V5Config(object): diff --git a/migrations/versions/c961d7b7a7ea_ajout_des_titres_et_trophées.py b/migrations/versions/c961d7b7a7ea_ajout_des_titres_et_trophées.py new file mode 100644 index 0000000..964c42e --- /dev/null +++ b/migrations/versions/c961d7b7a7ea_ajout_des_titres_et_trophées.py @@ -0,0 +1,47 @@ +"""Ajout des titres et trophées + +Revision ID: c961d7b7a7ea +Revises: a6e89f3510d9 +Create Date: 2019-06-06 15:18:09.893001 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'c961d7b7a7ea' +down_revision = 'a6e89f3510d9' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('trophy', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('type', sa.String(length=20), nullable=True), + sa.Column('name', sa.Text(_expect_unicode=True), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('title', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('title', sa.Text(_expect_unicode=True), nullable=True), + sa.ForeignKeyConstraint(['id'], ['trophy.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('trophy_member', + sa.Column('tid', sa.Integer(), nullable=True), + sa.Column('uid', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['tid'], ['trophy.id'], ), + sa.ForeignKeyConstraint(['uid'], ['member.id'], ) + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('trophy_member') + op.drop_table('title') + op.drop_table('trophy') + # ### end Alembic commands ###