Browse Source

forum: implement forum tree generation

This commit adds a forum tree YAML file (URL-based rather than an
actual tree...) and the 'forums' and 'create-forums' commands for
the master script.

A page /admin/forums is also used to currently display the forum
tree, although this will probably be turned into a full table with
forum descriptions, and a form with edition capabilities.
Lephe 1 month ago
Signed by: Lephe <> GPG Key ID: 1BBA026E13FC0495

+ 1
- 1
app/ View File

@@ -29,6 +29,6 @@ from import Forum
from app.models.notification import Notification
from app.routes import index, search, users # To load routes at initialization
from app.routes.account import login, account, notification
from app.routes.admin import index, groups, account, trophies
from app.routes.admin import index, groups, account, trophies, forums
from app.utils import pluralize # To use pluralize into the templates
from app.utils import is_title

+ 93
- 0
app/data/forums.yaml View File

@@ -0,0 +1,93 @@
# This file is a list of forums to create when setting up Planète Casio.
# * Keys are used as URLs paths and for unique identification.
# * Prefixes represent the privilege category for a forum. Owning privileges
# with this prefix allows the user to post in this forum and all its
# sub-forum regardless of their settings ("forum-root-*" are hyper powerful).
# * For open forums, use the prefix "open".

name: Forum de Planète Casio
prefix: root

# News

name: Actualités
prefix: news

name: Actualités des projets
prefix: projectnews
descr: Nouveautés des projets de la communauté.

name: Actualités des constructeurs de calculatrices
prefix: calcnews
descr: Nouveautés CASIO, nouveaux modèles de calculatrices, mises à jour du
système ou nouveautés d'autres constructeurs.

name: Événements organisés par Planète Casio
prefix: eventnews
descr: Tous les événements organisés par Planète Casio ou la communauté.

name: Autres nouveautés
prefix: othernews
descr: Actualités non catégorisées.

# Help

name: Aide et questions
prefix: help

name: Questions sur les tranferts
prefix: transferhelp
descr: Questions sur le transfert de fichiers et l'installation de programmes
sur la calculatrice.

name: Question sur l'utilisation des calculatrices
prefix: calchelp
descr: Questions sur l'utilisation des applications de la calculatrice,
paramètres, formats de fichiers...

name: Questions de programmation
prefix: proghelp
descr: Questions sur le développement et le debuggage de programmes.

name: Autres questions
prefix: otherhelp
descr: Questions non catégorisées.

# Projects

name: Forum des projets
prefix: projects

name: Projets de jeux
prefix: gameprojects

name: Projets d'applications, utilitaires, outils pour calculatrice
prefix: appprojects

name: Projets pour d'autres plateformes
prefix: toolprojetcs
descr: Tous les projets tournant sur ordinateur, téléphone, ou toute autre
plateforme que la calculatrice.

# Discussion

name: Discussion
prefix: discussion
descr: Sujets hors-sujet et discussion libre.

+ 17
- 7
app/models/ View File

@@ -5,22 +5,32 @@ class Forum(db.Model):
__tablename__ = 'forum'
id = db.Column(db.Integer, primary_key=True)

# Standalone properties
# Forum name, as displayed on the site (eg. "Problèmes de transfert")
name = db.Column(db.Unicode(64))
slug = db.Column(db.Unicode(64))
description = db.Column(db.UnicodeText)
# Privilege prefix (sort of slug) for single-forum privileges (lowercase)
prefix = db.Column(db.Unicode(64))
# Forum description, as displayed on the site
descr = db.Column(db.UnicodeText)
# Forum URL, for dynamic routes
url = db.Column(db.String(64))

# Relationships
parent_id = db.Column(db.Integer, db.ForeignKey(''), nullable=True)
parent = db.relationship('Forum', backref='sub_forums', remote_side=id,
lazy=True, foreign_keys=parent_id)

# Also [topics] which is provided by a backref from the Topic class

def __init__(self, name, description, priv_prefix):
def __init__(self, url, name, prefix, descr="", parent=None):
self.url = url = name
self.description = description
self.priv_prefix = priv_prefix
self.descr = descr
self.prefix = prefix

if isinstance(parent, str):
self.parent = Forum.query.filter_by(url=str).first()
self.parent = parent

def __repr__(self):
return f'<Forum: {}>'

+ 11
- 0
app/routes/admin/ View File

@@ -0,0 +1,11 @@
from app.utils.priv_required import priv_required
from app.utils.render import render
from import Forum
from app import app, db

@app.route('/admin/forums', methods=['GET'])
def adm_forums():
main_forum = Forum.query.filter_by(parent=None).first()

return render('admin/forums.html', main_forum=main_forum)

+ 32
- 0
app/templates/admin/forums.html View File

@@ -0,0 +1,32 @@
{% extends "base/base.html" %}

