Reputation: 2990
In my application, I have Users and Posts as models. Each post has a foreign key to a username. When I create a ModelView on top of my Posts model I can create posts as specific users in the admin interface as seen in the screenshot below
After I have added a post and click "Save and Add Another", the "User" reverts back to "user1". How can I make the form remember the previous value "user2"?
My reserach has led me to believe it can be done by modifying on_model_change and on_form_prefill, and saving the previous value in the flask session, but it seems to be overengineering such a simple task. There must be a simpler way.
My code can be seen below
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import flask_admin
from flask_admin.contrib import sqla
app = Flask(__name__)
db = SQLAlchemy()
admin = flask_admin.Admin(name='Test')
class Users(db.Model):
"""
Contains users of the database
"""
user_id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True, nullable=False)
def __str__(self):
return self.username
class Posts(db.Model):
"""
Contains users of the database
"""
post_id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(11), db.ForeignKey(Users.username), nullable=False)
post = db.Column(db.String(256))
user = db.relation(Users, backref='user')
def build_sample_db():
db.drop_all()
db.create_all()
data = {'user1': 'post1', 'user1': 'post2', 'user2': 'post1'}
for user, post in data.items():
u = Users(username=user)
p = Posts(username=user, post=post)
db.session.add(u)
db.session.add(p)
db.session.commit()
class MyModelView(sqla.ModelView):
pass
if __name__ == '__main__':
app.config['SECRET_KEY'] = '123456790'
app.config['DATABASE_FILE'] = 'sample_db.sqlite'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///database'
app.config['SQLALCHEMY_ECHO'] = True
db.init_app(app)
admin.init_app(app)
admin.add_view(MyModelView(Posts, db.session))
with app.app_context():
build_sample_db()
# Start app
app.run(debug=True)
Upvotes: 12
Views: 2534
Reputation: 6500
I have come across this situation before and i have solved it using 2 functions. its pretty easy and small.
@expose('/edit/', methods=('GET', 'POST'))
def edit_view(self):
#write your logic to populate the value into html
self._template_args["arg_name"] = stored_value
# in your html find this value to populate it as you need
the above function will let you populate the values in html when user tries to edit any value. This can be populated using the value stored. And below is a function that helps you save the value from previous edit.
within this class MyModelView(sqla.ModelView):
you need to add the below 2 functions.
def on_model_change(self, form, model, is_created):
stored_value = model.user # this is your user name stored
# get the value of the column from your model and save it
This is a 2 step operation that's pretty small and does not need a lot of time. I have added just a skeleton/pseudo code for now.
Upvotes: 8
Reputation: 1522
on_form_prefill will not help you with this problem, as it only works for the edit form.
As the lib code says -> on_form_prefill => Perform additional actions to pre-fill the edit form
When a request hit, the below flow of code happens:
(Base class) View(create_view) ->create_form->_create_form_class->get_create_form->get_form-> scaffold_form
Hence If we want to change the default value of the create form being loaded we can update it in the methods handling the form as explained in the above flow.
Hence we can override create_form.
def create_form(self):
form = super().create_form()
# set the user id we want in the form as default.
form.user_id.default = 'USER_2_ID'
return form
This was to get the default value for the form.
To set the value we override the on_model_change
def on_model_change(self, form, model, is_created):
# set the USER_ID from this model.user_id
pass
Now the way to share this data(USER_ID) from the setter and getter methods are following,
This data has to be shared between two different requests hence storing it in application context, g won't work. The application context "will not be shared between requests"
def create_form(self):
form = super().create_form()
user_id = request.args.get('user_id')
form.user_id.default = user_id
# or if you want only one option to be available
user_query = self.session.query(User).filter(User.id = user_id).one()
form.user.query = [user_query]
return form
As the above answer mention to use edit_view, create_view, that can also be used to add
An easy option but not a good one will be to query on creating that will give you the last entry(But this approach is not optimal one)
@expose('/new/', methods=('GET', 'POST'))
def create_view(self):
model = self.get_model()
# query the model here to get the latest value of the user_id
self._template_args['user_id'] = user_id
return super(YourAdmin, self).details_view()
Upvotes: 1