backoffice: add possibility to open snapshots (#4960)

This commit is contained in:
Frédéric Péters 2020-08-15 17:03:46 +02:00
parent c5f3c1a1f8
commit a804ac37fe
9 changed files with 169 additions and 22 deletions

View File

@ -468,19 +468,19 @@ def test_form_workflow_link(pub):
app = login(get_app(pub))
resp = app.get('/backoffice/forms/%s/' % formdef.id)
assert '../workflows/_default/' in resp.text
assert '/backoffice/workflows/_default/' in resp.text
formdef.workflow = workflow
formdef.store()
resp = app.get('/backoffice/forms/%s/' % formdef.id)
assert '../workflows/%s/' % workflow.id in resp.text
assert '/backoffice/workflows/%s/' % workflow.id in resp.text
# check workflow link is not displayed if user has no access right
pub.cfg['admin-permissions'] = {'workflows': ['x']} # block access
pub.write_cfg()
resp = app.get('/backoffice/forms/%s/' % formdef.id)
assert '../workflows/%s/' % workflow.id not in resp.text
assert '/backoffice/workflows/%s/' % workflow.id not in resp.text
def test_form_workflow_remapping(pub):

View File

@ -1,8 +1,11 @@
import pytest
from wcs.blocks import BlockDef
from wcs.carddef import CardDef
from wcs.formdef import FormDef
from wcs.data_sources import NamedDataSource
from wcs.formdef import FormDef
from wcs.workflows import Workflow
from wcs.wscalls import NamedWsCall
from utilities import get_app, login, create_temporary_pub, clean_temporary_pub
from test_admin_pages import create_superuser, create_role
@ -31,6 +34,7 @@ def formdef_with_history(pub):
for i in range(5):
formdef.name = 'testform %s' % i
formdef.description = 'this is a description (%s)' % i
formdef.store()
return formdef
@ -207,3 +211,109 @@ def test_form_snapshot_restore(pub, formdef_with_history):
formdef = FormDef.get(resp.location.split('/')[-2])
assert formdef.id == formdef_with_history.id
assert formdef.url_name == formdef_with_history.url_name
def test_block_snapshot_browse(pub, blocks_feature):
create_superuser(pub)
create_role()
BlockDef.wipe()
blockdef = BlockDef()
blockdef.name = 'testblock'
blockdef.fields = []
blockdef.store()
app = login(get_app(pub))
resp = app.get('/backoffice/forms/blocks/%s/history/' % blockdef.id)
snapshot = pub.snapshot_class.select_object_history(blockdef)[0]
resp = resp.click(href='%s/view/' % snapshot.id)
assert 'This block of fields is readonly.' in resp
def test_card_snapshot_browse(pub):
create_superuser(pub)
create_role()
CardDef.wipe()
carddef = CardDef()
carddef.name = 'testcard'
carddef.fields = []
carddef.store()
app = login(get_app(pub))
resp = app.get('/backoffice/cards/%s/history/' % carddef.id)
snapshot = pub.snapshot_class.select_object_history(carddef)[0]
resp = resp.click(href='%s/view/' % snapshot.id)
assert 'This card model is readonly' in resp
resp = resp.click('Geolocation')
assert [x[0].name for x in resp.form.fields.values() if x[0].tag == 'button'] == ['cancel']
def test_datasource_snapshot_browse(pub):
create_superuser(pub)
create_role()
NamedDataSource.wipe()
datasource = NamedDataSource(name='test')
datasource.data_source = {'type': 'formula',
'value': repr([('1', 'un'), ('2', 'deux')])}
datasource.store()
app = login(get_app(pub))
resp = app.get('/backoffice/forms/data-sources/%s/history/' % datasource.id)
snapshot = pub.snapshot_class.select_object_history(datasource)[0]
resp = resp.click(href='%s/view/' % snapshot.id)
assert 'This data source is readonly' in resp
with pytest.raises(IndexError):
resp = resp.click('Edit')
def test_form_snapshot_browse(pub, formdef_with_history):
create_superuser(pub)
create_role()
app = login(get_app(pub))
resp = app.get('/backoffice/forms/%s/history/' % formdef_with_history.id)
snapshot = pub.snapshot_class.select_object_history(formdef_with_history)[2]
resp = resp.click(href='%s/view/' % snapshot.id)
assert 'This form is readonly' in resp
resp = resp.click('Description')
assert resp.form['description'].value == 'this is a description (2)'
assert [x[0].name for x in resp.form.fields.values() if x[0].tag == 'button'] == ['cancel']
def test_workflow_snapshot_browse(pub):
create_superuser(pub)
create_role()
Workflow.wipe()
workflow = Workflow(name='test')
workflow.store()
app = login(get_app(pub))
resp = app.get('/backoffice/workflows/%s/history/' % workflow.id)
snapshot = pub.snapshot_class.select_object_history(workflow)[0]
resp = resp.click(href='%s/view/' % snapshot.id)
assert 'This workflow is readonly' in resp
def test_wscall_snapshot_browse(pub):
create_superuser(pub)
create_role()
NamedWsCall.wipe()
wscall = NamedWsCall(name='test')
wscall.store()
app = login(get_app(pub))
resp = app.get('/backoffice/settings/wscalls/%s/history/' % wscall.id)
snapshot = pub.snapshot_class.select_object_history(wscall)[0]
resp = resp.click(href='%s/view/' % snapshot.id)
assert 'This webservice call is readonly' in resp
with pytest.raises(IndexError):
resp = resp.click('Edit')

View File

@ -160,9 +160,9 @@ class NamedDataSourcePage(Directory):
('history', 'snapshots_dir'),]
do_not_call_in_templates = True
def __init__(self, component):
def __init__(self, component=None, instance=None):
try:
self.datasource = NamedDataSource.get(component)
self.datasource = instance or NamedDataSource.get(component)
except KeyError:
raise errors.TraversalError()
self.datasource_ui = NamedDataSourceUI(self.datasource)

