|
|
@@ -0,0 +1,96 @@
|
|
|
+Title: SQLAlchemy : papa, comment on fait les relations ?
|
|
|
+Category: Le développement web pour les nuls
|
|
|
+Tags: développement, SQLAlchemy, Python, web
|
|
|
+Summary:
|
|
|
+Image: /images/sqlalchemy_logo.png
|
|
|
+Lang: fr
|
|
|
+Status: draft
|
|
|
+
|
|
|
+___
|
|
|
+
|
|
|
+![Logo SQLAlchemy][sqlalchemy-logo]
|
|
|
+
|
|
|
+
|
|
|
+Maintenant que l'on a
|
|
|
+[une belle base de données toute propre et de quoi faire des modèles basiques][sqlalchemy-intro],
|
|
|
+il va peut être falloir un peu étoffer tout ça. Parce qu'une base de données
|
|
|
+sans relation entre les modèles, ça n'a pas grand intérêt.
|
|
|
+
|
|
|
+Là aussi, les choses vont être un peu plus compliquées que dans Django, où
|
|
|
+comme d'habitude Django faisait beaucoup de choses dans notre dos. Et de
|
|
|
+nouveau, certaines choses vont devoir être explicitées.
|
|
|
+
|
|
|
+Je vais de nouveau conseiller
|
|
|
+[l'excellent tutorial officiel][sqlalchemy-tutorial-relationship].
|
|
|
+
|
|
|
+## Déclaration des relations
|
|
|
+
|
|
|
+Dans le cadre d'une relation entre deux objets, il y a deux notions
|
|
|
+complètement différentes que Django ne m'avait pas vraiment habitué à distinguer,
|
|
|
+et qu'il faudra donc déclarer explicitement dans SQLAlchemy : celle de
|
|
|
+`ForeignKey` et celle de `relationship`.
|
|
|
+
|
|
|
+La `ForeignKey` est simplement la `PrimaryKey` d'une autre table, qui permet
|
|
|
+donc d'identifier de manière unique un objet d'un autre type avec lequel existe
|
|
|
+une relation. L'établissement d'une `relationship`, quant à lui, se fait au
|
|
|
+niveau de l'ORM, et va nous permettre de récupérer directement une instance de
|
|
|
+l'objet de l'autre table avec lequel on est en relation.
|
|
|
+
|
|
|
+En pratique, déclarer une relation entre deux objets ressemble à ça (ici, une
|
|
|
+relation 'one-to-many' de `User` vers `Thing`)
|
|
|
+
|
|
|
+ :::python
|
|
|
+ class User(Base):
|
|
|
+ __tablename__ = 'users'
|
|
|
+
|
|
|
+ id = Column(Integer, primary_key=True)
|
|
|
+ name = Column(String)
|
|
|
+ fullname = Column(String)
|
|
|
+
|
|
|
+ def __repr__(self):
|
|
|
+ return "<User(name='{}', fullname='{}')>".format(self.name, self.fullname)
|
|
|
+
|
|
|
+ class Thing(Base):
|
|
|
+ __tablename__ = 'things'
|
|
|
+
|
|
|
+ id = Column(Integer, primary_key=True)
|
|
|
+ name = Column(String)
|
|
|
+
|
|
|
+ # Déclaration de la ForeignKey
|
|
|
+ user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
|
|
|
+
|
|
|
+ # Déclaration de la relationship
|
|
|
+ user = relationship('User', backref='things')
|
|
|
+
|
|
|
+ def __repr__(self):
|
|
|
+ return "<Thing(name={})>".format(self.name)
|
|
|
+
|
|
|
+L'avantage de ce petit surplus de verbosité est qu'il permet beaucoup plus de
|
|
|
+souplesse ; si la `ForeignKey` se déclare toujours du côté 'many' d'une relation,
|
|
|
+la `relationship` peut se déclarer d'un côté comme de l'autre, avec les noms
|
|
|
+que l'on souhaite ... voire ne pas se déclarer du tout si elle n'est pas utile.
|
|
|
+
|
|
|
+SQLAlchemy permet d'ailleurs d'utiliser deux mots clés différents pour déclarer
|
|
|
+la relation réciproque : `backref`, qui crée automatiquement le champ réciproque
|
|
|
+sur l'objet lié (ici `things` sur `User`), et `back_populates`, qui ne le fait
|
|
|
+pas (il faudrait en plus des déclarations ci-dessus écrire explicitement
|
|
|
+`things = relationship('Thing', back_populates='user')` dans la classe `User`).
|
|
|
+
|
|
|
+`relationship` prend [de nombreux arguments optionels][sqlalchemy-relationships]
|
|
|
+qui permettent une immense souplesse d'utilisation.
|
|
|
+
|
|
|
+## Utilisation
|
|
|
+
|
|
|
+Alors là
|
|
|
+
|
|
|
+ :::python
|
|
|
+ user = User(name='theenglishway', fullname='theenglishway')
|
|
|
+ user.things = [Thing(name='something')]
|
|
|
+ dbm.Session.add(user)
|
|
|
+ dbm.Session.commit()
|
|
|
+
|
|
|
+
|
|
|
+[sqlalchemy-intro]: {filename}sqlalchemy-intro.md
|
|
|
+[sqlalchemy-tutorial-relationship]: https://docs.sqlalchemy.org/en/latest/orm/tutorial.html#building-a-relationship
|
|
|
+[sqlalchemy-logo]: /images/sqlalchemy_logo.png
|
|
|
+[sqlalchemy-relationships]: https://docs.sqlalchemy.org/en/latest/orm/relationship_api.html#sqlalchemy.orm.relationship
|