SQLAlchemy. How to order on many to many relationship? - python

I have a SQLAlchemy model named NoteType with a relationship named sections. The NoteSection table is joined to the NoteType table through NoteTypeToSectionMap.
I want the sections list on the NoteType model to be ordered by the position field on the NoteTypeToSectionMap model. The code I have below seems to randomly be ordering the sections.
Does anyone know how to get the ordering to work?
Thanks!
class NoteType(ModelAbstract):
__tablename__ = "noteType"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255))
description = db.Column(db.String(255))
sections = db.relationship("NoteSection",
secondary=NoteTypeToSectionMap.__table__,
primaryjoin=id==NoteTypeToSectionMap.__table__.c.noteTypeId,
secondaryjoin=id==NoteTypeToSectionMap.__table__.c.noteSectionId,
order_by=NoteTypeToSectionMap.__table__.c.position)
-
class NoteTypeToSectionMap(ModelAbstract):
__tablename__ = "noteTypeToSectionMap"
id = db.Column(db.Integer, primary_key=True)
noteTypeId = db.Column(db.Integer, db.ForeignKey("noteType.id"))
noteSectionId = db.Column(db.Integer, db.ForeignKey("noteSection.id"))
position = db.Column(db.Integer)

Re-write the relationship as follows.
sections = db.relationship("NoteSection",
secondary=NoteTypeToSectionMap.__table__,
order_by=NoteTypeToSectionMap.__table__.c.position)

Related

Python SQLalchemy Join lookup by User

Wrapping my head around a way to get a list of Jobs associated to a User. My DB Model goes a little something like this.
class Job(db.Model):
id = db.Column(db.Integer, primary_key=True)
# Relationship Rows
actions = db.relationship('JobAction', backref='job')
class JobAction(db.Model):
id = db.Column(db.Integer, primary_key=True)
# Linked Rows
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
# Relationship Rows
user = db.relationship('User', foreign_keys=[user_id], backref='jobactions')
I need to get a list of Jobs that are associated to a User. I can use either the User already matching a logged in users details. Or the user.id.
I was looking at something like the below, but no dice. I can see it's overly optimistic a query, but can't see what's up. Potentially a missing Join.
# Get User first.
user = User.query.filter_by(id=1).first()
# Get their Jobs
jobs = Job.query.filter_by(actions.user=user).all()
Any ideas would be greatly appreciated.
Cheers,
I'm guessing you are missing a foreign key. If your database model looked like this:
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
jobactions = db.relationship("JobAction", back_populates="user")
class Job(db.Model):
__tablename__ = 'jobs'
id = db.Column(db.Integer, primary_key=True)
jobactions = db.relationship('JobAction', backref='job')
class JobAction(db.Model):
__tablename__ = 'jobactions'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
job_id = db.Column(db.Integer, db.ForeignKey('jobs.id'))
user = db.relationship(User, back_populates="jobactions")
job = db.relationship(Job, back_populates="jobactions")
Then you could use:
jobs = [ jobaction.job for jobaction in user.jobactions ]

Ordering relationship by property of child elements

I use flask-sqlalchemy on a Flask project to model my database.
I need to sort the elements of a many-to-many relationship based on properties of different child elements of one side.
I have "Work" (the parent element), "Tag" (the children), "Type" (a one-to-many relationship on Tag) and "Block" (a one-to-many relationship on Type). Tags and Works are joined with a mapping table "work_tag_mapping".
In essence, each tag has exactly one type, each type belongs to exactly one block, and many tags can be added on many works.
I now want the list of tags on a work be sorted by block first and type second (both have a "position" column for that purpose).
Here are my tables (simplified for the sake of the question):
class Work(db.Model):
__tablename__ = 'work'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255, collation='utf8_bin'))
tags = db.relationship('Tag', order_by="Tag.type.block.position, Tag.type.position", secondary=work_tag_mapping)
class Tag(db.Model):
__tablename__ = 'tag'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255, collation='utf8_bin'))
type_id = db.Column(db.Integer, db.ForeignKey('type.id'), nullable=False)
type = db.relationship('Type')
work_tag_mapping = db.Table('work_tag_mapping',
db.Column('id', db.Integer, primary_key=True),
db.Column('work_id', db.Integer, db.ForeignKey('work.id'), nullable=False),
db.Column('tag_id', db.Integer, db.ForeignKey('tag.id'), nullable=False)
)
class Type(db.Model):
__tablename__ = 'type'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255, collation='utf8_bin'))
position = db.Column(db.Integer)
block_id = db.Column(db.Integer, db.ForeignKey('block.id'), nullable=False)
block = db.relationship('Block')
class Block(db.Model):
__tablename__ = 'block'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255, collation='utf8_bin'))
position = db.Column(db.Integer)
Now, it is the "order_by" in the "tags" relationship that doesn't work as I initially hoped.
The error I get is "sqlalchemy.exc.InvalidRequestError: Property 'type' is not an instance of ColumnProperty (i.e. does not correspond directly to a Column)."
I am new to SQLalchemy, Flask and indeed Python, and none of the ressources or questions here mention a case like this.
While this appears not to be possible directly, adding a getter and performing the sorting on retrieval does the trick. Adding lazy='dynamic' ensures the collection behaves as a query, so joins can be performed.
_tags = db.relationship('Tag', lazy='dynamic')
#hybrid_property
def tags(self):
return self._tags.join(Type).join(Block).order_by(Block.position, Type.position)

"has_many :through" construct in sqlalchemy

