Lucas Lucas - 1 month ago 16
Python Question

How to create a field with a list of foreign keys in SQLAlchemy?

I am trying to store a list of models within the field of another model. Here is a trivial example below, where I have an existing model,

Actor
, and I want to create a new model,
Movie
, with the field
Movie.list_of_actors
:

import uuid

from sqlalchemy import Boolean, Column, Integer, String, DateTime
from sqlalchemy.schema import ForeignKey
rom sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship

Base = declarative_base()


class Actor(Base):
__tablename__ = 'actors'

id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
name = Column(String)
nickname = Column(String)
academy_awards = Column(Integer)


# This is my new model:
class Movie(Base):
__tablename__ = 'movies'

id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
title = Column(String)
# How do I make this a list of foreign keys???
list_of_actors = Column(UUID(as_uuid=True), ForeignKey('actors.id'))


I understand that this can be done with a many-to-many relationship, but is there a more simple solution? Note that I don't need to look up which
Movie
's an
Actor
is in
- I just want to create a new
Movie
model and access the list of my
Actor
's. And ideally, I would prefer not to add any new fields to my
Actor
model.

I've gone through the tutorials using the relationships API, which outlines the various one-to-many/many-to-many combinations using
back_propagates
and
backref
here: http://docs.sqlalchemy.org/en/latest/orm/basic_relationships.html But I can't seem to implement my list of foreign keys without creating a full-blown many-to-many implementation.

But if a many-to-many implementation is the only way to proceed, is there a way to implement it without having to create an "association table"? The "association table" is described here: http://docs.sqlalchemy.org/en/latest/orm/basic_relationships.html#many-to-many ? Either way, an example would be very helpful!




Also, if it matters, I am using Postgres 9.5. I see from this post there might be support for arrays in Postgres, so any thoughts on that could be helpful.

Update

It looks like the only reasonable approach here is to create an association table, as shown in the selected answer below. I tried using ARRAY from SQLAlchemy's Postgres Dialect but it doesn't seem to support Foreign Keys. In my example above, I used the following column:

list_of_actors = Column('actors', postgresql.ARRAY(ForeignKey('actors.id')))


but it gives me an error. It seems like support for Postgres ARRAY with Foreign Keys is in progress, but still isn't quite there. Here is the most up to date source of information that I found: http://blog.2ndquadrant.com/postgresql-9-3-development-array-element-foreign-keys/

Answer

If you want many actors to be associated to a movie, and many movies be associated to an actor, you want a many-to-many. This means you need an association table. Otherwise, you could chuck away normalisation and use a NoSQL database.

An association table solution might resemble:

class Actor(Base):
    __tablename__ = 'actors'

    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    name = Column(String)
    nickname = Column(String)
    academy_awards = Column(Integer)

class Movie(Base):
    __tablename__ = 'movies'

    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    title = Column(String)

    actors = relationship('ActorMovie', uselist=True, backref='movies')

class ActorMovie(Base):
    __tablename__ = 'actor_movies'

    actor_id = Column(UUID(as_uuid=True), ForeignKey('actors.id'))
    movie_id = Column(UUID(as_uuid=True), ForeignKey('movies.id'))

If you don't want ActorMovie to be an object inheriting from Base, you could use sqlachlemy.schema.Table.