Add progress support for bulk deletion
plus bulk-delete all tempmon readings when deleting client or probe
This commit is contained in:
parent
d8be651e95
commit
3205d61ba6
|
@ -29,6 +29,7 @@ from __future__ import unicode_literals, absolute_import
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from rattail.db import model
|
from rattail.db import model
|
||||||
|
from rattail.util import progress_loop
|
||||||
|
|
||||||
from pyramid import httpexceptions
|
from pyramid import httpexceptions
|
||||||
from pyramid.renderers import render_to_response
|
from pyramid.renderers import render_to_response
|
||||||
|
@ -86,6 +87,9 @@ class View(object):
|
||||||
"""
|
"""
|
||||||
return httpexceptions.HTTPFound(location=url, **kwargs)
|
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):
|
def render_progress(self, kwargs):
|
||||||
"""
|
"""
|
||||||
Render the progress page, with given kwargs as context.
|
Render the progress page, with given kwargs as context.
|
||||||
|
|
|
@ -32,9 +32,11 @@ from sqlalchemy import orm
|
||||||
|
|
||||||
import sqlalchemy_continuum as continuum
|
import sqlalchemy_continuum as continuum
|
||||||
|
|
||||||
|
from rattail.db import Session as RattailSession
|
||||||
from rattail.db.continuum import model_transaction_query
|
from rattail.db.continuum import model_transaction_query
|
||||||
from rattail.util import prettify
|
from rattail.util import prettify
|
||||||
from rattail.time import localtime
|
from rattail.time import localtime
|
||||||
|
from rattail.threads import Thread
|
||||||
|
|
||||||
import formalchemy as fa
|
import formalchemy as fa
|
||||||
from pyramid import httpexceptions
|
from pyramid import httpexceptions
|
||||||
|
@ -43,6 +45,7 @@ from webhelpers2.html import HTML, tags
|
||||||
|
|
||||||
from tailbone import forms, grids
|
from tailbone import forms, grids
|
||||||
from tailbone.views import View
|
from tailbone.views import View
|
||||||
|
from tailbone.progress import SessionProgress
|
||||||
|
|
||||||
|
|
||||||
class MasterView(View):
|
class MasterView(View):
|
||||||
|
@ -702,20 +705,60 @@ class MasterView(View):
|
||||||
Delete all records matching the current grid query
|
Delete all records matching the current grid query
|
||||||
"""
|
"""
|
||||||
if self.request.method == 'POST':
|
if self.request.method == 'POST':
|
||||||
query = self.get_effective_query(sortable=False)
|
key = '{}.bulk_delete'.format(self.model_class.__tablename__)
|
||||||
count = query.count()
|
objects = self.get_effective_data()
|
||||||
self.bulk_delete_objects(query)
|
progress = SessionProgress(self.request, key)
|
||||||
self.request.session.flash("Deleted {:,d} {}".format(count, self.get_model_title_plural()))
|
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:
|
else:
|
||||||
self.request.session.flash("Sorry, you must POST to do a bulk delete operation")
|
self.request.session.flash("Sorry, you must POST to do a bulk delete operation")
|
||||||
return self.redirect(self.get_index_url())
|
return self.redirect(self.get_index_url())
|
||||||
|
|
||||||
def bulk_delete_objects(self, query):
|
def bulk_delete_objects(self, session, objects, progress=None):
|
||||||
# TODO: sometimes the first makes sense, and would be preferred for
|
|
||||||
# efficiency's sake. might even need to add progress to latter?
|
def delete(obj, i):
|
||||||
# query.delete(synchronize_session=False)
|
session.delete(obj)
|
||||||
for obj in query:
|
|
||||||
self.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):
|
def get_merge_fields(self):
|
||||||
if hasattr(self, 'merge_fields'):
|
if hasattr(self, 'merge_fields'):
|
||||||
|
|
|
@ -114,6 +114,19 @@ class TempmonClientView(MasterView):
|
||||||
del fs.probes
|
del fs.probes
|
||||||
del fs.online
|
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):
|
def restartable_client(self, client):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,8 @@ Common stuff for tempmon views
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
from __future__ import unicode_literals, absolute_import
|
||||||
|
|
||||||
|
from rattail_tempmon.db import Session as RawTempmonSession
|
||||||
|
|
||||||
from formalchemy.fields import SelectFieldRenderer
|
from formalchemy.fields import SelectFieldRenderer
|
||||||
from webhelpers2.html import tags
|
from webhelpers2.html import tags
|
||||||
|
|
||||||
|
@ -39,6 +41,9 @@ class MasterView(views.MasterView2):
|
||||||
"""
|
"""
|
||||||
Session = TempmonSession
|
Session = TempmonSession
|
||||||
|
|
||||||
|
def get_bulk_delete_session(self):
|
||||||
|
return RawTempmonSession()
|
||||||
|
|
||||||
|
|
||||||
class ClientFieldRenderer(SelectFieldRenderer):
|
class ClientFieldRenderer(SelectFieldRenderer):
|
||||||
|
|
||||||
|
|
|
@ -109,6 +109,19 @@ class TempmonProbeView(MasterView):
|
||||||
if self.creating or self.editing:
|
if self.creating or self.editing:
|
||||||
del fs.status
|
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):
|
def includeme(config):
|
||||||
TempmonProbeView.defaults(config)
|
TempmonProbeView.defaults(config)
|
||||||
|
|
Loading…
Reference in a new issue