Add progress support for bulk deletion

plus bulk-delete all tempmon readings when deleting client or probe
This commit is contained in:
Lance Edgar 2017-08-04 16:11:45 -05:00
parent d8be651e95
commit 3205d61ba6
5 changed files with 88 additions and 10 deletions

View file

@ -29,6 +29,7 @@ from __future__ import unicode_literals, absolute_import
import os
from rattail.db import model
from rattail.util import progress_loop
from pyramid import httpexceptions
from pyramid.renderers import render_to_response
@ -86,6 +87,9 @@ class View(object):
"""
return httpexceptions.HTTPFound(location=url, **kwargs)
def progress_loop(self, func, items, factory, *args, **kwargs):
return progress_loop(func, items, factory, *args, **kwargs)
def render_progress(self, kwargs):
"""
Render the progress page, with given kwargs as context.

View file

@ -32,9 +32,11 @@ from sqlalchemy import orm
import sqlalchemy_continuum as continuum
from rattail.db import Session as RattailSession
from rattail.db.continuum import model_transaction_query
from rattail.util import prettify
from rattail.time import localtime
from rattail.threads import Thread
import formalchemy as fa
from pyramid import httpexceptions
@ -43,6 +45,7 @@ from webhelpers2.html import HTML, tags
from tailbone import forms, grids
from tailbone.views import View
from tailbone.progress import SessionProgress
class MasterView(View):
@ -702,20 +705,60 @@ class MasterView(View):
Delete all records matching the current grid query
"""
if self.request.method == 'POST':
query = self.get_effective_query(sortable=False)
count = query.count()
self.bulk_delete_objects(query)
self.request.session.flash("Deleted {:,d} {}".format(count, self.get_model_title_plural()))
key = '{}.bulk_delete'.format(self.model_class.__tablename__)
objects = self.get_effective_data()
progress = SessionProgress(self.request, key)
thread = Thread(target=self.bulk_delete_thread, args=(objects, progress))
thread.start()
return self.render_progress({
'key': key,
'cancel_url': self.get_index_url(),
'cancel_msg': "Bulk deletion was canceled",
})
else:
self.request.session.flash("Sorry, you must POST to do a bulk delete operation")
return self.redirect(self.get_index_url())
def bulk_delete_objects(self, query):
# TODO: sometimes the first makes sense, and would be preferred for
# efficiency's sake. might even need to add progress to latter?
# query.delete(synchronize_session=False)
for obj in query:
self.Session.delete(obj)
def bulk_delete_objects(self, session, objects, progress=None):
def delete(obj, i):
session.delete(obj)
self.progress_loop(delete, objects, progress,
message="Deleting objects")
def get_bulk_delete_session(self):
return RattailSession()
def bulk_delete_thread(self, objects, progress):
"""
Thread target for bulk-deleting current results, with progress.
"""
session = self.get_bulk_delete_session()
objects = objects.with_session(session).all()
try:
self.bulk_delete_objects(session, objects, progress=progress)
# If anything goes wrong, rollback and log the error etc.
except Exception as error:
session.rollback()
log.exception("execution failed for batch results")
session.close()
if progress:
progress.session.load()
progress.session['error'] = True
progress.session['error_msg'] = "Bulk deletion failed: {}: {}".format(type(error).__name__, error)
progress.session.save()
# If no error, check result flag (false means user canceled).
else:
session.commit()
session.close()
if progress:
progress.session.load()
progress.session['complete'] = True
progress.session['success_url'] = self.get_index_url()
progress.session.save()
def get_merge_fields(self):
if hasattr(self, 'merge_fields'):

View file

@ -114,6 +114,19 @@ class TempmonClientView(MasterView):
del fs.probes
del fs.online
def delete_instance(self, client):
# bulk-delete all readings first
readings = self.Session.query(tempmon.Reading)\
.filter(tempmon.Reading.client == client)
readings.delete(synchronize_session=False)
self.Session.flush()
self.Session.refresh(client)
# Flush immediately to force any pending integrity errors etc.; that
# way we don't set flash message until we know we have success.
self.Session.delete(client)
self.Session.flush()
def restartable_client(self, client):
return True

View file

@ -26,6 +26,8 @@ Common stuff for tempmon views
from __future__ import unicode_literals, absolute_import
from rattail_tempmon.db import Session as RawTempmonSession
from formalchemy.fields import SelectFieldRenderer
from webhelpers2.html import tags
@ -39,6 +41,9 @@ class MasterView(views.MasterView2):
"""
Session = TempmonSession
def get_bulk_delete_session(self):
return RawTempmonSession()
class ClientFieldRenderer(SelectFieldRenderer):

View file

@ -109,6 +109,19 @@ class TempmonProbeView(MasterView):
if self.creating or self.editing:
del fs.status
def delete_instance(self, probe):
# bulk-delete all readings first
readings = self.Session.query(tempmon.Reading)\
.filter(tempmon.Reading.probe == probe)
readings.delete(synchronize_session=False)
self.Session.flush()
self.Session.refresh(probe)
# Flush immediately to force any pending integrity errors etc.; that
# way we don't set flash message until we know we have success.
self.Session.delete(probe)
self.Session.flush()
def includeme(config):
TempmonProbeView.defaults(config)