Perdón si esto ha sido respondido en otro lado. He estado buscando SO y no he podido traducir las preguntas y respuestas aparentemente relevantes a mi escenario.
Estoy trabajando en un divertido proyecto personal en el que tengo 4 esquemas principales (salvo relaciones por ahora):
Restricciones (Base de las relaciones):
He diseñado la estructura de la base de datos así: 
Esto genera el siguiente sql:
DROP TABLE IF EXISTS episodes; DROP TABLE IF EXISTS personas; DROP TABLE IF EXISTS personas_episodes; DROP TABLE IF EXISTS clips; DROP TABLE IF EXISTS personas_clips; DROP TABLE IF EXISTS images; DROP TABLE IF EXISTS personas_images; CREATE TABLE episodes ( id INT NOT NULL PRIMARY KEY, title VARCHAR(120) NOT NULL UNIQUE, plot TEXT, tmdb_id VARCHAR(10) NOT NULL, tvdb_id VARCHAR(10) NOT NULL, imdb_id VARCHAR(10) NOT NULL); CREATE TABLE personas ( id INT NOT NULL PRIMARY KEY, name VARCHAR(30) NOT NULL, bio TEXT NOT NULL); CREATE TABLE personas_episodes ( persona_id INT NOT NULL, episode_id INT NOT NULL, PRIMARY KEY (persona_id,episode_id), FOREIGN KEY(persona_id) REFERENCES personas(id), FOREIGN KEY(episode_id) REFERENCES episodes(id)); CREATE TABLE clips ( id INT NOT NULL PRIMARY KEY, title VARCHAR(100) NOT NULL, timestamp VARCHAR(7) NOT NULL, link VARCHAR(100) NOT NULL, episode_id INT NOT NULL, FOREIGN KEY(episode_id) REFERENCES episodes(id)); CREATE TABLE personas_clips ( clip_id INT NOT NULL, persona_id INT NOT NULL, PRIMARY KEY (clip_id,persona_id), FOREIGN KEY(clip_id) REFERENCES clips(id), FOREIGN KEY(persona_id) REFERENCES personas(id)); CREATE TABLE images ( id INT NOT NULL PRIMARY KEY, link VARCHAR(120) NOT NULL UNIQUE, path VARCHAR(120) NOT NULL UNIQUE, episode_id INT NOT NULL, FOREIGN KEY(episode_id) REFERENCES episodes(id)); CREATE TABLE personas_images ( persona_id INT NOT NULL, image_id INT NOT NULL, PRIMARY KEY (persona_id,image_id), FOREIGN KEY(persona_id) REFERENCES personas(id), FOREIGN KEY(image_id) REFERENCES images(id));Y he intentado crear el mismo esquema en los modelos SQLAchemy (teniendo en cuenta SQLite para pruebas, PostgreSQL para producción) así:
# db is a configured Flask-SQLAlchemy instance from app import db # Alias common SQLAlchemy names Column = db.Column relationship = db.relationship class PkModel(Model): """Base model class that adds a 'primary key' column named ``id``.""" __abstract__ = True id = Column(db.Integer, primary_key=True) def reference_col( tablename, nullable=False, pk_name="id", foreign_key_kwargs=None, column_kwargs=None ): """Column that adds primary key foreign key reference. Usage: :: category_id = reference_col('category') category = relationship('Category', backref='categories') """ foreign_key_kwargs = foreign_key_kwargs or {} column_kwargs = column_kwargs or {} return Column( db.ForeignKey(f"{tablename}.{pk_name}", **foreign_key_kwargs), nullable=nullable, **column_kwargs, ) personas_episodes = db.Table( "personas_episodes", db.Column("persona_id", db.ForeignKey("personas.id"), primary_key=True), db.Column("episode_id", db.ForeignKey("episodes.id"), primary_key=True), ) personas_clips = db.Table( "personas_clips", db.Column("persona_id", db.ForeignKey("personas.id"), primary_key=True), db.Column("clip_id", db.ForeignKey("clips.id"), primary_key=True), ) personas_images = db.Table( "personas_images", db.Column("persona_id", db.ForeignKey("personas.id"), primary_key=True), db.Column("image_id", db.ForeignKey("images.id"), primary_key=True), ) class Persona(PkModel): """One of Roger's personas.""" __tablename__ = "personas" name = Column(db.String(80), unique=True, nullable=False) bio = Column(db.Text) # relationships episodes = relationship("Episode", secondary=personas_episodes, back_populates="personas") clips = relationship("Clip", secondary=personas_clips, back_populates="personas") images = relationship("Image", secondary=personas_images, back_populates="personas") def __repr__(self): """Represent instance as a unique string.""" return f"<Persona({self.name!r})>" class Image(PkModel): """An image of one of Roger's personas from an episode of American Dad.""" __tablename__ = "images" link = Column(db.String(120), unique=True) path = Column(db.String(120), unique=True) episode_id = reference_col("episodes") # relationships personas = relationship("Persona", secondary=personas_images, back_populates="images") class Episode(PkModel): """An episode of American Dad.""" # FIXME: We can add Clips and Images linked to Personas that are not assigned to this episode __tablename__ = "episodes" title = Column(db.String(120), unique=True, nullable=False) plot = Column(db.Text) tmdb_id = Column(db.String(10)) tvdb_id = Column(db.String(10)) imdb_id = Column(db.String(10)) # relationships personas = relationship("Persona", secondary=personas_episodes, back_populates="episodes") images = relationship("Image", backref="episode") clips = relationship("Clip", backref="episode") def __repr__(self): """Represent instance as a unique string.""" return f"<Episode({self.title!r})>" class Clip(PkModel): """A clip from an episode of American Dad that contains one or more of Roger's personas.""" __tablename__ = "clips" title = Column(db.String(80), unique=True, nullable=False) timestamp = Column(db.String(7), nullable=True) # 00M:00S link = Column(db.String(7), nullable=True) episode_id = reference_col("episodes") # relationships personas = relationship("Persona", secondary=personas_clips, back_populates="clips") Sin embargo, observe el comentario de FIXME . Tengo problemas para descubrir cómo restringir las relaciones de muchos a muchos en personas+imágenes, personas+clips y personas+episodios de manera que todos se miren entre sí antes de agregar una nueva entrada para restringir las posibles adiciones. al subconjunto de elementos que cumplen los criterios de esas otras relaciones de muchos a muchos.
¿Puede alguien proporcionar una solución para garantizar que las relaciones de muchos a muchos respeten la relación de episode_id en las tablas principales?
Edite para agregar un ejemplo de pseudomodelo del comportamiento esperado
# omitting some detail fields for brevity e1 = Episode(title="Some Episode") e2 = Episode(title="Another Episode") p1 = Persona(name="Raider Dave", episodes=[e1]) p2 = Persona(name="Ricky Spanish", episodes=[e2]) c1 = Clip(title="A clip", episode=e1, personas=[p2]) # should fail i1 = Image(title="An image", episode=e2, personas=[p1]) # should fail c2 = Clip(title="Another clip", episode=e1, personas=[p1]) # should succeed i2 = Image(title="Another image", episode=e2, personas=[p2]) # should succeedNo puedo pensar en ninguna forma de agregar esta lógica en la base de datos. ¿Sería aceptable administrar estas restricciones en su código? Me gusta esto:
Evento: se insertaría una nueva imagen en DB
# create new image with id = 1 and linked episode = 1 my_image = Image(...) # my personas Persona.query.all() [<Persona('Homer')>, <Persona('Marge')>, <Persona('Pikachu')>] # my episodes >>> Episode.query.all() [<Episode('the simpson')>] #my images >>> Image.query.all() [<Image 1>, <Image 2>] # personas in first image >>> Image.query.all()[0].personas [<Persona('Marge')>] # which episode? >>> Image.query.all()[0].episode <Episode('the simpson')> # same as above but with next personas (Note that Pikachu is linked to the wrong episode) >>> Image.query.all()[1].personas [<Persona('Pikachu')>] >>> Image.query.all()[1].episode <Episode('the simpson')> # before saving the Image object check its episode id, then for that episode get a list of personas that appear. # Look for your persona id of Image inside this list to see if this persona appear in the episode my_image.episode.id # return 1 # get a list of persona(s).id that appear in the episode linked to the image! personas_in_episode = [ persona.id for persona in Episode.query.filter_by(id=1).first().personas ] # return list of id of Persona objects [1,2] (Homer and Marge as episode with id 1 is The Simpson) my_image_personas = [ persona.id for persona in my_image.personas ] # return a list of id of Persona objects linked in image [3] Persona is Pikachu >>> my_image_personas [3] >>> for persona_id in my_image_personas: ... if persona_id not in personas_in_episode: ... print(f"persona with id {persona_id} does not appear in the episode for this image") ... persona with id 3 does not appear in the episode for this imageLo mismo ocurre con el clip.
Creo que necesita modificar personas_images agregar la columna id_episodio y luego agregar una clave externa compuesta a personas_episode en id_episodio/id_personas y modificar la clave externa de la imagen para que sea un compuesto en id_imagen/id_episodio. Esto asegura que la persona esté en el episodio y que la imagen esté en el mismo episodio.
Luego haz lo mismo con los clips.
Revisé su ER y encontré un problema en la relación de las siguientes entidades. Después de los cambios a continuación, su esquema se completará.
CREATE TABLE episodes ( id INT NOT NULL PRIMARY KEY, title VARCHAR(120) NOT NULL UNIQUE, plot TEXT, tmdb_id VARCHAR(10) NOT NULL, tvdb_id VARCHAR(10) NOT NULL, imdb_id VARCHAR(10) NOT NULL -- Need relationship as below ); CREATE TABLE clips ( id INT NOT NULL PRIMARY KEY, title VARCHAR(100) NOT NULL, timestamp VARCHAR(7) NOT NULL, link VARCHAR(100) NOT NULL, episode_id INT NOT NULL, FOREIGN KEY(episode_id) REFERENCES episodes(id) -- No need as below ); CREATE TABLE images ( id INT NOT NULL PRIMARY KEY, link VARCHAR(120) NOT NULL UNIQUE, path VARCHAR(120) NOT NULL UNIQUE, episode_id INT NOT NULL, FOREIGN KEY(episode_id) REFERENCES episodes(id)-- No need as below );La relación correcta debe ser la siguiente:
CREATE TABLE episodes ( id INT NOT NULL PRIMARY KEY, title VARCHAR(120) NOT NULL UNIQUE, plot TEXT, tmdb_id VARCHAR(10) NOT NULL, tvdb_id VARCHAR(10) NOT NULL, imdb_id VARCHAR(10) NOT NULL, clips_id VARCHAR(10) NOT NULL, clips_id INT NOT NULL , images_id INT NOT NULL , FOREIGN KEY(clips_id) REFERENCES clips(id) FOREIGN KEY(images_id) REFERENCES images(id) ); CREATE TABLE clips ( id INT NOT NULL PRIMARY KEY, title VARCHAR(100) NOT NULL, timestamp VARCHAR(7) NOT NULL, link VARCHAR(100) NOT NULL, episode_id INT NOT NULL ); CREATE TABLE images ( id INT NOT NULL PRIMARY KEY, link VARCHAR(120) NOT NULL UNIQUE, path VARCHAR(120) NOT NULL UNIQUE, episode_id INT NOT NULL );