Flask admin remember form value
Asked Answered
P

2

12

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

enter image description here

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)

Porphyroid answered 17/7, 2019 at 7:53 Comment(5)
[Save and Add Another] means clearing current form and filling new one to the form. If you would like use previous form user as default value in select box, you could use cookie(client) or session(server) to save user value and implement it.Andreas
I don't feel that the solution you got from your research is over-engineering. As @Andreas said you either have to maintain a cookie or a session to make it possible.Iaria
Forgive me @Porphyroid if I misunderstand your question, but are you interested in changing the default loading options for the form to be what is currently selected, or to dynamically add the user and set it as default in the form?Scirrhus
@calestini I am interested in having the form remember the value the user last selected. So that if an admin wants to add several posts for user2 he doesn't have to reselect user2 on each insert.Porphyroid
@user787267, the overall options would be to (1) send the form asynchronously so the current values remain pre-loaded, (2) cookie as aforementioned, (3) pass a url parameter such as user_id=1 and dynamically change the default value on the form, or (4) save the session. Option 3 is a simple, hacky way, but it exposes your internal ids on the url.Scirrhus
L
8

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.

Lakin answered 26/7, 2019 at 17:54 Comment(1)
Thank you! I’d appreciate if you could add the remaining codePorphyroid
F
1

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,

  1. We set this in cookies and get on the create request.
  2. Update the "save and add another" button link to add the user_id in the query strings.

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()
Flying answered 28/7, 2019 at 8:48 Comment(1)
This is a good answer too! I never managed to get it to work with _template_args, but setting if 'user_id' in flask.session and not form.case.data: form.case.data=db.session.query(models.Users).filter_by(user_id=flask.session['user_id']).first() I got it working. For some strange reason I had to overwrite form.case.data and not form.case.defaultPorphyroid

© 2022 - 2024 — McMap. All rights reserved.