diff --git a/app/data/trophies.yaml b/app/data/trophies.yaml index 6097e21..d56a62d 100644 --- a/app/data/trophies.yaml +++ b/app/data/trophies.yaml @@ -1,120 +1,187 @@ # This is a list of trophies. For each trophies, the following keys may be set: -# name Trophy name as displayed on the site. -# is_title If True, the trophy can be worn as a title next to the avatar. +# name Trophy name as displayed on the site. +# is_title If True, the trophy can be worn as a title next to the avatar. +# description Detailed description to be shown on profile page. # Manually awarded - name: Membre de CreativeCalc is_title: True + description: Membre de l'association qui gère Planète Casio ! + hidden: True - name: Membre d'honneur is_title: True + description: Gloire, honneur et déférence sont dûs. + hidden: True - name: Grand Manitou is_title: True + description: Le seul. L'unique. Le GRAND MANITOU. + hidden: True - name: Gourou is_title: True + description: Possède des trophées obscurs. + hidden: True - name: Grand Maître des traits d'esprit is_title: True + description: Inégalé pour l'heure. + hidden: True # Number of posts of any kind - name: Premiers mots is_title: False + description: Écrire 20 messages. + hidden: False - name: Beau parleur is_title: False + description: Écrire 500 messages. + hidden: False - name: Plume infaillible is_title: False + description: Écrire 1500 messages. + hidden: False - name: Romancier émérite is_title: True + description: Écrire 5000 messages ! + hidden: False # Number of posted tutorials - name: Pédagogue is_title: False + description: Élaborer 5 tutoriels. + hidden: False - name: Encyclopédie vivante is_title: False + description: Élaborer 10 tutoriels. + hidden: False - name: Guerrier du savoir is_title: True + description: Élaborer 25 tutoriels ! + hidden: False # Account age (awarded on login only) - name: Initié is_title: False + description: Être membre pendant 1 mois. + hidden: False - name: Aficionado is_title: False + description: Être membre pendant 1 an. + hidden: False - name: Veni, vidi, casii is_title: False + description: Être membre pendant 2 ans. + hidden: False - name: Papy Casio is_title: True + description: Être membre pendant 5 ans. + hidden: False - name: Vétéran mythique is_title: True + description: Être membre pendant 10 ans ! + hidden: False # Number of "good" programs - name: Programmeur du dimanche is_title: False + description: Publier 5 prorammes. + hidden: False - name: Codeur invétéré is_title: False + description: Publier 10 programmes. + hidden: False - name: Je code donc je suis is_title: True + description: Publier 20 programmes ! + hidden: False # Number of posted tests - name: Testeur is_title: False + description: Partager 5 tests. + hidden: False - name: Grand joueur is_title: False + description: Partager 25 tests. + hidden: False - name: Hard tester is_title: True + description: Partager 100 tests ! + hidden: False # Number of event participations - name: Participant is_title: False + description: Participer à un événement du site. + hidden: False - name: Concourant encore is_title: False + description: Participer à 5 événements du site. + hidden: False - name: Concurrent de l'extrême is_title: True + description: Participer à 15 événements du site ! + hidden: False # Number of posted art - name: Dessinateur en herbe is_title: False + description: Publier 5 assets graphiques. + hidden: False - name: Open pixel is_title: False + description: Publier 30 assets graphiques. + hidden: False - name: Roi du pixel is_title: True + description: Publier 100 assets graphiques ! + hidden: False # Miscellaneous automatically awarded - name: Actif is_title: False + description: Être présent 30 jours de suite. + hidden: False - name: Artiste is_title: False + description: Modifier son avatar. + hidden: False - name: Maître du code is_title: True + description: Être décoré 5 fois du label de qualité ! + hidden: False - name: Bourreau des cœurs is_title: True + description: Foudroyer les cœurs à 5 reprises ! + hidden: False diff --git a/app/forms/trophies.py b/app/forms/trophies.py index bc86176..62825c9 100644 --- a/app/forms/trophies.py +++ b/app/forms/trophies.py @@ -6,7 +6,7 @@ from flask_wtf.file import FileField # Cuz' wtforms' FileField is shitty class TrophyForm(FlaskForm): name = StringField( - 'Nom', + 'Nom', validators=[ DataRequired(), ], @@ -14,15 +14,25 @@ class TrophyForm(FlaskForm): icon = FileField( 'Icône', ) + desc = StringField( + 'Description' + ) title = BooleanField( - 'Titre', - description='Un titre peut être affiché en dessous du pseudo.', + 'Titre', + description='Un titre peut être affiché en dessous du pseudo.', + validators=[ + Optional(), + ], + ) + hidden = BooleanField( + 'Caché', + description='Le trophée n\'est pas affiché grisé dans la page de profil', validators=[ Optional(), ], ) css = StringField( - 'CSS', + 'CSS', description='CSS appliqué au titre, le cas échéant.', ) submit = SubmitField( @@ -31,10 +41,10 @@ class TrophyForm(FlaskForm): class DeleteTrophyForm(FlaskForm): delete = BooleanField( - 'Confirmer la suppression', + 'Confirmer la suppression', validators=[ DataRequired(), - ], + ], description='Attention, cette opération est irréversible !', ) submit = SubmitField( diff --git a/app/models/trophies.py b/app/models/trophies.py index a884e6a..74936b0 100644 --- a/app/models/trophies.py +++ b/app/models/trophies.py @@ -15,12 +15,18 @@ class Trophy(db.Model): # Trophy name (in French) name = db.Column(db.Unicode(64), index=True) + # Description + description = db.Column(db.UnicodeText, index=True) + # Hidden by default + hidden = db.Column(db.Boolean, default=False) owners = db.relationship('Member', secondary=lambda: TrophyMember, back_populates='trophies') - def __init__(self, name): + def __init__(self, name, description, hidden=False): self.name = name + self.description = description + self.hidden = hidden def __repr__(self): return f'' @@ -33,8 +39,10 @@ class Title(Trophy): id = db.Column(db.Integer, db.ForeignKey('trophy.id'), primary_key=True) css = db.Column(db.UnicodeText) - def __init__(self, name, css=""): + def __init__(self, name, description, hidden=False, css=""): self.name = name + self.description = description + self.hidden = hidden self.css = css def __repr__(self): diff --git a/app/models/users.py b/app/models/users.py index e69a830..1b7f4c9 100644 --- a/app/models/users.py +++ b/app/models/users.py @@ -214,8 +214,12 @@ class Member(User): db.session.commit() # Save the new avatar im.save(V5Config.AVATARS_FOLDER + self.avatar, 'PNG') - # If nothing has failed, remove old one - os.remove(old_avatar) + # If nothing has failed, remove old one (allow failure to regularize + # exceptional situations like missing avatar or folder migration) + try: + os.remove(old_avatar) + except FileNotFoundError: + pass def get_public_data(self): """ Returns the public information of the member.""" @@ -340,7 +344,7 @@ class Member(User): if context in ["new-post", "new-program", "new-tutorial", "new-test", None]: - # Cannot use ORM tools because it adds circular import issues + # FIXME: Use ORM tools with careful, non-circular imports post_count = db.session.execute(f"""SELECT COUNT(*) FROM post INNER JOIN member ON member.id = post.author_id WHERE member.id = {self.id}""").first()[0] diff --git a/app/routes/admin/trophies.py b/app/routes/admin/trophies.py index 869d18e..5eb59d0 100644 --- a/app/routes/admin/trophies.py +++ b/app/routes/admin/trophies.py @@ -14,9 +14,11 @@ def adm_trophies(): if form.validate_on_submit(): is_title = form.title.data if is_title: - trophy = Title(form.name.data, form.css.data) + trophy = Title(form.name.data, form.desc.data, + form.hidden.data, form.css.data) else: - trophy = Trophy(form.name.data) + trophy = Trophy(form.name.data, form.desc.data, + form.hidden.data) db.session.add(trophy) db.session.commit() flash(f'Nouveau {["trophée", "titre"][is_title]} ajouté', 'ok') @@ -39,10 +41,14 @@ def adm_edit_trophy(trophy_id): is_title = form.title.data != "" if is_title: trophy.name = form.name.data + trophy.description = form.desc.data trophy.title = form.title.data + trophy.hidden = form.hidden.data trophy.css = form.css.data else: trophy.name = form.name.data + trophy.description = form.desc.data + trophy.hidden = form.hidden.data db.session.merge(trophy) db.session.commit() flash(f'{["Trophée", "Titre"][is_title]} modifié', 'ok') diff --git a/app/static/css/form.css b/app/static/css/form.css index 8fe0db7..262f9b4 100644 --- a/app/static/css/form.css +++ b/app/static/css/form.css @@ -42,6 +42,7 @@ .form input[type='search']:focus, .form textarea:focus { border-color: var(--border-focused); + box-shadow: 0 0 0 3px var(--shadow-focused); } .form textarea { diff --git a/app/static/css/theme.css b/app/static/css/theme.css index 4565dfd..5d54a32 100644 --- a/app/static/css/theme.css +++ b/app/static/css/theme.css @@ -30,6 +30,7 @@ --text: #000000; --border: 1px solid #c8c8c8; --border-focused: #7cade0; + --shadow-focused: rgba(87, 143, 228, 0.5); } .editor button { diff --git a/app/templates/account/user.html b/app/templates/account/user.html index d9a0d1c..d2473bc 100644 --- a/app/templates/account/user.html +++ b/app/templates/account/user.html @@ -17,12 +17,6 @@
Modifier le compte
{% endif %} {% endif %} - -
@@ -50,12 +44,12 @@

