flask admin one to one inline_models - python

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

Related

Flask Rest API SQLAlchemy foreign key error

I got SQLALchemy error, when I tried "flask db migrate"
NoReferencedTableError: Foreign key associated with column 'user.menu_id' could not find table 'menu' with which to generate a foreign key to target column 'id
Menu table
class Menu(db.Model):
__tablename__ = 'menus'
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(64), index=True, unique=True)
price = db.Column(db.String(64), index=True, unique=True)
description = db.Column(db.String(64), index=True, unique=True)
picture = db.Column(db.String(64), index=True, unique=True)
create_date = db.Column(db.DateTime, default=datetime.utcnow)
users = db.relationship('User', backref="menu", lazy=True)
User table
class User(Model):
""" User model for storing user related data """
id = Column(db.Integer, primary_key=True)
email = Column(db.String(64), unique=True, index=True)
username = Column(db.String(15), unique=True, index=True)
name = Column(db.String(64))
password_hash = Column(db.String(128))
admin = Column(db.Boolean, default=False)
joined_date = Column(db.DateTime, default=datetime.utcnow)
userdataset = db.relationship("Dataset", backref="user", lazy="dynamic")
menu_id = Column(db.Integer(), db.ForeignKey('menu.id'), nullable=False)
def __init__(self, **kwargs):
super(User, self).__init__(**kwargs)
How can ı solve this problem? Where am i doing wrong?
You have renamed your 'Menu' table to 'menus' with this __tablename__ property in your 'Menu' model:
__tablename__ = 'menus'
You then try to reference to the 'Menu' table, when in fact, its name has been changed to 'menus'. The simplest way to solve this would be to change your User.menu_id column to this:
menu_id = Column(db.Integer(), db.ForeignKey('menus.id'), nullable=False)
Another way of fixing this issue would be modifying the __tablename__ property to 'menu'. (You could also just delete it.)

Checks on foreign key of foreign key in SQLAlchemy

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

Using SQLAlchemy, how can I make a field in a class that is a list of other instances of said class? [duplicate]

This question already has an answer here:
How to implement following/followers relationship in SQLAlchemy
(1 answer)
Closed 5 years ago.
I have a user class as detailed below:
class User(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.Unicode(length=128), unique=True)
username = db.Column(db.Unicode(length=128), unique=True)
_password = db.Column("password", db.String(length=60))
admin = db.Column(db.Boolean, default=False)
joined = db.Column(db.DateTime, default=datetime.utcnow)
confirmed = db.Column(db.Boolean, default=False)
profile_picture = db.Column(db.Unicode(length=128), unique=True, nullable=True)
twitter = db.Column(db.Unicode(length=256), unique=True, nullable=True)
github = db.Column(db.Unicode(length=256), unique=True, nullable=True)
I would like to add another column to the user class which is a list of users. How can I accomplish this?
I think the proper name of what I am looking for is a self-referential one-to-many relationship.
Based on your comment you want to store a association table that stores which user follows which user(s). This is what is known as a many-to-many relation. Since a user can follow many other users, and a user can be followed by many users.
For that we need to define an additional table, and relationship(http://docs.sqlalchemy.org/en/latest/orm/basic_relationships.html#many-to-many) to specify the usage of that table, for instance:
class UserFollows(db.Model):
__tablename__ = 'user_follows'
follower = Column(Integer, ForeignKey('users.id'))
followee = Column(Integer, ForeignKey('users.id'))
Now we can define two virtual columns to the User class and specify that SQLAlchemy should look into the user_follows table for this:
class User(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.Unicode(length=128), unique=True)
username = db.Column(db.Unicode(length=128), unique=True)
_password = db.Column("password", db.String(length=60))
admin = db.Column(db.Boolean, default=False)
joined = db.Column(db.DateTime, default=datetime.utcnow)
confirmed = db.Column(db.Boolean, default=False)
profile_picture = db.Column(db.Unicode(length=128), unique=True, nullable=True)
twitter = db.Column(db.Unicode(length=256), unique=True, nullable=True)
github = db.Column(db.Unicode(length=256), unique=True, nullable=True)
followers = db.relationship('User',
secondary = followers,
primaryjoin = (UserFollows.c.follower == id),
secondaryjoin = (followers.c.followee == id))
follows = db.relationship('User',
secondary = followers,
primaryjoin = (UserFollows.c.followee == id),
secondaryjoin = (followers.c.follower == id))
Now a User object has two attributes followers and follows which act as collections of users that store the persons the User follows as well as the followers of that User.

nonSQL related attribute add to SQLAlchemy model error

I'm developing a web app with python and flask. I use Flask, SQLAlchemy and PostgreSQL for development. I have many-to-one related models. By this models one company can have many users but each user can only have one company.
models.py
class Company(ResourceMixin, db.Model):
__tablename__ = 'companies'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), unique=True, index=True,
nullable=False, server_default='')
phone = db.Column(db.String(24))
email = db.Column(db.String(255), index=True)
address = db.Column(db.String(255))
# Relations
users = db.relationship('User', backref='company')
class User(UserMixin, ResourceMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
# User details
name = db.Column(db.String(50), index=True)
phone = db.Column(db.String(24))
address = db.Column(db.String(255))
email = db.Column(db.String(255), unique=True, index=True, nullable=False,
server_default='')
password = db.Column(db.String(128), nullable=False, server_default='')
# Relations
company_id = db.Column(db.Integer, db.ForeignKey('companies.id',
onupdate='CASCADE',
ondelete='SET NULL'),
index=True)
views.py
app.route('/')
def index():
company = Company.query.get(1)
flash(company.name, company.user_count)
return render_template('index.html')
Error summary: "user_count" attribute is not part of the Company model.
I want to get the number of the users dynamically from Company model. Attribute should count users on each call of the model and serve it on a regular attribute (like company.user_count). I made it by creating a class method and calling it in view function but i want it to make the process automatic without calling method prior to use attribute.
I tried init function like this:
def __init__(self):
self.user_count = len(self.users)
And like this:
def __init__(self):
self.status()
def status(self):
self.user_count = len(self.users)
return True
And like this:
def __init__(self):
self.status()
#classmethod
def status(self):
self.user_count = len(self.users)
return True
all three versions throws same error. How can i overcome the problem.
Thanks a lot!
You can use a property:
class User(Base):
...
#property
def user_count(self):
return len(self.users)

How to set one to many and one to one relationship at same time in Flask-SQLAlchemy?

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

Categories

Resources