user787267
user787267

Reputation: 2990

Flask admin remember form value

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)

Upvotes: 12

Views: 2534

Answers (2)

Tejus Prasad
Tejus Prasad

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

abhishek kasana
abhishek kasana

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,

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

Upvotes: 1

Related Questions