4

I am using an application factory to add views to my flask application like so :

(this is not my actual application factory, and has been shortened for the sake of brevity)

def create_app(config_name='default'):
    app = Flask(__name__, template_folder="templates", static_folder='static')
    admin_instance = Admin(app, name='Admin')
    admin_instance.add_view(EntityAdmin(Entity, db.session))

My EntityAdmin class looks like this :

class EntityAdmin(ModelView):
    column_filters = [
        MyCustomFilter(column=None, name='Custom')
    ]

My custom filter looks like this :

class MyCustomFilter(BaseSQLAFilter):
    def get_options(self, view):
        entities = Entity.query.filter(Entity.active == True).all()
        return [(entity.id, entity.name) for entity in entities]

The problem is that it seems that the get_options function is called when the app is instantiated, running a select query every time the create_app function gets called.

So if I update my database schema and run the flask db migrate command, I get an error because the new column I added does not exist when the select query is run. The query raises an error because my database schema is not in sync with the actual database.

Can I register my views only when an actual HTTP request is made ? How can I differentiate between a request and a command ?

1 Answers1

1

You have one more problem with this filter: its options are created on the application instantiation so if your list of entities was changed during the application running it would still return the same list of options.

To fix both problems you don't need to postpone views registrations. You need the filter to get the list of options every time it is used.

This SO answer to the question "Resetting generator object in Python" describes a way to reuse a generator (in your case — a database query):

from flask import has_app_context

def get_entities():
    # has_app_context is used to prevent database access
    # when application is not ready yet
    if has_app_context():
        for entity in Entity.query.filter(Entity.active.is_(True)):
            yield entity.id, entity.name

class ReloadingIterator:
    def __init__(self, iterator_factory):
        self.iterator_factory = iterator_factory

    def __iter__(self):
        return self.iterator_factory()

class MyCustomFilter(BaseSQLAFilter):
    def get_options(self, view):
        # This will return a generator which is
        # reloaded every time it is used
        return ReloadingIterator(get_entities)

The problem is that the query to the Entity table can be called multiple times during request. So I usually cache the result for a single request using Flask globals:

def get_entities():
    if has_app_context():
        if not hasattr(g, 'entities'):
            query = Entity.query.filter(Entity.active.is_(True))
            g.entities = [(entity.id, entity.name) for entity in query]
        for entity_id, entity_name in g.entities:
            yield entity_id, entity_name
Sergey Shubin
  • 3,040
  • 4
  • 24
  • 36