I have three SQL tables. Two are independent of each other and third that will have 2 columns (among others) each with relevant id from the first two tables
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
import datetime
class Dad(db.Model):
__tablename__ = "dad"
id = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
name = db.Column(db.String, unique=True)
children = db.relationship('Children', backref='dad', lazy='dynamic')
class Mom(db.Model):
__tablename__ = "mom"
id = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
name = db.Column(db.String, unique=True)
children = db.relationship('Children', backref='mom', lazy='dynamic')
class Children(db.Model):
__tablename__ = "children"
id = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
name = db.Column(db.String, unique=True)
dad_name = db.Column(db.String)
mom_name = db.Column(db.String)
dad_id = db.Column(db.Integer, db.ForeignKey('dad.id'))
mom_id = db.Column(db.Integer, db.ForeignKey('mom.id'))
created_at = db.Column(db.DateTime(6), default=datetime.datetime.utcnow)
updated_at= db.Column(db.DateTime(6), default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow)
I have 2 issues that I need help with
If I do dad.children.append({'name':'Joe', 'dad_name':'Bill', 'mom_name':'Samantha'}) it attaches the dad.id automatically but not the mom.id. I want it to attach mom.id as well based on her name even if I create it from dad.
When I try to update the record from pgAdmin, it should update the updated_at column with the update time but it doesn't.
Thanks!
Related
I'm learning flask and i'm looking to implement a many to many realtionship. I searched on internet but there are different ways,
this is what i have tried so far
class Group(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
class Course(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
class Group_Course(db.Model):
id = db.Column(db.Integer, primary_key=True)
group_id = db.Column(db.Integer, db.ForeignKey(Group.id))
theory_id = db.Column(db.Integer, db.ForeignKey(Course.id))
I'm not sure this is the right way to do it. I'm having issues with delete endpoint. I think that i should add on delete cascade but i don't know how to do it.
there are some sites that add relationships to the table association, so the association table looks like that.
class Group_Course(db.Model):
id = db.Column(db.Integer, primary_key=True)
group_id = db.Column(db.Integer, db.ForeignKey(Group.id))
course_id = db.Column(db.Integer, db.ForeignKey(Course.id))
course = db.relationship(Course, backref="course",cascade='all,
delete')
group = db.relationship(Group, backref="group", cascade='all,
delete`)
there are another examples where they are including a relationship field in both tables like that:
class Group(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
courses = db.relationship("Course", secondary=course_groups,
back_populates="courses")
class Course(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
groups = db.relationship("Group", secondary=course_groups,
back_populates="groups")
So i'm confused, which one is the most correct ?
Consider the following many-to-many relationship:
class Hashes(db.Model):
id = db.Column(db.Integer, primary_key=True)
hash = db.Column(db.String, nullable=False, unique=True)
xref_hashes_users = db.Table("xref_hashes_users",
db.Column('hash', db.ForeignKey('hashes.id'), primary_key=True),
db.Column('user', db.ForeignKey('user.id'), primary_key=True))
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String)
hashes = db.relationship("Hashes", secondary="xref_hashes_users", backref="users")
Let's say I want to allow users to store some additional information about their hashes, perhaps a label. It makes sense to me that given this is a user-specific piece of information about a hash, it would go in the association table like:
xref_hashes_users = db.Table("xref_hashes_users",
db.Column('hash', db.ForeignKey('hashes.id'), primary_key=True),
db.Column('user', db.ForeignKey('user.id'), primary_key=True),
db.Column('label', db.String))
Given this schema, is it possible to use the ORM to add/remove/update labels? How would I do this?
It looks like the answer is to use an Association Object to store the extra data.
https://docs-sqlalchemy.readthedocs.io/ko/latest/orm/basic_relationships.html#association-object
So my example becomes:
class Hashes(db.Model):
id = db.Column(db.Integer, primary_key=True)
hash = db.Column(db.String, nullable=False, unique=True)
users = db.relationship("UserHashAssociation", back_populates="hashes")
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String)
hashes = db.relationship("UserHashAssociation", back_populates="users")
class UserHashAssociation(db.Model):
__tablename__ = "xref_hashes_users"
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), primary_key=True)
hash_id = db.Column(db.Integer, db.ForeignKey("hashes.id"), primary_key=True)
label = db.Column(db.String)
users = db.relationship("User", back_populates="hashes")
hashes = db.relationship("Hashes", back_populates="users")
Then I am able to update the label like:
hash = Hashes(hash=hash_string)
user_hash_association = UserHashAssociation(label="foo", user_id=user.id)
hash.users.append(user_hash_association)
db.session.add(hash)
db.session.commit()
I am new to using Flask SQLAlchemy and I have a few questions on how can how can I link separate databases together to form a query.
I have 3 databases: User, Homework, Questions, where 1 user can have many homework and 1 homework can have many questions. I have made 3 separate .py files for the 3 classes and each file calls simple functions like query.all(), and some simple filtering.
I have previously learned MySQL and I am wondering how can I create an SQLAlchemy equivalent of the following query:
SELECT * FROM user, homework, questions
WHERE user.user_id = homework.user_id
AND homework.homework_id = questions.homework_id
My question is how do I go about achieving this? Do I make a new file and find a way to bind these databases together or is there a more optimal way of doing it?
Also, how do I go about retrieving aggregated function values? For instance, the average marks one would get for each homework.
User.py
class User(db.Model):
__tablename__ = 'user'
user_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(20), nullable=False)
Homework.py
class Homework(db.Model):
__tablename__ = 'homework'
homework_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
user_id= db.Column(db.Integer, nullable=False)
subject = db.Column(db.String(20), nullable=False)
Question.py
class Question(db.Model):
__tablename__ = 'question'
question_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
homework_id = db.Column(db.Integer, nullable=False)
marks = db.Column(db.Integer, nullable=False)
You can create relationships amongst your tables using the following
class User(db.Model):
__tablename__ = 'user'
user_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(20), nullable=False)
class Homework(db.Model):
__tablename__ = 'homework'
homework_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.user_id'), nullable=False)
subject = db.Column(db.String(20), nullable=False)
class Question(db.Model):
__tablename__ = 'question'
question_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
homework_id = db.Column(db.Integer, db.ForeignKey('homework.homework_id'), nullable=False)
marks = db.Column(db.Integer, nullable=False)
Then you can access info from a given table using the following
insert your desired user_id in replace for xxx
user_object = User.query.filter_by(user_id=xxx).first()
username = user_object.username
user_id_value = user_object.user_id
homework_object = Homework.query.filter_by(user_id=user_id_value).first()
subject = homework_object.subject
homework_id_value = homework_object.homework_id
question_object = Question.query.filter_by(homework_id=homework_id_value).first()
marks = question_object.marks
I am coming from a python-django and am trying to get a grasp on flask-SQLAlchemy:
class Author(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(128), nullable=False)
class Book(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
title = db.Column(db.String(128), nullable=False)
author = db.Column(db.Integer, db.ForeignKey('author.id'), nullable=False)
I want to get a joined result list:
results = Book.query.filter(Author.name=='tom')
for result in results:
print(result.title, result.???.name)
How do I access the fields of the joined tables?
I figured it out:
class Book(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
title = db.Column(db.String(128), nullable=False)
author_id = db.Column(db.Integer, db.ForeignKey('author.id'), nullable=False)
author = relationship("Author")
I needed to add the line
author = relationship("Author")
to the model. It seems to be necessary to declare the relationship on the object level. I did miss this.
Now the line can be:
print(result.title, result.author.name)
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