2 relationships of the same tablename confusing SQLAlchemy - sqlalchemy

IE:
class Share(db.Model):
"""
Stores the Checkpoint shares between users
"""
id = db.Column(db.Integer, primary_key=True)
user_from_id = db.Column(db.Integer, db.ForeignKey('user.id'))
user_from = db.relationship("User")
user_to_id = db.Column(db.Integer, db.ForeignKey('user.id'))
user_to = db.relationship("User")
I get an error:
ArgumentError: Could not determine join condition between parent/child
tables on relationship Share.user_from. Specify a 'primaryjoin'
expression. If 'secondary' is present, 'secondaryjoin' is needed as
well.
How can I fix this?
Thanks!

As the error message suggests, you need to specify primaryjoin value for your relationships:
class Share(db.Model):
# ...
user_from = relationship("User", primaryjoin="Share.user_from_id==User.id")
user_to = relationship("User", primaryjoin="Share.user_to_id==User.id")
# ...
See Specifying Alternate Join Conditions to relationship() for more information.

Related

SqlAlchemy - make a query where Relationship Attribute is a list

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()
)

Implementation of One-to-One relationship in Flask SQLAlchemy

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>))

Count related items in a sqlalchemy model

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

Many-to-many self-referential relationship in sqlalchemy

I'm trying to make a self-referential many-to-many relationship (it means that Line can have many parent lines and many child lines) in sqlalchemy like this:
Base = declarative_base()
class Association(Base):
__tablename__ = 'association'
prev_id = Column(Integer, ForeignKey('line.id'), primary_key=True)
next_id = Column(Integer, ForeignKey('line.id'), primary_key=True)
class Line(Base):
__tablename__ = 'line'
id = Column(Integer, primary_key = True)
text = Column(Text)
condition = Column(Text)
action = Column(Text)
next_lines = relationship(Association, backref="prev_lines")
class Root(Base):
__tablename__ = 'root'
name = Column(String, primary_key = True)
start_line_id = Column(Integer, ForeignKey('line.id'))
start_line = relationship('Line')
But I get the following error:
sqlalchemy.exc.ArgumentError: Could not determine join condition between parent/
child tables on relationship Line.next_lines. Specify a 'primaryjoin' expressio
n. If 'secondary' is present, 'secondaryjoin' is needed as well.
Do you know how I could remedy this?
You should just need:
prev_lines = relationship(
Association,
backref="next_lines",
primaryjoin=id==Association.prev_id)
Since this specifies the next_lines back reference there is no need to have a next_lines relationship.
You can also do this using the remote_side parameter to a relationship: http://www.sqlalchemy.org/trac/browser/examples/adjacency_list/adjacency_list.py

How to insert the 'answer' model?

The title may be not exactly, but I don't know how to express it.
I have 3 class: User, Question, Answer. The simple code is:
Session = scoped_session(sessionmaker())
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
questions = relationship('Question', backref="user")
answers = relationship('Answer', backref="user")
class Question(Base):
__tablename__ = 'questions'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('users.id'))
answers = relationship('Answer', backref="user")
class Answer(Base):
__tablename__ = 'answers'
user_id = Column(Integer, ForeignKey('users.id'))
question_id = Column(Integer, ForeignKey('questions.id'))
id = Column(Integer, primary_key=True)
Now, A user asked a question, so there will be an answer created:
user = get_user_from_session()
question = get_question(question_id)
# create answer
answer = Answer()
answer.user = user
answer.question = question
Session.add(answer) # !!!
Session.commit()
I hope the answer will be inserted to database, but unfortunately, there is an error reported:
AttributeError: 'module' object has no attribute '_sa_instance_state'
Is there something I've missed? How to fix it?
UPDATE
Thanks for #dhaffey, I've fixed the typos. I recreate a test file to test this, found no error happened again, but answer.user_id and answer.question_id are null in database after commit.
This is my code, you can run it directly.
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import *
engine = create_engine('sqlite:///test.sqlite', echo=True)
Session = scoped_session(sessionmaker())
Base = declarative_base()
Base.metadata.bind=engine
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String)
questions = relationship('Question')
answers = relationship('Answer')
class Question(Base):
__tablename__ = 'questions'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('users.id'))
title = Column(String)
answers = relationship('Answer')
class Answer(Base):
__tablename__ = 'answers'
user_id = Column(Integer, ForeignKey('users.id'))
question_id = Column(Integer, ForeignKey('questions.id'))
id = Column(Integer, primary_key=True)
Base.metadata.create_all()
user = User()
user.name = 'aaa'
Session.add(user)
Session.flush()
question = Question()
question.title = 'ttt'
question.user = user
Session.add(question)
Session.flush()
answer = Answer()
answer.user = user
answer.question = question
Session.add(answer)
Session.commit()
print answer.id # not None
found = Session.query(Answer).filter_by(id=answer.id).one()
print found.user.name # not None
print found.question.title # not None
# !!! It seems all models are saved correctly,
# but please open the test.sqlite database, (not querying by sqlahchemy)
# the question.user_id and answer.user_id and answer.question_id are null
Your class declarations don't "compile" for me, so I'm wondering if you've run this code, and which SQLAlchemy version you're using if so. The line
user_id = Column(Integer, ForeignKey='users.id')
raises
sqlalchemy.exc.ArgumentError: Unknown arguments passed to Column: ['ForeignKey']
with SQLAlchemy 0.6.4. You're trying to declare the foreign key with a keyword argument, but the correct usage is to construct a ForeignKey object and pass it positionally, like this:
user_id = Column(Integer, ForeignKey('users.id'))
With the foreign keys fixed, your example works as expected for me.
Note that you don't need to explicitly provide the primaryjoin argument on these relationships when the corresponding foreign keys are appropriately declared - SQLAlchemy infers the correct join.