I'm trying to get the following contrived example to work:
class Person(Base):
__tablename__ = "persons"
__table_args__ = {"mysql_charset": "utf8mb4"}
id = Column(Integer, primary_key=True)
name = Column(String(50), index=True, unique=True)
birthday = Column(Boolean, default=False)
#hybrid_property
def is_birthday_person(self):
return self.birthday == True
class Party(Base):
__tablename__ = "parties"
__table_args__ = {"mysql_charset": "utf8mb4"}
id = Column(BigInteger, primary_key=True)
one_id = Column(Integer, ForeignKey("persons.id"))
two_id = Column(Integer, ForeignKey("persons.id"))
one_person = relationship("Person", foreign_keys=[one_id])
two_person = relationship("Person", foreign_keys=[two_id])
sunshine = Column(Boolean, default=False)
#hybrid_property
def everyones_birthday_in_sunshine(self):
return self.one_person.is_birthday_person and self.two_person.is_birthday_person and self.sunshine
What I am trying to achieve is to filter all parties and only return parties where both persons celebrate their birthday and the sun is shining when I run the following query:
session.Query(Party).filter(Party.everyones_birthday_in_sunshine).all()
The error I am getting is:
AttributeError: Neither 'InstrumentedAttribute' object nor 'Comparator' object associated with Party.one_person has an attribute 'is_birthday_person'
When I change everyones_birthday_in_sunshine to:
return self.sunshine == True
It works and return all parties where the sun in shining when I run:
session.Query(Party).filter(Party.everyones_birthday_in_sunshine).all()
So the problem lies in the relationship between Party and Person, but I can't figure out how to get it to work.
Edit:
When I try to print properties from the person instance in Party: everyones_birthday_in_sunshine eg: one_person.name it just prints out Party.one_person. Definitly a relationship issue I guess.
Related
I seem to have a hard time locating an issue I have with the following many-to-many relationship:
class UserTable(Base):
__tablename__ = "user"
id = Column(String, primary_key=True)
name = Column(UnicodeText)
courses = relationship("CourseTable", secondary="user_course")
class CourseTable(Base):
__tablename__ = "course"
id = Column(String, primary_key=True)
title = Column(UnicodeText)
users = relationship("UserTable", secondary="user_course")
class UserCourseTable(Base):
__tablename__ = "user_course"
user_id = Column(String, ForeignKey('user.id'), primary_key=True)
course_id = Column(String, ForeignKey('course.id'), primary_key=True)
user = relationship("UserTable", backref=backref("user_course"))
course = relationship("CourseTable", backref=backref("user_course"))
When querying for User
user = await self.session.execute(
select(UserTable)
.where(UserTable.id == user_id)
)
I get the following error:
When initializing mapper mapped class UserCourseTable->user_course, expression 'CourseTable' failed to locate a name ('CourseTable'). If this is a class name, consider adding this relationship() to the <class 'app.models.db.user_course.UserCourseTable'> class after both dependent classes have been defined.
I tried everything described in their docs, but to no avail: the error still persists
Any suggestions?
Thanks!
EDIT:
SQLAlchemy==1.4.36
I am trying to build a model where there is the default values then there is the user defined values. So the default values would come from the spices table. Yes the spices table would contain default data. The user would define the composite spice and make modifications as desired for a specific recipe. If you think I am structuring this wrong please provide your expertise. I feel lost on how to do this.
class User(db.Model, UserMixin):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=True)
#... extra
#... extra
class Spice(db.Model):
__tablename__ = 'spices'
code = db.Column(db.String(5), primary_key=True) # this is the id code
name = db.Column(db.String(60))
origin = db.Column(db.String(15))
def __init__(self, code, name, origin):
self.code = code
self.name = name
self.origin = origin
class Seasoning(Spice):
__tablename__ = 'seasonings'
# Note that the below item should come from Recipe. How would I do this?
recipe_id = db.Column(db.Integer, db.ForeignKey('recipe.id'), nullable=False)
class Recipe(db.Model):
__tablename__ = 'recipe'
user = db.relationship(User)
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(60))
description = db.Column(db.Text(), nullable=False)
date = db.Column(db.DateTime, default=datetime.datetime.utcnow)
ingredient = db.relationship('Ingredient', backref='recipe', lazy='dynamic', primaryjoin="Recipe.id == Seasoning.recipe_id")
def __init__(self, id, name, description, date):
self.id = id
self.name = name
self.description = description
self.date = date
in my views.py I have
...
seasoning = Seasoning(code=from.code.data, name=form.name.data, origin=form.origin,
recipe_id=recipe_id)
db.session.add(seasoning)
db.create_all()
db.session.commit()
...
When I run this I do get an error when I try to commit() to seasoning. How do I resolve this?
sqlalchemy.exc.OperationalError: (raised as a result of Query-invoked
autoflush; consider using a session.no_autoflush block if this flush
is occurring prematurely) (sqlite3.OperationalError) table spices has
no column named recipe_id
You need to describe recipe_id in your spices class
table spices has no column named recipe_id
I am trying to create a working relationship between two objects Mentor and Student, and be able to retrieve student objects from their mentor:
class Mentor_Student(Base):
__tablename__ = 'mentor_student'
__table_args__ = {'extend_existing': True}
mentor_id = Column("mentor_id", Integer, ForeignKey('mentor.mentor_id'), primary_key = True)
student_id = Column("student_id", Integer, ForeignKey('student.student_id'), primary_key = True)
def __init__(self, student_id):
self.mentor_id = random.choice(list(session.query(Mentor.mentor_id)))[0]
self.student_id = student_id
session.add(self)
session.commit()
class Mentor(Base):
__tablename__ = 'mentor'
__table_args__ = {'extend_existing': True}
mentor_id = Column(Integer, primary_key=True)
name = Column(String(255), nullable=False)
phone = Column(String(20), nullable=False)
mentees = relationship(
"Mentor",
secondary='mentor_student',
backref=backref("student", lazy='joined'))
def __init__(self):
self.name = Faker().name()
self.phone = Faker().phone_number()
session.add(self)
session.commit()
def __str__(self):
return self.name
class Student(Base):
__tablename__ = 'student'
__table_args__ = {'extend_existing': True}
student_id = Column(Integer, primary_key=True)
name = Column(String(255), nullable=False)
phone = Column(String(20), nullable=False)
mentors = relationship(
"Student",
secondary='mentor_student',
backref=backref("mentor",
lazy='joined'))
def __init__(self):
self.name = Faker().name()
self.phone = Faker().phone_number()
session.add(self)
session.commit()
Mentor_Student(self.student_id)
def __str__(self):
return self.name
Every mentor has multiple students. I would like to create a query that will return the students(mentees) associated with each Mentor. Please observe below:
for x, y in session.query(Mentors, Mentors.mentees).all()
print(x,':',y)
could produce the result:
MentorObject : [StudentObject, StudentObject, StudentObject]
Right now the closest I can get is printing out a single mentor object and a single student object associated with it. I also could hard code it with the accumulator pattern into a dicitonary:
maps = {}
for student, mentor in session.query(Student, Mentor).filter(Student.student_id == Mentor_Student.student_id, Mentor_Student.mentor_id == Mentor.mentor_id).all():
if mentor in maps.keys():
maps[mentor].append(student)
else:
maps[mentor] = [student]
Which gives me the result:
{<__main__.Mentor object at 0x7f3309887070>: [<__main__.Student object at 0x7f3309850f40>, <__main__.Student object at 0x7f3309887280>, <__main__.Student object at 0x7f3309887580>, <__main__.Student object at 0x7f330988f7c0>, <__main__.Student object at 0x7f330988fa00>, <__main__.Student object at 0x7f330982b4c0>],
...
<__main__.Mentor object at 0x7f33097e2550>: [<__main__.Student object at 0x7f33097e2490>, <__main__.Student object at 0x7f33097e2790>]}
'''
But this does not seem like a refined solution. Any ideas how I can improve my code. I am relatively new to SQLAlchemy.
I've seen a few questions similar to this but none quite hit the nail on the head. Essentially I have three table models Center(), Business(), and CenterBusiness() in a Flask Application using SQLAlchemy. Currently I'm adding to said relationship in this manner:
biz = Business(typId=form.type.data, name=form.name.data,
contact=form.contact.data, phone=form.phone.data)
db.session.add(biz)
db.session.commit()
assoc = CenterBusiness(bizId=biz.id, cenId=session['center'])
db.session.add(assoc)
db.session.commit()
As you can see that's a bit ugly and I know there is a way to do it in one hit with the relationship as they are defined. I see on SQLAlchemy's docs they have a explanation of working with such a table but I can't seem to get it to work.
#Directly from SQLAlchemy Docs
p = Parent()
a = Association(extra_data="some data")
a.child = Child()
p.children.append(a)
#My Version Using my Tables
center = Center.query.get(session['center']
assoc = CenterBusiness()
assoc.business = Business(typId=form.type.data, name=form.name.data,
contact=form.contact.data, phone=form.phone.data)
center.businesses.append(assoc)
db.session.commit()
Unfortunately, that doesn't seem to be doing the trick... Any help would be greatly appreciated and below I've posted the models involved.
class Center(db.Model):
id = db.Column(MEDIUMINT(8, unsigned=True), primary_key=True,
autoincrement=False)
phone = db.Column(VARCHAR(10), nullable=False)
location = db.Column(VARCHAR(255), nullable=False)
businesses = db.relationship('CenterBusiness', lazy='dynamic')
employees = db.relationship('CenterEmployee', lazy='dynamic')
class Business(db.Model):
id = db.Column(MEDIUMINT(8, unsigned=True), primary_key=True,
autoincrement=True)
typId = db.Column(TINYINT(2, unsigned=True),
db.ForeignKey('biz_type.id',
onupdate='RESTRICT',
ondelete='RESTRICT'),
nullable=False)
type = db.relationship('BizType', backref='businesses',
lazy='subquery')
name = db.Column(VARCHAR(255), nullable=False)
contact = db.Column(VARCHAR(255), nullable=False)
phone = db.Column(VARCHAR(10), nullable=False)
documents = db.relationship('Document', backref='business',
lazy='dynamic')
class CenterBusiness(db.Model):
cenId = db.Column(MEDIUMINT(8, unsigned=True),
db.ForeignKey('center.id',
onupdate='RESTRICT',
ondelete='RESTRICT'),
primary_key=True)
bizId = db.Column(MEDIUMINT(8, unsigned=True),
db.ForeignKey('business.id',
onupdate='RESTRICT',
ondelete='RESTRICT'),
primary_key=True)
info = db.relationship('Business', backref='centers',
lazy='joined')
archived = db.Column(TINYINT(1, unsigned=True), nullable=False,
server_default='0')
I was able to get this working, my problem lied in the following bit of code (error in bold):
#My Version Using my Tables
center = Center.query.get(session['center']
assoc = CenterBusiness()
**assoc.info** = Business(typId=form.type.data, name=form.name.data,
contact=form.contact.data, phone=form.phone.data)
center.businesses.append(assoc)
db.session.commit()
As explained in my comment in the question:
Alright my issue was that I was not using the relationship key "info"
I have in my CenterBusiness model to define the appended association.
I was saying center.business thinking that the term business in that
case was arbitrary. However, I needed to actually reference that
relationship. As such, the appropriate key I had setup already in
CenterBusiness was info.
I will still accept any updates and/or better ways to handle this situation, though I think this is the best route at the time.
below example can help u
more details http://docs.sqlalchemy.org/en/latest/orm/extensions/associationproxy.html
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String(64))
# association proxy of "user_keywords" collection
# to "keyword" attribute
keywords = association_proxy('user_keywords', 'keyword')
def __init__(self, name):
self.name = name
class UserKeyword(Base):
__tablename__ = 'user_keyword'
user_id = Column(Integer, ForeignKey('user.id'), primary_key=True)
keyword_id = Column(Integer, ForeignKey('keyword.id'), primary_key=True)
special_key = Column(String(50))
# bidirectional attribute/collection of "user"/"user_keywords"
user = relationship(User,
backref=backref("user_keywords",
cascade="all, delete-orphan")
)
# reference to the "Keyword" object
keyword = relationship("Keyword")
def __init__(self, keyword=None, user=None, special_key=None):
self.user = user
self.keyword = keyword
self.special_key = special_key
class Keyword(Base):
__tablename__ = 'keyword'
id = Column(Integer, primary_key=True)
keyword = Column('keyword', String(64))
def __init__(self, keyword):
self.keyword = keyword
def __repr__(self):
return 'Keyword(%s)' % repr(self.keyword)
I'm trying to create one-to-one and one-to-many relationship at the same time in Flask-SQLAlchemy. I want to achieve this:
"A group has many members and one administrator."
Here is what I did:
class Group(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(140), index=True, unique=True)
description = db.Column(db.Text)
created_at = db.Column(db.DateTime, server_default=db.func.now())
members = db.relationship('User', backref='group')
admin = db.relationship('User', backref='admin_group', uselist=False)
def __repr__(self):
return '<Group %r>' % (self.name)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
group_id = db.Column(db.Integer, db.ForeignKey('group.id'))
admin_group_id = db.Column(db.Integer, db.ForeignKey('group.id'))
created_at = db.Column(db.DateTime, server_default=db.func.now())
However I got an error:
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join
condition between parent/child tables on relationship Group.members -
there are multiple foreign key paths linking the tables. Specify the
'foreign_keys' argument, providing a list of those columns which
should be counted as containing a foreign key reference to the parent
table.
Does anyone know how to do that properly?
The solution is to specify the foreign_keys argument on all relationships:
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
group_id = Column(Integer, ForeignKey('groups.id'))
admin_group_id = Column(Integer, ForeignKey('groups.id'))
class Group(Base):
__tablename__ = 'groups'
id = Column(Integer, primary_key=True)
members = relationship('User', backref='group', foreign_keys=[User.group_id])
admin = relationship('User', backref='admin_group', uselist=False, foreign_keys=[User.admin_group_id])
Perhaps consider the admin relation in the other direction to implement "a group has many members and one admin":
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
group_id = Column(Integer, ForeignKey('groups.id'))
group = relationship('Group', foreign_keys=[group_id], back_populates='members')
class Group(Base):
__tablename__ = 'groups'
id = Column(Integer, primary_key=True)
members = relationship('User', foreign_keys=[User.group_id], back_populates='group')
admin_user_id = Column(Integer, ForeignKey('users.id'))
admin = relationship('User', foreign_keys=[admin_user_id], post_update=True)
See note on post_update in the documentation. It is necessary when two models are mutually dependent, referencing each other.
The problem you're getting comes from the fact that you've defined two links between your classes - a User has a group_id (which is a Foreign Key), and a Group has an admin (which is also defined by a Foreign Key). If you remove the Foreign Key from the admin field the connection is no longer ambiguous and the relationship works. This is my solution to your problem (making the link one-to-one):
from app import db,app
class Group(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(140), index=True, unique=True)
description = db.Column(db.Text)
created_at = db.Column(db.DateTime, server_default=db.func.now())
admin_id = db.Column(db.Integer) #, db.ForeignKey('user.id'))
members = db.relationship('User', backref='group')
def admin(self):
return User.query.filter_by(id=self.admin_id).first()
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), unique=True)
group_id = db.Column(db.Integer, db.ForeignKey('group.id'))
created_at = db.Column(db.DateTime, server_default=db.func.now())
The one drawback to this is that the group object doesn't have a neat admin member object you can just use - you have to call the function group.admin() to retrieve the administrator. However, the group can have many members, but only one of them can be the administrator. Obviously there is no DB-level checking to ensure that the administrator is actually a member of the group, but you could add that check into a setter function - perhaps something like:
# setter method
def admin(self, user):
if user.group_id == self.id:
self.admin_id = user.id
# getter method
def admin(self):
return User.query.filter_by(id=self.admin_id).first()
Ok, I found a workaround for this problem finally. The many-to-many relationship can coexist with one-to-many relationship between the same two tables at the same time.
Here is the code:
groups_admins = db.Table('groups_admins',
db.Column('user_id', db.Integer, db.ForeignKey('user.id')),
db.Column('group_id', db.Integer, db.ForeignKey('group.id'))
)
class Group(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(140), index=True, unique=True)
description = db.Column(db.Text)
created_at = db.Column(db.DateTime, server_default=db.func.now())
members = db.relationship('User', backref='group')
admins = db.relationship('User',
secondary=groups_admins,
backref=db.backref('mod_groups', lazy='dynamic'),
lazy='dynamic')
def __repr__(self):
return '<Group %r>' % (self.name)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
group_id = db.Column(db.Integer, db.ForeignKey('group.id'))
created_at = db.Column(db.DateTime, server_default=db.func.now())
I still want someone to tell me how to set one-to-many and one-to-one relationship at the same time, so I leave my answer here and won't accept it forever.
This link solved it for me
most important thing is to specify foreign_keys value in the relation as well as the primary join