SQLAlchemy - Traditional and Declarative mapping in many-to-many - sqlalchemy

According to SQLAlchemy's documentation for many-to-many relationships, the join table is declared using Traditional mappings. The other tables are declared using Declarative mappings.
Why not just one type of mapping, like Declarative? Is that possible in this case?
association_table = Table('association', Base.metadata,
Column('left_id', Integer, ForeignKey('left.id')),
Column('right_id', Integer, ForeignKey('right.id'))
)
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship("Child",
secondary=association_table)
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)

It's absolutely possible, but the reason people don't typically do it is simply because they usually don't want to use the association table like an object.
It'd look something like:
class Left(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True
class Right(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
class M2MRightLeft(Base):
__tablename__ = 'm2m_right_left'
left_id = Column(Integer, ForeignKey('left.id'), primary_key=True)
right_id = Column(Integer, ForeignKey('right.id'), primary_key=True)
That being said, I'd generally stick with the traditional mappings for M2M relationships. Generally, I only use the declarative style if there are additional columns I want to add to the M2M table.

Related

SqlAlchemy - apply filter before join statement

I try to add some contribution on 3c7's cool project, and I want to apply a filter on a join query(sqlalchemy).
Simple statement: multiple rules(table) can have multiple tags(table) - I want to filter out some rules based on some tags.
[Rules][2] table (rule_id, etc)
[Tags][2] Table (tag_id, etc)
tags_rules(Junction table) (rule_id,tag_id) -- no declaration
Issue: Applying a filter after join of course that will remove only the rules that have only one tag(the one that I specify). If a rule has multiple tags, one record from the join result will be removed, but the rule will still appear in there are any other tags associated with that rule
Sql alchemy declaration:
class Rule(Base):
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
name = Column(String(255), index=True)
meta = relationship("Meta", back_populates="rule", cascade="all, delete, delete-orphan")
strings = relationship("String", back_populates="rule", cascade="all, delete, delete-orphan")
condition = Column(Text)
imports = Column(Integer)
tags = relationship("Tag", back_populates="rules", secondary=tags_rules)
ruleset_id = Column(Integer, ForeignKey("ruleset.id"))
ruleset = relationship("Ruleset", back_populates="rules")
class Tag(Base):
id = Column(Integer, primary_key=True, autoincrement=True, index=True)
name = Column(String(255), index=True)
rules = relationship("Rule", back_populates="tags", secondary=tags_rules)
I tried with sub queries but the most feasible seem to apply a filter on the tags table before the join.
Current implementation:
rules = rules.select_from(Tag).join(Rule.tags).filter(~Tag.name.in_(tags))
Any idea is greatly appreciated.

SQLAlchemy polymorphic Many-to-Many-Relation with Association Object

I have items of different type, say City, Country and Continent. And I have Labels. I want to be able to add multiple Labels to either City, Country and Continent objects. And each Label could have multiple items, too.
I cannot use table inheritance for defining City, Country and Continent here, since the models and their respective database tables are already existing and populated. Migrating the database to a different structure is impossible in my situation due to the size of the database. So I tried to make it with an Association Object, but I cannot figure out, how to create the relation from, say, Country to the Association in a way, that it only retrieves the lablables which have lablable_type = "country"
So, what I have looks like this:
Base = declarative_base()
class Lablable(Base):
__tablename__ = 'lablables'
label_id = db.Column(db.Integer, db.ForeignKey('labels.id'), primary_key=True)
lablable_id = db.Column(db.Integer)
lablable_type = db.Column(db.String(100))
labels = db.relationship("Label")
class Label(db.Model):
__tablename__ = "labels"
id = db.Column(db.Integer, primary_key=True, autoincrement=True, nullable=False)
caption = db.Column(db.String(200))
class Country(db.Model):
__tablename__ = "countries"
lablable_id = db.Column(db.Integer(), db.ForeignKey('lablables.id'))
lables = association_proxy('lablables', 'label',
creator=lambda x: Lablable(x=label))
How can I design my models to achieve what I want?

flask-sqlalchemy: how to define the 'comment' model (multi foreign keys referring to the same table)?

I'm working on a video sharing site, where users can comment under a video, or comment to someone else's comment. Here's my simplified user model and comment model:
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(MAX_USERNAME), unique=True)
comments = db.relationship('Comment', backref='replier', lazy='dynamic')
replied = db.relationship('Comment', backref='repliee', lazy='dynamic')
class Comment(db.Model):
__tablename__ = 'comments'
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.Text)
replier_id = db.Column(db.Integer, db.ForeignKey('users.id'), index=True)
repliee_id = db.Column(db.Integer, db.ForeignKey('users.id'), index=True)
video_id = db.Column(db.Integer, db.ForeignKey('videos.id'), index=True)
It prompts me with error:
Could not determine join condition between parent/child tables on relationship User.comments - there are multiple foreign key paths linking the tables. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference to the parent table
Anybody can help? New to flask and sqlalchemy.
The comments and replied relationships don't know which foreign key column on Comment to use, since both replier_id and repliee_id refer to users.id.
So add the appropriate foreign_keys arguments to comments and replied:
comments = db.relationship('Comment', backref='replier', lazy='dynamic', foreign_keys='[Comment.replier_id]')
replied = db.relationship('Comment', backref='repliee', lazy='dynamic', foreign_keys='[Comment.repliee_id]')

