Les modèles (dans /app/models
) représentent tous les contenus qui se trouvent dans la base de données. Généralement, une classe correspond à une table avec des attributs. Cette page du wiki décrit les relations entre les différentes parties du modèle, ainsi que les conventions sur le code.
Schéma du modèle complet
À date de 19d90c6845
(14 Avril 2022) :
- Les classes en bleu sont les classes standard/banales.
- Les classes en vert sont les classes de contenus principaux, qui héritent de
Post
(Comment
ne compte pas). - Les classes en blanc sont les parties non implémentées ou encore en discussion.
Gestion des Post
et contenus principaux
La première version du modèle voulait faire de l'héritage polymorphe en cascade, mais ce n'est pas pas supporté par SQLAlchemy, d'où la forme actuellement qui est simplifiée.
Architecture générale
Un Post
est soit un commentaire soit un des « contenus principaux » du site (topics, programmes, tutoriels, assets graphiques essentiellement). Tout ce qu'un utilisateur crée comme contenu est donc un Post
, et cette classe traque les dates et auteurs.
La classe Thread
représente un fil de commentaires liant des Comment
entre eux. Le top_comment
peut être changé dynamiquement.
Les classes de type Topic
, Program
possèdent un attribut vers le Thread
leur étant associé, qui est unique (les Thread
ne sont pas partagés).
Promotion des topics
La v5 a une idée assez centrale d'avoir un seul fil de discussion par projet, avec tout le contexte nécessaire du projet. En particulier, avoir à la fois un topic et les commentaires d'un programme sur la v4 est assez courant et assez casse-pieds.
Le système de promotion de topics revient à dire qu'un programme c'est juste un topic avec des téléchargements, qu'un tutoriel c'est juste un topic avec des outils spéciaux pour visualiser, etc. On s'autorise donc à convertir un topic en programme/tutoriel/asset graphique lorsque le contenu est publié.
Pour ne pas casser les liens vers le topic, cette promotion est enregistrée dans le champ promotion_id
du topic et une redirection est mise en place. Il faut donc toujours vérifier promotion_id
quand on travaille avec un topic.
Post principaux
Chaque Thread
possèdant un top_comment
, les Topic
, Program
, etc. n'ont plus d'attribut particulier contenant le post principal. Cette souplesse sur le top_comment
permet de changer dynamiquement le post principal, eg. de poster un nouveau commentaire et de le désigner comme post principal sans perdre l'ancien (qui est important pour avoir le contexte des commentaires précédents).
Conventions sur le code
Références à d'autres modèles
Pour faire référence à un autre objet de la base de données, comme par exemple un topic fait référence au forum dans lequel il se trouve, créez deux champs : l'entier servant à identifier l'ID distant et la relation servant à l'utiliser sous forme d'objet.
# Parent forum
forum_id = db.Column(db.Integer, db.ForeignKey('forum.id'), nullable=False)
forum = db.relationship('Forum', backref='topics',foreign_keys=forum_id)
Une fois que c'est fait, utilisez toujours la version objet. Vous ne devez jamais assigner une valeur à forum_id
ou prendre un ID entier en paramètre d'une fonction. Il suffit d'écrire my_topic.forum = my_forum
pour assigner l'attribut.
Utiliser backref et non back_populates
Pour les relations one-to-one et one-to-many, on place le champ d'ID et la relation d'un côté one (n'importe lequel), et on utilise backref
pour créer un attribut dans la classe référencée si on veut pouvoir lire la relation dans l'autre sens. Dans l'exemple ci-dessus, on a ajouté un attribut topics
à la classe Forum
, ce qui est signalé par un commentaire côté Forum
:
# Other fields populated automatically through relations:
# <topics> List of topics in this exact forum (of type Topic)
Avec back_populates
, il faut ajouter une relation dans les deux classes, ce qui est moins pratique parce que :
- Il y a plus de champs et les deux relations doivent 99% du temps avoir les même paramètres donc il faut assurer la cohérence de deux versions des paramètres.
- Il ne faut ajouter de champ
id
que d'un côté, ce qui augmente encore la confusion. Avecbackref
il y a toujours une paire ID/objet et on ne se pose pas de questions.
Relations many-to-many
Pour créer une relation many-to-many, il faut une table intermédiaire et des secondary
dans les relations. Il y a plusieurs exemples dans le code, voyez par exemple GroupMember
et GroupPrivilege
(app/models.privs.py
) pour un exemple simple et un exemple avec des propriétés supplémentaires, respectivement.
Ne pas importer les modèles pour les références
Le premier argument de db.relationship()
est une chaîne de caractères qui est résolue dynamiquement. Il n'y a pas besoin d'importer le modèle correspondant pour l'utiliser (par exemple Forum
dans le premier exemple), ce qui poserait plein de problèmes de dépendances circulaires.
Ne pas utiliser db.session
Les méthodes de modèles n'ont aucune raison d'appeller db.session
pour sauvegarder des changements dans la base de données. La route qui s'exécute se charge d'ajouter les objets et de les sauvegarder au moment opportun.