Reputation: 1229
I have a multi-customer plateform, persons that can have several labels, and let say a foo object with one label. I don't want users from each customer to share their contacts (persons) or labels or foo with other customers. Each label name is unique per customer.
How could I have a compound key for label (name, customer_name) and a n-m relationship between persons and labels ?
I tried this :
person_label_table = Table('person_label', Base.metadata,
Column('person_id', Integer, ForeignKey('person.id'), primary_key=True),
Column('name', Unicode(32), ForeignKey('label.name'), primary_key=True),
Column('customer_name', String(32), ForeignKey('label.customer_name'), primary_key=True)
)
class Person(Base, SaveMixin):
__tablename__ = 'person'
id = Column(Integer, primary_key=True)
labels = relationship('Label', secondary=person_label_table)
class Label(Base, SaveMixin):
__tablename__ = 'label'
name = Column(Unicode(32), primary_key=True)
customer_name = Column(String(32), ForeignKey('customer.name'), primary_key=True)
color = Column(String(32))
But I have this error :
sqlalchemy.exc.AmbiguousForeignKeysError: Can't determine join between 'label' and 'person_label'; tables have more than one foreign key constraint relationship between them. Please specify the 'onclause' of this join explicitly.
I tried also with a more classic link table person_label(label_id, person_id) and adding an id to the labels but the label id's must be loaded to the front web else when I do a session.merge()
with labels (without id) I have :
sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) UNIQUE constraint failed: label.name, label.customer_name [SQL: 'INSERT INTO label (name, customer_name, color) VALUES (?, ?, ?)'] [parameters: ('foo', 'bar', 'grey')]
So how would you handle this case ?
Thank you for your time.
EDIT : Just in case it could help, after the Brendan Abel response, and the shown code, I had this error:
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship Person.labels - there are multiple foreign key paths linking the tables via secondary table 'person_label'. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference from the secondary table to each of the parent and child tables.
so I changed the definitions of labels in person:
labels = relationship('Label', secondary=person_label_table, foreign_keys=[id]):
But I had then :
sqlalchemy.exc.NoForeignKeysError: Could not determine join condition between parent/child tables on relationship Person.labels - there are no foreign keys linking these tables via secondary table 'person_label'. Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or specify 'primaryjoin' and 'secondaryjoin' expressions.
I ended up with (adding the primaryjoin/secondaryjoin on person and removing the ForeignKey on compound keys) :
class Person(Base, SaveMixin):
__tablename__ = 'person'
labels = relationship('Label', secondary=person_label_table,
primaryjoin='and_(Person.id == person_label.c.person_id)',
secondaryjoin='and_(person_label.c.name == Label.name, person_label.c.customer_name == Label.customer_name)')
person_label_table = Table('person_label', Base.metadata,
Column('person_id', Integer, ForeignKey('person.id'), primary_key=True),
Column('name', Unicode(32), primary_key=True),
Column('customer_name', String(32), primary_key=True),
ForeignKeyConstraint(['name', 'customer_name'], ['label.name', 'label.customer_name'])
)
Upvotes: 3
Views: 1025
Reputation: 37599
You'll have to explicitly indicate the composite relationship using a ForeignKeyConstraint
:
person_label_table = Table('person_label', Base.metadata,
Column('person_id', Integer, ForeignKey('person.id'), primary_key=True),
Column('name', Unicode(32), primary_key=True),
Column('customer_name', String(32), primary_key=True),
ForeignKeyConstraint(['name', 'customer_name'], ['label.name', 'label.customer_name'])
)
The docs provide some examples of this.
It’s important to note that the
ForeignKeyConstraint
is the only way to define a composite foreign key. While we could also have placed individual ForeignKey objects on both the invoice_item.invoice_id and invoice_item.ref_num columns, SQLAlchemy would not be aware that these two values should be paired together - it would be two individual foreign key constraints instead of a single composite foreign key referencing two columns.
Upvotes: 3