post: add a tagging system, with a common base set of tags
Adds the tagging system, with 3 types of tags: * Calculator models grouped by compatibility classes * Programming languages * Game, tools, and course categories [MIGRATION] This commit contains a new version of the schema. [BREAKS] This commit breaks existing tag assignments. [MASTER] Run the 'update-tags' command of master.py.
This commit is contained in:
parent
f4b9110ce2
commit
c74abf3fcc
|
@ -0,0 +1,102 @@
|
|||
# This is a list of all tags, sorted by category. The category names are used
|
||||
# to name CSS rules and shouldn't be changed directly.
|
||||
|
||||
# The following category groups calculators by common compatibility properties.
|
||||
# Each comment indicates why the group should exist on its own rather than
|
||||
# being merged with another one.
|
||||
calc:
|
||||
# Middle-school level, only basic algorithms; a unique property in this list.
|
||||
fx92:
|
||||
pretty: fx-92 Scientifique Collège+
|
||||
# Some of the most limited Graph models, no add-ins.
|
||||
g25:
|
||||
pretty: Graph 25/25+E/25+EII
|
||||
# The whole series with more Basic constructs than g25, but SH3 for add-ins
|
||||
# We don't separate based on whether an OS update is required (deemed safe)
|
||||
gsh3:
|
||||
pretty: Graph 35+/75/85/95 (SH3)
|
||||
# Same as gsh3, but with SH4 for add-ins; support CasioPython
|
||||
gsh4:
|
||||
pretty: Graph 35+/35+E/75+/75+E (SH4)
|
||||
# Like gsh3, but has Python; also; issues with the display and MonochromLib
|
||||
g35+e2:
|
||||
pretty: Graph 35+E II
|
||||
# Color display, nothing like the previous models
|
||||
cg20:
|
||||
pretty: fx-CG 10/20/Prizm
|
||||
# Like cg20, but has Python, and some incompatibilities on add-in
|
||||
g90+e:
|
||||
pretty: Graph 90+E
|
||||
# Different series entirely; has an SDK for add-ins
|
||||
cp300:
|
||||
pretty: Classpad 300/330
|
||||
# Like cp300, but does not have an SDK
|
||||
cp330+:
|
||||
pretty: Classpad 330+
|
||||
# Color display, entirely new model; no SDK
|
||||
cp400:
|
||||
pretty: Classpad 400/400+E
|
||||
|
||||
lang:
|
||||
basic:
|
||||
pretty: Basic CASIO
|
||||
cbasic:
|
||||
pretty: C.Basic
|
||||
python:
|
||||
pretty: Python
|
||||
c:
|
||||
pretty: C/C++ (add-in)
|
||||
lua:
|
||||
pretty: LuaFX
|
||||
other:
|
||||
pretty: "Langage: autre"
|
||||
|
||||
games:
|
||||
adventure:
|
||||
pretty: Aventure
|
||||
fighting:
|
||||
pretty: Combat
|
||||
other:
|
||||
pretty: "Jeu: autre"
|
||||
platform:
|
||||
pretty: Plateforme
|
||||
puzzle:
|
||||
pretty: Puzzle
|
||||
rpg:
|
||||
pretty: RPG
|
||||
rythm:
|
||||
pretty: Rythme
|
||||
shooting:
|
||||
pretty: Tir/FPS
|
||||
simulation:
|
||||
pretty: Simulation
|
||||
sport:
|
||||
pretty: Sport
|
||||
strategy:
|
||||
pretty: Stratégie
|
||||
survival:
|
||||
pretty: Survie
|
||||
|
||||
tools:
|
||||
conversion:
|
||||
pretty: Outil de conversion
|
||||
graphics:
|
||||
pretty: Outil graphique
|
||||
science:
|
||||
pretty: Outil scientifique
|
||||
programming:
|
||||
pretty: Outil pour programmer
|
||||
other:
|
||||
pretty: "Outil: autre"
|
||||
|
||||
courses:
|
||||
math:
|
||||
pretty: Maths
|
||||
physics:
|
||||
pretty: Physique
|
||||
engineering:
|
||||
pretty: SI/Électronique
|
||||
economics:
|
||||
pretty: Économie
|
||||
other:
|
||||
pretty: "Cours: autre"
|
|
@ -13,12 +13,16 @@ class Program(Post):
|
|||
|
||||
# TODO: Category (games/utilities/lessons)
|
||||
# TODO: Compatible calculator models
|
||||
# TODO: Number of views, statistics, etc
|
||||
|
||||
# Thread with the program description (top comment) and comments
|
||||
thread_id = db.Column(db.Integer,db.ForeignKey('thread.id'),nullable=False)
|
||||
thread = db.relationship('Thread', foreign_keys=thread_id,
|
||||
back_populates='owner_program')
|
||||
|
||||
# TODO: Number of views, statistics, attached files, etc
|
||||
# Implicit attributes:
|
||||
# * tags (inherited from Post)
|
||||
# * attachements (available at thread.top_comment.attachments)
|
||||
|
||||
def __init__(self, author, title, thread):
|
||||
"""
|
||||
|
|
|
@ -1,15 +1,43 @@
|
|||
from app import db
|
||||
|
||||
class TagInformation(db.Model):
|
||||
"""Detailed information about tags, by dot-string tag identifier."""
|
||||
|
||||
__tablename__ = 'tag_information'
|
||||
# The ID is the dot-string of the tag (eg. "calc.g35+e2")
|
||||
id = db.Column(db.String(64), primary_key=True)
|
||||
|
||||
# List of uses. Note how we load tag information along individual tags, but
|
||||
# we don't load uses unless the field is accessed.
|
||||
uses = db.relationship('Tag', back_populates='tag', lazy='dynamic')
|
||||
|
||||
# Pretty name
|
||||
pretty = db.Column(db.String(64))
|
||||
|
||||
# ... any other static information about tags
|
||||
|
||||
def __init__(self, id):
|
||||
self.id = id
|
||||
|
||||
class Tag(db.Model):
|
||||
"""Association between a Post and a dot-string tag identifier."""
|
||||
|
||||
__tablename__ = 'tag'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
|
||||
# Tagged post
|
||||
post_id = db.Column(db.Integer, db.ForeignKey('post.id'), index=True)
|
||||
post = db.relationship('Post', back_populates='tags', foreign_keys=post_id)
|
||||
# Tag name
|
||||
name = db.Column(db.String(64), index=True)
|
||||
# Tag name. Note how we always load the information along the tag, but not
|
||||
# the other way around.
|
||||
tag_id = db.Column(db.String(64), db.ForeignKey('tag_information.id'),
|
||||
index=True)
|
||||
tag = db.relationship('TagInformation', back_populates='uses',
|
||||
foreign_keys=tag_id, lazy='joined')
|
||||
|
||||
def __init__(self, post, tag):
|
||||
self.post = post
|
||||
self.name = tag
|
||||
|
||||
if isinstance(tag, str):
|
||||
tag = TagInformation.query.filter_by(id=tag).one()
|
||||
self.tag = tag
|
||||
|
|
44
master.py
44
master.py
|
@ -5,6 +5,7 @@ from app.models.user import Member, Group, GroupPrivilege
|
|||
from app.models.priv import SpecialPrivilege
|
||||
from app.models.trophy import Trophy, Title
|
||||
from app.models.forum import Forum
|
||||
from app.models.tag import TagInformation
|
||||
from app.utils import unicode_names
|
||||
import os
|
||||
import sys
|
||||
|
@ -23,11 +24,13 @@ Listing commands:
|
|||
groups Show privilege groups
|
||||
forums Show forum tree
|
||||
trophies Show trophies
|
||||
tags Show tags
|
||||
|
||||
Install and update commands:
|
||||
update-groups Create or update groups from app/data/
|
||||
update-forums Create or update the forum tree from app/data/
|
||||
update-trophies Create or update trophies
|
||||
update-tags Create or update tag information
|
||||
generate-trophy-icons Regenerate all trophy icons
|
||||
create-common-accounts Remove and recreate 'Planète Casio' and 'GLaDOS'
|
||||
add-group <member> #<id> Add <member> to group #<id> (presumably admins)
|
||||
|
@ -56,6 +59,11 @@ def trophies(*args):
|
|||
for t in Trophy.query.all():
|
||||
print(t)
|
||||
|
||||
def tags(*args):
|
||||
tags = TagInformation.query.all()
|
||||
for t in sorted(tags, key=lambda t: t.id):
|
||||
print(f"{t.id}: {t.pretty}")
|
||||
|
||||
#
|
||||
# Install and update commands
|
||||
#
|
||||
|
@ -160,7 +168,6 @@ def update_trophies():
|
|||
existing = Trophy.query.all()
|
||||
|
||||
# Get the list of what we want to obtain
|
||||
tr = []
|
||||
with open(os.path.join(app.root_path, "data", "trophies.yaml")) as fp:
|
||||
tr = yaml.safe_load(fp.read())
|
||||
tr = { t["name"]: t for t in tr }
|
||||
|
@ -207,6 +214,39 @@ def update_trophies():
|
|||
db.session.commit()
|
||||
|
||||
|
||||
def update_tags():
|
||||
existing = TagInformation.query.all()
|
||||
|
||||
with open(os.path.join(app.root_path, "data", "tags.yaml")) as fp:
|
||||
data = yaml.safe_load(fp.read())
|
||||
tags = { ctgy + "." + name: data[ctgy][name]
|
||||
for ctgy in data for name in data[ctgy] }
|
||||
|
||||
# Remove bad tags
|
||||
for t in existing:
|
||||
if t.id not in tags:
|
||||
print(f"[tags] Deleted '{t.id}'")
|
||||
db.session.delete(t)
|
||||
db.session.commit()
|
||||
|
||||
for name, info in tags.items():
|
||||
pretty = info.get("pretty", name)
|
||||
t = TagInformation.query.filter_by(id=name).first()
|
||||
|
||||
if t is not None:
|
||||
changes = (t.pretty != pretty)
|
||||
t.pretty = pretty
|
||||
if changes:
|
||||
print(f"[tags] Updated '{name}'")
|
||||
else:
|
||||
t = TagInformation(name)
|
||||
t.pretty = pretty
|
||||
print(f"[tags] Created '{name}'")
|
||||
|
||||
db.session.add(t)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
def generate_trophy_icons():
|
||||
tr = []
|
||||
with open(os.path.join(app.root_path, "data", "trophies.yaml")) as fp:
|
||||
|
@ -308,9 +348,11 @@ commands = {
|
|||
"groups": groups,
|
||||
"forums": forums,
|
||||
"trophies": trophies,
|
||||
"tags": tags,
|
||||
"update-groups": update_groups,
|
||||
"update-forums": update_forums,
|
||||
"update-trophies": update_trophies,
|
||||
"update-tags": update_tags,
|
||||
"generate-trophy-icons": generate_trophy_icons,
|
||||
"create-common-accounts": create_common_accounts,
|
||||
"add-group": add_group,
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
"""add tag information
|
||||
|
||||
Revision ID: 189bbc0e1543
|
||||
Revises: fa34c9f43c24
|
||||
Create Date: 2022-06-09 22:26:58.562710
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '189bbc0e1543'
|
||||
down_revision = 'fa34c9f43c24'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('tag_information',
|
||||
sa.Column('id', sa.String(length=64), nullable=False),
|
||||
sa.Column('pretty', sa.String(length=64), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.add_column('tag', sa.Column('tag_id', sa.String(length=64), nullable=True))
|
||||
op.drop_index('ix_tag_name', table_name='tag')
|
||||
op.create_index(op.f('ix_tag_tag_id'), 'tag', ['tag_id'], unique=False)
|
||||
op.create_foreign_key(None, 'tag', 'tag_information', ['tag_id'], ['id'])
|
||||
op.drop_column('tag', 'name')
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('tag', sa.Column('name', sa.VARCHAR(length=64), autoincrement=False, nullable=True))
|
||||
op.drop_constraint(None, 'tag', type_='foreignkey')
|
||||
op.drop_index(op.f('ix_tag_tag_id'), table_name='tag')
|
||||
op.create_index('ix_tag_name', 'tag', ['name'], unique=False)
|
||||
op.drop_column('tag', 'tag_id')
|
||||
op.drop_table('tag_information')
|
||||
# ### end Alembic commands ###
|
Loading…
Reference in New Issue