View File

@ -384,9 +384,9 @@ class FormDefPage(Directory):
'The form has been successfully overwritten. '
'Do note it kept its existing address and role and workflow parameters.')
def __init__(self, component):
def __init__(self, component, instance=None):
try:
self.formdef = self.formdef_class.get(component)
self.formdef = instance or self.formdef_class.get(component)
except KeyError:
raise TraversalError()
self.formdefui = self.formdef_ui_class(self.formdef)
@ -458,12 +458,12 @@ class FormDefPage(Directory):
'<span class="label">%(label)s</span> '
'<span class="value offset">%(current_value)s</span>'
'</a>'
'<a class="extra-link" title="%(title)s" href="../../workflows/%(workflow_id)s/">↗</a>'
'<a class="extra-link" title="%(title)s" href="%(workflow_url)s">↗</a>'
'</li>' % {
'link': 'workflow',
'label': _('Workflow'),
'title': _('Open workflow page'),
'workflow_id': self.formdef.workflow.id,
'workflow_url': self.formdef.workflow.get_admin_url(),
'current_value': self.formdef.workflow.name or '-'})
else:
r += add_option_line('workflow', _('Workflow'),

View File

@ -1373,26 +1373,30 @@ class WorkflowPage(Directory):
('history', 'snapshots_dir'),
]
def __init__(self, component, html_top):
if component == '_carddef_default':
def __init__(self, component, instance=None):
if instance:
self.workflow = instance
elif component == '_carddef_default':
self.workflow = CardDef.get_default_workflow()
else:
try:
self.workflow = Workflow.get(component)
except KeyError:
raise errors.TraversalError()
self.html_top = html_top
self.workflow_ui = WorkflowUI(self.workflow)
self.status_dir = WorkflowStatusDirectory(self.workflow, html_top)
self.status_dir = WorkflowStatusDirectory(self.workflow, self.html_top)
self.variables_dir = VariablesDirectory(self.workflow)
self.backoffice_fields_dir = BackofficeFieldsDirectory(self.workflow)
self.functions_dir = FunctionsDirectory(self.workflow)
self.global_actions_dir = GlobalActionsDirectory(self.workflow, html_top)
self.global_actions_dir = GlobalActionsDirectory(self.workflow, self.html_top)
self.criticality_levels_dir = CriticalityLevelsDirectory(self.workflow)
self.logged_errors_dir = LoggedErrorsDirectory(parent_dir=self, workflow_id=self.workflow.id)
self.snapshots_dir = SnapshotsDirectory(self.workflow)
get_response().breadcrumb.append((component + '/', self.workflow.name))
def html_top(self, title):
return html_top('workflows', title)
def _q_index(self):
self.html_top(title = _('Workflow - %s') % self.workflow.name)
r = TemplateIO(html=True)
@ -1919,7 +1923,7 @@ class WorkflowsDirectory(Directory):
return r.getvalue()
def _q_lookup(self, component):
return WorkflowPage(component, html_top=self.html_top)
return WorkflowPage(component)
def p_import(self):
form = Form(enctype = 'multipart/form-data')

