How to define direct getters for a many to many relationship with an Association Object using sqla?

StackOverflow https://stackoverflow.com/questions/23271024

Question

I have a many to many relationship that needs an extra field (order)
According to the documentation, I need an Association Object for that and I can not use 'secondary'.

So here is my code:

# Create Many to Many table with extra field
class NodesAssets(db.Model):
    node_id = db.Column(db.Integer, db.ForeignKey('node.id'), primary_key = True)
    node = db.relationship('Node', backref=db.backref('assets_rel'))

    asset_id = db.Column(db.Integer, db.ForeignKey('asset.id'), primary_key = True)
    asset = db.relationship('Asset', backref=db.backref('nodes_rel'))

    order = db.Column('order', db.Integer)

    def __str__(self):
        return "Nodes-Assets %s-%s" % (self.node, self.asset,)


class Node(db.Model):
    id = db.Column(db.Integer, primary_key = True)
    ...

    @property
    def assets(self):
        # return assets in order

    def __str__(self):
        return self.name


class Asset(db.Model): 
    id = db.Column(db.Integer, primary_key=True)
    ...

    @property
    def nodes(self):
        # return nodes, order is not important

    def __str__(self):
        return self.name

Question 1: how do I define a direct connection from one one table to the other as I would have if it was a secondary table (marked in the code as 'nodes' and 'assets') I'd like this both for the python instances and for direct queries. (so hybrid?)

Question 2: As it is not a 'secondary' relationship, I will not have the entries in the relation table deleted if the corresponding entries are deleted in one of the main tables?

Was it helpful?

Solution

I have came to an answer with the help of agronholm from #sqlalchemy at freenode. (thank you!)

SQLAlchemy already predicts this kind of situations (wanting a getter/setter for a relationship) http://docs.sqlalchemy.org/en/rel_0_9/orm/extensions/associationproxy.html
There is even the very specific case of my question documented with an example: http://docs.sqlalchemy.org/en/rel_0_9/orm/extensions/associationproxy.html#simplifying-association-objects

So, as to question 1, the answer is association proxies. By implementing it, the queries I made before converting the relationship from secondary to using an Association Object just work with no changes. To order the results as I needed, there is an order_by keyword.

For question 2, there are also keywords: cascade='all, delete-orphan'

Here is the full result:

class Node(db.Model):
    id = db.Column(db.Integer, primary_key = True)
    ...

    assets = association_proxy('node_assets', 'asset')

    def __str__(self):
        return self.name


class Asset(db.Model): 
    id = db.Column(db.Integer, primary_key=True)
    ...

    nodes = association_proxy('asset_nodes', 'node')

    def __str__(self):
        return self.name


# Create Many to Many table with extra field
class NodesAssets(db.Model):
    order = db.Column('order', db.Integer)

    node_id = db.Column(db.Integer, db.ForeignKey('node.id'), primary_key = True)
    node = db.relationship(Node, backref=db.backref('node_assets', order_by=order, cascade='all, delete-orphan'))

    asset_id = db.Column(db.Integer, db.ForeignKey('asset.id'), primary_key = True)
    asset = db.relationship(Asset, backref=db.backref('asset_nodes', cascade='all, delete-orphan'))

    def __str__(self):
        return "Nodes-Assets %s-%s" % (self.node, self.asset,)

However, not everything went well :(
While this is everything I wanted for the frontend, the backend with flask-admin is having some problems:

  • I wanted to have the Association table as an inline model for the Assets and Nodes, but that is currently not possible, because of lack of support for tables with multiple primary keys: flask-admin/issues/505
  • Forgetting the inline model part and just having the field there is also not possible: flask-admin/issues/236
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top