In rails we can simply define relationships with the has_many :through syntax in order to access 2nd, 3rd .. nth degree relations.
In SQLAlchemy however, this seems to be more difficult. I'm trying to avoid going down the route of writing joins, as I find them to be anti-patterns in trying to keep a clean code base.
My tables look like following:
class Message(db.Model):
__tablename__ = 'message'
id = db.Column(db.Integer, primary_key=True)
text = db.Column(db.String())
user_id = db.Column(db.ForeignKey("user.id"))
user = db.relationship('User', backref="messages")
class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String())
class Level(db.Model):
__tablename__ = 'level'
number = db.Column(db.Integer, nullable=False, primary_key=True)
name = db.Column(db.String(), nullable=False, primary_key=True)
users = db.relationship(
"User",
secondary="user_level",
backref="levels")
class UserLevel(db.Model):
__tablename__ = 'user_level'
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True)
number = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(), primary_key=True)
__table_args__ = (
db.ForeignKeyConstraint(
['number', 'name'],
['level.number', 'level.name']
),
)
The idea is that a user can have multiple authorisation levels (e.g. a user can be at level 1, 3 and 6 at the same time). As the data I have does not contain unique sequence numbers for available levels, I had to resort to the use of composite keys to keep the data consistent with future updates.
To get all messages for a level I can currently do something like this:
users = Level.query[0].users
for user in users:
results.append(user.messages)
return results
This gives me all users on a level. But in order to get all messages for a certain level, I have to loop through these users and append them to a results list.
What I'd like to do is:
return Level.query[0].users.messages
This is more like the syntax I am used to from rails. How would one accomplish this in flask-SQLAlchemy?

query to hierarchical mapper

I have two models, related with many-to-many, one of them is hierarchical model:
#hierarchical model
class Tag(Base):
__tablename__ = "tags"
id = Column(Integer, primary_key=True)
name = Column(String)
Tag.parent_id = Column(Integer, ForeignKey(Tag.id, ondelete='CASCADE'))
Tag.childs = relationship(Tag, backref=backref('parent', remote_side=[Tag.id]),
cascade="all, delete")
class Subject(Base):
__tablename__ = "subjects"
id = Column(Integer, primary_key=True, doc="ID")
name = Column(String)
tags = relationship(Tag, secondary="tags_subjects", backref="subjects")
#many-to-many relations model
class TagsSubjects(Base):
__tablename__ = "tags_subjects"
id = Column(Integer, primary_key=True)
tag_id = Column(Integer, ForeignKey("tags.id"))
subject_id = Column(Integer, ForeignKey("subjects.id"))
So, I'll try to explain what I want to do... I want to make one (or several) query, for search all Subject's objects,
that have 'name' field value like 'foo' OR that has related tags having names with values like 'foo'
OR that has related tags, that has one or more parents (or above by hierarchy) tag with 'name' value like 'foo'
I've tried to do somethis like this:
>>> subjects = session.query(Subject).filter(or_(
Subject.name.ilike('%{0}%'.format('foo')),
Subject.tags.any(
Tag.name.ilike('%{0}%'.format('foo')))
)).order_by(Subject.name).all()
But it isn't correct and "flat" query, without hierarchical feature :(
How to do this by SQLAlchemy's API?
Thanks!
P.S. I'm using SQLite backend

Sqlalchemy many to many mapping with extra fields

I created a many to many relationship with sqlalchemy like this:
subject_books = Table('subject_books', Base.metadata,
Column('subject_id', Integer, ForeignKey('subjects.id')),
Column('book_id', Integer, ForeignKey('books.id')),
Column('group', Integer)
)
class Subject(Base):
__tablename__ = 'subjects'
id = Column(Integer, primary_key=True)
value = Column(Unicode(255), unique=True)
class Book(Base):
__tablename__ = 'books'
id = Column(Integer, primary_key=True)
title = Column(Unicode(255))
isbn = Column(Unicode(24))
subjects = relationship('Subject', secondary=subject_books, collection_class=attribute_mapped_collection('group'), backref='books')
after that I created a test like following:
book = Book(title='first book',isbn='test')
book.subjects[0] = Subject(value='first subject')
book.subjects[1] = Subject(value='second subject')
session.add(book)
transaction.commit()
and it works fine. But what I really want is to store more than one subject with the same group value, so I tried the following test:
book = Book(title='first book',isbn='test')
book.subjects[0] = [Subject(value='first subject'),Subject(value='second subject')]
book.subjects[1] = [Subject(value='third subject'),Subject(value='forth subject')]
session.add(book)
transaction.commit()
but it does not work.
Can this be done using sqlalchemy?
Thanks in Advance
Razi
I think you are constructing wrong relation ship.
Your relation ship must be
book M2M subject
subject M2M group
So you have to create one more model for group and that must be assign as m2m in Subject
Your models will be like.
subject_books = Table('subject_books', Base.metadata,
Column('subject_id', Integer, ForeignKey('subjects.id')),
Column('book_id', Integer, ForeignKey('books.id')),
)
subject_group = Table('subject_groups', Base.metadata,
Column('group_id', Integer, ForeignKey('groups.id')),
Column('subject_id', Integer, ForeignKey('subjects.id')),
)
class Subject(Base):
__tablename__ = 'subjects'
id = Column(Integer, primary_key=True)
value = Column(Unicode(255), unique=True)
groups = relationship('Groups', secondary=subject_groups, backref='subjects')
class Groups(Base):
__tablename__ = 'groups'
id = Column(Integer, primary_key=True)
name = Column(Unicode(255), unique=True)
class Book(Base):
__tablename__ = 'books'
id = Column(Integer, primary_key=True)
title = Column(Unicode(255))
isbn = Column(Unicode(24))
subjects = relationship('Subject', secondary=subject_books, backref='books')
I also check the docs for attribute_mapped_collection. But each time I found that each key is associated with only one object not more then one. If you read anywhere then please provide the link so I can check that how it will be fit in your code.
I think this will be help you.

Categories