backoffice: add popup to cleanup logged errors (#40821) #1024
|
@ -284,3 +284,109 @@ def test_logged_error_trace(pub):
|
|||
resp = app.get(f'/backoffice/studio/logged-errors/{logged_error.id}/')
|
||||
assert 'pub.record_error(\'Exception' in resp.pyquery('.stack-trace--code')[0].text
|
||||
assert '\n locals:' in resp.text
|
||||
|
||||
|
||||
def test_logged_error_cleanup(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
FormDef.wipe()
|
||||
CardDef.wipe()
|
||||
Workflow.wipe()
|
||||
pub.loggederror_class.wipe()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'foo'
|
||||
formdef.store()
|
||||
carddef = CardDef()
|
||||
carddef.name = 'bar'
|
||||
carddef.store()
|
||||
workflow = Workflow()
|
||||
workflow.name = 'blah'
|
||||
workflow.store()
|
||||
|
||||
# FormDef error
|
||||
error1 = pub.loggederror_class()
|
||||
error1.summary = 'LoggedError'
|
||||
error1.formdef_class = 'FormDef'
|
||||
error1.formdef_id = formdef.id
|
||||
error1.workflow_id = workflow.id
|
||||
error1.first_occurence_timestamp = error1.latest_occurence_timestamp = datetime.datetime.now()
|
||||
error1.store()
|
||||
|
||||
# CardDef error
|
||||
error2 = pub.loggederror_class()
|
||||
error2.summary = 'LoggedError'
|
||||
error2.formdef_class = 'CardDef'
|
||||
error2.formdef_id = carddef.id
|
||||
error2.workflow_id = workflow.id
|
||||
error2.first_occurence_timestamp = error2.latest_occurence_timestamp = datetime.datetime.now()
|
||||
error2.store()
|
||||
|
||||
# workflow-only error
|
||||
error3 = pub.loggederror_class()
|
||||
error3.summary = 'LoggedError'
|
||||
error3.workflow_id = workflow.id
|
||||
error3.first_occurence_timestamp = error3.latest_occurence_timestamp = datetime.datetime.now()
|
||||
error3.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/studio/logged-errors/')
|
||||
resp = resp.click('Cleanup')
|
||||
resp = resp.form.submit('submit')
|
||||
assert pub.loggederror_class().count() == 3 # nothing removed
|
||||
|
||||
# check there's a form error if nothing is checked
|
||||
resp = app.get('/backoffice/studio/logged-errors/')
|
||||
resp = resp.click('Cleanup')
|
||||
resp.form['types$elementformdef'].checked = False
|
||||
resp.form['types$elementcarddef'].checked = False
|
||||
resp.form['types$elementothers'].checked = False
|
||||
resp = resp.form.submit('submit')
|
||||
assert resp.pyquery('[data-widget-name="types"].widget-with-error')
|
||||
|
||||
# check cleanup of only formdef errors
|
||||
error1.first_occurence_timestamp = (
|
||||
error1.latest_occurence_timestamp
|
||||
) = datetime.datetime.now() - datetime.timedelta(days=280)
|
||||
error1.store()
|
||||
error2.first_occurence_timestamp = datetime.datetime.now() - datetime.timedelta(days=120)
|
||||
error2.latest_occurence_timestamp = datetime.datetime.now() - datetime.timedelta(days=80)
|
||||
error2.store()
|
||||
error3.first_occurence_timestamp = (
|
||||
error3.latest_occurence_timestamp
|
||||
) = datetime.datetime.now() - datetime.timedelta(days=280)
|
||||
error3.store()
|
||||
resp = app.get('/backoffice/studio/logged-errors/')
|
||||
resp = resp.click('Cleanup')
|
||||
resp.form['types$elementcarddef'].checked = False
|
||||
resp.form['types$elementothers'].checked = False
|
||||
resp = resp.form.submit('submit')
|
||||
assert {x.id for x in pub.loggederror_class().select()} == {error2.id, error3.id}
|
||||
|
||||
# check cleanup latest occurence value (error2 should not be cleaned)
|
||||
resp = app.get('/backoffice/studio/logged-errors/')
|
||||
resp = resp.click('Cleanup')
|
||||
resp.form['latest_occurence'] = (datetime.datetime.now() - datetime.timedelta(days=100)).strftime(
|
||||
'%Y-%m-%d'
|
||||
)
|
||||
resp = resp.form.submit('submit')
|
||||
assert {x.id for x in pub.loggederror_class().select()} == {error2.id}
|
||||
|
||||
# check with a more recent date (error2 should be cleaned this time)
|
||||
resp = app.get('/backoffice/studio/logged-errors/')
|
||||
resp = resp.click('Cleanup')
|
||||
resp.form['latest_occurence'] = (datetime.datetime.now() - datetime.timedelta(days=10)).strftime(
|
||||
'%Y-%m-%d'
|
||||
)
|
||||
resp = resp.form.submit('submit')
|
||||
assert {x.id for x in pub.loggederror_class().select()} == set()
|
||||
|
||||
# make formdefs not accessible to current user
|
||||
pub.cfg['admin-permissions'] = {'forms': ['X']}
|
||||
pub.write_cfg()
|
||||
resp = app.get('/backoffice/studio/logged-errors/')
|
||||
resp = resp.click('Cleanup')
|
||||
assert [x.attrib['name'] for x in resp.pyquery('[type="checkbox"]')] == [
|
||||
'types$elementcarddef',
|
||||
'types$elementothers',
|
||||
]
|
||||
|
|
|
@ -14,16 +14,18 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import datetime
|
||||
import re
|
||||
|
||||
from django.utils.text import Truncator
|
||||
from quixote import get_publisher, get_request, get_response, redirect
|
||||
from quixote.directory import Directory
|
||||
from quixote.directory import AccessControlled, Directory
|
||||
from quixote.html import TemplateIO, htmltext
|
||||
|
||||
from wcs.backoffice.pagination import pagination_links
|
||||
from wcs.qommon import N_, _, errors, misc, ngettext, template
|
||||
from wcs.sql_criterias import Equal, NotEqual, NotNull
|
||||
from wcs.qommon.form import CheckboxesWidget, DateWidget, Form
|
||||
from wcs.sql_criterias import Equal, Less, NotEqual, NotNull, Null, Or
|
||||
|
||||
|
||||
class LoggedErrorDirectory(Directory):
|
||||
|
@ -132,8 +134,8 @@ class LoggedErrorDirectory(Directory):
|
|||
return redirect('..')
|
||||
|
||||
|
||||
class LoggedErrorsDirectory(Directory):
|
||||
_q_exports = ['']
|
||||
class LoggedErrorsDirectory(AccessControlled, Directory):
|
||||
_q_exports = ['', 'cleanup']
|
||||
|
||||
@classmethod
|
||||
def get_errors(cls, offset, limit, formdef_class=None, formdef_id=None, workflow_id=None):
|
||||
|
@ -208,7 +210,7 @@ class LoggedErrorsDirectory(Directory):
|
|||
self.formdef_id = formdef_id
|
||||
self.workflow_id = workflow_id
|
||||
|
||||
def _q_index(self):
|
||||
def _q_access(self):
|
||||
|
||||
backoffice_root = get_publisher().get_backoffice_root()
|
||||
if not (
|
||||
backoffice_root.is_accessible('forms')
|
||||
|
@ -217,6 +219,7 @@ class LoggedErrorsDirectory(Directory):
|
|||
):
|
||||
raise errors.AccessForbiddenError()
|
||||
|
||||
def _q_index(self):
|
||||
get_response().breadcrumb.append(('logged-errors/', _('Logged Errors')))
|
||||
get_response().set_title(_('Logged Errors'))
|
||||
limit = misc.get_int_or_400(
|
||||
|
@ -240,6 +243,58 @@ class LoggedErrorsDirectory(Directory):
|
|||
},
|
||||
)
|
||||
|
||||
def cleanup(self):
|
||||
backoffice_root = get_publisher().get_backoffice_root()
|
||||
form = Form(enctype='multipart/form-data')
|
||||
options = []
|
||||
if backoffice_root.is_accessible('forms'):
|
||||
options.append(('formdef', _('Forms'), 'formdef'))
|
||||
if backoffice_root.is_accessible('cards'):
|
||||
options.append(('carddef', _('Card Models'), 'carddef'))
|
||||
if backoffice_root.is_accessible('workflows'):
|
||||
options.append(('others', _('Others'), 'others'))
|
||||
form.add(
|
||||
CheckboxesWidget,
|
||||
'types',
|
||||
title=_('Error types'),
|
||||
fpeters
commented
Un champ pour choisir les types d'erreur. Un champ pour choisir les types d'erreur.
|
||||
value=[x[0] for x in options], # check all by default
|
||||
options=options,
|
||||
required=True,
|
||||
)
|
||||
form.add(
|
||||
DateWidget,
|
||||
'latest_occurence',
|
||||
title=_('Latest occurence'),
|
||||
value=datetime.date.today() - datetime.timedelta(days=180),
|
||||
fpeters
commented
Un autre pour choisir la date max pour la dernière occurence, valeur par défaut à à peu près 6 mois, c'est assez arbitraire. Un autre pour choisir la date max pour la dernière occurence, valeur par défaut à à peu près 6 mois, c'est assez arbitraire.
|
||||
required=True,
|
||||
)
|
||||
form.add_submit('submit', _('Submit'))
|
||||
form.add_submit('cancel', _('Cancel'))
|
||||
|
||||
if form.get_widget('cancel').parse():
|
||||
return redirect('.')
|
||||
|
||||
if form.get_submit() == 'submit' and not form.has_errors():
|
||||
type_criterias = []
|
||||
if 'formdef' in form.get_widget('types').parse():
|
||||
type_criterias.append(Equal('formdef_class', 'FormDef'))
|
||||
if 'carddef' in form.get_widget('types').parse():
|
||||
type_criterias.append(Equal('formdef_class', 'CardDef'))
|
||||
if 'others' in form.get_widget('types').parse():
|
||||
type_criterias.append(Null('formdef_class'))
|
||||
lguerin
commented
Possible d'ajouter un test avec aucune des cases présente cochée ? Juste pour vérifier que le form sera en erreur (parce que champ required), et qu'on ne passera pas dans un cas où il n'y a pas de critère sur formdef_class, ce qui supprimerait tout. Possible d'ajouter un test avec aucune des cases présente cochée ? Juste pour vérifier que le form sera en erreur (parce que champ required), et qu'on ne passera pas dans un cas où il n'y a pas de critère sur formdef_class, ce qui supprimerait tout.
fpeters
commented
Voilà j'ai ajouté ce test, ainsi que quelques lignes de commentaire dans le test pour expliciter les différentes intentions. Voilà j'ai ajouté ce test, ainsi que quelques lignes de commentaire dans le test pour expliciter les différentes intentions.
|
||||
criterias = [
|
||||
Less('latest_occurence_timestamp', form.get_widget('latest_occurence').parse()),
|
||||
Or(type_criterias),
|
||||
]
|
||||
get_publisher().loggederror_class.wipe(clause=criterias)
|
||||
return redirect('.')
|
||||
|
||||
get_response().set_title(_('Cleanup'))
|
||||
r = TemplateIO(html=True)
|
||||
r += htmltext('<h2>%s</h2>') % _('Cleanup')
|
||||
r += form.render()
|
||||
return r.getvalue()
|
||||
|
||||
def _q_lookup(self, component):
|
||||
try:
|
||||
error = get_publisher().loggederror_class.get(component)
|
||||
|
|
|
@ -3,6 +3,10 @@
|
|||
|
||||
{% block appbar-title %}{% trans "Logged Errors" %}{% endblock %}
|
||||
|
||||
{% block appbar-actions %}
|
||||
<a rel="popup" href="cleanup">{% trans "Cleanup" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<ul class="objects-list single-links">
|
||||
{% for error in errors %}
|
||||
|
|
Loading…
Reference in New Issue
Ajout de la classe AccessControlled, le contrôle d'acccès qui était posé en haut de _q_index est mis dans la nouvelle méthode _q_access, pour être effectif également pour la nouvelle vue.