Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

sqlalchemy many-to-many, but inverse?

I'm sorry if inverse is not the preferred nomenclature, which may have hindered my searching. In any case, I'm dealing with two sqlalchemy declarative classes, which is a many-to-many relationship. The first is Account, and the second is Collection. Users "purchase" collections, but I want to show the first 10 collections the user hasn't purchased yet.

from sqlalchemy import *
from sqlalchemy.orm import scoped_session, sessionmaker, relation
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

engine = create_engine('sqlite:///:memory:', echo=True)
Session = sessionmaker(bind=engine)

account_to_collection_map = Table('account_to_collection_map', Base.metadata,
                                Column('account_id', Integer, ForeignKey('account.id')),
                                Column('collection_id', Integer, ForeignKey('collection.id')))

class Account(Base):
    __tablename__ = 'account'

    id = Column(Integer, primary_key=True)
    email = Column(String)

    collections = relation("Collection", secondary=account_to_collection_map)

    # use only for querying?
    dyn_coll = relation("Collection", secondary=account_to_collection_map, lazy='dynamic')

    def __init__(self, email):
        self.email = email

    def __repr__(self):
        return "<Acc(id=%s email=%s)>" % (self.id, self.email)

class Collection(Base):
    __tablename__ = 'collection'

    id = Column(Integer, primary_key=True)
    slug = Column(String)

    def __init__(self, slug):
        self.slug = slug

    def __repr__(self):
        return "<Coll(id=%s slug=%s)>" % (self.id, self.slug)

So, with account.collections, I can get all collections, and with dyn_coll.limit(1).all() I can apply queries to the list of collections...but how do I do the inverse? I'd like to get the first 10 collections that the account does not have mapped.

Any help is incredibly appreciated. Thanks!

like image 367
Hoopes Avatar asked Oct 21 '10 01:10

Hoopes


People also ask

What is many to many relationship in SQLAlchemy?

Many to Many relationship between two tables is achieved by adding an association table such that it has two foreign keys - one from each table's primary key.

What is back populates?

Learn about Klaviyo's back-populate feature which allows you to queue people for flow actions retroactively. This is useful when you create a new flow, as back-populating allows you to populate contacts into your flow that would have been queued in real-time had the flow existed earlier.

What is Backref in SQLAlchemy?

In Flask-SQLAlchemy, the backref parameter in relationship method allows you to declare a new property under a specified class as seen in the example in their docs: class Person(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(50)) addresses = db.relationship('Address', backref='person ...


1 Answers

I would not use the relationship for the purpose, as technically it it not a relationship you are building (so all the tricks of keeping it synchronized on both sides etc would not work).
IMO, the cleanest way would be to define a simple query which will return you the objects you are looking for:

class Account(Base):
    ...
    # please note added *backref*, which is needed to build the 
    #query in Account.get_other_collections(...)
    collections = relation("Collection", secondary=account_to_collection_map, backref="accounts")

    def get_other_collections(self, maxrows=None):
        """ Returns the collections this Account does not have yet.  """
        q = Session.object_session(self).query(Collection)
        q = q.filter(~Collection.accounts.any(id=self.id))
        # note: you might also want to order the results
        return q[:maxrows] if maxrows else q.all()
...
like image 183
van Avatar answered Oct 16 '22 13:10

van