From f67129a36b4466b013579bfa926ac514388f3d42 Mon Sep 17 00:00:00 2001 From: Darks Date: Fri, 7 Jun 2019 01:44:04 +0200 Subject: [PATCH] =?UTF-8?q?Ajout=20des=20troph=C3=A9es=20et=20du=20panel?= =?UTF-8?q?=20pour=20les=20g=C3=A9rer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/forms/trophies.py | 14 +++-- app/models/trophies.py | 8 ++- app/routes/admin/account.py | 4 +- app/routes/admin/trophies.py | 60 ++++++++++++++++--- app/templates/admin/delete_trophy.html | 28 +++++++++ app/templates/admin/edit_trophy.html | 38 ++++++++++++ app/templates/admin/trophies.html | 37 ++++++------ app/utils/validators.py | 20 ++++++- ...59d74cf54_update_des_titres_et_trophées.py | 30 ++++++++++ 9 files changed, 202 insertions(+), 37 deletions(-) create mode 100644 app/templates/admin/delete_trophy.html create mode 100644 app/templates/admin/edit_trophy.html create mode 100644 migrations/versions/6ae59d74cf54_update_des_titres_et_trophées.py diff --git a/app/forms/trophies.py b/app/forms/trophies.py index 626be35..49535ba 100644 --- a/app/forms/trophies.py +++ b/app/forms/trophies.py @@ -1,12 +1,16 @@ from flask_wtf import FlaskForm -from wtforms import StringField, BooleanField, SubmitField +from wtforms import StringField, SubmitField, BooleanField from wtforms.validators import DataRequired, Optional from flask_wtf.file import FileField # Cuz' wtforms' FileField is shitty -class CreateTrophyForm(FlaskForm): +class TrophyForm(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') + title = StringField('Titre', description='Titre affiché dans le cas échéant. Laisser vide pour un simple trophée.', validators=[Optional()]) + css = StringField('CSS', description='CSS appliqué au titre, le cas échéant.') + submit = SubmitField('Envoyer') + +class DeleteTrophyForm(FlaskForm): + delete = BooleanField('Confirmer la suppression', validators=[DataRequired()], description='Attention, cette opération est irréversible !') + submit = SubmitField('Supprimer le trophée') diff --git a/app/models/trophies.py b/app/models/trophies.py index 104855a..6a56d25 100644 --- a/app/models/trophies.py +++ b/app/models/trophies.py @@ -11,7 +11,7 @@ class Trophy(db.Model): 'polymorphic_on': type } # Standalone properties - name = db.Column(db.Text(convert_unicode=True)) + name = db.Column(db.Unicode(64), index=True) owners = db.relationship('Member', secondary=lambda: TrophyMember, back_populates='trophies') @@ -24,11 +24,13 @@ class Title(Trophy): __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)) + title = db.Column(db.Unicode(64)) + css = db.Column(db.Text(convert_unicode=True)) - def __init__(self, name, title): + def __init__(self, name, title, css): self.name = name self.title = title + self.css = css # Many-to-many relation for users earning trophies diff --git a/app/routes/admin/account.py b/app/routes/admin/account.py index 53d1bbb..55a400b 100644 --- a/app/routes/admin/account.py +++ b/app/routes/admin/account.py @@ -6,7 +6,7 @@ from app.utils.render import render from app import app, db -@app.route('/admin/edit-account/', methods=['GET', 'POST']) +@app.route('/admin/account//edit', methods=['GET', 'POST']) @priv_required('access-admin-panel', 'edit-account') def adm_edit_account(user_id): user = Member.query.filter_by(id=user_id).first_or_404() @@ -42,7 +42,7 @@ def adm_edit_account(user_id): return render('admin/edit_account.html', user=user, form=form) -@app.route('/admin/edit-account//delete', methods=['GET', 'POST']) +@app.route('/admin/account//delete', methods=['GET', 'POST']) @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/trophies.py b/app/routes/admin/trophies.py index 42b76c9..dda263c 100644 --- a/app/routes/admin/trophies.py +++ b/app/routes/admin/trophies.py @@ -1,29 +1,71 @@ -from flask import request, flash +from flask import request, flash, redirect, url_for from app.utils.priv_required import priv_required from app.models.trophies import Trophy, Title -from app.forms.trophies import CreateTrophyForm +from app.forms.trophies import TrophyForm, DeleteTrophyForm from app.utils.render import render from app import app, db @app.route('/admin/trophies', methods=['GET', 'POST']) -@priv_required('access-admin-panel', ) +@priv_required('access-admin-panel', 'edit-trophies') def adm_trophies(): - form = CreateTrophyForm() + form = TrophyForm() if request.method == "POST": if form.validate_on_submit(): - if form.is_title.data: - trophy = Title(form.name.data, form.title.data) - type = 'titre' + is_title = form.title.data != "" + if is_title: + trophy = Title(form.name.data, form.title.data, form.css.data) else: trophy = Trophy(form.name.data) - type = 'trophée' db.session.add(trophy) db.session.commit() - flash(f'Nouveau {type} ajoutée', 'ok') + flash(f'Nouveau {["trophée", "titre"][is_title]} ajouté', '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) + + +@app.route('/admin/trophies//edit', methods=['GET', 'POST']) +@priv_required('access-admin-panel', 'edit-trophies') +def adm_edit_trophy(trophy_id): + trophy = Trophy.query.filter_by(id=trophy_id).first_or_404() + + form = TrophyForm() + if request.method == "POST": + if form.validate_on_submit(): + is_title = form.title.data != "" + if is_title: + trophy.name = form.name.data + trophy.title = form.title.data + trophy.css = form.css.data + else: + trophy.name = form.name.data + db.session.merge(trophy) + db.session.commit() + flash(f'{["Trophée", "Titre"][is_title]} modifié', 'ok') + return redirect(url_for('adm_trophies')) + else: + flash('Erreur lors de la création du trophée', 'error') + return render('admin/edit_trophy.html', trophy=trophy, form=form) + + +@app.route('/admin/trophies//delete', methods=['GET', 'POST']) +@priv_required('access-admin-panel', 'edit-trophies') +def adm_delete_trophy(trophy_id): + trophy = Trophy.query.filter_by(id=trophy_id).first_or_404() + + # TODO: Add an overview of what will be deleted. + del_form = DeleteTrophyForm() + if request.method == "POST": + if del_form.validate_on_submit(): + db.session.delete(trophy) + db.session.commit() + flash('Trophée supprimé', 'ok') + return redirect(url_for('adm_trophies')) + else: + flash('Erreur lors de la suppression du trophée', 'error') + del_form.delete.data = False # Force to tick to delete the account + return render('admin/delete_trophy.html', trophy=trophy, del_form=del_form) diff --git a/app/templates/admin/delete_trophy.html b/app/templates/admin/delete_trophy.html new file mode 100644 index 0000000..1ae2813 --- /dev/null +++ b/app/templates/admin/delete_trophy.html @@ -0,0 +1,28 @@ +{% extends "base/base.html" %} + +{% block title %} +Panneau d'administration » Titres et trophées »