View File

@ -99,9 +99,9 @@ class NamedWsCallPage(Directory):
_q_exports = ['', 'edit', 'delete', 'export',
('history', 'snapshots_dir'),]
def __init__(self, component):
def __init__(self, component, instance=None):
try:
self.wscall = NamedWsCall.get(component)
self.wscall = instance or NamedWsCall.get(component)
except KeyError:
raise errors.TraversalError()
self.wscall_ui = NamedWsCallUI(self.wscall)

View File

@ -97,12 +97,12 @@ class CardDefPage(FormDefPage):
'<span class="label">%(label)s</span> '
'<span class="value offset">%(current_value)s</span>'
'</a>'
'<a class="extra-link" title="%(title)s" href="../../workflows/%(workflow_id)s/">↗</a>'
'<a class="extra-link" title="%(title)s" href="%(workflow_url)s">↗</a>'
'</li>' % {
'link': 'workflow',
'label': _('Workflow'),
'title': _('Open workflow page'),
'workflow_id': self.formdef.workflow.id,
'workflow_url': self.formdef.workflow.get_admin_url(),
'current_value': self.formdef.workflow.name or '-'})
else:
r += add_option_line('workflow', _('Workflow'),

View File

@ -66,7 +66,7 @@ class SnapshotsDirectory(Directory):
class SnapshotDirectory(Directory):
_q_exports = ['', 'export', 'restore']
_q_exports = ['', 'export', 'restore', 'view']
def __init__(self, instance, snapshot):
self.obj = instance
@ -80,7 +80,7 @@ class SnapshotDirectory(Directory):
return super()._q_traverse(path)
def _q_index(self):
return ''
return redirect('view/')
def export(self):
response = get_response()
@ -114,3 +114,34 @@ class SnapshotDirectory(Directory):
r += htmltext('<h2>%s</h2>') % _('Restore snapshot')
r += form.render()
return r.getvalue()
@property
def view(self):
from wcs.blocks import BlockDef
from wcs.carddef import CardDef
from wcs.data_sources import NamedDataSource
from wcs.formdef import FormDef
from wcs.workflows import Workflow
from wcs.wscalls import NamedWsCall
klass = self.snapshot.get_object_class()
if klass is BlockDef:
from wcs.admin.blocks import BlockDirectory
return BlockDirectory(section='forms', objectdef=self.snapshot.instance)
if klass is FormDef:
from wcs.admin.forms import FormDefPage
return FormDefPage(component='view', instance=self.snapshot.instance)
if klass is CardDef:
from wcs.backoffice.cards import CardDefPage
return CardDefPage(component='view', instance=self.snapshot.instance)
if klass is Workflow:
from wcs.admin.workflows import WorkflowPage
return WorkflowPage(component='view',
instance=self.snapshot.instance)
if klass is NamedDataSource:
from wcs.admin.data_sources import NamedDataSourcePage
return NamedDataSourcePage(component='view',
instance=self.snapshot.instance)
if klass is NamedWsCall:
from wcs.admin.wscalls import NamedWsCallPage
return NamedWsCallPage(component='view',
instance=self.snapshot.instance)

View File

@ -74,6 +74,7 @@ class Snapshot:
if self._instance is None:
tree = ET.fromstring(self.serialization)
self._instance = self.get_object_class().import_from_xml_tree(tree, include_id=True)
self._instance.readonly = True
return self._instance
@property
@ -101,6 +102,7 @@ class Snapshot:
if hasattr(current_object, attr):
setattr(instance, attr, getattr(current_object, attr))
delattr(instance, 'readonly')
instance.store(comment=_('Restored snapshot %(id)s (%(timestamp)s)') % {
'id': self.id,
'timestamp': misc.localstrftime(self.timestamp)})