Flask-Admin shows child objects defined by relationships in its standard edit view. For example, if User
objects have Address
children, looking at the edit view for User
will show the Address
child in the appropriate field. The user can then remove the object, or add another one.
I want users to be able to click through, or otherwise have the ability to enter the edit view of child objects. In the example I'm describing, the user should be able to access the edit view of the Address
object directly from the edit view of the User
object.
The only thing I've found at all related is inline_models
, but this isn't a solution. The implementation is extremely fragile (it can't handle long distance relationships, for example). Flask-Admin is aware of child objects! I can see them in the view! I just want them to become a link to their own edit view...
Anyone have any idea how to accomplish this or can link to an example?
Here is a single file simple example of placing a link to another model's edit view in an edit view. It may help you or not.
I've used a User - Address relationship, a user has an address and address can have many users.
I've used Faker to generate sample data so you'll need to pip install faker
into your environment.
The idea is to use Flask-Admin form rules and in this case I'm configuring form_edit_rules
.
I've created two custom rules:
Link
, inheriting BaseRule
. The constructor takes three values; an endpoint, a name of an attribute to pass along with the endpoint in the Flask url_for
method and finally the text to appear as the link. In this example the endpoint is 'address.edit_view'
because this is the view we want to link to.
MultiLink
, similar to Link
accepts it works with a relation.
Here's the code (there's little error checking):
from random import randint
from flask import Flask, url_for
from flask_admin.contrib import sqla
from flask_admin import Admin
from flask_admin.form.rules import BaseRule
from faker import Faker
from flask_sqlalchemy import SQLAlchemy
from markupsafe import Markup
from sqlalchemy import func, select
from sqlalchemy.ext.hybrid import hybrid_property
fake = Faker()
# Create application
app = Flask(__name__)
# Create dummy secrey key so we can use sessions
app.config['SECRET_KEY'] = '123456790'
# Create in-memory database
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
# app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)
# Flask views
@app.route('/')
def index():
return '<a href="/admin/">Click me to get to Admin!</a>'
class Address(db.Model):
__tablename__ = 'addresses'
id = db.Column(db.Integer, primary_key=True)
number = db.Column(db.String(255))
street = db.Column(db.String(255))
city = db.Column(db.String(255))
country = db.Column(db.String(255))
@hybrid_property
def user_count(self):
return len(self.users)
@user_count.expression
def user_count(cls):
return select([func.count(User.id)]).where(User.address_id == cls.id).label("user_count")
def __unicode__(self):
return ', '.join(filter(None, [self.number, self.street, self.city, self.country]))
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(255))
last_name = db.Column(db.String(255))
email = db.Column(db.String(254))
address_id = db.Column(db.Integer, db.ForeignKey('addresses.id'), index=True)
address = db.relationship(Address, backref=db.backref('users'))
def __str__(self):
return unicode(self).encode('utf-8')
def __unicode__(self):
return '{} {}'.format(self.first_name, self.last_name)
class Link(BaseRule):
def __init__(self, endpoint, attribute, text):
super(Link, self).__init__()
self.endpoint = endpoint
self.text = text
self.attribute = attribute
def __call__(self, form, form_opts=None, field_args=None):
if not field_args
field_args = {}
_id = getattr(form._obj, self.attribute, None)
if _id:
return Markup('<a href="{url}">{text}</a>'.format(url=url_for(self.endpoint, id=_id), text=self.text))
class MultiLink(BaseRule):
def __init__(self, endpoint, relation, attribute):
super(MultiLink, self).__init__()
self.endpoint = endpoint
self.relation = relation
self.attribute = attribute
def __call__(self, form, form_opts=None, field_args=None):
if not field_args
field_args = {}
_hrefs = []
_objects = getattr(form._obj, self.relation)
for _obj in _objects:
_id = getattr(_obj, self.attribute, None)
_link = '<a href="{url}">Edit {text}</a>'.format(url=url_for(self.endpoint, id=_id), text=str(_obj))
_hrefs.append(_link)
return Markup('<br>'.join(_hrefs))
class UserAdmin(sqla.ModelView):
can_view_details = True
form_edit_rules = (
'first_name',
'last_name',
'email',
'address',
Link(endpoint='address.edit_view', attribute='address_id', text='Edit Address')
)
class AddressAdmin(sqla.ModelView):
can_view_details = True
column_list = ['number', 'street', 'city', 'country', 'user_count', 'users']
form_edit_rules = (
'number',
'street',
'city',
'country',
'users',
MultiLink(endpoint='user.edit_view', relation='users', attribute='id')
)
admin = Admin(app, template_mode="bootstrap3")
admin.add_view(UserAdmin(User, db.session))
admin.add_view(AddressAdmin(Address, db.session))
def build_db():
db.drop_all()
db.create_all()
for _ in range(0, 20):
_users = []
for _ in range(0, randint(1, 10)):
_user = User(
first_name=fake.first_name(),
last_name=fake.last_name(),
email=fake.safe_email(),
)
_users.append(_user)
_address = Address(
number=fake.random_digit_not_null(),
street=fake.secondary_address(),
city=fake.city(),
country=fake.country(),
users = _users
)
db.session.add(_address)
db.session.commit()
@app.before_first_request
def first_request():
build_db()
if __name__ == '__main__':
app.run(port=5000, debug=True)