I'm fairly new to flask-admin and would like to create a batch action for a view that will primarily do two things:
- update the Status of the selected record(s) in the table that the action is "Running"
- 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.
- 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 thedb.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
- 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'))