I have these 3 sql alchemy (sqla) models:
class Site(Base):
__tablename__ = "site"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
name = Column(String, nullable=False)
created_at = Column(DateTime, default=datetime.utcnow())
updated_at = Column(DateTime, nullable=True, default=datetime.utcnow(), onupdate=datetime.utcnow)
class Camera(Base):
__tablename__ = "camera"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
site_id = Column(UUID(as_uuid=True), ForeignKey("site.id"), nullable=False)
name = Column(String, nullable=False)
created_at = Column(DateTime, default=datetime.utcnow())
updated_at = Column(DateTime, nullable=True, default=datetime.utcnow(), onupdate=datetime.utcnow)
site = relationship("Site", backref="cameras")
class RtspServerEndpoint(Base):
__tablename__ = "rtsp_server_endpoint"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
camera_id = Column(UUID(as_uuid=True), ForeignKey("camera.id"), nullable=False)
rtsp_url_endpoint = Column(String, nullable=False)
rtsp_username = Column(String, nullable=False)
rtsp_encrypted_password = Column(String, nullable=False)
name = Column(String, nullable=False)
created_at = Column(DateTime, default=datetime.utcnow())
updated_at = Column(DateTime, nullable=True, default=datetime.utcnow(), onupdate=datetime.utcnow)
camera = relationship("Camera", backref="rtsp_server_endpoint", lazy="joined")
camera_id is the foreign key of rtspserverendpoint table and site_id is the foreign key for the Camera table.
When a user wants to add a new rtspserverendpoint record, he makes an HTTP request such as:
POST sites/<site_id>/camera/<camera_id>/rtspserverendpoint
Before adding the new rtspserverendpoint, I would like to make sure that the <site_id> and the <camera_id> are consistent, as a security. I can probably make a separate query just to check that, such as:
check_record_exist = db.session.query(Camera).filter(Camera.site_id == site_id).first()
if not check_record_exist:
raise ("No such camera with this site_id")
But what I would like to know, is if there is a more elegant way to check that: For example, adding a constraint in my Base models that would forbid adding such an inconsistent record in the database.
I am not aware of any straightforward way to implement this 2-level check on the database directly.
In fact, the only consistency that the database should know about is that your new RtspServerEndpoint instance will belong to the correct Camera instance. But this will be correct by default by the way you will be creating the RtspServerEndpoint instance.
Therefore, in my opinion, the check of the correctness of the site_id in the URL of the POST request should be implemented in the logic of your code. I would probably do it along these lines:
#handler(..., method='POST')
def rtspserverendpoint(site_id: int, camera_id: int):
# find camera, which will allow us to check the correctness of the site_id as well
camera = db.session.query(Camera).get(camera_id)
if camera is None:
raise Exception(f"No camera with this {camera_id=}.")
if camera.site_id != site_id:
raise Exception(f"Camera with this {camera_id=} does not belong to the site with {site_id=}.")
new_obj = RtspServerEndpoint(
...,
camera_id=camera_id,
...,
)
db.session.add(new_obj)
db.session.commit()
Related
How would you map a column that is not unique and is not a key into another schema(table)?
class TaskEntity(Base, BaseMixin):
__tablename__ = "task_entity"
__table_args__ = (UniqueConstraint("dag_no", "ordinal_position", name="dag_ordinal_uq_constraint"),)
task_no = Column(BIGINT(20), primary_key=True, autoincrement=True, nullable=False)
job_no = Column(BIGINT(20), ForeignKey("job_tb.job_no"), nullable=False)
task_name = Column(String(128), unique=True, nullable=False)
ordinal_position = Column(SMALLINT(6), nullable=False, default=1)
ordinal_position is not unique on its own, but is unique per task_no which is unique per job_no.
Ex) job_no.A can only have 1 of task_no.A which can only have 1 of ordinal_position.C. But job_no.B can have a task_no.A and ordinal_position.C.
I am trying to create the below schema in conjunction with class TaskEntity above, but am returning a "errno: 150 "Foreign key constraint is incorrectly formed" which I am assuing comes from the fact that ordinal_position is not unique.
class TaskLog(Base, BaseMixin):
__tablename__ = "task_log"
task_log_no = Column(BIGINT(20), nullable=False, autoincrement=True, primary_key=True)
execution_datetime = Column(TIMESTAMP, nullable=False)
start_datetime = Column(TIMESTAMP, nullable=False)
duration = Column(Float, nullable=False)
job_no = Column(BIGINT(20), ForeignKey("job_tb.job_no"), nullable=False)
task_no = Column(BIGINT(20), ForeignKey("task_entity.task_no"), nullable=False)
task_name = Column(String(128), ForeignKey("task_entity.task_name"), nullable=False)
# this is the declaration causing issues:
task_ordinal_position = Column(SMALLINT(6), ForeignKey("task_entity.ordinal_position"), nullable=False)
Have tried using relationships and "primary_join=", but the mapping seems to be very off once the data comes in.
Any inputs, much appreciated.
If I'm reading this correctly, then you probably want a UniqueConstraint across all three columns:
__table_args__ = (
UniqueConstraint('task_no', 'job_no', 'ordinal_position'),
)
I have two tables, User and Profiles. A user can have many profiles. If the user is deleted all the profiles are deleted. However I want to also make it so if a user has no profiles the user is also delete (assume I always insert a user with at least one profile).
I looked into delete-orphan but I'm not totally sure where I should put this or if it does what I need?
Any help is appreciated.
https://docs.sqlalchemy.org/en/13/orm/cascades.html#delete-orphan
class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
active = db.Column(db.Boolean(), default=True, nullable=False)
created_date = db.Column(db.DateTime, default=func.now(), nullable=False)
profile_ = db.relationship("Profile", back_populates="user_", cascade="all,delete")
class Profile(db.Model):
__tablename__ = 'profile'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
user_ = db.relationship("User", back_populates="profile_")
i have those 2 models:
#derive_schema
class Organization(db.Model):
id = Column(UUID(as_uuid=True), unique=True, primary_key=True, server_default=sqlalchemy.text("uuid_generate_v4()"))
name = Column(String, nullable=False, unique=True)
code = Column(String, nullable=False, unique=True)
owner_email = Column(String, nullable=False)
labels = Column(JSONB)
status = Column(Enum(OrganizationStatus), nullable=False)
logo_url = Column(String)
configuration = Column(JSONB, nullable=False)
def __repr__(self):
return self.name
#derive_schema
class PortalSettings(db.Model):
id = Column(UUID(as_uuid=True), unique=True, primary_key=True, server_default=sqlalchemy.text("uuid_generate_v4()"))
organization_id = db.Column(UUID(as_uuid=True), ForeignKey('organization.id'), nullable=False)
portal_settings = Column(JSONB)
organization = relationship(Organization, backref=backref('portal_settings', uselist=False, lazy="joined"))
def __repr__(self):
return self.portal_settings
and this ModelView
class OrganizationView(ConfigurationModelView):
inline_models = (PortalSettings,)
the relationship between organization and portal settings should be one to one,
but i dont understand why in flask admin i got this field when i can add as many portal settings as i want instead of just seen a input field with the portal_settings JSONB field
Based on this gist
https://gist.github.com/DrecDroid/398a05e4945805bc09d1
i've created PR onto Flask-Admin repo and maybe soon it will be merged. Anyway, you may copy-paste code from Gist and use it in your project
https://github.com/flask-admin/flask-admin/pull/2091
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
I have tables that I want to do an ORM based query.
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
is_active = Column(Boolean, server_default="1", nullable=False)
is_deleted = Column(Boolean, server_default="0", nullable=False)
created_at = Column(DateTime, nullable = False, default=func.now())
first_name = Column(String(30), nullable=False)
surname_name = Column(String(30), nullable=False)
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
is_active = Column(Boolean, server_default="1", nullable=False)
is_deleted = Column(Boolean, server_default="0", nullable=False)
created_at = Column(DateTime, nullable = False, default=func.now())
first_name = Column(String(30), nullable=False)
surname_name = Column(String(30), nullable=False)
appointments = relationship("ChildAppointment", backref="child")
class ParentChild(Base):
'''provides a many to many relationship for parent and child'''
__tablename__ = 'parent_child'
id = Column(Integer, primary_key=True)
is_active = Column(Boolean, nullable=False, server_default="1")
is_deleted = Column(Boolean, nullable=False, server_default="0")
parent_id = Column(Integer, ForeignKey('parent.id'), nullable=False)
child_id = Column(Integer, ForeignKey('child.id'))
parents = relationship("Parent", backref="parent_child")
children = relationship("Child", backref="parent_child")
class ChildAppointment(Base):
__tablename__ = 'child_appointment'
id = Column(Integer, primary_key=True)
is_active = Column(Boolean, nullable=False, server_default="1")
is_deleted = Column(Boolean, nullable=False, server_default="0")
created_at = Column(DateTime, nullable = False, default=func.now())
child_id = Column(Integer, ForeignKey('child.id'))
date_appointment = Column(DateTime, nullable = False)
I want to query Table ParentChild and through SQLAlchemy relationship magic, I want to get the latest appointment date for the child meaning I need the result ordered by date_appointment in the child table.
I have followed the example by #zeek here and I've come up with the following:
for data in session.query(ParentChild).join(Child.appointments).filter(ParentChild.parent_id == 1).\
order_by(Child.appointments.date_appointment.desc()).all():
However, I get the error attributeError: Neither 'InstrumentedAttribute' object nor 'Comparator' object associated with Child.appointments has an attribute 'date_appointment'
My scenario breaks what was given in that link because mine goes an extra table in as I have 4 tables so am unable to adapt his answer to suit mine.
Thanks.
Two things: First of all, for the join condition, you would want to specify it as a kind of chain:
ParentChild -> Child
Child -> ChildAppointment
That is done like this:
session.query(ParentChild).join(ParentChild.children).join(Child.appointments)
This will yield the correct join. I think if it is unambigous you could also do this:
session.query(ParentChild).join(Child).join(ChildAppointment)
But you'd have to try it out.
The real error, however, comes from this part:
Child.appointments.date_appointment
Instead it should look like this:
ChildAppointment.date_appointment
SQLAlchemy already knows how to get this ChildAppointment related to Child and ParentChild because you specified it in the joins above. And if you think about it in raw SQL terms it makes much more sense this way.
So you final solution:
session.query(ParentChild).join(ParentChild.children).join(Child.appointments).filter(ParentChild.parent_id == 1).order_by(ChildAppointment.date_appointment.desc()).all():