{# This macro will allow us to perform recursive HTML generation #}
{% macro forumtree(f) %}
<li> {{ }}
{% for subf in f.sub_forums %}
{{ forumtree(subf) }}
{% endfor %}
{% endmacro %}

{% block title %}
<a href="{{ url_for('adm') }}">Panneau d'administration</a> » <h1>Forums</h1>
{% endblock %}

{% block content %}
<p>Cette page permet de gérer l'arbre des forums.</p>

<h2>Arbre des forums</h2>

{% if main_forum == None %}
<p>Il n'y a aucun forum.</p>
{% else %}
{{ forumtree(main_forum) }}
{% endif %}
{% endblock %}

+ 2
- 1
app/templates/admin/index.html View File

@@ -8,8 +8,9 @@
<p>Pages générales du panneau d'administration :</p>
<li><a href="{{ url_for('adm_groups') }}">Groupes et privilèges</a></li>
<li><a href="{{ url_for('adm_groups') }}">Groupes et privilèges</a></li>
<li><a href="{{ url_for('adm_trophies') }}">Titres et trophées</a></li>
<li><a href="{{ url_for('adm_forums') }}">Arbre des forums</a></li>
{% endblock %}

+ 49
- 0 View File

@@ -4,6 +4,7 @@ from app import app, db
from app.models.users import Member, Group, GroupPrivilege
from app.models.privs import SpecialPrivilege
from app.models.trophies import Trophy, Title, TrophyMember
from import Forum
from app.utils import unicode_names
import os
import sys
@@ -19,6 +20,7 @@ Type a category name to see a list of elements. Available categories are:
'groups' Privilege groups
'trophies' Trophies
'trophy-members' Trophies owned by members
'forums' Forum tree

Type a category name followed by 'clear' to remove all entries in the category.

@@ -36,6 +38,8 @@ the database.
Type 'add-group <member> #<group-id>' to add a new member to a group.

Type 'create-trophies' to reset trophies and titles.

Type 'create-forums' to reset the forum tree.

@@ -84,6 +88,19 @@ def trophy_members(*args):
for m in t.owners:
print(f" {m}")

def forums(*args):
if args == ("clear",):
for f in Forum.query.all():
print("Removed all forums.")

for f in Forum.query.all():
parent = f"in {f.parent.url}" if f.parent is not None else "root"
print(f"{f.url} ({parent}) [{f.prefix}]: {}")
print(f" {f.descr}")

# Creation and edition
@@ -163,6 +180,36 @@ def create_trophies():

print(f"Created {len(tr)} trophies.")

def create_forums():
# Clean up forums

# Create the forum tree
fr = []
success = 0
with open(os.path.join(app.root_path, "data", "forums.yaml")) as fp:
fr = yaml.safe_load(

for url, f in fr.items():
if url == "/":
parent = None
parent_url = url.rsplit('/', 1)[0]
if parent_url == "":
parent_url = "/"
parent = Forum.query.filter_by(url=parent_url).first()

if parent is None:
print(f"error: no parent with url {parent_url} for {url}")

f = Forum(url, f['name'], f['prefix'], f.get('descr', ''), parent)
success += 1

print(f"Created {success} forums.")

def add_group(member, group):
if group[0] != "#":
print(f"error: group id {group} should start with '#'")
@@ -194,8 +241,10 @@ commands = {
"groups": groups,
"trophies": trophies,
"trophy-members": trophy_members,
"forums": forums,
"create-groups-and-privs": create_groups_and_privs,
"create-trophies": create_trophies,
"create-forums": create_forums,
"add-group": add_group,

+ 28
- 0
migrations/versions/ View File

@@ -0,0 +1,28 @@
"""add forum urls

Revision ID: 49427f8eb285
Revises: a7aac1469393
Create Date: 2019-09-02 21:16:06.971807

from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision = '49427f8eb285'
down_revision = 'a7aac1469393'
branch_labels = None
depends_on = None

def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('forum', sa.Column('url', sa.String(length=64), nullable=True))
# ### end Alembic commands ###

def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('forum', 'url')
# ### end Alembic commands ###

+ 34
- 0
migrations/versions/ View File

@@ -0,0 +1,34 @@
"""forum editions

Revision ID: a7aac1469393
Revises: e3b140752719
Create Date: 2019-09-02 21:12:52.236043

from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision = 'a7aac1469393'
down_revision = 'e3b140752719'
branch_labels = None
depends_on = None

def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('forum', sa.Column('descr', sa.UnicodeText(), nullable=True))
op.add_column('forum', sa.Column('prefix', sa.Unicode(length=64), nullable=True))
op.drop_column('forum', 'slug')
op.drop_column('forum', 'description')
# ### end Alembic commands ###

def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('forum', sa.Column('description', sa.TEXT(), autoincrement=False, nullable=True))
op.add_column('forum', sa.Column('slug', sa.VARCHAR(length=64), autoincrement=False, nullable=True))
op.drop_column('forum', 'prefix')
op.drop_column('forum', 'descr')
# ### end Alembic commands ###