Suppression du trophée '{{ trophy.name }}'

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

Confirmer la suppression du trophée

+

Le trophée '{{ trophy.name }}' que vous allez supprimer est lié à :

+
    +
  • {{ trophy.owners | length }} membre{{ trophy.owners|length|pluralize }}
  • +
+ +
+ {{ del_form.hidden_tag() }} +
+ {{ del_form.delete.label }} + {{ del_form.delete(checked=False) }} +
{{ del_form.delete.description }}
+ {% for error in del_form.delete.errors %} + {{ error }} + {% endfor %} +
+
{{ del_form.submit(class_="bg-red") }}
+
+
+{% endblock %} diff --git a/app/templates/admin/edit_trophy.html b/app/templates/admin/edit_trophy.html new file mode 100644 index 0000000..83aacd9 --- /dev/null +++ b/app/templates/admin/edit_trophy.html @@ -0,0 +1,38 @@ +{% extends "base/base.html" %} + +{% block title %} +Panneau d'administration » Titres et trophées »

Édition du trophée '{{ trophy.name }}'

+{% endblock %} + +{% block content %} +
+
+ {{ form.hidden_tag() }} +

Éditer le trophée

+ +
+ {{ form.name.label }} + {{ form.name(value=trophy.name) }} + {% for error in form.name.errors %} + {{ error }} + {% endfor %} +
+
+ {{ form.title.label }} + {{ form.title(value=trophy.title) }} +
{{ form.title.description }}
+ {% for error in form.title.errors %} + {{ error }} + {% endfor %} +
+
+ {{ form.css.label }} + {{ form.css(value=trophy.css) }} +
{{ form.css.description }}
+ {% for error in form.css.errors %} + {{ error }} + {% endfor %} +
+
{{ form.submit(class_="bg-green") }}
+
+{% endblock %} diff --git a/app/templates/admin/trophies.html b/app/templates/admin/trophies.html index 5c5434b..80e628f 100644 --- a/app/templates/admin/trophies.html +++ b/app/templates/admin/trophies.html @@ -11,13 +11,17 @@

