testdef, exécuter les tests automatiquement (#74302) #110
|
@ -9,6 +9,7 @@ from wcs.blocks import BlockDef
|
|||
from wcs.categories import BlockCategory
|
||||
from wcs.formdef import FormDef
|
||||
from wcs.qommon.http_request import HTTPRequest
|
||||
from wcs.testdef import TestDef, TestResult
|
||||
from wcs.workflows import Workflow
|
||||
|
||||
from ..utilities import clean_temporary_pub, create_temporary_pub, get_app, login
|
||||
|
@ -536,3 +537,50 @@ def test_block_field_statistics_data_update(pub):
|
|||
|
||||
formdata.refresh_from_storage()
|
||||
assert formdata.statistics_data == {'bool': [True]}
|
||||
|
||||
|
||||
def test_block_test_results(pub):
|
||||
create_superuser(pub)
|
||||
BlockDef.wipe()
|
||||
block = BlockDef()
|
||||
block.name = 'foobar'
|
||||
block.fields = [fields.StringField(id='123', required=True, label='Test', type='string')]
|
||||
block.store()
|
||||
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'form title'
|
||||
formdef.fields = [
|
||||
fields.BlockField(id='0', label='test', type='block:%s' % block.slug),
|
||||
]
|
||||
formdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/forms/blocks/%s/' % block.id)
|
||||
resp = resp.click(href=re.compile('123/$'))
|
||||
resp.form['varname'] = 'test'
|
||||
resp = resp.form.submit('submit').follow()
|
||||
assert TestResult.count() == 0
|
||||
|
||||
pub.site_options.set('options', 'enable-tests', 'true')
|
||||
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
|
||||
pub.site_options.write(fd)
|
||||
|
||||
resp = resp.click(href=re.compile('123/$'))
|
||||
resp.form['varname'] = 'test_2'
|
||||
resp = resp.form.submit('submit').follow()
|
||||
assert TestResult.count() == 0
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.data['1'] = 'a'
|
||||
formdata.store()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.name = 'First test'
|
||||
testdef.store()
|
||||
|
||||
resp = resp.click(href=re.compile('123/$'))
|
||||
resp.form['varname'] = 'test_3'
|
||||
resp = resp.form.submit('submit').follow()
|
||||
assert TestResult.count() == 1
|
||||
|
|
|
@ -6,6 +6,7 @@ import xml.etree.ElementTree as ET
|
|||
|
||||
import pytest
|
||||
import responses
|
||||
from django.utils.timezone import now
|
||||
from pyquery import PyQuery
|
||||
from webtest import Upload
|
||||
|
||||
|
@ -17,6 +18,7 @@ from wcs.data_sources import NamedDataSource
|
|||
from wcs.formdef import FormDef
|
||||
from wcs.qommon.errors import ConnectionError
|
||||
from wcs.qommon.http_request import HTTPRequest
|
||||
from wcs.testdef import TestResult
|
||||
from wcs.workflows import Workflow, WorkflowBackofficeFieldsFormDef, WorkflowVariablesFieldsFormDef
|
||||
from wcs.wscalls import NamedWsCall
|
||||
|
||||
|
@ -3792,3 +3794,53 @@ def test_form_field_statistics_data_update(pub):
|
|||
|
||||
formdata.refresh_from_storage()
|
||||
assert formdata.statistics_data == {'bool': [True]}
|
||||
|
||||
|
||||
def test_forms_last_test_result(pub, formdef):
|
||||
TestResult.wipe()
|
||||
create_superuser(pub)
|
||||
create_role(pub)
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/forms/1/')
|
||||
assert '<h2>form title</h2>' in resp.text
|
||||
|
||||
pub.site_options.set('options', 'enable-tests', 'true')
|
||||
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
|
||||
pub.site_options.write(fd)
|
||||
|
||||
resp = app.get('/backoffice/forms/1/')
|
||||
assert '<h2>form title</h2>' in resp.text
|
||||
|
||||
test_result = TestResult()
|
||||
test_result.object_type = formdef.get_table_name()
|
||||
test_result.object_id = formdef.id
|
||||
test_result.timestamp = now()
|
||||
test_result.success = True
|
||||
test_result.reason = ''
|
||||
test_result.results = []
|
||||
test_result.store()
|
||||
|
||||
for url in ('/backoffice/forms/1/', '/backoffice/forms/1/fields/'):
|
||||
resp = app.get(url)
|
||||
assert '<h2>form title <a href="%s"' % test_result.get_admin_url() in resp.text
|
||||
assert 'test-success' in resp.text
|
||||
assert 'test-failure' not in resp.text
|
||||
|
||||
test_result.success = False
|
||||
test_result.store()
|
||||
|
||||
for url in ('/backoffice/forms/1/', '/backoffice/forms/1/fields/'):
|
||||
resp = app.get(url)
|
||||
assert 'test-failure' in resp.text
|
||||
assert 'test-success' not in resp.text
|
||||
|
||||
test_result.success = True
|
||||
test_result.id = None
|
||||
test_result.store()
|
||||
assert TestResult.count() == 2
|
||||
|
||||
for url in ('/backoffice/forms/1/', '/backoffice/forms/1/fields/'):
|
||||
resp = app.get(url)
|
||||
assert 'test-success' in resp.text
|
||||
assert 'test-failure' not in resp.text
|
||||
|
|
|
@ -9,7 +9,7 @@ from webtest import Upload
|
|||
from wcs import fields
|
||||
from wcs.formdef import FormDef
|
||||
from wcs.qommon.http_request import HTTPRequest
|
||||
from wcs.testdef import TestDef
|
||||
from wcs.testdef import TestDef, TestResult
|
||||
|
||||
from ..utilities import clean_temporary_pub, create_temporary_pub, get_app, login
|
||||
from .test_all import create_superuser
|
||||
|
@ -32,6 +32,7 @@ def pub():
|
|||
|
||||
FormDef.wipe()
|
||||
TestDef.wipe()
|
||||
TestResult.wipe()
|
||||
return pub
|
||||
|
||||
|
||||
|
@ -64,14 +65,6 @@ def test_tests_page(pub):
|
|||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
formdef.fields = [
|
||||
fields.PageField(
|
||||
id='0',
|
||||
label='1st page',
|
||||
type='page',
|
||||
post_conditions=[
|
||||
{'condition': {'type': 'django', 'value': 'form_var_text == "a"'}, 'error_message': 'Error'}
|
||||
],
|
||||
),
|
||||
fields.StringField(id='1', label='Text', varname='text'),
|
||||
]
|
||||
formdef.store()
|
||||
|
@ -81,7 +74,6 @@ def test_tests_page(pub):
|
|||
resp = app.get(formdef.get_admin_url())
|
||||
resp = resp.click('Tests')
|
||||
assert 'There are no tests yet.' in resp.text
|
||||
assert 'Run' not in resp.text
|
||||
assert 'Tests cannot be created because there are no completed forms.' in resp.text
|
||||
assert 'New' not in resp.text
|
||||
|
||||
|
@ -95,7 +87,6 @@ def test_tests_page(pub):
|
|||
resp = app.get(formdef.get_admin_url())
|
||||
resp = resp.click('Tests')
|
||||
assert 'There are no tests yet.' in resp.text
|
||||
assert 'Run' not in resp.text
|
||||
assert 'New tests cannot be created' not in resp.text
|
||||
|
||||
resp = resp.click('New')
|
||||
|
@ -107,9 +98,6 @@ def test_tests_page(pub):
|
|||
assert 'First test' in resp.text
|
||||
assert 'no tests yet' not in resp.text
|
||||
|
||||
resp = resp.click('Run')
|
||||
assert 'First test: Success!' in resp.text
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
|
@ -125,10 +113,6 @@ def test_tests_page(pub):
|
|||
assert 'First test' in resp.text
|
||||
assert 'Second test' in resp.text
|
||||
|
||||
resp = resp.click('Run')
|
||||
assert 'First test: Success!' in resp.text
|
||||
assert 'Second test: Page 1 post condition was not met (form_var_text == "a").' in resp.text
|
||||
|
||||
|
||||
def test_tests_page_formdefs_isolation(pub):
|
||||
formdef = FormDef()
|
||||
|
@ -282,8 +266,8 @@ def test_tests_edit(pub):
|
|||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.data['1'] = 'aaa'
|
||||
formdata.data['3'] = 'bbb'
|
||||
formdata.data['1'] = 'test 1'
|
||||
formdata.data['3'] = 'test 2'
|
||||
formdata.user_id = user.id
|
||||
formdata.store()
|
||||
|
||||
|
@ -294,13 +278,88 @@ def test_tests_edit(pub):
|
|||
app = login(get_app(pub))
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/' % testdef.id)
|
||||
assert 'aaa' in resp.text
|
||||
assert 'bbb' in resp.text
|
||||
assert 'test 1' in resp.text
|
||||
assert 'test 2' in resp.text
|
||||
|
||||
resp = resp.click('Edit')
|
||||
resp.form['f1'] = 'xxx'
|
||||
resp.form['f1'] = 'test 3'
|
||||
resp = resp.form.submit('submit')
|
||||
resp = resp.form.submit('submit').follow() # change nothing on second page
|
||||
assert 'aaa' not in resp.text
|
||||
assert 'xxx' in resp.text
|
||||
assert 'bbb' in resp.text
|
||||
assert 'test 1' not in resp.text
|
||||
assert 'test 3' in resp.text
|
||||
assert 'test 2' in resp.text
|
||||
|
||||
|
||||
def test_tests_manual_run(pub):
|
||||
user = create_superuser(pub)
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
formdef.fields = [
|
||||
fields.StringField(id='1', label='String', varname='string'),
|
||||
]
|
||||
formdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
resp = app.get(formdef.get_admin_url() + 'tests/')
|
||||
resp = resp.click('Test results')
|
||||
assert 'No test results yet.' in resp.text
|
||||
assert 'Run tests' not in resp.text
|
||||
|
||||
# create test
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.data['1'] = 'a'
|
||||
formdata.user_id = user.id
|
||||
formdata.store()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.name = 'First test'
|
||||
testdef.store()
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/results/')
|
||||
assert 'No test results yet.' in resp.text
|
||||
|
||||
resp = resp.click('Run tests')
|
||||
assert resp.location == 'http://example.net/backoffice/forms/1/tests/results/1/'
|
||||
|
||||
resp = resp.follow()
|
||||
assert 'Started by: Manual run.' in resp.text
|
||||
assert len(resp.pyquery('tr')) == 1
|
||||
assert 'Success!' in resp.text
|
||||
|
||||
resp = resp.click('First test')
|
||||
assert 'Edit' in resp.text
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/results/')
|
||||
assert 'No test results yet.' not in resp.text
|
||||
assert len(resp.pyquery('tr')) == 1
|
||||
assert len(resp.pyquery('span.test-success')) == 1
|
||||
assert len(resp.pyquery('span.test-failure')) == 0
|
||||
|
||||
# add required field
|
||||
formdef.fields.append(fields.StringField(id='2', label='String', varname='string'))
|
||||
formdef.store()
|
||||
|
||||
resp = resp.click('Run tests')
|
||||
assert resp.location == 'http://example.net/backoffice/forms/1/tests/results/2/'
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/results/')
|
||||
assert len(resp.pyquery('tr')) == 2
|
||||
assert len(resp.pyquery('span.test-success')) == 1
|
||||
assert len(resp.pyquery('span.test-failure')) == 1
|
||||
|
||||
resp = resp.click('#2')
|
||||
assert 'Started by: Manual run.' in resp.text
|
||||
assert 'Success!' not in resp.text
|
||||
assert 'Empty value for field' in resp.text
|
||||
assert 'disabled' not in resp.text
|
||||
|
||||
TestDef.remove_object(testdef.id)
|
||||
resp = app.get('/backoffice/forms/1/tests/results/2/')
|
||||
assert 'disabled' in resp.text
|
||||
|
||||
# access unknown test result
|
||||
app.get('/backoffice/forms/1/tests/results/42/', status=404)
|
||||
|
|
|
@ -15,7 +15,9 @@ from wcs.fields import CommentField, ItemField, PageField, StringField
|
|||
from wcs.formdef import FormDef
|
||||
from wcs.mail_templates import MailTemplate
|
||||
from wcs.qommon.form import UploadedFile
|
||||
from wcs.qommon.http_request import HTTPRequest
|
||||
from wcs.qommon.misc import localstrftime
|
||||
from wcs.testdef import TestDef
|
||||
from wcs.wf.form import WorkflowFormFieldsFormDef
|
||||
from wcs.workflows import Workflow, WorkflowVariablesFieldsFormDef
|
||||
from wcs.wscalls import NamedWsCall
|
||||
|
@ -26,7 +28,9 @@ from .utilities import clean_temporary_pub, create_temporary_pub, get_app, login
|
|||
|
||||
@pytest.fixture
|
||||
def pub(emails):
|
||||
pub = create_temporary_pub(lazy_mode=True)
|
||||
pub = create_temporary_pub()
|
||||
req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'})
|
||||
pub.set_app_dir(req)
|
||||
pub.cfg['identification'] = {'methods': ['password']}
|
||||
pub.cfg['language'] = {'language': 'en'}
|
||||
pub.write_cfg()
|
||||
|
@ -1196,3 +1200,64 @@ def test_category_snapshot_browse(pub):
|
|||
assert '<p>%s</p>' % localstrftime(snapshot.timestamp) in resp.text
|
||||
with pytest.raises(IndexError):
|
||||
resp = resp.click('Edit')
|
||||
|
||||
|
||||
def test_snapshots_test_results(pub):
|
||||
user = create_superuser(pub)
|
||||
|
||||
pub.site_options.set('options', 'enable-tests', 'true')
|
||||
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
|
||||
pub.site_options.write(fd)
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
formdef.fields = [
|
||||
StringField(id='1', label='String', varname='string'),
|
||||
]
|
||||
formdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
# make a change while there are no tests
|
||||
resp = app.get('/backoffice/forms/1/fields/1/')
|
||||
resp.form['label'] = 'New label'
|
||||
resp.form.submit('submit').follow()
|
||||
|
||||
resp = app.get('/backoffice/forms/1/history/')
|
||||
assert 'New label' in resp.text
|
||||
assert '/tests/results/' not in resp.text
|
||||
assert resp.pyquery('td.test-result').text().strip() == ''
|
||||
|
||||
# create test
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.data['1'] = 'a'
|
||||
formdata.user_id = user.id
|
||||
formdata.store()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.name = 'First test'
|
||||
testdef.store()
|
||||
|
||||
# add field
|
||||
resp = app.get('/backoffice/forms/1/fields/')
|
||||
resp.forms[0]['label'] = 'Foobar'
|
||||
resp.forms[0]['type'] = 'string'
|
||||
resp.forms[0].submit().follow()
|
||||
|
||||
# field is required, tests failed
|
||||
resp = app.get('/backoffice/forms/1/history/')
|
||||
assert 'Foobar' in resp.text
|
||||
assert '/tests/results/' in resp.text
|
||||
assert len(resp.pyquery('span.test-failure')) == 1
|
||||
assert len(resp.pyquery('span.test-success')) == 0
|
||||
|
||||
# make field optional
|
||||
resp = app.get('/backoffice/forms/1/fields/2/')
|
||||
resp.form['required'] = False
|
||||
resp.form.submit('submit').follow()
|
||||
|
||||
resp = app.get('/backoffice/forms/1/history/')
|
||||
assert 'Foobar' in resp.text
|
||||
assert len(resp.pyquery('span.test-failure')) == 1
|
||||
assert len(resp.pyquery('span.test-success')) == 1
|
||||
|
|
|
@ -20,7 +20,7 @@ from wcs import compat, custom_views, sessions, sql
|
|||
from wcs.qommon import force_str
|
||||
from wcs.qommon.tokens import Token
|
||||
from wcs.roles import Role
|
||||
from wcs.testdef import TestDef
|
||||
from wcs.testdef import TestDef, TestResult
|
||||
from wcs.tracking_code import TrackingCode
|
||||
from wcs.users import User
|
||||
|
||||
|
@ -167,6 +167,7 @@ def create_temporary_pub(pickle_mode=False, lazy_mode=False):
|
|||
sql.Audit.do_table()
|
||||
sql.do_meta_table()
|
||||
TestDef.do_table()
|
||||
TestResult.do_table()
|
||||
sql.WorkflowTrace.do_table()
|
||||
sql.init_global_table()
|
||||
|
||||
|
|
|
@ -563,6 +563,7 @@ class FieldsDirectory(Directory):
|
|||
def index_top(self):
|
||||
r = TemplateIO(html=True)
|
||||
r += htmltext('<h2>%s') % self.objectdef.name
|
||||
r += utils.last_test_result_block(self.objectdef)
|
||||
if self.page_id:
|
||||
current_page_no = 0
|
||||
for field in self.objectdef.fields:
|
||||
|
|
|
@ -697,7 +697,9 @@ class FormDefPage(Directory):
|
|||
DateWidget.prepare_javascript()
|
||||
|
||||
r += htmltext('<div id="appbar">')
|
||||
r += htmltext('<h2>%s</h2>') % self.formdef.name
|
||||
r += htmltext('<h2>%s') % self.formdef.name
|
||||
r += utils.last_test_result_block(self.formdef)
|
||||
r += htmltext('</h2>')
|
||||
r += htmltext('<span class="actions">')
|
||||
if not self.formdef.is_readonly():
|
||||
r += htmltext('<a rel="popup" href="title">%s</a>') % _('change title')
|
||||
|
|
|
@ -17,18 +17,21 @@
|
|||
import json
|
||||
|
||||
from django.template.loader import render_to_string
|
||||
from quixote import get_response, get_session, redirect
|
||||
from django.utils.timezone import now
|
||||
from quixote import get_publisher, get_request, get_response, get_session, redirect
|
||||
from quixote.directory import Directory
|
||||
from quixote.html import TemplateIO, htmltext
|
||||
|
||||
from wcs.backoffice.management import FormBackofficeEditPage, FormBackOfficeStatusPage
|
||||
from wcs.forms.common import FormStatusPage
|
||||
from wcs.qommon import _, template
|
||||
from wcs.qommon import _, misc, template
|
||||
from wcs.qommon.afterjobs import AfterJob
|
||||
from wcs.qommon.backoffice.listing import pagination_links
|
||||
from wcs.qommon.backoffice.menu import html_top
|
||||
from wcs.qommon.errors import TraversalError
|
||||
from wcs.qommon.form import FileWidget, Form, SingleSelectWidget, StringWidget
|
||||
from wcs.qommon.storage import Equal, StrictNotEqual
|
||||
from wcs.testdef import TestDef, TestError
|
||||
from wcs.testdef import TestDef, TestError, TestResult
|
||||
|
||||
|
||||
class TestPage(FormBackOfficeStatusPage):
|
||||
|
@ -97,11 +100,12 @@ class TestPage(FormBackOfficeStatusPage):
|
|||
|
||||
|
||||
class TestsDirectory(Directory):
|
||||
_q_exports = ['', 'new', 'run', ('import', 'p_import')]
|
||||
_q_exports = ['', 'new', ('import', 'p_import'), 'results']
|
||||
section = 'tests'
|
||||
|
||||
def __init__(self, objectdef):
|
||||
self.objectdef = objectdef
|
||||
self.results = TestResultsDirectory(objectdef)
|
||||
|
||||
def _q_traverse(self, path):
|
||||
get_response().breadcrumb.append(('tests/', _('Tests')))
|
||||
|
@ -122,18 +126,20 @@ class TestsDirectory(Directory):
|
|||
for x in self.objectdef.fields
|
||||
),
|
||||
}
|
||||
return template.QommonTemplateResponse(templates=['wcs/backoffice/tests.html'], context=context)
|
||||
get_response().add_javascript(['popup.js'])
|
||||
return template.QommonTemplateResponse(
|
||||
templates=['wcs/backoffice/tests.html'], context=context, is_django_native=True
|
||||
)
|
||||
|
||||
def new(self):
|
||||
form = Form(enctype='multipart/form-data')
|
||||
form.add(StringWidget, 'name', title=_('Name'), required=True, size=50)
|
||||
formadata_options = sorted(
|
||||
[
|
||||
(x.id, '%s - %s' % (x.id_display, x.user or _('Unknown User')))
|
||||
for x in self.objectdef.data_class().select([StrictNotEqual('status', 'draft')])
|
||||
],
|
||||
key=lambda x: x[1],
|
||||
)
|
||||
formadata_options = [
|
||||
(x.id, '%s - %s' % (x.id_display, x.user or _('Unknown User')))
|
||||
for x in self.objectdef.data_class().select(
|
||||
[StrictNotEqual('status', 'draft')], order_by='-receipt_time'
|
||||
)
|
||||
]
|
||||
form.add(SingleSelectWidget, 'formdata_id', title=_('Form'), required=True, options=formadata_options)
|
||||
form.add_submit('submit', _('Submit'))
|
||||
form.add_submit('cancel', _('Cancel'))
|
||||
|
@ -157,22 +163,6 @@ class TestsDirectory(Directory):
|
|||
|
||||
return redirect('.')
|
||||
|
||||
def run(self):
|
||||
get_response().breadcrumb.append(('run', _('Run tests')))
|
||||
|
||||
testdef = TestDef.select(
|
||||
[Equal('object_type', self.objectdef.get_table_name()), Equal('object_id', self.objectdef.id)]
|
||||
)
|
||||
for test in testdef:
|
||||
try:
|
||||
test.run(self.objectdef)
|
||||
except TestError as e:
|
||||
test.error = e
|
||||
|
||||
return template.QommonTemplateResponse(
|
||||
templates=['wcs/backoffice/test_results.html'], context={'tests': testdef}
|
||||
)
|
||||
|
||||
def p_import(self):
|
||||
form = Form(enctype='multipart/form-data')
|
||||
|
||||
|
@ -209,3 +199,130 @@ class TestsDirectory(Directory):
|
|||
|
||||
get_session().message = ('info', _('Test "%s" has been successfully imported.') % testdef.name)
|
||||
return redirect('.')
|
||||
|
||||
|
||||
class TestResultPage(Directory):
|
||||
_q_exports = ['']
|
||||
|
||||
def __init__(self, component, objectdef):
|
||||
try:
|
||||
self.test_result = TestResult.get(component)
|
||||
except KeyError:
|
||||
raise TraversalError()
|
||||
|
||||
self.objectdef = objectdef
|
||||
|
||||
def _q_traverse(self, path):
|
||||
get_response().breadcrumb.append(
|
||||
(str(self.test_result.id) + '/', _('Result #%s') % self.test_result.id)
|
||||
)
|
||||
return super()._q_traverse(path)
|
||||
|
||||
def _q_index(self):
|
||||
testdefs = TestDef.select(
|
||||
[Equal('object_type', self.objectdef.get_table_name()), Equal('object_id', self.objectdef.id)]
|
||||
)
|
||||
testdefs_by_id = {x.id: x for x in testdefs}
|
||||
for test in self.test_result.results:
|
||||
if test['id'] in testdefs_by_id:
|
||||
test['url'] = testdefs_by_id[test['id']].get_admin_url()
|
||||
|
||||
return template.QommonTemplateResponse(
|
||||
templates=['wcs/backoffice/test-result.html'],
|
||||
context={'test_result': self.test_result},
|
||||
is_django_native=True,
|
||||
)
|
||||
|
||||
|
||||
class TestResultsDirectory(Directory):
|
||||
_q_exports = ['', 'run']
|
||||
section = 'test_results'
|
||||
|
||||
def __init__(self, objectdef):
|
||||
self.objectdef = objectdef
|
||||
|
||||
def _q_traverse(self, path):
|
||||
get_response().breadcrumb.append(('results/', _('Test results')))
|
||||
html_top('test-results', '%s - %s' % (self.objectdef.name, _('Test results')))
|
||||
return super()._q_traverse(path)
|
||||
|
||||
def _q_lookup(self, component):
|
||||
return TestResultPage(component, self.objectdef)
|
||||
|
||||
def _q_index(self):
|
||||
criterias = [
|
||||
Equal('object_type', self.objectdef.get_table_name()),
|
||||
Equal('object_id', self.objectdef.id),
|
||||
]
|
||||
|
||||
offset = misc.get_int_or_400(get_request().form.get('offset', 0))
|
||||
limit = misc.get_int_or_400(get_request().form.get('limit', 25))
|
||||
total_count = TestResult.count(criterias)
|
||||
|
||||
context = {
|
||||
'test_results': TestResult.select(criterias, offset=offset, limit=limit, order_by='-id'),
|
||||
'has_testdefs': bool(TestDef.count(criterias)),
|
||||
'pagination_links': pagination_links(offset, limit, total_count, load_js=False),
|
||||
}
|
||||
return template.QommonTemplateResponse(
|
||||
templates=['wcs/backoffice/test-results.html'], context=context, is_django_native=True
|
||||
)
|
||||
|
||||
def run(self):
|
||||
test_result = TestsAfterJob.run_tests(self.objectdef, _('Manual run.'))
|
||||
return redirect(test_result.get_admin_url())
|
||||
|
||||
|
||||
class TestsAfterJob(AfterJob):
|
||||
def __init__(self, objectdef, reason, snapshot=None, **kwargs):
|
||||
super().__init__(
|
||||
objectdef_class=objectdef.__class__,
|
||||
objectdef_id=objectdef.id,
|
||||
reason=reason,
|
||||
snapshot_id=snapshot.id if snapshot else None,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def execute(self):
|
||||
objectdef = self.kwargs['objectdef_class'].get(self.kwargs['objectdef_id'])
|
||||
reason = self.kwargs['reason']
|
||||
|
||||
result = self.run_tests(objectdef, reason)
|
||||
|
||||
if result and self.kwargs['snapshot_id'] is not None:
|
||||
snapshot = get_publisher().snapshot_class.get(self.kwargs['snapshot_id'])
|
||||
snapshot.test_result_id = result.id
|
||||
snapshot.store()
|
||||
|
||||
@staticmethod
|
||||
def run_tests(objectdef, reason):
|
||||
testdefs = TestDef.select(
|
||||
[Equal('object_type', objectdef.get_table_name()), Equal('object_id', objectdef.id)]
|
||||
)
|
||||
if not testdefs:
|
||||
return
|
||||
|
||||
for test in testdefs:
|
||||
try:
|
||||
test.run(objectdef)
|
||||
except TestError as e:
|
||||
test.error = str(e)
|
||||
|
||||
test_result = TestResult()
|
||||
test_result.object_type = objectdef.get_table_name()
|
||||
test_result.object_id = objectdef.id
|
||||
test_result.timestamp = now()
|
||||
test_result.success = not any(hasattr(test, 'error') for test in testdefs)
|
||||
test_result.reason = str(reason)
|
||||
test_result.results = [
|
||||
{
|
||||
'id': test.id,
|
||||
'name': str(test),
|
||||
'error': getattr(test, 'error', None),
|
||||
}
|
||||
for test in testdefs
|
||||
]
|
||||
test_result.results.sort(key=lambda x: bool(x['error']))
|
||||
test_result.store()
|
||||
|
||||
return test_result
|
||||
|
|
|
@ -16,11 +16,13 @@
|
|||
|
||||
import time
|
||||
|
||||
from django.template.loader import render_to_string
|
||||
from quixote import get_publisher, get_request
|
||||
from quixote.html import TemplateIO, htmltext
|
||||
|
||||
from wcs.qommon import _
|
||||
from wcs.qommon.misc import localstrftime
|
||||
from wcs.qommon.storage import Equal
|
||||
|
||||
|
||||
def last_modification_block(obj):
|
||||
|
@ -88,3 +90,24 @@ def snapshot_info_block(snapshot, url_prefix='../../', url_suffix=''):
|
|||
r += htmltext(' <a class="button disabled" href="#">≫</a>')
|
||||
r += htmltext('</p>')
|
||||
return r.getvalue()
|
||||
|
||||
|
||||
def last_test_result_block(objectdef):
|
||||
from wcs.testdef import TestResult
|
||||
|
||||
if not get_publisher().has_site_option('enable-tests'):
|
||||
return ''
|
||||
|
||||
test_results = TestResult.select(
|
||||
[
|
||||
Equal('object_type', objectdef.get_table_name()),
|
||||
Equal('object_id', objectdef.id),
|
||||
],
|
||||
order_by='-id',
|
||||
)
|
||||
|
||||
if not test_results:
|
||||
return ''
|
||||
|
||||
context = {'result': test_results[0], 'add_link': True}
|
||||
return ' ' + htmltext(render_to_string('wcs/backoffice/includes/test-result-fragment.html', context))
|
||||
|
|
|
@ -28,6 +28,7 @@ from wcs.formdef import FormdefImportError
|
|||
from wcs.qommon import _, errors, misc, template
|
||||
from wcs.qommon.backoffice.menu import html_top
|
||||
from wcs.qommon.form import Form, RadiobuttonsWidget, StringWidget
|
||||
from wcs.qommon.storage import Equal
|
||||
from wcs.workflows import WorkflowImportError
|
||||
|
||||
|
||||
|
@ -47,7 +48,8 @@ class SnapshotsDirectory(Directory):
|
|||
def _q_index(self):
|
||||
html_top('', _('History'))
|
||||
return template.QommonTemplateResponse(
|
||||
templates=['wcs/backoffice/snapshots.html'], context={'view': self}
|
||||
templates=['wcs/backoffice/snapshots.html'],
|
||||
context={'view': self, 'enable_tests': get_publisher().has_site_option('enable-tests')},
|
||||
)
|
||||
|
||||
def save(self):
|
||||
|
@ -213,8 +215,14 @@ class SnapshotsDirectory(Directory):
|
|||
}
|
||||
|
||||
def snapshots(self):
|
||||
from wcs.testdef import TestResult
|
||||
|
||||
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)]
|
||||
)
|
||||
test_results_by_id = {x.id: x for x in test_results}
|
||||
day_snapshot = None
|
||||
for snapshot in snapshots:
|
||||
if snapshot.timestamp.date() != current_date:
|
||||
|
@ -224,6 +232,7 @@ class SnapshotsDirectory(Directory):
|
|||
day_snapshot = snapshot
|
||||
else:
|
||||
day_snapshot.day_other_count += 1
|
||||
snapshot.test_result = test_results_by_id.get(snapshot.test_result_id)
|
||||
return snapshots
|
||||
|
||||
def _q_lookup(self, component):
|
||||
|
|
|
@ -20,7 +20,7 @@ import uuid
|
|||
import xml.etree.ElementTree as ET
|
||||
from contextlib import contextmanager
|
||||
|
||||
from quixote import get_publisher, get_request
|
||||
from quixote import get_publisher, get_request, get_response
|
||||
from quixote.html import htmltag, htmltext
|
||||
|
||||
from . import data_sources, fields
|
||||
|
@ -96,6 +96,14 @@ class BlockDef(StorableObject):
|
|||
for field in objdef.get_all_fields():
|
||||
if field.key == 'block' and field.type == 'block:%s' % self.slug:
|
||||
objdef.store()
|
||||
|
||||
if get_publisher().has_site_option('enable-tests') and get_response():
|
||||
from wcs.admin.tests import TestsAfterJob
|
||||
|
||||
context = _('in field block "%s"') % field.label
|
||||
get_response().add_after_job(
|
||||
TestsAfterJob(objdef, reason='%s (%s)' % (comment, context))
|
||||
)
|
||||
break
|
||||
|
||||
def get_new_field_id(self):
|
||||
|
|
|
@ -2623,3 +2623,13 @@ div#main-content > h3.field-edit--subtitle {
|
|||
.journal-table--datetime {
|
||||
width: 10em;
|
||||
}
|
||||
|
||||
span.test-success::before {
|
||||
font-family: FontAwesome;
|
||||
content: "\f00c"; /* check */
|
||||
}
|
||||
|
||||
span.test-failure::before {
|
||||
font-family: FontAwesome;
|
||||
content: "\f00d"; /* times */
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import re
|
|||
import xml.etree.ElementTree as ET
|
||||
|
||||
from django.utils.timezone import now
|
||||
from quixote import get_publisher, get_session
|
||||
from quixote import get_publisher, get_response, get_session
|
||||
|
||||
from wcs.qommon import _, misc
|
||||
|
||||
|
@ -130,6 +130,7 @@ class Snapshot:
|
|||
serialization = None
|
||||
patch = None
|
||||
label = None # (named snapshot)
|
||||
test_result_id = None
|
||||
|
||||
# cache
|
||||
_instance = None
|
||||
|
@ -183,6 +184,13 @@ class Snapshot:
|
|||
# else: keep serialization and ignore patch
|
||||
obj.store()
|
||||
|
||||
if get_publisher().has_site_option('enable-tests') and get_response():
|
||||
from wcs.admin.tests import TestsAfterJob
|
||||
|
||||
get_response().add_after_job(
|
||||
TestsAfterJob(instance, reason=obj.label or obj.comment, snapshot=obj)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_recent_changes(cls, object_types=None, user=None, limit=5, offset=0):
|
||||
elements = cls._get_recent_changes(object_types=object_types, user=user, limit=limit, offset=offset)
|
||||
|
|
110
wcs/sql.py
110
wcs/sql.py
|
@ -1389,7 +1389,8 @@ def do_snapshots_table():
|
|||
comment TEXT,
|
||||
serialization TEXT,
|
||||
patch TEXT,
|
||||
label VARCHAR
|
||||
label VARCHAR,
|
||||
test_result_id INTEGER
|
||||
)'''
|
||||
% table_name
|
||||
)
|
||||
|
@ -1401,9 +1402,10 @@ def do_snapshots_table():
|
|||
)
|
||||
existing_fields = {x[0] for x in cur.fetchall()}
|
||||
|
||||
# migrations
|
||||
if 'patch' not in existing_fields:
|
||||
cur.execute('''ALTER TABLE %s ADD COLUMN patch TEXT''' % table_name)
|
||||
# generic migration for new columns
|
||||
for field_name, field_type in Snapshot._table_static_fields:
|
||||
if field_name not in existing_fields:
|
||||
cur.execute('''ALTER TABLE %s ADD COLUMN %s %s''' % (table_name, field_name, field_type))
|
||||
|
||||
needed_fields = {x[0] for x in Snapshot._table_static_fields}
|
||||
|
||||
|
@ -3820,6 +3822,7 @@ class Snapshot(SqlMixin, wcs.snapshots.Snapshot):
|
|||
('serialization', 'text'),
|
||||
('patch', 'text'),
|
||||
('label', 'varchar'),
|
||||
('test_result_id', 'integer'),
|
||||
]
|
||||
_table_select_skipped_fields = ['serialization', 'patch']
|
||||
|
||||
|
@ -3867,7 +3870,7 @@ class Snapshot(SqlMixin, wcs.snapshots.Snapshot):
|
|||
def _row2ob(cls, row, **kwargs):
|
||||
o = cls()
|
||||
for field, value in zip(cls._table_static_fields, tuple(row)):
|
||||
if field[1] in ('serial', 'timestamptz'):
|
||||
if field[1] in ('serial', 'timestamptz', 'integer'):
|
||||
setattr(o, field[0], value)
|
||||
elif field[1] in ('varchar', 'text'):
|
||||
setattr(o, field[0], str_encode(value))
|
||||
|
@ -4302,6 +4305,95 @@ class TestDef(SqlMixin):
|
|||
return []
|
||||
|
||||
|
||||
class TestResult(SqlMixin):
|
||||
_table_name = 'test_result'
|
||||
_table_static_fields = [
|
||||
('id', 'serial'),
|
||||
('object_type', 'varchar'),
|
||||
('object_id', 'varchar'),
|
||||
('timestamp', 'timestamptz'),
|
||||
('success', 'boolean'),
|
||||
('reason', 'varchar'),
|
||||
('results', 'jsonb[]'),
|
||||
]
|
||||
|
||||
id = None
|
||||
|
||||
@classmethod
|
||||
@guard_postgres
|
||||
def do_table(cls, conn=None, cur=None):
|
||||
conn, cur = get_connection_and_cursor()
|
||||
table_name = cls._table_name
|
||||
|
||||
cur.execute(
|
||||
'''SELECT COUNT(*) FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = %s''',
|
||||
(table_name,),
|
||||
)
|
||||
if cur.fetchone()[0] == 0:
|
||||
cur.execute(
|
||||
'''CREATE TABLE %s (id SERIAL PRIMARY KEY,
|
||||
object_type varchar NOT NULL,
|
||||
object_id varchar NOT NULL,
|
||||
timestamp timestamptz,
|
||||
success boolean NOT NULL,
|
||||
reason varchar NOT NULL,
|
||||
results jsonb[]
|
||||
)'''
|
||||
% table_name
|
||||
)
|
||||
|
||||
conn.commit()
|
||||
cur.close()
|
||||
|
||||
@guard_postgres
|
||||
def store(self):
|
||||
sql_dict = {x[0]: getattr(self, x[0], None) for x in self._table_static_fields if x[0] != 'id'}
|
||||
|
||||
conn, cur = get_connection_and_cursor()
|
||||
column_names = list(sql_dict.keys())
|
||||
|
||||
column_values = []
|
||||
for name in column_names:
|
||||
value = '%%(%s)s' % name
|
||||
if name == 'results':
|
||||
value += '::jsonb[]'
|
||||
column_values.append(value)
|
||||
|
||||
if not self.id:
|
||||
sql_statement = '''INSERT INTO %s (id, %s)
|
||||
VALUES (DEFAULT, %s)
|
||||
RETURNING id''' % (
|
||||
self._table_name,
|
||||
', '.join(column_names),
|
||||
', '.join(column_values),
|
||||
)
|
||||
cur.execute(sql_statement, sql_dict)
|
||||
self.id = cur.fetchone()[0]
|
||||
else:
|
||||
sql_dict['id'] = self.id
|
||||
sql_statement = '''UPDATE %s SET %s WHERE id = %%(id)s RETURNING id''' % (
|
||||
self._table_name,
|
||||
', '.join(['%s = %%(%s)s' % (x, x) for x in column_names]),
|
||||
)
|
||||
cur.execute(sql_statement, sql_dict)
|
||||
|
||||
conn.commit()
|
||||
cur.close()
|
||||
|
||||
@classmethod
|
||||
def _row2ob(cls, row, **kwargs):
|
||||
o = cls.__new__(cls)
|
||||
for attr, value in zip([x[0] for x in cls._table_static_fields], row):
|
||||
setattr(o, attr, value)
|
||||
return o
|
||||
|
||||
@classmethod
|
||||
def get_data_fields(cls):
|
||||
return []
|
||||
|
||||
|
||||
class WorkflowTrace(SqlMixin):
|
||||
_table_name = 'workflow_traces'
|
||||
_table_static_fields = [
|
||||
|
@ -4966,7 +5058,7 @@ def get_period_total(
|
|||
# latest migration, number + description (description is not used
|
||||
# programmaticaly but will make sure git conflicts if two migrations are
|
||||
# separately added with the same number)
|
||||
SQL_LEVEL = (82, 'add statistics data column to wcs_all_forms, for real')
|
||||
SQL_LEVEL = (83, 'add test_result table')
|
||||
|
||||
|
||||
def migrate_global_views(conn, cur):
|
||||
|
@ -5092,10 +5184,11 @@ def migrate():
|
|||
raise RuntimeError()
|
||||
if sql_level < 1: # 1: introduction of tracking_code table
|
||||
do_tracking_code_table()
|
||||
if sql_level < 63:
|
||||
if sql_level < 83:
|
||||
# 42: create snapshots table
|
||||
# 54: add patch column
|
||||
# 63: add index
|
||||
# 83: add test_result table
|
||||
do_snapshots_table()
|
||||
if sql_level < 50:
|
||||
# 49: store Role in SQL
|
||||
|
@ -5148,6 +5241,9 @@ def migrate():
|
|||
if sql_level < 78:
|
||||
# 78: add audit table
|
||||
Audit.do_table()
|
||||
if sql_level < 83:
|
||||
# 83: add test_result table
|
||||
TestResult.do_table()
|
||||
if sql_level < 52:
|
||||
# 2: introduction of formdef_id in views
|
||||
# 5: add concerned_roles_array, is_at_endpoint and fts to views
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
{% load i18n %}{% spaceless %}
|
||||
|
||||
{% if add_link %}<a href="{{ result.get_admin_url }}">{% endif %}
|
||||
{% if result and result.success %}
|
||||
<span class="test-success" title="{% trans "Success" %}" aria-label="{% trans "Success" %}"></span>
|
||||
{% elif result %}
|
||||
<span class="test-failure" title="{% trans "Failure" %}" aria-label="{% trans "Failure" %}"></span>
|
||||
{% endif %}
|
||||
{% if add_link %}</a>{% endif %}
|
||||
|
||||
{% endspaceless %}
|
|
@ -22,6 +22,7 @@
|
|||
<th colspan="3">{% trans "Description" %}</th>
|
||||
<th>{% trans 'User' %}</th>
|
||||
<th colspan="2">{% trans 'Actions' %}</th>
|
||||
{% if enable_tests %}<th>{% trans "Tests" %}</th>{% endif %}
|
||||
</thead>
|
||||
<tbody class="snapshots-list">
|
||||
{% for snapshot in snapshots %}
|
||||
|
@ -58,6 +59,11 @@
|
|||
—
|
||||
<a href="{{snapshot.id}}/export">{% trans "Export" %}</a>
|
||||
</td>
|
||||
{% if enable_tests %}
|
||||
<td class="test-result">
|
||||
{% include "wcs/backoffice/includes/test-result-fragment.html" with result=snapshot.test_result add_link=True %}
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
{% extends "wcs/backoffice.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar-title %}{% trans "Result" %} #{{ test_result.id }}{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="section">
|
||||
<h3>{% trans "Details" %}</h3>
|
||||
<div>
|
||||
<ul>
|
||||
<li>{% trans "Started by:" %} {{ test_result.reason }}</li>
|
||||
<li>{% trans "Date:" %} {{ test_result.timestamp }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h3>Result</h3>
|
||||
<div>
|
||||
<table class="main">
|
||||
<thead>
|
||||
<th>{% trans "Name" %}</th>
|
||||
<th>{% trans "Result" %}</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for test in test_result.results %}
|
||||
<tr>
|
||||
<td><a {% if test.url %}href="{{ test.url }}"{% else %}disabled{% endif %}>{{ test.name }}</a></td>
|
||||
<td>{% firstof test.error _("Success!") %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -0,0 +1,38 @@
|
|||
{% extends "wcs/backoffice.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar-title %}{% trans "Test results" %}{% endblock %}
|
||||
|
||||
{% block appbar-actions %}
|
||||
{% if has_testdefs %}
|
||||
<a href="run">{% trans "Run tests" %}</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
{% if test_results %}
|
||||
<div class="section">
|
||||
<table class="main">
|
||||
<thead>
|
||||
<th>{% trans "Identifier" %}</th>
|
||||
<th>{% trans "Date" %}</th>
|
||||
<th>{% trans "Started by" %}</th>
|
||||
<th>{% trans "Status" %}</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for result in test_results %}
|
||||
<tr>
|
||||
<td><a href="{{ result.id }}/">#{{ result.id }}</a></td>
|
||||
<td>{{ result.timestamp }}</td>
|
||||
<td>{{ result.reason }}</td>
|
||||
<td>{% include "wcs/backoffice/includes/test-result-fragment.html" %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{{ pagination_links|safe }}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="infonotice"><p>{% trans "No test results yet." %}</p></div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -1,14 +0,0 @@
|
|||
{% extends "wcs/backoffice/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar-title %}{% trans "Tests results" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="section">
|
||||
<ul>
|
||||
{% for test in tests %}
|
||||
<li>{{ test }}{% trans ":" %} {% firstof test.error _("Success!") %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,18 +1,19 @@
|
|||
{% extends "wcs/backoffice/base.html" %}
|
||||
{% extends "wcs/backoffice.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar-title %}{% trans "Tests" %}{% endblock %}
|
||||
{% block appbar-actions %}
|
||||
<a href="import" rel="popup">{% trans "Import" %}</a>
|
||||
{% if testdefs %}
|
||||
<a href="run">{% trans "Run tests" %}</a>
|
||||
{% endif %}
|
||||
<a class="extra-actions-menu-opener"></a>
|
||||
<a href="results/">{% trans "Test results" %}</a>
|
||||
{% if formdata and not has_deprecated_fields %}
|
||||
<a href="new" rel="popup">{% trans "New" %}</a>
|
||||
{% endif %}
|
||||
<ul class="extra-actions-menu">
|
||||
<li><a href="import" rel="popup">{% trans "Import" %}</a></li>
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block body %}
|
||||
<div class="section">
|
||||
<h3>{% trans "Test form data" %}</h3>
|
||||
|
||||
|
|
|
@ -48,6 +48,10 @@ class TestDef(sql.TestDef):
|
|||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_admin_url(self):
|
||||
base_url = get_publisher().get_backoffice_url()
|
||||
return '%s/forms/%s/tests/%s/' % (base_url, self.object_id, self.id)
|
||||
|
||||
def store(self, *args, comment=None, snapshot_store_user=True, **kwargs):
|
||||
if not self.slug:
|
||||
existing_slugs = {
|
||||
|
@ -302,3 +306,18 @@ class TestDef(sql.TestDef):
|
|||
|
||||
testdef.store()
|
||||
return testdef
|
||||
|
||||
|
||||
class TestResult(sql.TestResult):
|
||||
_names = 'test_result'
|
||||
|
||||
object_type = None # (formdef, carddef, etc.)
|
||||
object_id = None
|
||||
timestamp = None
|
||||
success = None
|
||||
reason = None # reason for tests execution
|
||||
results = None # results for each test associated to object
|
||||
|
||||
def get_admin_url(self):
|
||||
base_url = get_publisher().get_backoffice_url()
|
||||
return '%s/forms/%s/tests/results/%s/' % (base_url, self.object_id, self.id)
|
||||
|
|
Loading…
Reference in New Issue