testdef: add snapshots (#88755)
gitea/wcs/pipeline/head There was a failure building this commit Details

This commit is contained in:
Valentin Deniaud 2024-03-27 15:50:38 +01:00
parent 6090b32990
commit 25cec5404e
7 changed files with 90 additions and 15 deletions

View File

@ -363,6 +363,55 @@ def test_tests_status_page_image_field(pub):
resp.follow(status=404)
def test_tests_history_page(pub):
user = create_superuser(pub)
formdef = FormDef()
formdef.name = 'test title'
formdef.fields = [fields.StringField(id='1', varname='test_field', label='Test Field')]
formdef.store()
formdata = formdef.data_class()()
formdata.just_created()
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
formdata.data['1'] = 'This is a test'
formdata.user_id = user.id
formdata.store()
testdef = TestDef.create_from_formdata(formdef, formdata)
testdef.name = 'First test'
testdef.store()
testdef.name = 'Changed test'
testdef.store()
app = login(get_app(pub))
resp = app.get('/backoffice/forms/1/tests/1/')
resp = resp.click('History')
assert [x.attrib['class'] for x in resp.pyquery.find('.snapshots-list tr')] == ['new-day', 'collapsed']
# export snapshot
resp_export = resp.click('Export', index=1)
assert resp_export.content_type == 'application/x-wcs-snapshot'
assert '>First test<' in resp_export.text
assert TestDef.count() == 1
# view snapshot
resp = resp.click('View', index=1)
assert '<h2>First test</h2>' in resp.text
# restore as new
resp = app.get('/backoffice/forms/1/tests/1/history/')
resp = resp.click('Restore')
resp.form['action'] = 'as-new'
resp = resp.form.submit('submit').follow()
assert TestDef.count() == 2
assert '<h2>First test</h2>' in resp.text
def test_tests_edit(pub):
create_superuser(pub)
user = pub.user_class(name='test user')

View File

@ -28,6 +28,9 @@ from wcs.admin.workflow_tests import WorkflowTestsDirectory
from wcs.api import posted_json_data_to_formdata_data
from wcs.backoffice.management import FormBackofficeEditPage, FormBackOfficeStatusPage
from wcs.backoffice.pagination import pagination_links
from wcs.backoffice.snapshots import SnapshotsDirectory
from wcs.carddef import CardDef
from wcs.formdef import FormDef
from wcs.forms.common import FormStatusPage
from wcs.qommon import _, misc, template
from wcs.qommon.afterjobs import AfterJob
@ -148,13 +151,19 @@ class TestPage(FormBackOfficeStatusPage):
'duplicate',
('workflow', 'workflow_tests'),
('webservice-responses', 'webservice_responses'),
('history', 'snapshots_dir'),
]
def __init__(self, component, objectdef):
def __init__(self, component, objectdef=None, instance=None):
try:
self.testdef = TestDef.get(component)
self.testdef = instance or TestDef.get(component)
except KeyError:
raise TraversalError()
if not objectdef:
klass = FormDef if self.testdef.object_type == 'formdefs' else CardDef
objectdef = klass.get(self.testdef.object_id)
self.testdef.formdef = objectdef
filled = self.testdef.build_formdata(objectdef, include_fields=True)
@ -162,6 +171,7 @@ class TestPage(FormBackOfficeStatusPage):
self.workflow_tests = WorkflowTestsDirectory(self.testdef, self.formdef)
self.webservice_responses = WebserviceResponseDirectory(self.testdef)
self.snapshots_dir = SnapshotsDirectory(self.testdef)
@property
def edit_data(self):
@ -391,7 +401,7 @@ class TestsDirectory(Directory):
if not creation_mode_widget or creation_mode_widget.parse() == 'empty':
testdef = TestDef.create_from_formdata(self.objectdef, self.objectdef.data_class()())
testdef.name = form.get_widget('name').parse()
testdef.agent_id = get_session().user
testdef.agent_id = str(get_session().user)
testdef.store()
return redirect(testdef.get_admin_url() + 'edit-data/')
else:
@ -404,7 +414,7 @@ class TestsDirectory(Directory):
add_workflow_tests=bool(creation_mode_widget.parse() == 'formdata-wf'),
)
testdef.name = form.get_widget('name').parse()
testdef.agent_id = get_session().user
testdef.agent_id = str(get_session().user)
testdef.store()
return redirect(testdef.get_admin_url())

View File

@ -52,7 +52,8 @@ class SnapshotsDirectory(Directory):
templates=['wcs/backoffice/snapshots.html'],
context={
'view': self,
'form_has_tests': bool(TestDef.select_for_objectdef(self.obj)),
'form_has_tests': self.object_type in ('formdef', 'carddef')
and bool(TestDef.select_for_objectdef(self.obj)),
},
)
@ -261,7 +262,7 @@ class SnapshotsDirectory(Directory):
current_date = None
snapshots = get_publisher().snapshot_class.select_object_history(self.obj)
test_results = TestResult.select(
[Equal('object_type', self.obj.get_table_name()), Equal('object_id', self.obj.id)]
[Equal('object_type', self.obj.get_table_name()), Equal('object_id', str(self.obj.id))]
)
test_results_by_id = {x.id: x for x in test_results}
day_snapshot = None

View File

