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.
This commit is contained in:
Lephe 2019-09-02 21:35:16 +02:00
parent de83f09024
commit aa75ff09a1
Signed by: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
9 changed files with 267 additions and 9 deletions

View File

@ -29,6 +29,6 @@ from app.models.forum 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
app/data/forums.yaml Normal file
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
/news:
name: Actualités
prefix: news
/news/projects:
name: Actualités des projets
prefix: projectnews
descr: Nouveautés des projets de la communauté.
/news/calc:
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.
/news/events:
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é.
/news/other:
name: Autres nouveautés
prefix: othernews
descr: Actualités non catégorisées.
# Help
/help:
name: Aide et questions
prefix: help
/help/transfers:
name: Questions sur les tranferts
prefix: transferhelp
descr: Questions sur le transfert de fichiers et l'installation de programmes
sur la calculatrice.
/help/calc:
name: Question sur l'utilisation des calculatrices
prefix: calchelp
descr: Questions sur l'utilisation des applications de la calculatrice,
paramètres, formats de fichiers...
/help/prog:
name: Questions de programmation
prefix: proghelp
descr: Questions sur le développement et le debuggage de programmes.
/help/other:
name: Autres questions
prefix: otherhelp
descr: Questions non catégorisées.
# Projects
/projects:
name: Forum des projets
prefix: projects
/projects/games:
name: Projets de jeux
prefix: gameprojects
/projects/apps:
name: Projets d'applications, utilitaires, outils pour calculatrice
prefix: appprojects
/projects/tools:
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
/discussion:
name: Discussion
prefix: discussion
descr: Sujets hors-sujet et discussion libre.

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('forum.id'), nullable=True)
parent = db.relationship('Forum', backref='sub_forums', remote_side=id,
lazy=True)
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
self.name = 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()
else:
self.parent = parent
def __repr__(self):
return f'<Forum: {self.name}>'

View File

@ -0,0 +1,11 @@
from app.utils.priv_required import priv_required
from app.utils.render import render
from app.models.forum import Forum
from app import app, db
@app.route('/admin/forums', methods=['GET'])
@priv_required('access-admin-panel')
def adm_forums():
main_forum = Forum.query.filter_by(parent=None).first()
return render('admin/forums.html', main_forum=main_forum)

View File

@ -0,0 +1,32 @@
{% extends "base/base.html" %}
{# This macro will allow us to perform recursive HTML generation #}
{% macro forumtree(f) %}
<li> {{ f.name }}
<ul>
{% for subf in f.sub_forums %}
{{ forumtree(subf) }}
{% endfor %}
</ul>
</li>
{% endmacro %}
{% block title %}
<a href="{{ url_for('adm') }}">Panneau d'administration</a> » <h1>Forums</h1>
{% endblock %}
{% block content %}
<section>
<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 %}
<ul>
{{ forumtree(main_forum) }}
</ul>
{% endif %}
</section>
{% endblock %}

View File

@ -8,8 +8,9 @@
<section>
<p>Pages générales du panneau d'administration :</p>
<ul>
<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>
</ul>
</section>
{% endblock %}

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 app.models.forum 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():
db.session.delete(f)
db.session.commit()
print("Removed all forums.")
return
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}]: {f.name}")
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
forums("clear")
# Create the forum tree
fr = []
success = 0
with open(os.path.join(app.root_path, "data", "forums.yaml")) as fp:
fr = yaml.safe_load(fp.read())
for url, f in fr.items():
if url == "/":
parent = None
else:
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}")
continue
f = Forum(url, f['name'], f['prefix'], f.get('descr', ''), parent)
db.session.add(f)
success += 1
db.session.commit()
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,
}

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 ###

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 ###