I'm using flask-admin with flask-sqlalchemy as a simple admin interface to create & edit items in our database/flask app.
We have two models one which is a parent and one a child. At the moment our process is:
create a new parent item with no children
create a new child and set the child's parent
To save time I would like a single form for creating a new parent where we can enter all the details of the parent as well as a child and create the parent then create the child setting a foreign key between the child and the parent that was just created.
Is there a way to do this with flask-admin?
Have you tried Flask Admin inline models?
Here is an example with a one-to-many relationship. Unfortunately, FlaskAdmin does not provide complete support for one-to-many relationships (source).
Currently, it only supports providing an inline model on the "one" side of the relationship. Given two models: Post, and Guide, where one guide has many posts, you would have something like this:
from flask import Flask
from flask_admin import Admin
from flask_admin.contrib.sqla import ModelView
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///storage.sqlite3'
app.secret_key = 'secret'
db = SQLAlchemy(app)
# defining the "one" side of the relationship
class Guide(db.Model):
__tablename__ = "guides"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255), nullable=False, unique=True)
description = db.Column(db.String(512), nullable=False)
posts = db.relationship("Post", back_populates="guide")
# defining the "many" side of the relationship
class Post(db.Model):
__tablename__ = "posts"
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(255), nullable=False)
content = db.Column(db.Text, nullable=False)
guide_id = db.Column(
db.Integer, db.ForeignKey("guides.id", ondelete="CASCADE"), nullable=False
)
guide = db.relationship("Guide", back_populates="posts")
db.create_all()
admin = Admin(app, template_mode="bootstrap3")
# creating a custom model view for the Guide model.
class ModelViewWithInlineModel(ModelView):
# here you provide a tuple with the inline models you want to
# render to the user
inline_models = (Post,)
# using FlaskAdmin ModelView default implementation for the Post model
admin.add_view(ModelView(Post, db.session))
# using our custom ModelView with the Post as an inline model
admin.add_view(ModelViewWithInlineModel(Guide, db.session))
if __name__ == '__main__':
app.run(debug=True)
Related
I'm starting with FastAPI and SQLAlchemy, and I have a question about loading models in the correct order to satisfy SQLAlchemy models relationship. I've got two models, Profile and Account:
Profile:
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import relationship
from project.core.database import Base
class Profile(Base):
__tablename__ = "profiles"
id = Column(Integer, primary_key=True)
name = Column(String(10), nullable=False)
slug = Column(String(10), nullable=False)
order = Column(Integer, unique=True, nullable=False)
accounts = relationship(
"Account", foreign_keys="[Account.profile_id]", back_populates="profile"
)
Account:
from sqlalchemy import Column, Integer, String, LargeBinary, ForeignKey
from sqlalchemy.orm import relationship
from project.core.database import Base
class Account(Base):
__tablename__ = "accounts"
id = Column(Integer, primary_key=True)
name = Column(String(255), nullable=False)
email = Column(String(255), nullable=False)
password = Column(LargeBinary, nullable=False)
profile_id = Column(Integer, ForeignKey("profiles.id"))
profile = relationship(
"Profile", foreign_keys=[profile_id], back_populates="accounts"
)
And in my project.core.database file, I created a method to import the models, since I was having issues with models not being located when the relationship was attempted to be made.
def import_all():
import project.models.profile
import project.models.account
def init_db():
import_all()
My question is, is there a smarter way to load the models in the correct order? Because now I only have two models, but soon it can grow to dozens of models and I think this will become such a monster to manage.
I looked for resources and examples, but everything I found created the models in a single file.
Thanks in advance!
Let's take a look at full-stack-fastapi-postgresql, a boilerplate template created by the author of FastAPI:
base.py
from app.db.base_class import Base
from app.models.item import Item
from app.models.user import User
init_db.py
from app.db import base # noqa: F401 import db models
# make sure all SQL Alchemy models are imported (app.db.base) before initializing DB
# otherwise, SQL Alchemy might fail to initialize relationships properly
# for more details: https://github.com/tiangolo/full-stack-fastapi-postgresql/issues/28
def init_db(db: Session) -> None:
...
So, as you can see it's a polucalar way to import models, here you can find additional discussion: https://github.com/tiangolo/full-stack-fastapi-postgresql/issues/28
I have two models, one is Identification which contains two IDs (first_id and second_id) and the second is User. The idea is that only authorised users will be given their first_id and second_id pair of values. They go to the site and login by entering the two id's plus a username and password (which they generate there and then).
I am trying to achieve two things here:
Pre-populate the Identification table with many (let's say 100) first_id/second_id values that will serve as the correct value pairs for logging in.
Set up the User class in such a way that only if the user enters a correct first_id/second_id pair in the login form can they log in (presumable this involves checking the form data with the Identification table somehow).
Here are the model classes:
class Identification(db.Model):
id = db.Column(db.Integer, primary_key=True)
first_id= db.Column(db.Text, unique=True)
second_id= db.Column(db.Text, unique=True)
def __init__(self, first_id, second_id):
self.first_id= first_id
self.second_id= second_id
def __repr__(self):
return f"ID: {self.id}, first_id: {self.first_id}, second_id: {self.second_id}"
class User(db.Model, UserMixin):
__tablename__ = 'user'
first_id= db.relationship('Identification', backref = 'identificationFID', uselist=False)
second_id = db.relationship('Identification', backref = 'identificationSID', uselist=False)
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.Text, unique=True, index=True)
password_hash = db.Column(db.Text(128))
identification_id = db.Column(db.Integer, db.ForeignKey('identification.id'), unique=True)
first_id = db.Column(db.Text, unique=True)
second_id = db.Column(db.Text, unique=True)
I would appreciate any help on this as I'm struggling and this is really above my understanding of python/Flask. Thanks all!
The answer above didn't work for me, because the create_tables() function since being part of the User class, requested that I pass an Instance of that class.
The solution I came up with, was to call the function after db.create_all(). This seemed like a good place to put the call, because of the #app.before_first_request decorator.
init.py
#app.before_first_request
def create_tables():
"""Create Tables and populate certain ones"""
db.create_all()
from app.models.init_defaults import init_defaults
init_defaults()
init_defaults.py
def init_defaults():
"""Pre-Populate Role Table"""
if Role.query.first() is None:
roles = ['Admin', 'Master', 'Apprentice']
for role in roles:
user_role = Role(access_level=role)
if role != 'Apprentice':
user_role.set_password('Passw0rd!')
db.session.add(user_role)
db.session.commit()
pass
Due to the decorator the function is now only called once per instance. Another solution I could imagine working, would be to use events:
https://dzone.com/articles/how-to-initialize-database-with-default-values-in
Note: This is a development solution not fit for production.
You can use mock data to populate these tables.
create a function in this py file where you can add objects to DB using ORM
and then call the function in __init__.py, which will populate data once your flask server starts.
Update:-
here is a code for your reference.
Model.py
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import sessionmaker
Base = declarative_base()
engine = create_engine('sqlite:///:memory:', echo=True)
Session = sessionmaker(bind=engine)
session = Session()
class User(Base):
\__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String)
fullname = Column(String)
#Create getter setters
def create_tables():
Base.metadata.create_all(engine)
user = User()
user.id=1
user.name="ABC"
user.fullname="ABCDEF"
session.add(user)
# similarly create more user objects with mock data and add it using session
__init__.py
from model import User
User.create_tables()
Reference
I am using flask with flask-restplus and sqlalchemy.
My rest API function looks like the following:
#ns.route('/user')
class UsersCollection(Resource):
#jwt_optional
#permissions.user_has('admin_view')
#ns.marshal_list_with(user_details)
def get(self):
"""
Returns list of users.
"""
users = User.query.all()
return users
I am willing to add additional data to users collection which should be returned by the API. Some of this data may be stored in other DB tables, while other is stored in memory.
What I am usually doing is something like this:
#ns.route('/user')
class UsersCollection(Resource):
#jwt_optional
#permissions.user_has('admin_view')
#ns.marshal_list_with(user_details)
def get(self):
"""
Returns list of users.
"""
users = User.query.all()
# Add additional data
for user in users:
user.activity = UserActivity.query ... # from DB
user.online_status = "Active" # Taken somewhere from memory
return users
Obviously I don't like this approach of adding data to the User object on the fly. But what is the best design pattern to achieve it?
About the design pattern, I recommend a DAO and Service approach, here's a good and short article about it:
https://levelup.gitconnected.com/structuring-a-large-production-flask-application-7a0066a65447
About the models User/Activity relationship, I presume you have a mapped Activity model. If this is a One to One relationship, in the User model create a activityId field for the FK field and a activity relation where your ORM (I'll assume SQLAlchemy) will retrieve when user.activity is accessed.
class Activity(db.Model):
id = db.Column(db.Integer, primary_key=True)
descrption = db.Column(db.String(255))
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
activity = db.relationship(Activity)
activityId = db.Column(db.Integer, db.ForeignKey(Activity.id))
If you have a Many to Many relationship you'll have to create a third class called ActivityUser to map the relation
class ActivityUser(db.Model):
id = db.Column(db.Integer, primary_key=True)
activity = db.relationship(Activity, lazy=True)
user = db.relationship(User, lazy=True)
activityId = db.Column(db.ForeignKey(Activity))
userId = db.Column(db.ForeignKey(User))
and you can retrieve the user activities like this (again assuming you use SQLAlchemy):
ActivityUser.query.filter(ActivityUser.userId == user.id).all()
I have developed an application in flask. I want to trace create and change. For admin I'm using flask-admin:
class MyModelAdmin(ModelView):
def on_model_change(self, form, model, is_created):
history = HistoryModel(
..
..
)
db.session.add(history)
db.session.commit()
I have History model for save changes:
class HistoryModel(db.Model):
__tablename__ = 'history'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
date = db.Column(db.DateTime)
user_id = db.Column(db.Integer, nullable=True)
object_type = db.Column(db.String(32), nullable=False)
I'm not sure that it is the right approach. BTW, how can I trace change and create for save in HistoryModel?
SQLAlchemy provide event listen for you. There is a decorate named listens_for in sqlalchemy.event. You use it to listens_for(HistoryModel, "after_insert") and listens_for(HistoryModel.date, "set"). Check the document about events and orm events.
The extension SQLAlchemy-continuum might be a worthwhile option to look into. It versions your model (data)
I am having some difficulty setting up a one to one relationship between two models in my flask application. I have two models Employeeand `Photo'. An employee has only one photo associated with it and vice-versa.
This is the code that I have in my models.py file:
class Employee(db.Model):
__tablename__ = 'employees'
id = db.Column(db.Integer, primary_key=True)
photo = db.relationship("Photo", uselist=False, back_populates='employees')
class Photo(db.Model):
__tablename__ = 'photos'
id = db.Column(db.Integer, primary_key=True)
employee_id = db.Column(db.Integer, db.ForeignKey('employees.id'))
employee = db.relationship('Photo', back_populates='photo')
I've followed the instruction on the SQL Alchemy documentation found hereSQL Alchemy simple relationships. The error that I keep encountering is shown below:
sqlalchemy.exc.NoForeignKeysError: Could not determine join condition between parent/child tables on relationship Photo.employee
- there are no foreign keys linking these tables.
Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or specify a 'primaryjoin' expression.
I clearly specify the foreign key right here employee_id = db.Column(db.Integer, db.ForeignKey('employees.id')) . I'm not sure what I'm doing wrong. Additionally, I was reading the documentation and it doesn't help that uselist, backref, and back_populates are so similar.
Can someone assist me with this? Help would be greatly appreciated.
One to One relationship stack overflow question
backref automatically adds the reverse relationship to the related model. You can pass a db.backref object to it to specify options to the relationship. back_populates tells SQLAlchemy to populate an existing reverse relationship, rather than creating it. uselist tells SQLAlchemy whether the relationship is a list or not, for cases where it can't determine that automatically.
In your example, you need one relationship, with one backref that is a single item.
You have two typos in your code. First, back_populates='employees' should refer to 'employee', which is what you called the property on the related model. Second, employee = relationship('Photo' is pointing at the wrong model, it should relate to Employee.
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
db = SQLAlchemy(app)
db.engine.echo = True
class Photo(db.Model):
id = db.Column(db.Integer, primary_key=True)
class Employee(db.Model):
id = db.Column(db.Integer, primary_key=True)
photo_id = db.Column(db.ForeignKey(Photo.id))
photo = db.relationship(Photo, backref=db.backref('employee', uselist=False))
db.create_all()
db.session.add(Employee(photo=Photo()))
db.session.commit()
print(Employee.query.get(1).photo)