@ -578,6 +578,7 @@ class WcsPublisher(QommonPublisher):
from wcs.data_sources import NamedDataSource
from wcs.formdef import FormDef
from wcs.mail_templates import MailTemplate
from wcs.testdef import TestDef
from wcs.workflows import Workflow
from wcs.wscalls import NamedWsCall
@ -597,6 +598,7 @@ class WcsPublisher(QommonPublisher):
MailTemplateCategory,
CommentTemplateCategory,
DataSourceCategory,
TestDef,
):
if klass.xml_root_node == object_type:
return klass

View File

@ -3640,12 +3640,12 @@ class Snapshot(SqlMixin, wcs.snapshots.Snapshot):
@classmethod
def select_object_history(cls, obj, clause=None):
return cls.select(
[Equal('object_type', obj.xml_root_node), Equal('object_id', obj.id)] + (clause or []),
[Equal('object_type', obj.xml_root_node), Equal('object_id', str(obj.id))] + (clause or []),
order_by='-timestamp',
)
def is_from_object(self, obj):
return self.object_type == obj.xml_root_node and self.object_id == obj.id
return self.object_type == obj.xml_root_node and self.object_id == str(obj.id)
@classmethod
def _row2ob(cls, row, **kwargs):
@ -3676,7 +3676,7 @@ class Snapshot(SqlMixin, wcs.snapshots.Snapshot):
)
cur.execute(
sql_statement,
{'object_type': object_type, 'object_id': object_id, 'max_timestamp': max_timestamp},
{'object_type': object_type, 'object_id': str(object_id), 'max_timestamp': max_timestamp},
)
row = cur.fetchone()
cur.close()

View File

@ -10,6 +10,7 @@
<h3>{% trans "Navigation" %}</h3>
<ul class="sidebar--buttons">
<li><a class="button button-paragraph" rel="popup" href="edit">{% trans "Options" %}</a></li>
<li><a class="button button-paragraph" href="history/">{% trans "History" %}</a></li>
<li><a class="button button-paragraph" href="webservice-responses/">{% trans "Webservice responses" %}</a></li>
<li><a class="button button-paragraph" href="inspect">{% trans "Inspect" %}</a></li>
</ul>

View File

@ -60,6 +60,7 @@ class TestDefXmlProxy(XmlStorableObject):
xml_root_node = 'testdef'
_names = 'testdef'
readonly = True
backoffice_class = 'wcs.admin.tests.TestPage'
# prevent pytest from trying to collect this class
__test__ = False
@ -74,7 +75,7 @@ class TestDefXmlProxy(XmlStorableObject):
'boolean': 'bool',
'jsonb': 'jsonb',
}
excluded_fields = ['id', 'object_type', 'object_id']
excluded_fields = ['id']
extra_fields = [
('_webservice_responses', 'webservice_responses'),
('workflow_tests', 'workflow_tests'),
@ -111,6 +112,7 @@ class TestDefXmlProxy(XmlStorableObject):
class TestDef(sql.TestDef):
_names = 'testdef'
xml_root_node = TestDefXmlProxy.xml_root_node
name = ''
object_type = None # (formdef, carddef, etc.)
@ -131,10 +133,14 @@ class TestDef(sql.TestDef):
'tablerows',
'ranked-items',
)
backoffice_class = 'wcs.admin.tests.TestPage'
def __str__(self):
return self.name
def get_table_name(self):
return self.xml_root_node
@property
def workflow_tests(self):
from wcs.workflow_tests import WorkflowTests
@ -159,8 +165,8 @@ class TestDef(sql.TestDef):
objects_dir = 'forms' if self.object_type == 'formdefs' else 'cards'
return '%s/%s/%s/tests/%s/' % (base_url, objects_dir, self.object_id, self.id)
def store(self, *args, **kwargs):
super().store(*args, **kwargs)
def store(self, comment=None):
super().store()
self.workflow_tests.testdef_id = self.id
self.workflow_tests.testdef = self
@ -172,6 +178,9 @@ class TestDef(sql.TestDef):
response.testdef_id = self.id
response.store()
if get_publisher().snapshot_class:
get_publisher().snapshot_class.snap(instance=self, comment=comment)
@classmethod
def remove_object(cls, id):
super().remove_object(id)
@ -524,7 +533,7 @@ class TestDef(sql.TestDef):
def export_to_xml(self, include_id=False):
self._webservice_responses = self.get_webservice_responses()
testdef_xml = TestDefXmlProxy()
testdef_xml = TestDefXmlProxy(id=str(self.id))
for field, dummy in TestDefXmlProxy.XML_NODES: # pylint: disable=not-an-iterable
setattr(testdef_xml, field, getattr(self, field))
@ -539,11 +548,14 @@ class TestDef(sql.TestDef):
return cls.import_from_xml_tree(tree, formdef, include_id=include_id)
@classmethod
def import_from_xml_tree(cls, tree, formdef, include_id=False):
testdef = TestDef.create_from_formdata(formdef, formdef.data_class()())
def import_from_xml_tree(cls, tree, formdef=None, include_id=False, **kwargs):
testdef = TestDef.create_from_formdata(formdef, formdef.data_class()()) if formdef else TestDef()
testdef_xml = TestDefXmlProxy.import_from_xml_tree(tree, include_id)
for field, dummy in TestDefXmlProxy.XML_NODES: # pylint: disable=not-an-iterable
if formdef and field in ('object_type', 'object_id'):
continue
if hasattr(testdef_xml, field):
setattr(testdef, field, getattr(testdef_xml, field))