Home > Mobile >  flask-admin custom form action and kick off background processes
flask-admin custom form action and kick off background processes

Time:10-16

I'm fairly new to flask-admin and would like to create a batch action for a view that will primarily do two things:

  1. update the Status of the selected record(s) in the table that the action is "Running"
  2. kick off a deamon or background process that runs some queries, optimizations, etc.

Edit: For #2) I'll probably want to use Celery. If that's too much weight for standalone question, I'm happy to focus on simply #1), which is: How do I simply update the records I've selected? Seems super trivial but nothing is working.

Edit #2: I found this question which seems to answer the question fairly simply, however I don't undestand what and where the transaction_service.recalculate_transaction is: Flask Admin extend "with select"-dropdown menu with custom button

Here's what I have thus far, but I keep getting a 302 status from the action. So nothing actually happens.

Any help will be greatly appreciated.

__init__.py

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SECRET_KEY'] = '12345' 
app.config['DEBUG'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql pymysql://root:password@localhost/testdb'

db = SQLAlchemy(app)

import test_project.views

views.py

from test_project import app, db
from flask_socketio import SocketIO, emit
from flask import render_template, url_for, redirect
from flask_admin import Admin
from flask_admin.actions import action
from flask_admin.contrib.sqla import ModelView

from test_project.db import TargetTable

socketio = SocketIO(app)
admin = Admin(app, name='Test Tool', template_mode='bootstrap4')


class CustomView(ModelView):
    # Not really using yet.
    pass


class TargetTableAdmin(CustomView):

    form_excluded_columns = ['status']
    column_display_pk = True
    create_modal = True
    can_edit = False
    can_delete = False

    def on_model_change(self, form, model, is_created):
        ###############
        ##### Note: ###  When a record is created the Status is set to "Not Run"
        ###############
        model.status = 'Not Run' #["Running", "Completed", "Failed", "Not Run"]
    
    ################################
    # HOW DO I GET THIS TO WORK?? ##
    ################################
    @action('run', 'Run')    
    def run_target(self, ids):
        query = TargetTable.query.filter(TargetTable.id.in_(ids))
        for target in query.all():
            target.status = 'Running'
            db.session.commit()
            # THIS DOESN'T WORK :(

admin.add_view(TargetTableAdmin(TargetTable, db.session, category='Target'))

db.py

from test_project import app
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey

from flask_migrate import Migrate

db = SQLAlchemy(app)
migrate = Migrate(app, db)


class TargetTable(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    start_date = db.Column(db.DateTime, nullable=False)
    end_date = db.Column(db.DateTime, nullable=False)
    status = db.Column(db.String(100), nullable=True)

    def __init__(self, start_date, end_date, status):
        self.start_date = start_date
        self.end_date = end_date
        self.status = status
    

CodePudding user response:

OK.

  1. So ultimately #1 was easy to answer, as I mention in my comments above. I was importing the db from the __init__.py file, where I should have been importing from the db.py file. I'm not sure I need to initialize the DB in the __init__.py file, but that's a different question. Here is the fix:

views.py

from test_project import app #### -> remove import here, db 
from flask_socketio import SocketIO, emit
from flask import render_template, url_for, redirect
from flask_admin import Admin
from flask_admin.actions import action
from flask_admin.contrib.sqla import ModelView

from test_project.db import TargetTable, db #### <-- add import here
  1. This gets harder? to answer because there are lots of ways to skin this cat. You could use a couple options / packages:
  • Celery / Redis
  • Threading
  • Flask-Executor
  • Flask-SocketIO

For now, I'm using Flask-SocketIO. And specifically I'm using a function in that package called: start_background_task. The function is non-blocking and once the function is called the page will refresh all the while the process runs in the background. It's possible this might scale, but I'm not too concerned with that right now as this is just an internal tool with minimal users at the moment. Updated file:

views.py:

# Imports above ^

socketio = SocketIO(app)
admin = Admin(app, name='Test Tool', template_mode='bootstrap4')


class CustomView(ModelView):
    # Not really using yet.
    pass


def testFunct(record):
    for i in range(10):
        print(i)
        time.sleep(1)
    updateRecord = TargetTable.query.filter( (TargetTable.id == record.id) ).first()
    updateRecord.status = "Completed"
    db.session.commit()


class TargetTableAdmin(CustomView):

    form_excluded_columns = ['status']
    column_display_pk = True
    create_modal = True
    can_edit = False
    can_delete = False

    def on_model_change(self, form, model, is_created):
        ###############
        ##### Note: ###  When a record is created the Status is set to "Not Run"
        ###############
        model.status = 'Not Run' #["Running", "Completed", "Failed", "Not Run"]
    
    ###############
    # IT WORKS!! ##
    ###############
    @action('run', 'Run')    
    def run_target(self, ids):
        query = TargetTable.query.filter(TargetTable.id.in_(ids))
        for t in query.all():
            t.status = 'Running'
            db.session.commit()
            # Kick off background task:
            task = socketio.start_background_task(testFunct, t)

admin.add_view(TargetTableAdmin(TargetTable, db.session, category='Target'))

            
  • Related