I have next model for SqlAlchemy:
class Post(db.Model):
__tablename__ = 'posts'
post_id = db.Column(db.Integer, primary_key=True)
created_at = db.Column(db.DateTime)
# I dont want list of all title translations: titles = db.relationship("Content").
# How can I pass lang_code here for properly title value?
title = db.relationship("Content",
primaryjoin="and_(Content.post_id==Post.post_id,
Content.lang_code=='%s')" % lang_code, uselist=False)
class Content(db.Model):
__tablename__ = 'content'
content_id = db.Column(db.Integer, primary_key=True)
value = db.Column(db.String())
lang_code = db.Column(db.String(2)) # for ex: 'en', 'fr', 'de', 'ru'
post_id = db.Column(db.Integer, db.ForeignKey('posts.post_id'))
I want to get Post contains 'en' title without list of all translations in my code. I want to pass lang_code value into it but I don't know how.
And access it with something like this:
result = Post.query.filter_by(author_id=1).pass_custom_var_to_all_posts_in_query(lang_code='en').paginate(page, 20, False)
Or, may be, there are exists way to filter child rows (and show one title with current language instead of list of all titles)
I would use Association Proxy with attribute_mapped_collection.
Your model somewhat reworked:
class Post(Base):
__tablename__ = 'posts'
post_id = Column(Integer, primary_key=True)
name = Column(String)
created_at = Column(DateTime)
_titles = relationship(
"Content",
collection_class=attribute_mapped_collection("lang_code"),
)
titles = association_proxy(
'_titles', 'lang_code',
creator=lambda k, v: Content(lang_code=k, value=v),
)
class Content(Base):
__tablename__ = 'content'
content_id = Column(Integer, primary_key=True)
value = Column(String())
lang_code = Column(String(2)) # for ex: 'en', 'fr', 'de', 'ru'
post_id = Column(Integer, ForeignKey('posts.post_id'))
Then you can create Post instances:
p = Post(
name="no english",
titles={
'de': 'liebe',
'es': 'amor',
},
)
session.add(p)
Adding/Updating is a simple as p.titles['it'] = 'amore', and deleting as del p.titles['it'].
Finally, your query with filter for Title which have translation to the specific language:
q = (
session.query(Post)
# .filter(Post.author_id == 1)
.filter(Post.titles.contains('en'))
)
you can use this https://sqlalchemy-i18n.readthedocs.io/en/latest/
this library will give the ability to create table for translation for each model and get the localized data
Related
I have two models:
class Profile(Base):
__tablename__ = 'profiles'
id = Column(Integer, primary_key=True)
...
stagesP_list = relationship(
'StageP',
back_populates='profiles_list',
secondary=stageP_profile
)
class Project(Base):
__tablename__ = 'projects'
id = Column(Integer, primary_key=True)
...
stagesP_list = relationship(
'StageP',
back_populates='projects_list',
secondary=stageP_project
)
I need to select Profiles for which at least one value of the Profile.stagesP_list is contained in the project.stagesP_list.
Please help to compose the query or indicate the direction in which to search.
If you have project instance loaded, you can compose the following query:
project = ...
stageP_ids = [obj.id for obj in project.stagesP_list]
query = session.query(Profile).filter(
Profile.stagesP_list.any(StageP.id.in_(stageP_ids))
)
You can also perform joins on the database directly from having only project_id:
query = (
session.query(Profile)
.join(StageP, Profile.stagesP_list)
.join(Project, StageP.projects_list)
.where(Project.id == project_id)
.distinct()
)
I have two model classes:
class Programs(db.Model):
__tablename__ = "programs"
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
description = db.Column(db.String(100), nullable=False)
duration = db.Column(db.Integer, nullable=False)
date_created = db.Column(db.DATE, default=datetime.now())
created_by = db.Column(db.String(100))
program_sessions = db.relationship('Program_Session',backref='programs')
class Program_Session(db.Model):
__tablename__ = "program_session"
id = db.Column(db.Integer, primary_key=True)
session_title = db.Column(db.String(100), nullable=False)
session_description = db.Column(db.String(100))
session_year = db.Column(db.Integer)
program_id = db.Column(db.Integer, db.ForeignKey("programs.id"), nullable=False)
students = db.relationship('Student_Registration', backref='program_session')
date_created = db.Column(db.DATE, default=datetime.now())
created_by = db.Column(db.String(100))
I create an object of Programs with:
program = Programs.query.first()
Now I can access all the Program_Sessions from the selected Program:
print(pro.program_sessions)
Is it possible to subquery/query to retrieve only those Program_session in Program whose year is 2021?
Option-1: filter on 'python' (in memory)
Once you get all Program_Sessions (all_sessions = pro.program_sessions), you filter them by sessions_2021 = [item for item in all_sessions if item.session_year == 2021].
Needless to say, this is not efficient at all as lots of data will be loaded from the database to be immediately discarded.
Option2: use Dynamic Relationship Loaders
Define the relationship with lazy="dynamic", which will return a Query and hence you will be able to apply additional criteria to the query:
class Programs(db.Model):
# ...
program_sessions = db.relationship('Program_Session', backref='programs', lazy="dynamic")
program = Programs.query.first()
sessions_2021 = program.program_sessions.filter(Program_Session.year == 2021).all()
Option3: use orm.with_parent [BEST]
sessions_2021 = select(Program_Session).where(with_parent(program, Program_Session.programs)).where(Program_Session.year == 2021)
The answer is yes...
van's answer shows you options for playing with sqlalchemy's query mechanism. But what if you want to write this logic on the Programs class itself? That way anywhere you have a Programs object, you can access the filter.
You can do it in pretty plain python by altering the Programs class like so:
class Programs(db.Model):
__tablename__ = "programs"
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
description = db.Column(db.String(100), nullable=False)
duration = db.Column(db.Integer, nullable=False)
date_created = db.Column(db.DATE, default=datetime.now())
created_by = db.Column(db.String(100))
program_sessions = db.relationship('Program_Session',backref='programs')
'''Here I add a filter that returns only the sessions for a particular year
'''
def program_sessions_by_year(self, year):
return filter(lambda ps: ps.session_year == year, self.program_sessions)
If you care about efficiency, you can get the database to do the filtering for you using a bit more sqlalchemy magic:
from sqlalchemy.orm import object_session
class Programs(db.Model):
__tablename__ = "programs"
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
description = db.Column(db.String(100), nullable=False)
duration = db.Column(db.Integer, nullable=False)
date_created = db.Column(db.DATE, default=datetime.now())
created_by = db.Column(db.String(100))
program_sessions = db.relationship('Program_Session',backref='programs')
'''Improve efficiency by using DB's SQL engine to filter the object.
'''
def program_sessions_by_year(self, year):
return object_session(self)\
.query(Program_Session)\
.filter_by(session_year=year, program_id=self.id)\
.all()
Either way you can then write (where-ever you have a Program object):
# lets say you just want the first program
first_program = Programs.query.first()
# to get the program sessions by year 2021
first_program.program_sessions_by_year(2021)
There's probably a bunch of other ways you could do something like this. SqlAlchemy is a big library. For more background on my answer, have a look at the SQL expressions as Mapped Attributes docs.
I am inexperienced programmer. I'd like to add one-to-one relationship CurrencyDefault (that value will be assigned to the field in FlaskForm) between User and Currency:
class User(UserMixin, db.Model):
id = db.Column(db.Integer, db.Sequence('user_id_seq'), primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(64), index=True, unique=True)
pswd_hash = db.Column(db.String(128))
expenses = db.relationship('Expense', backref='user')
currency = db.relationship('Currency', backref='user')
curr_default = ?
# ...
class Currency(db.Model):
id = db.Column(db.Integer, db.Sequence('expense_id_seq'), primary_key=True)
abbr = db.Column(db.String(10))
name = db.Column(db.String(64))
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
default = ?
# ...
What I want to achieve is to assign to each user.id one currency.id (one-to-one)
I'd like to ask for some advice what is the best way to make it.
After considering the problem I have some ideas like:
Association Table with uselist=False relationship,
Create a new class CurrencyDefault(id, user_id, currency_id),
Or maybe there is other, better way to achieve it?
I'm very curious of your point of view on this problem.
Implementing solution:
This is how my classes look like now:
class User(UserMixin, db.Model):
id = db.Column(db.Integer, db.Sequence('user_id_seq'), primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(64), index=True, unique=True)
pswd_hash = db.Column(db.String(128))
currency_default_choice = db.Column(db.Integer, b.ForeignKey('currency.id'))
expenses = db.relationship('Expense', backref='user')
currency = db.relationship('Currency', backref='user', foreign_keys="Currency.user_id")
# currency_default = db.relationship(
# 'Currency',
# foreign_keys='User.currency_default_choice',
# backref='currency_default',
# uselist=False,
# )
# ...
class Currency(db.Model):
id = db.Column(db.Integer, db.Sequence('expense_id_seq'), primary_key=True)
abbr = db.Column(db.String(10), b.ForeignKey('currency_official_abbr.abbr'))
name = db.Column(db.String(64))
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
currency_default = db.relationship(
'User',
foreign_keys='User.currency_default_choice',
backref='currency_default',
uselist=False,
)
The first problem I find is that I can set Currency.id object created by other_user as currency_default_choice. How to restrict currency_default_choice only to the <Currency> that was created by this user?
What is the difference between setting relationship having foreign key in User class (currency_default_choice = db.Column(db.Integer, b.ForeignKey('currency.id'))) with:
class Currency(db.Model):
# ...
currency_default = db.relationship(
'User',
foreign_keys='User.currency_default_choice',
backref='currency_default',
uselist=False,
)
and setting this relationship on User side with:
class User(db.Model):
# ...
currency_default = db.relationship(
'Currency',
foreign_keys='User.currency_default_choice',
backref='currency_default',
uselist=False,
)
Ad.2. What seems to me is that there is no difference between these two ways because the backref parameter implicates bidirectional behavior so it doesn't matter if I placed db.relationship() in User or Currency class. Is it correct?
Using Python shell I added value to the User.currency_default
>>> app = create_app()
>>> app.app_context().push()
>>> admin = User.query.filter_by(username='admin').first()
<User(id= 1, username = admin, email = admin#admin.com)
>>> currency = Currency.query.filter_by(user=admin)
>>> currency
<flask_sqlalchemy.BaseQuery object at 0x03EA05D0>
>>> currency[0].id
1
>>> admin.currency_default = currency[0]
>>> db.session.commit()
>>> currency[0].currency_default
<User(id= 1, username = admin, email = admin#admin.com)
>>> admin.currency_default_choice
1
and then using Admin Panel after running flask run I wanted to remove introduced value but I got error that I don't understand. Why there is circular dependency between (Currency.currency_default),(User.currency_default) and (User.currency)? I don't understand what is happening. How to fix it?
sqlalchemy.exc.CircularDependencyError: Circular dependency detected.
(ProcessState(OneToManyDP(Currency.currency_default),
<Currency at 0x46542b0>, delete=False),
ProcessState(ManyToOneDP(User.currency_default),
<User at 0x4669b10>, delete=False),
SaveUpdateState(<Currency at 0x46542b0>),
ProcessState(OneToManyDP(User.currency), <User at 0x4669b10>, delete=False),
SaveUpdateState(<User at 0x4669b10>))
I'm trying to count the number of items in their respective categories and end up with a collection that I can iterate through in a jinja template. My final output is something like:
category1, 5
category2, 10
category3, 0
The zero items case is important.
My model is:
class Category(Base):
__tablename__ = 'category'
id = Column(Integer, primary_key=True)
name = Column(String(80), unique=True)
user_id = Column(Integer, ForeignKey('user.id'))
user = relationship(User)
class Item(Base):
__tablename__ = 'item'
id = Column(Integer, primary_key=True)
name = Column(String(80))
description = Column(String(500))
category_id = Column(Integer, ForeignKey('category.id'))
category = relationship(Category)
user_id = Column(Integer, ForeignKey('user.id'))
user = relationship(User)
date_added = Column(DateTime, default=datetime.datetime.now)
I have been kindly pointed in the direction of Stackoverflow: Counting relationships in SQLAlchemy, which led me to the query
count_categories = db_session.query(Category.name, func.count(Item.id)).join(Item.category).group_by(Category.id).all()
Which is almost correct, but it does not handle the zero case. When a category has zero items, I still need the category returned by the query.
Any help, much appreciated.
Actually, I've figured it out:
count_categories = db_session.query(
Category.name, func.count(Item.id)).outerjoin(
Item).group_by(Category.id).all()
See SQLAlchemy documentation on Joins
I have the following setup
class Content(Base):
"""Content object"""
__tablename__ = "content"
id = Column(Integer, primary_key=True)
name = Column(Unicode(255),unique=True, nullable=False)
title = Column(Unicode(255))
body = Column(UnicodeText)
created = Column(DateTime, default=func.now())
modified = Column(DateTime, onupdate=func.now())
type = Column(String(20))
__mapper_args__ = {
'polymorphic_on':type,
'polymorphic_identity':'content',
'with_polymorphic':'*'
}
class Locality(Content):
__tablename__ = "local"
id = Column(Integer, ForeignKey('content.id'),primary_key=True)
city_name = Column(Unicode(80))
__mapper_args__ = {'polymorphic_identity':'city'}
Now I dropped the Locality table using alembic.
Each time I query Content, I get
AssertionError: No such polymorphic_identity 'city' is defined
How do I drop this polymorphic_identity
I got over this by applying MySQL 'delete from' command on the content through MySQL Console, finding those contents whose type is 'city'
delete from content where content.type='city';