I have the classes User and Listing, and I am trying to create a many-to-many relationship so that a User can have many favorite listings, and any listing can be favorited by many users.
I've been using this as a reference but this is my first many-to-many relationship so any help would be appreciated.
InvalidRequestError: One or more mappers failed to initialize - can't
proceed with initialization of other mappers. Original exception was:
Could not determine relationship direction for primaryjoin condition
'favorites_table.user_id = :user_id_1', on relationship
User.favorites. Ensure that the referencing Column objects have a
ForeignKey present, or are otherwise part of a ForeignKeyConstraint on
their parent Table, or specify the foreign_keys parameter to this
relationship.
models.py
favorites_table = db.Table('favorites_table',
db.Column('user_id', db.Integer, db.ForeignKey('listing.id')),
db.Column('listing_id', db.Integer, db.ForeignKey('user.id'))
)
class User(db.Model):
id = db.Column(db.Integer, primary_key = True)
listings = db.relationship('Listing', backref = 'manager', lazy = 'dynamic')
favorites = db.relationship('Listing',
secondary=favorites_table,
primaryjoin = ('favorites_table.c.user_id == id'),
secondaryjoin = ('favorites_table.c.listing_id == id'),
backref = db.backref('user', lazy = 'dynamic'),
lazy = 'dynamic')
def favorite_listing(self, listing):
if not self.is_favorite(listing):
self.favorites.append(listing)
return self
def unfavorite_listing(self, listing):
if self.is_favorite(listing):
self.favorites.remove(listing)
return self
def is_favorite(self, listing):
return self.favorites.filter(favorites_table.c.listing_id == listing.id).count() > 0
class Listing(db.Model):
id = db.Column(db.Integer, primary_key = True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
Since your model has all required ForeignKeys specified, sqlachemy is smart enough to figure out the parameters for primaryjoin and secondaryjoin itself. So this should work just fine:
favorites = db.relationship('Listing',
secondary = favorites_table,
# primaryjoin = 'favorites_table.c.user_id == User.id',
# secondaryjoin = 'favorites_table.c.listing_id == Listing.id',
backref = db.backref('users', lazy = 'dynamic'),
lazy = 'dynamic',
)
If you really wish to be explicit, you can uncomment the two rows above for exactly the same result. Note that I added a model name before each id column specified.
Please note that in your favorites_table relationship table column user_id points to Listing.id while listing_id -> User.id, while it looks like it should be the other way around.
Related
I have a versioning system of annotations
class Annotation(db.Model):
id = db.Column(db.Integer, primary_key=True)
class AnnotationVersion(db.Model):
id = db.Column(db.Integer, primary_key=True)
book_id = db.Column(db.Integer, db.ForeignKey("book.id"))
previous_id = db.Column(db.Integer, db.ForeignKey("post_version.id"), default=None)
pointer_id = db.Column(db.Integer, db.ForeignKey("annotation.id"))
current = db.Column(db.Boolean, index=True)
first_line_num = db.Column(db.Integer)
last_line_num = db.Column(db.Integer)
class Line(db.Model):
id = db.Column(db.Integer, primary_key=True)
book_id = db.Column(db.Integer, db.ForeignKey("book.id")
line = db.Column(db.String(255))
I have the following two relationships on the Annotation class:
lines = db.relationship("Line", secondary="annotation_version",
primaryjoin="and_(Annotation.id==AnnotationVersion.pointer_id,"
"AnnotationVersion.current==True)",
secondaryjoin="and_(Line.l_num>=AnnotationVersion.first_line_num,"
"Line.l_num<=AnnotationVersion.last_line_num,"
"Line.book_id==AnnotationVersion.book_id)",
viewonly=True, uselist=True)
context = db.relationship("Line", secondary="annotation_version",
primaryjoin="and_(Annotation.id==AnnotationVersion.pointer_id,"
"AnnotationVersion.current==True)",
secondaryjoin="and_(Line.l_num>=AnnotationVersion.first_line_num-5,"
"Line.l_num<=AnnotationVersion.last_line_num+5,"
"Line.book_id==AnnotationVersion.book_id)",
viewonly=True, uselist=True)
As you can see, the context is simply the first_line_num-5 and last_line_num+5; in other words, the context of the annotation is simply the the prior five and next five lines to the actual body of the text of the annotation.
I am trying to define the same context relationship on the actual AnnotationVersion:
context = db.relationship("Line",
primaryjoin="and_(Line.l_num>=AnnotationVersion.first_line_num-5,"
"Line.l_num<=AnnotationVersion.last_line_num+5,"
"Line.book_id==AnnotationVersion.book_id)",
viewonly=True, uselist=True)
But this exact definition always returns a failure of
sqlalchemy.exc.ArgumentError: Could not locate any relevant foreign
key columns for primary join condition 'line.l_num >=
annotation_version.first_line_num - :first_line_num_1 AND line.l_num
<= annotation_version.last_line_num + :last_line_num_1 AND
line.book_id = annotation_version.book_id' on relationship
AnnotationVersion.context. Ensure that referencing columns are
associated with a ForeignKey or ForeignKeyConstraint, or are annotated
in the join condition with the foreign() annotation.
If I remove either the +5 or the -5 it works. But as soon as I define both, I get that error.
What on earth could cause this particular failure? As you can see it only happens when defined in the primaryjoin condition, because it works perfectly as a secondaryjoin condition.
Ilja Everila's reference to the documentation helped solve it. All I had to do was specify the foreign_key param:
context = db.relationship("Line",
primaryjoin="and_(Line.l_num>=AnnotationVersion.first_line_num-5,"
"Line.l_num<=AnnotationVersion.last_line_num+5,"
"Line.book_id==AnnotationVersion.book_id)",
foreign_keys=[first_line_num, last_line_num],
viewonly=True, uselist=True)
I have following models:
class Details(db.Model):
details_id = db.Column(db.Integer, primary_key=True)
details_main = db.Column(db.String(50))
details_desc = db.Column(db.String(50))
class Data(db.Model):
data_id = db.Column(db.Integer, primary_key=True)
data_date = db.Column(db.Date)
details_main = db.Column(db.String(50))
#property
def details_desc(self):
result = object_session(self).\
scalar(
select([Details.details_desc]).
where(Details.details_main == self.details_main)
)
return result
Now, I would like to run query using filter which depends on defined property. I get empty results (of course proper data is in DB). It doesn't work because, probably, I have to map this property. The question is how to do this? (One limitation: FK are not allowed).
Data.query\
.filter(Data.details_desc == unicode('test'))\
.all()
You can implement this with a regular relationship and an association proxy:
class Data(db.Model):
data_id = db.Column(db.Integer, primary_key=True)
data_date = db.Column(db.Date)
details_main = db.Column(db.String(50))
details = relationship(
Details,
primaryjoin=remote(Details.details_main) == foreign(details_main))
details_desc = association_proxy('details', 'details_desc')
Since there are no foreign keys in the schema, you need to tell SQLAlchemy yourself what the join condition for the relationship should be. This is what the remote() and foreign() annotations do.
With that in place, you can use an association_proxy "across" the relationship to create a property on Data which will work the way you want.
I have a many to many relationship to itself, that means, my model is User and one field is friends, which would be Users. I have done it the relationship but my problem comes when I try to do the query for the friends of an User. My model and the relation looks like this:
friendship = db.Table('friends',
Column('friend_id', db.Integer, db.ForeignKey('monkeys.id')),
Column('myfriend_id', db.Integer, db.ForeignKey('monkeys.id'))
)
class Monkey (db.Model):
_tablename__ = "monkeys"
id = Column(Integer, primary_key=True)
username = Column(String(50), unique = True)
email = Column(String(120), unique = True)
password = Column(String(50))
date = Column (DateTime(), default=datetime.now())
friends = relationship('Monkey',
secondary = friendship,
primaryjoin = (friendship.c.friend_id == id),
secondaryjoin = (friendship.c.myfriend_id == id),
backref = backref('friendship', lazy = 'dynamic'),
lazy = 'dynamic')
And from de view, if I want to do the query, I have tried with something
friends_list = Monkey.query.join(Monkey.friends).filter(Monkey.id == user.id).all()
Bu it does not work... any help please? thanks!
You don't need to create join by yourself.
change lazy='joined' - items will be loaded “eagerly” in the same query as that of the parent.
When You get object Monkey you already have access to friends
monkey = session.query(Monkey).get(user.id)
friends_list = monkey.friends
How do I initialize a group object with the list of members that are part of the group (and the users have to be aware of the groups that they are a part of)?
Users have many groups. Groups have many users.
I have also tried an add_to_group method in the User class, but that didn't really work out.
This is my first time dealing with a many-to-many relationship, so I haven't figured out how to do it yet, and all SO posts refer to query many-to-many relationships rather than creating objects that use them.
class User(db.Model):
__tablename__ = 'User'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), unique = False, nullable = False)
username = db.Column(db.String(80), unique = True, nullable = False)
fb_id = db.Column(db.String(80), unique = True, nullable = False)
groups = db.relationship('Groups', secondary=groups, backref=db.backref('User', lazy='dynamic'))
groups = db.Table('groups',
db.Column('group_id', db.Integer, db.ForeignKey('Group.id')),
db.Column('user_id', db.Integer, db.ForeignKey('User.id'))
)
class Group(db.Model):
__tablename__ = 'Group'
id = db.Column(db.Integer, primary_key=True)
last_updated = db.Column(db.DateTime, mutable=True)
def __init__(self):
self.last_updated = datetime.utcnow();
Thanks!
In SQLAlchemy, many-to-many relations are modeled as attributes whose values are lists.
u = User()
g = [Group(), Group(), Group()]
u.groups = g
You can also alter the list in-place:
g1 = Group()
u.groups.append(g)
The other side of the relationship would work in the same way:
g.users.append(User())
When you alter one side of a many-to-many relationship, SQLAlchemy keeps the other side up-to-date—in other words, if you remove a User from a Group's users list, then that Group will no longer appear in that User's groups list.
I'm getting a bit stuck with relationships within relationships in sqlalchemy. I have a model like this:
engine = create_engine('sqlite:///tvdb.db', echo=True)
Base = declarative_base(bind=engine)
episodes_writers = Table('episodes_writers_assoc', Base.metadata,
Column('episode_id', Integer, ForeignKey('episodes.id')),
Column('writer_id', Integer, ForeignKey('writers.id')),
Column('series_id', Integer, ForeignKey('episodes.series_id'))
class Show(Base):
__tablename__ = 'shows'
id = Column(Integer, primary_key = True)
episodes = relationship('Episode', backref = 'shows')
class Episode(Base):
__tablename__ = 'episodes'
id = Column(Integer, primary_key = True)
series_id = Column(Integer, ForeignKey('shows.id'))
writers = relationship('Writer', secondary = 'episodes_writers',
backref = 'episodes')
class Writer(Base):
__tablename__ = 'writers'
id = Column(Integer, primary_key = True)
name = Column(Unicode)
So there is one-to-many between shows and episodes, and many-to-many between episodes and writers, but I now want to create a many-to-many relationship between shows and writers, based on the fact that a writer is associated with an episode of a show. I tried to add another column to the assoc table but that results in the following exception:
sqlalchemy.exc.ArgumentError: Could not determine join condition between parent/child tables on relationship Episode.writers. Specify a 'primaryjoin' expression. If 'secondary' is present, 'secondaryjoin' is needed as well.
So I'm obviously doing something wrong with my relationship declarations. How do I create the right primaryjoins to achieve what I'm trying to do here?
The fix is suggested right in the error message. Anyway, you have no need for a column that denormalises your schema; writer.shows can be an associationproxy.