Projet

Général

Profil

0002-testdef-add-views-to-run-tests-71296.patch

Valentin Deniaud, 14 décembre 2022 18:09

Télécharger (26,3 ko)

Voir les différences:

Subject: [PATCH 2/3] testdef: add views to run tests (#71296)

 tests/admin_pages/test_tests.py               | 217 ++++++++++++++++++
 tests/test_testdef.py                         | 117 ++++++++++
 wcs/admin/forms.py                            |   6 +
 wcs/admin/tests.py                            | 173 ++++++++++++++
 wcs/fields.py                                 |   3 +-
 .../wcs/backoffice/test_results.html          |  14 ++
 wcs/templates/wcs/backoffice/tests.html       |  38 +++
 wcs/testdef.py                                | 110 +++++++++
 8 files changed, 677 insertions(+), 1 deletion(-)
 create mode 100644 tests/admin_pages/test_tests.py
 create mode 100644 tests/test_testdef.py
 create mode 100644 wcs/admin/tests.py
 create mode 100644 wcs/templates/wcs/backoffice/test_results.html
 create mode 100644 wcs/templates/wcs/backoffice/tests.html
tests/admin_pages/test_tests.py
1
import datetime
2
import json
3
import os
4

  
5
import pytest
6
from django.utils.html import escape
7
from webtest import Upload
8

  
9
from wcs import fields
10
from wcs.formdef import FormDef
11
from wcs.qommon.http_request import HTTPRequest
12
from wcs.testdef import TestDef
13

  
14
from ..utilities import clean_temporary_pub, create_temporary_pub, get_app, login
15
from .test_all import create_superuser
16

  
17

  
18
@pytest.fixture
19
def pub():
20
    pub = create_temporary_pub()
21

  
22
    req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'})
23
    pub.set_app_dir(req)
24
    pub.cfg['identification'] = {'methods': ['password']}
25
    pub.write_cfg()
26

  
27
    if not pub.site_options.has_section('options'):
28
        pub.site_options.add_section('options')
29
    pub.site_options.set('options', 'enable-tests', 'true')
30
    with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
31
        pub.site_options.write(fd)
32

  
33
    FormDef.wipe()
34
    TestDef.do_table()
35
    TestDef.wipe()
36
    return pub
37

  
38

  
39
def teardown_module(module):
40
    clean_temporary_pub()
41

  
42

  
43
def test_tests_link_on_formdef_page(pub):
44
    formdef = FormDef()
45
    formdef.name = 'test title'
46
    formdef.store()
47

  
48
    create_superuser(pub)
49
    app = login(get_app(pub))
50

  
51
    resp = app.get(formdef.get_admin_url())
52
    assert 'Tests' in resp.text
53

  
54
    pub.site_options.set('options', 'enable-tests', 'false')
55
    with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
56
        pub.site_options.write(fd)
57

  
58
    resp = app.get(formdef.get_admin_url())
59
    assert 'Tests' not in resp.text
60

  
61

  
62
def test_tests_page(pub):
63
    user = create_superuser(pub)
64

  
65
    formdef = FormDef()
66
    formdef.name = 'test title'
67
    formdef.fields = [
68
        fields.PageField(
69
            id='0',
70
            label='1st page',
71
            type='page',
72
            post_conditions=[
73
                {'condition': {'type': 'django', 'value': 'form_var_text == "a"'}, 'error_message': 'Error'}
74
            ],
75
        ),
76
        fields.StringField(id='1', label='Text', varname='text'),
77
    ]
78
    formdef.store()
79

  
80
    app = login(get_app(pub))
81

  
82
    resp = app.get(formdef.get_admin_url())
83
    resp = resp.click('Tests')
84
    assert 'There are no tests yet.' in resp.text
85
    assert 'Run' not in resp.text
86
    assert 'Tests cannot be created because there are no completed forms.' in resp.text
87
    assert 'New' not in resp.text
88

  
89
    formdata = formdef.data_class()()
90
    formdata.just_created()
91
    formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
92
    formdata.data['1'] = 'a'
93
    formdata.user_id = user.id
94
    formdata.store()
95

  
96
    resp = app.get(formdef.get_admin_url())
97
    resp = resp.click('Tests')
98
    assert 'There are no tests yet.' in resp.text
99
    assert 'Run' not in resp.text
100
    assert 'New tests cannot be created' not in resp.text
101

  
102
    resp = resp.click('New')
103
    resp.form['name'] = 'First test'
104
    resp.form['formdata_id'].select(text='1-1 - admin')
105
    assert len(resp.form['formdata_id'].options) == 1
106

  
107
    resp = resp.form.submit().follow()
108
    assert 'First test' in resp.text
109
    assert 'no tests yet' not in resp.text
110

  
111
    resp = resp.click('Run')
112
    assert 'First test: Success!' in resp.text
113

  
114
    formdata = formdef.data_class()()
115
    formdata.just_created()
116
    formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
117
    formdata.data['1'] = 'b'
118
    formdata.store()
119

  
120
    resp = app.get('/backoffice/forms/1/tests/new')
121
    resp.form['name'] = 'Second test'
122
    resp.form['formdata_id'].select(text='1-2 - Unknown User')
123
    assert len(resp.form['formdata_id'].options) == 2
124

  
125
    resp = resp.form.submit().follow()
126
    assert 'First test' in resp.text
127
    assert 'Second test' in resp.text
128

  
129
    resp = resp.click('Run')
130
    assert 'First test: Success!' in resp.text
131
    assert 'Second test: Page 1 post condition was not met (form_var_text == "a").' in resp.text
132

  
133

  
134
def test_tests_page_formdefs_isolation(pub):
135
    formdef = FormDef()
136
    formdef.name = 'dummy'
137
    formdef.store()
138

  
139
    formdata = formdef.data_class()()
140
    formdata.just_created()
141
    formdata.store()
142

  
143
    other_formdef = FormDef()
144
    other_formdef.name = 'dummy2'
145
    other_formdef.store()
146

  
147
    formdata2 = other_formdef.data_class()()
148
    formdata2.just_created()
149
    formdata2.store()
150

  
151
    create_superuser(pub)
152
    app = login(get_app(pub))
153

  
154
    resp = app.get('/backoffice/forms/1/tests/new')
155
    assert len(resp.form['formdata_id'].options) == 1
156
    assert resp.form['formdata_id'].options[0][2] == '1-1 - Unknown User'
157

  
158
    resp = app.get('/backoffice/forms/2/tests/new')
159
    assert len(resp.form['formdata_id'].options) == 1
160
    assert resp.form['formdata_id'].options[0][2] == '2-1 - Unknown User'
161

  
162

  
163
def test_tests_import_export(pub):
164
    user = create_superuser(pub)
165

  
166
    formdef = FormDef()
167
    formdef.name = 'test title'
168
    formdef.fields = [fields.StringField(id='1', varname='test field', label='Test', type='string')]
169
    formdef.store()
170

  
171
    formdata = formdef.data_class()()
172
    formdata.just_created()
173
    formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
174
    formdata.data['1'] = 'a'
175
    formdata.user_id = user.id
176
    formdata.store()
177

  
178
    testdef = TestDef.create_from_formdata(formdef, formdata)
179
    testdef.name = 'First test'
180
    testdef.store()
181

  
182
    app = login(get_app(pub))
183

  
184
    resp = app.get('/backoffice/forms/1/tests/')
185
    export_resp = resp.click('First test')
186

  
187
    resp = resp.click('remove')
188
    resp = resp.form.submit().follow()
189
    assert 'First test' not in resp.text
190

  
191
    resp = resp.click('Import')
192
    resp.form['file'] = Upload('export.json', export_resp.body, 'application/json')
193
    resp = resp.form.submit().follow()
194
    assert TestDef.count() == 1
195
    assert 'First test' in resp.text
196
    assert escape('Test "First test" has been successfully imported.') in resp.text
197

  
198
    imported_testdef = TestDef.select()[0]
199
    assert imported_testdef.export_to_json() == testdef.export_to_json()
200

  
201
    export_json = json.loads(export_resp.body)
202
    export_json['data']['fields']['1'] = 'b'
203

  
204
    resp = resp.click('Import')
205
    resp.form['file'] = Upload('export.json', json.dumps(export_json).encode(), 'application/json')
206
    resp = resp.form.submit().follow()
207
    assert TestDef.count() == 1
208
    assert 'First test' in resp.text
209
    assert escape('Test "First test" has been successfully imported.') in resp.text
210

  
211
    imported_testdef = TestDef.get(imported_testdef.id)
212
    assert imported_testdef.data['fields']['1'] == 'b'
213

  
214
    resp = resp.click('Import')
215
    resp.form['file'] = Upload('export.json', b'invalid', 'application/json')
216
    resp = resp.form.submit()
217
    assert 'Expecting value: line 1 column 1' in resp.text
tests/test_testdef.py
1
import datetime
2

  
3
import pytest
4

  
5
from wcs import fields
6
from wcs.formdef import FormDef
7
from wcs.qommon.http_request import HTTPRequest
8
from wcs.testdef import TestDef, TestError
9

  
10
from .utilities import clean_temporary_pub, create_temporary_pub
11

  
12

  
13
@pytest.fixture
14
def pub():
15
    pub = create_temporary_pub()
16

  
17
    req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'})
18
    pub.set_app_dir(req)
19
    pub.write_cfg()
20

  
21
    FormDef.wipe()
22
    return pub
23

  
24

  
25
def teardown_module(module):
26
    clean_temporary_pub()
27

  
28

  
29
def test_testdef_slug_generation(pub):
30
    TestDef.do_table()
31

  
32
    testdef = TestDef()
33
    testdef.name = 'test'
34
    testdef.object_type = 'formdef'
35
    testdef.object_id = '1'
36
    testdef.store()
37
    assert testdef.slug == 'test'
38

  
39
    testdef.slug = testdef.id = None
40
    testdef.store()
41
    assert testdef.slug == 'test-2'
42

  
43
    testdef.slug = testdef.id = None
44
    testdef.object_id = '2'
45
    testdef.store()
46
    assert testdef.slug == 'test'
47

  
48

  
49
def test_page_post_conditions(pub):
50
    formdef = FormDef()
51
    formdef.name = 'test title'
52
    formdef.fields = [
53
        fields.TitleField(id='0', label='Title', type='title'),
54
        fields.PageField(
55
            id='1',
56
            label='1st page',
57
            type='page',
58
            post_conditions=[
59
                {'condition': {'type': 'django', 'value': 'form_var_text == "a"'}, 'error_message': 'Error'}
60
            ],
61
        ),
62
        fields.StringField(id='2', label='Text', varname='text'),
63
        fields.PageField(
64
            id='3',
65
            label='2nd page',
66
            type='page',
67
            post_conditions=[
68
                {'condition': {'type': 'django', 'value': 'form_var_text2 == "a"'}, 'error_message': 'Error'}
69
            ],
70
        ),
71
        fields.StringField(id='4', label='Text', varname='text2'),
72
    ]
73
    formdef.store()
74

  
75
    formdata = formdef.data_class()()
76
    formdata.just_created()
77
    formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
78
    formdata.data['2'] = 'a'
79
    formdata.data['4'] = 'a'
80

  
81
    testdef = TestDef.create_from_formdata(formdef, formdata)
82
    testdef.run(formdef)
83

  
84
    formdata.data['4'] = 'b'
85
    testdef = TestDef.create_from_formdata(formdef, formdata)
86
    with pytest.raises(TestError) as excinfo:
87
        testdef.run(formdef)
88
    assert str(excinfo.value) == 'Page 2 post condition was not met (form_var_text2 == "a").'
89

  
90
    formdata.data['2'] = 'b'
91
    testdef = TestDef.create_from_formdata(formdef, formdata)
92
    with pytest.raises(TestError) as excinfo:
93
        testdef.run(formdef)
94
    assert str(excinfo.value) == 'Page 1 post condition was not met (form_var_text == "a").'
95

  
96

  
97
def test_page_post_condition_invalid(pub):
98
    formdef = FormDef()
99
    formdef.name = 'test title'
100
    formdef.fields = [
101
        fields.PageField(
102
            id='0',
103
            label='1st page',
104
            type='page',
105
            post_conditions=[{'condition': {'type': 'django', 'value': '{{}'}, 'error_message': 'Error'}],
106
        ),
107
    ]
108
    formdef.store()
109

  
110
    formdata = formdef.data_class()()
111
    formdata.just_created()
112
    formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
113

  
114
    testdef = TestDef.create_from_formdata(formdef, formdata)
115
    with pytest.raises(TestError) as excinfo:
116
        testdef.run(formdef)
117
    assert str(excinfo.value) == 'Failed to evaluate page 1 post condition.'
wcs/admin/forms.py
614 614
        'qrcode',
615 615
        'information',
616 616
        'inspect',
617
        'tests',
617 618
        ('public-url', 'public_url'),
618 619
        ('backoffice-submission-roles', 'backoffice_submission_roles'),
619 620
        ('logged-errors', 'logged_errors_dir'),
......
641 642
    inspect_template_name = 'wcs/backoffice/formdef-inspect.html'
642 643

  
643 644
    def __init__(self, component, instance=None):
645
        from .tests import TestsDirectory
646

  
644 647
        try:
645 648
            self.formdef = instance or self.formdef_class.get(component)
646 649
        except KeyError:
......
653 656
        self.role = WorkflowRoleDirectory(self.formdef)
654 657
        self.role.html_top = self.html_top
655 658
        self.options = self.options_directory_class(self.formdef, self.formdefui)
659
        self.tests = TestsDirectory(self.formdef)
656 660
        self.logged_errors_dir = LoggedErrorsDirectory(
657 661
            parent_dir=self, formdef_class=self.formdef_class, formdef_id=self.formdef.id
658 662
        )
......
938 942
            r += htmltext('<li><a rel="popup" href="history/save">%s</a></li>') % _('Save snapshot')
939 943
            r += htmltext('<li><a href="history/">%s</a></li>') % _('History')
940 944
        r += htmltext('<li><a href="inspect">%s</a></li>') % _('Inspector')
945
        if get_publisher().has_site_option('enable-tests'):
946
            r += htmltext('<li><a href="tests/">%s</a></li>') % _('Tests')
941 947
        r += htmltext('</ul>')
942 948

  
943 949
        r += htmltext('<ul>')
wcs/admin/tests.py
1
# w.c.s. - web application for online forms
2
# Copyright (C) 2005-2022  Entr'ouvert
3
#
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, see <http://www.gnu.org/licenses/>.
16

  
17
import json
18

  
19
from quixote import get_response, get_session, redirect
20
from quixote.directory import Directory
21
from quixote.html import TemplateIO, htmltext
22

  
23
from wcs.qommon import _, template
24
from wcs.qommon.backoffice.menu import html_top
25
from wcs.qommon.errors import TraversalError
26
from wcs.qommon.form import FileWidget, Form, SingleSelectWidget, StringWidget
27
from wcs.qommon.storage import Equal, StrictNotEqual
28
from wcs.testdef import TestDef, TestError
29

  
30

  
31
class TestPage(Directory):
32
    _q_exports = ['delete', 'export']
33

  
34
    def __init__(self, component):
35
        try:
36
            self.testdef = TestDef.get(component)
37
        except KeyError:
38
            raise TraversalError()
39

  
40
    def delete(self):
41
        form = Form(enctype='multipart/form-data')
42
        form.add_submit('delete', _('Submit'))
43
        form.add_submit('cancel', _('Cancel'))
44

  
45
        if form.get_widget('cancel').parse():
46
            return redirect('.')
47
        if not form.is_submitted() or form.has_errors():
48
            get_response().breadcrumb.append(('delete', _('Delete')))
49
            r = TemplateIO(html=True)
50
            r += htmltext('<h2>%s %s</h2>') % (_('Deleting Test:'), self.testdef)
51
            r += form.render()
52
            return r.getvalue()
53
        else:
54
            TestDef.remove_object(self.testdef.id)
55
            return redirect('..')
56

  
57
    def export(self):
58
        get_response().set_content_type('application/json')
59
        get_response().set_header(
60
            'content-disposition', 'attachment; filename=wcs_test_%s.json' % self.testdef.name
61
        )
62
        return json.dumps(self.testdef.export_to_json())
63

  
64

  
65
class TestsDirectory(Directory):
66
    _q_exports = ['', 'new', 'run', ('import', 'p_import')]
67
    section = 'tests'
68

  
69
    def __init__(self, objectdef):
70
        self.objectdef = objectdef
71

  
72
    def _q_traverse(self, path):
73
        get_response().breadcrumb.append(('tests', _('Tests')))
74
        html_top('tests', '%s - %s' % (self.objectdef.name, _('Tests')))
75
        return super()._q_traverse(path)
76

  
77
    def _q_lookup(self, component):
78
        return TestPage(component)
79

  
80
    def _q_index(self):
81
        context = {
82
            'testdefs': TestDef.select(
83
                [Equal('object_type', self.objectdef.get_table_name()), Equal('object_id', self.objectdef.id)]
84
            ),
85
            'formdata': self.objectdef.data_class().select([StrictNotEqual('status', 'draft')]),
86
        }
87
        return template.QommonTemplateResponse(templates=['wcs/backoffice/tests.html'], context=context)
88

  
89
    def new(self):
90
        form = Form(enctype='multipart/form-data')
91
        form.add(StringWidget, 'name', title=_('Name'), required=True, size=50)
92
        formadata_options = sorted(
93
            [
94
                (x.id, '%s - %s' % (x.id_display, x.user or _('Unknown User')))
95
                for x in self.objectdef.data_class().select([StrictNotEqual('status', 'draft')])
96
            ],
97
            key=lambda x: x[1],
98
        )
99
        form.add(SingleSelectWidget, 'formdata_id', title=_('Form'), required=True, options=formadata_options)
100
        form.add_submit('submit', _('Submit'))
101
        form.add_submit('cancel', _('Cancel'))
102

  
103
        if form.get_widget('cancel').parse():
104
            return redirect('.')
105

  
106
        if not form.is_submitted() or form.has_errors():
107
            get_response().breadcrumb.append(('new', _('New')))
108
            r = TemplateIO(html=True)
109
            r += htmltext('<h2>%s</h2>') % _('New test')
110
            r += form.render()
111
            return r.getvalue()
112
        else:
113
            formdata_id = form.get_widget('formdata_id').parse()
114
            formdata = self.objectdef.data_class().get(formdata_id)
115

  
116
            testdef = TestDef.create_from_formdata(self.objectdef, formdata)
117
            testdef.name = form.get_widget('name').parse()
118
            testdef.store()
119

  
120
        return redirect('.')
121

  
122
    def run(self):
123
        get_response().breadcrumb.append(('run', _('Run tests')))
124

  
125
        testdef = TestDef.select(
126
            [Equal('object_type', self.objectdef.get_table_name()), Equal('object_id', self.objectdef.id)]
127
        )
128
        for test in testdef:
129
            try:
130
                test.run(self.objectdef)
131
            except TestError as e:
132
                test.error = e
133

  
134
        return template.QommonTemplateResponse(
135
            templates=['wcs/backoffice/test_results.html'], context={'tests': testdef}
136
        )
137

  
138
    def p_import(self):
139
        form = Form(enctype='multipart/form-data')
140

  
141
        form.add(FileWidget, 'file', title=_('File'), required=True)
142
        form.add_submit('submit', _('Import Test'))
143
        form.add_submit('cancel', _('Cancel'))
144

  
145
        if form.get_submit() == 'cancel':
146
            return redirect('.')
147

  
148
        if form.is_submitted() and not form.has_errors():
149
            try:
150
                return self.import_submit(form)
151
            except ValueError:
152
                pass
153

  
154
        get_response().breadcrumb.append(('import', _('Import')))
155
        r = TemplateIO(html=True)
156
        r += htmltext('<h2>%s</h2>') % _('Import Test')
157
        r += htmltext('<p>%s</p>') % _(
158
            'You can add a new test or update an existing one by importing a JSON file.'
159
        )
160
        r += form.render()
161
        return r.getvalue()
162

  
163
    def import_submit(self, form):
164
        fp = form.get_widget('file').parse().fp
165

  
166
        try:
167
            testdef = TestDef.import_from_json(json.loads(fp.read()))
168
        except Exception as e:
169
            form.set_error('file', str(e))
170
            raise ValueError()
171

  
172
        get_session().message = ('info', _('Test "%s" has been successfully imported.') % testdef.name)
173
        return redirect('.')
wcs/fields.py
614 614
            self.in_listing = None
615 615
        return changed
616 616

  
617
    def evaluate_condition(self, dict_vars, formdef, condition):
617
    @staticmethod
618
    def evaluate_condition(dict_vars, formdef, condition):
618 619
        return PageCondition(condition, {'dict_vars': dict_vars, 'formdef': formdef}).evaluate()
619 620

  
620 621
    def is_visible(self, dict, formdef):
wcs/templates/wcs/backoffice/test_results.html
1
{% extends "wcs/backoffice/base.html" %}
2
{% load i18n %}
3

  
4
{% block appbar-title %}{% trans "Tests results" %}{% endblock %}
5

  
6
{% block content %}
7
  <div class="section">
8
    <ul>
9
      {% for test in tests %}
10
        <li>{{ test }}{% trans ":" %} {% firstof test.error _("Success!") %}</li>
11
      {% endfor %}
12
    </ul>
13
  </div>
14
{% endblock %}
wcs/templates/wcs/backoffice/tests.html
1
{% extends "wcs/backoffice/base.html" %}
2
{% load i18n %}
3

  
4
{% block appbar-title %}{% trans "Tests" %}{% endblock %}
5
{% block appbar-actions %}
6
  <a href="import" rel="popup">{% trans "Import" %}</a>
7
  {% if testdefs %}
8
    <a href="run">{% trans "Run tests" %}</a>
9
  {% endif %}
10
  {% if formdata %}
11
    <a href="new" rel="popup">{% trans "New" %}</a>
12
  {% endif %}
13
{% endblock %}
14

  
15
{% block content %}
16
  <div class="section">
17
    <h3>{% trans "Test form data" %}</h3>
18

  
19
    {% if not formdata %}
20
      <div class="infonotice">
21
        <p>{% trans "Tests cannot be created because there are no completed forms." %}</p>
22
      </div>
23
    {% endif %}
24

  
25
    {% if testdefs %}
26
      <ul class="objects-list single-links">
27
        {% for test in testdefs %}
28
          <li>
29
            <a download href="{{ test.id }}/export">{{ test }}</a>
30
            <a rel="popup" class="delete" href="{{ test.id }}/delete">{% trans "remove" %}</a>
31
          </li>
32
        {% endfor %}
33
      </ul>
34
    {% else %}
35
      <div><p>{% trans "There are no tests yet." %}<p></div>
36
    {% endif %}
37
  </div>
38
{% endblock %}
wcs/testdef.py
14 14
# You should have received a copy of the GNU General Public License
15 15
# along with this program; if not, see <http://www.gnu.org/licenses/>.
16 16

  
17
from quixote import get_publisher
18

  
17 19
from wcs import sql
18 20

  
21
from .qommon import _, misc
22
from .qommon.storage import Equal
23

  
24

  
25
class TestError(Exception):
26
    pass
27

  
19 28

  
20 29
class TestDef(sql.TestDef):
21 30
    _names = 'testdef'
......
26 35
    object_id = None
27 36

  
28 37
    data = None  # (json export of formdata, carddata, etc.)
38

  
39
    def __str__(self):
40
        return self.name
41

  
42
    def store(self, *args, comment=None, snapshot_store_user=True, **kwargs):
43
        if not self.slug:
44
            existing_slugs = {
45
                x.slug
46
                for x in self.select(
47
                    [Equal('object_type', self.object_type), Equal('object_id', self.object_id)]
48
                )
49
            }
50
            base_slug = misc.simplify(self.name)
51
            self.slug = base_slug
52
            i = 2
53
            while self.slug in existing_slugs:
54
                self.slug = '%s-%s' % (base_slug, i)
55
                i += 1
56
        super().store(*args, **kwargs)
57

  
58
    @classmethod
59
    def create_from_formdata(cls, formdef, formdata):
60
        testdef = cls()
61
        testdef.object_type = formdef.get_table_name()
62
        testdef.object_id = formdef.id
63
        testdef.data = {
64
            'fields': formdata.data,
65
            'user': formdata.user.get_json_export_dict() if formdata.user else None,
66
        }
67
        return testdef
68

  
69
    def run(self, objectdef):
70
        formdata = objectdef.data_class()()
71
        if self.data['user']:
72
            formdata.set_user_from_json(self.data['user'])
73

  
74
        get_publisher().substitutions.reset()
75
        get_publisher().substitutions.feed(get_publisher())
76
        get_publisher().substitutions.feed(objectdef)
77
        get_publisher().substitutions.feed(formdata)
78

  
79
        page_no = 0
80
        page_post_conditions = []
81
        for field in objectdef.fields:
82
            if field.type == 'page':
83
                if page_post_conditions:
84
                    self.evaluate_page_conditions(page_no, formdata, objectdef, page_post_conditions)
85

  
86
                page_post_conditions = field.post_conditions or []
87
                page_no += 1
88
                continue
89

  
90
            if field.type in ('subtitle', 'title', 'comment', 'computed'):
91
                continue
92

  
93
            formdata.data[field.id] = self.data['fields'].get(field.id)
94
            get_publisher().substitutions.invalidate_cache()
95

  
96
        # evaluate last page post condition
97
        self.evaluate_page_conditions(page_no, formdata, objectdef, page_post_conditions)
98

  
99
    def evaluate_page_conditions(self, page_no, form_data, objectdef, conditions):
100
        from wcs.fields import Field
101

  
102
        for post_condition in conditions:
103
            condition = post_condition.get('condition', {})
104
            try:
105
                if not Field.evaluate_condition(form_data.data, objectdef, condition):
106
                    raise TestError(
107
                        _('Page %(no)s post condition was not met (%(condition)s).')
108
                        % {'no': page_no, 'condition': condition.get('value')}
109
                    )
110
            except RuntimeError:
111
                raise TestError(_('Failed to evaluate page %s post condition.') % page_no)
112

  
113
    def export_to_json(self):
114
        return {
115
            'name': self.name,
116
            'slug': self.slug,
117
            'object_type': self.object_type,
118
            'object_id': self.object_id,
119
            'data': self.data,
120
        }
121

  
122
    @classmethod
123
    def import_from_json(cls, data):
124
        testdefs = TestDef.select(
125
            [
126
                Equal('object_type', data['object_type']),
127
                Equal('object_id', data['object_id']),
128
                Equal('slug', data['slug']),
129
            ]
130
        )
131

  
132
        testdef = testdefs[0] if testdefs else TestDef()
133

  
134
        for k, v in data.items():
135
            setattr(testdef, k, v)
136

  
137
        testdef.store()
138
        return testdef
29
-