Trophées

- {% for t in trophies %} + {% for t in trophies if t in member.trophies or t.hidden == False %}
{{ t.name }} - Avoir posté 50 messages sur le forum + {{ t.description }}
{% endfor %} diff --git a/app/templates/admin/edit_trophy.html b/app/templates/admin/edit_trophy.html index d76a84e..d56f7a0 100644 --- a/app/templates/admin/edit_trophy.html +++ b/app/templates/admin/edit_trophy.html @@ -17,17 +17,32 @@ {{ error }} {% endfor %}
+
+ {{ form.desc.label }} + {{ form.desc(value=trophy.description) }} + {% for error in form.desc.errors %} + {{ error }} + {% endfor %} +
+
+ {{ form.hidden.label }} + {{ form.hidden(checked=trophy.hidden) }} +
{{ form.hidden.description }}
+ {% for error in form.hidden.errors %} + {{ error }} + {% endfor %} +
{{ form.title.label }} {{ form.title() }} -
{{ form.title.description }}
+
{{ form.title.description }}
{% for error in form.title.errors %} {{ error }} {% endfor %}
{{ form.css.label }} -
{{ form.css.description }}
+
{{ form.css.description }}
{{ form.css(value=trophy.css) }} {% for error in form.css.errors %} {{ error }} diff --git a/app/templates/admin/trophies.html b/app/templates/admin/trophies.html index 021248f..88b06c7 100644 --- a/app/templates/admin/trophies.html +++ b/app/templates/admin/trophies.html @@ -44,17 +44,32 @@ {{ error }} {% endfor %}
+
+ {{ form.desc.label }} + {{ form.desc }} + {% for error in form.desc.errors %} + {{ error }} + {% endfor %} +
+
+ {{ form.hidden.label }} + {{ form.hidden }} +
{{ form.hidden.description }}
+ {% for error in form.hidden.errors %} + {{ error }} + {% endfor %} +
{{ form.title.label }} {{ form.title }} -
{{ form.title.description }}
+
{{ form.title.description }}
{% for error in form.title.errors %} {{ error }} {% endfor %}
{{ form.css.label }} -
{{ form.css.description }}
+
{{ form.css.description }}
{{ form.css }} {% for error in form.css.errors %} {{ error }} diff --git a/master.py b/master.py index 98db175..b19c2c3 100755 --- a/master.py +++ b/master.py @@ -171,10 +171,13 @@ def create_trophies(): tr = yaml.safe_load(fp.read()) for t in tr: + description = t.get("description", "") + if t["is_title"]: - trophy = Title(t["name"], t.get("css", "")) + trophy = Title(t["name"], description, t["hidden"], + t.get("css", "")) else: - trophy = Trophy(t["name"]) + trophy = Trophy(t["name"], description, t["hidden"]) db.session.add(trophy) db.session.commit() diff --git a/migrations/versions/6fd4c15b8a7b_added_hidden_property_for_trophies.py b/migrations/versions/6fd4c15b8a7b_added_hidden_property_for_trophies.py new file mode 100644 index 0000000..cccaf51 --- /dev/null +++ b/migrations/versions/6fd4c15b8a7b_added_hidden_property_for_trophies.py @@ -0,0 +1,28 @@ +"""Added `hidden` property for trophies + +Revision ID: 6fd4c15b8a7b +Revises: b659d4c2a5d1 +Create Date: 2020-07-20 19:29:18.465491 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '6fd4c15b8a7b' +down_revision = 'b659d4c2a5d1' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('trophy', sa.Column('hidden', sa.Boolean(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('trophy', 'hidden') + # ### end Alembic commands ### diff --git a/migrations/versions/b659d4c2a5d1_add_a_description_to_trophies.py b/migrations/versions/b659d4c2a5d1_add_a_description_to_trophies.py new file mode 100644 index 0000000..710893b --- /dev/null +++ b/migrations/versions/b659d4c2a5d1_add_a_description_to_trophies.py @@ -0,0 +1,30 @@ +"""add a description to trophies + +Revision ID: b659d4c2a5d1 +Revises: d1b465f88d3b +Create Date: 2020-07-19 22:06:55.789405 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'b659d4c2a5d1' +down_revision = 'd1b465f88d3b' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('trophy', sa.Column('description', sa.UnicodeText(), nullable=True)) + op.create_index(op.f('ix_trophy_description'), 'trophy', ['description'], unique=False) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f('ix_trophy_description'), table_name='trophy') + op.drop_column('trophy', 'description') + # ### end Alembic commands ###