sqlalchemy: multiple relationships (many to many via association object)

I'm trying to do this:
class Foo(Base):
id = Column(Integer, primary_key=True)
class Bar(Foo):
id = Column(Integer, primary_key=True)
class FooBarAssociation(Base):
foo_id = Column(Integer, ForeignKey('foo_table.id'))
bar_id = Column(Integer, ForeignKey('bar_table.id'))
foo = relationship(Foo, backref=...)
bar = relationship(Bar, backref=...)
...but I get errors like this:
Could not determine join condition between parent/child tables on relationship FooBarAssociation.foo. Specify a 'primaryjoin' expression. If this is a many-to-many relationship, 'secondaryjoin' is needed as well.
I've tried specifying foreign_keys and primary_join-s in the relationship declarations, but all for naught. Help? Is the inheritance of Bar from Foo messing with me?
thanks!
Following should work (exactly what the error tells: missing primaryjoin):
class FooBarAssociation(Base):
foo_id = Column(Integer, ForeignKey('foo_table.id'), primary_key = True, )
bar_id = Column(Integer, ForeignKey('bar_table.id'), ForeignKey('foo_table.id'), primary_key = True, )
foo = relationship(Foo, backref="bars", primaryjoin=(foo_id==Foo.id))
bar = relationship(Bar, backref="foos", primaryjoin=(bar_id==Bar.id))
As you can see, there are two foreign keys on the bar_id column. This could be required due to inheritance, or you might remove one. But if you do not store any other information apart of the many-to-many relationship, then you might consider Association Proxy instead.

SQLAlchemy recursive many-to-many relation

I've a case where I'm using one table to store user and group related datas. This column is called profile. So, basically this table is many-to-many table for the cases where one user is belonging in to many groups or there are many users in one group.
I'm a bit confused how it should be described...
Here's a simplified presentation of the class.
Entity relationship model
user_group_table = Table('user_group', metadata,
Column('user_id', Integer,ForeignKey('profiles.id',
onupdate="CASCADE", ondelete="CASCADE")),
Column('group_id', Integer, ForeignKey('profiles.id',
onupdate="CASCADE", ondelete="CASCADE"))
)
class Profile(Base)
__tablename__ = 'profiles'
id = Column(Integer, autoincrement=True, primary_key=True)
name = Column(Unicode(16), unique=True) # This can be either user- / groupname
groups = relationship('Profile', secondary=user_group_table, backref = 'users')
users = relationship('Profile', secondary=user_group_table, backref = 'groups')
#Example of the usage:
user = Profile()
user.name = 'Peter'
salesGroup = Profile()
salesGroup.name = 'Sales'
user.groups.append(salesGroup)
salesGroup.users
>[peter]
First of all, I agree with Raven's comment that you should use separate tables for Users and Groups. The reason being that you might get some inconsistent data where a User might have other Users as its users relations, as well as you might have cycles in the relationship tree.
Having said that, to make the relationship work declare it as following:
...
class Profile(Base):
__tablename__ = 'profiles'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(Unicode(16), unique=True) # This can be either user- / groupname
groups = relationship('Profile',
secondary=user_group_table,
primaryjoin=user_group_table.c.user_id==id,
secondaryjoin=user_group_table.c.group_id==id,
backref='users')
...
Also see Specifying Alternate Join Conditions to relationship() documentation section.