Titres et trophées

- + + {% for trophy in trophies %} - + + - - + + + + {% endfor %}
IcôneNomTitreModifier
idIcôneNomTitreStyleModifierSupprimer
{{ trophy.name }}
{{ trophy.id }}{{ trophy.name }} {{ trophy.name }}{{ trophy.title }}Modifier{{ trophy.title }}{{ trophy.css }}ModifierSupprimer
@@ -27,30 +31,29 @@ {{ 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 }}
+
{{ form.title.description }}
{% for error in form.title.errors %} {{ error }} {% endfor %}
-
{{ form.submit(class_="bg-green") }}
+
+ {{ form.css.label }} + {{ form.css }} +
{{ form.css.description }}
+ {% for error in form.css.errors %} + {{ error }} + {% endfor %} +
+
{{ form.submit(class_="bg-green") }}
{% endblock %} diff --git a/app/utils/validators.py b/app/utils/validators.py index a922f38..5705f1c 100644 --- a/app/utils/validators.py +++ b/app/utils/validators.py @@ -1,6 +1,6 @@ from flask_login import current_user from wtforms.validators import ValidationError -from app.models.users import User, Member +from app.models.users import Member from app.utils.valid_name import valid_name from app.utils.unicode_names import normalize from config import V5Config @@ -65,3 +65,21 @@ def old_password(form, field): raise ValidationError('Votre ancien mot de passe est requis pour cette modification.') if not current_user.check_password(form.old_password.data): raise ValidationError('Mot de passe actuel erroné.') + + +def id_exists(object): + """Check if an id exists in a table""" + def _id_exists(form, id): + try: + id = int(id.data) + except ValueError: + raise ValidationError('L\'id n\'est pas un entier valide') + r = object.query.filter_by(id=id) + if not r: + raise ValidationError('L\'id n\'existe pas dans la BDD') + return _id_exists + + +def css(form, css): + """Check if input is valid and sane CSS""" + pass diff --git a/migrations/versions/6ae59d74cf54_update_des_titres_et_trophées.py b/migrations/versions/6ae59d74cf54_update_des_titres_et_trophées.py new file mode 100644 index 0000000..93ceafc --- /dev/null +++ b/migrations/versions/6ae59d74cf54_update_des_titres_et_trophées.py @@ -0,0 +1,30 @@ +"""Update des titres et trophées + +Revision ID: 6ae59d74cf54 +Revises: c961d7b7a7ea +Create Date: 2019-06-06 23:34:53.521239 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '6ae59d74cf54' +down_revision = 'c961d7b7a7ea' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('title', sa.Column('css', sa.Text(_expect_unicode=True), nullable=True)) + op.create_index(op.f('ix_trophy_name'), 'trophy', ['name'], unique=False) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f('ix_trophy_name'), table_name='trophy') + op.drop_column('title', 'css') + # ### end Alembic commands ###