Projet

Général

Profil

0001-formdef-add-internal-identifier-attribute-separated-.patch

Frédéric Péters, 20 juillet 2017 12:54

Télécharger (16 ko)

Voir les différences:

Subject: [PATCH 1/2] formdef: add internal identifier attribute, separated
 from url_name (#15663)

This will allow changing url name behaviour without impacting on storage
matters.
 tests/test_admin_pages.py | 18 ++++++++-----
 tests/test_formdef.py     | 66 +++++++++++++++++++++++++++++++++--------------
 tests/test_sql.py         |  2 +-
 wcs/admin/forms.py        |  5 ++--
 wcs/formdef.py            | 58 +++++++++++++++++++++++++++++++----------
 wcs/sql.py                |  2 +-
 6 files changed, 106 insertions(+), 45 deletions(-)
tests/test_admin_pages.py
328 328
    assert resp.location == 'http://example.net/backoffice/forms/1/'
329 329
    resp = resp.follow()
330 330
    assert FormDef.get(1).name == 'new title'
331
    assert FormDef.get(1).url_name == 'new-title'
331
    assert FormDef.get(1).url_name == 'form-title'
332
    assert FormDef.get(1).internal_identifier == 'new-title'
332 333

  
333 334
def test_form_category(pub):
334 335
    create_superuser(pub)
......
745 746
    resp = resp.forms[0].submit()
746 747
    assert FormDef.count() == 1
747 748

  
748
    # import the same formdef a second time, make sure the urlname is not
749
    # reused
749
    # import the same formdef a second time, make sure url name and internal
750
    # identifier are not reused
750 751
    resp = app.get('/backoffice/forms/')
751 752
    resp = resp.click(href='import')
752 753
    resp.forms[0]['file'] = Upload('formdef.wcs', formdef_xml)
......
754 755
    assert FormDef.count() == 2
755 756
    assert FormDef.get(1).url_name == 'form-title'
756 757
    assert FormDef.get(2).url_name == 'form-title-1'
758
    assert FormDef.get(1).internal_identifier == 'form-title'
759
    assert FormDef.get(2).internal_identifier == 'form-title-1'
757 760

  
758
    # import a formdef with an url_name that doesn't match its title,
759
    # it should be updated to match.
761
    # import a formdef with an url name that doesn't match its title,
762
    # it should be kept intact.
760 763
    formdef.url_name = 'xxx-other-form-title'
761 764
    formdef_xml = ET.tostring(formdef.export_to_xml(include_id=True))
762 765

  
......
764 767
    resp = resp.click(href='import')
765 768
    resp.forms[0]['file'] = Upload('formdef.wcs', formdef_xml)
766 769
    resp = resp.forms[0].submit()
767
    assert FormDef.get(3).url_name == 'form-title-2'
770
    assert FormDef.get(3).url_name == 'xxx-other-form-title'
771
    assert FormDef.get(3).internal_identifier == 'form-title-2'
768 772

  
769 773
    # import an invalid file
770 774
    resp = app.get('/backoffice/forms/')
......
1418 1422

  
1419 1423
    app = login(get_app(pub))
1420 1424
    resp = app.get('/backoffice/forms/%s/' % formdef_id)
1421
    resp = resp.click(href='overwrite')
1425
    resp = resp.click(href='overwrite', index=0)
1422 1426
    resp.forms[0]['file'] = Upload('formdef.wcs', formdef_xml)
1423 1427
    resp = resp.forms[0].submit()
1424 1428
    assert 'The form removes and changes fields' in resp.body
tests/test_formdef.py
1
import cPickle
1 2
import datetime
2 3
import json
3 4
import sys
......
9 10
from quixote import cleanup
10 11
from wcs import formdef
11 12
from wcs.formdef import FormDef
13
from wcs.qommon.http_request import HTTPRequest
12 14
from wcs.workflows import Workflow
13 15
from wcs.fields import StringField, FileField, DateField, ItemField
14 16

  
15
from utilities import create_temporary_pub
17
from utilities import create_temporary_pub, clean_temporary_pub
16 18

  
17
def setup_module(module):
18
    cleanup()
19
def pytest_generate_tests(metafunc):
20
    if 'pub' in metafunc.fixturenames:
21
        metafunc.parametrize('pub', ['pickle', 'sql'], indirect=True)
19 22

  
20
    global pub
21

  
22
    pub = create_temporary_pub()
23
@pytest.fixture
24
def pub(request):
25
    pub = create_temporary_pub(sql_mode=(request.param == 'sql'))
26
    req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'})
27
    pub.set_app_dir(req)
23 28
    pub.cfg['language'] = {'language': 'en'}
29
    pub.write_cfg()
30

  
31
    return pub
24 32

  
25 33
def teardown_module(module):
26
    shutil.rmtree(pub.APP_DIR)
34
    clean_temporary_pub()
27 35

  
28 36

  
29
def test_is_disabled():
37
def test_is_disabled(pub):
30 38
    formdef = FormDef()
31 39
    assert not formdef.is_disabled()
32 40

  
......
34 42
    assert formdef.is_disabled()
35 43

  
36 44

  
37
def test_is_disabled_publication_date():
45
def test_is_disabled_publication_date(pub):
38 46
    formdef = FormDef()
39 47

  
40 48
    formdef.publication_date = '%s-%02d-%02d' % (datetime.datetime.today() - datetime.timedelta(1)).timetuple()[:3]
......
44 52
    assert formdef.is_disabled()
45 53

  
46 54

  
47
def test_is_disabled_expiration_date():
55
def test_is_disabled_expiration_date(pub):
48 56
    formdef = FormDef()
49 57

  
50 58
    formdef.expiration_date = '%s-%02d-%02d' % (datetime.datetime.today() - datetime.timedelta(1)).timetuple()[:3]
......
54 62
    assert not formdef.is_disabled()
55 63

  
56 64

  
57
def test_is_disabled_publication_datetime():
65
def test_is_disabled_publication_datetime(pub):
58 66
    formdef = FormDef()
59 67

  
60 68
    formdef.publication_date = '%s-%02d-%02d %02d:%02d' % (
......
66 74
    assert formdef.is_disabled()
67 75

  
68 76

  
69
def test_is_disabled_expiration_datetime():
77
def test_is_disabled_expiration_datetime(pub):
70 78
    formdef = FormDef()
71 79

  
72 80
    formdef.expiration_date = '%s-%02d-%02d %02d:%02d' % (
......
77 85
            datetime.datetime.now() + datetime.timedelta(hours=1)).timetuple()[:5]
78 86
    assert not formdef.is_disabled()
79 87

  
80
def test_title_change():
88
def test_title_change(pub):
81 89
    formdef = FormDef()
82 90
    formdef.name = 'foo'
83 91
    formdef.store()
84 92
    assert FormDef.get(formdef.id).name == 'foo'
85 93
    assert FormDef.get(formdef.id).url_name == 'foo'
94
    assert FormDef.get(formdef.id).internal_identifier == 'foo'
86 95

  
87 96
    formdef.name = 'bar'
88 97
    formdef.store()
89 98
    assert FormDef.get(formdef.id).name == 'bar'
90
    assert FormDef.get(formdef.id).url_name == 'bar'
99
    assert FormDef.get(formdef.id).url_name == 'foo'
100
    assert FormDef.get(formdef.id).internal_identifier == 'bar'
91 101

  
92
    # makes sure the url_name doesn't change if there are submitted forms
102
    # makes sure the internal_name doesn't change if there are submitted forms
93 103
    formdef.data_class()().store()
94 104
    formdef.name = 'baz'
95 105
    formdef.store()
96 106
    assert FormDef.get(formdef.id).name == 'baz'
97
    assert FormDef.get(formdef.id).url_name == 'bar' # didn't change
107
    assert FormDef.get(formdef.id).internal_identifier == 'bar' # didn't change
98 108

  
99
def test_substitution_variables():
109
def test_substitution_variables(pub):
100 110
    formdef = FormDef()
101 111
    formdef.name = 'foo'
102 112
    formdef.store()
......
121 131
    assert formdef.get_substitution_variables()['form_option_bar'] == 'Bar'
122 132
    assert formdef.get_substitution_variables()['form_option_bar_raw'] == 'bar'
123 133

  
124
def test_schema_with_date_variable():
134
def test_schema_with_date_variable(pub):
125 135
    FormDef.wipe()
126 136
    formdef = FormDef()
127 137
    formdef.name = 'foo'
......
137 147
    formdef.workflow_options = {'foo':  time.gmtime(time.mktime((2016, 4, 2, 0, 0, 0, 0, 0, 0)))}
138 148
    assert json.loads(formdef.export_to_json())['options']['foo'].startswith('2016-04')
139 149

  
140
def test_substitution_variables_object():
150
def test_substitution_variables_object(pub):
141 151
    formdef = FormDef()
142 152
    formdef.name = 'foo'
143 153
    formdef.store()
......
157 167
    with pytest.raises(AttributeError):
158 168
        assert substs.foobar
159 169

  
160
def test_file_field_migration():
170
def test_file_field_migration(pub):
161 171
    pub.cfg['filetypes'] = {1:
162 172
            {'mimetypes': [
163 173
                'application/pdf',
......
185 195
    assert formdef.fields[0].document_type['mimetypes'] == ['image/*', 'application/pdf,application/vnd.oasis.opendocument.text,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.oasis.opendocument.spreadsheet,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet']
186 196
    assert formdef.fields[1].document_type['label'] == 'Image files'
187 197
    assert formdef.fields[0].document_type['label'] == 'Image files, Documents'
198

  
199
def test_internal_identifier_migration(pub):
200
    FormDef.wipe()
201
    formdef = FormDef()
202
    formdef.name = 'foo'
203
    formdef.fields = []
204
    formdef.store()
205

  
206
    obj = cPickle.load(open(formdef.get_object_filename()))
207
    del obj.internal_identifier
208
    cPickle.dump(obj, open(formdef.get_object_filename(), 'w'))
209
    assert cPickle.load(open(formdef.get_object_filename())).internal_identifier is None
210
    assert FormDef.get(formdef.id, ignore_migration=True).internal_identifier is None
211

  
212
    formdef = FormDef.get(formdef.id)
213
    assert formdef.internal_identifier == 'foo'
tests/test_sql.py
569 569

  
570 570
    formdef.name = 'tests2'
571 571
    formdef.store()
572
    assert formdef.url_name == 'tests2'
572
    assert formdef.url_name == 'tests'
573 573

  
574 574
    formdef.name = 'tests'
575 575
    formdef.store()
wcs/admin/forms.py
949 949
        return redirect('.')
950 950

  
951 951
    def overwrite_by_formdef(self, new_formdef):
952
        # keep current formdef id, url_name and sql table name
952
        # keep current formdef id, url_name, internal identifier and sql table name
953 953
        new_formdef.id = self.formdef.id
954
        new_formdef.internal_identifier = self.formdef.internal_identifier
954 955
        new_formdef.url_name = self.formdef.url_name
955 956
        new_formdef.table_name = self.formdef.table_name
956 957
        # keep currently assigned category and workflow
......
1552 1553
                form.set_error('file', msg)
1553 1554
            raise ValueError()
1554 1555

  
1555
        formdef.url_name = None # a new one will be set in .store()
1556
        formdef.internal_identifier = None # a new one will be set in .store()
1556 1557
        formdef.disabled = True
1557 1558
        formdef.store()
1558 1559
        get_session().message = ('info',
wcs/formdef.py
69 69
    description = None
70 70
    keywords = None
71 71
    url_name = None
72
    internal_identifier = None # mostly for pickle
72 73
    table_name = None # for SQL only
73 74
    fields = None
74 75
    category_id = None
......
101 102

  
102 103
    # declarations for serialization
103 104
    TEXT_ATTRIBUTES = ['name', 'url_name', 'description', 'keywords',
104
            'publication_date', 'expiration_date',
105
            'publication_date', 'expiration_date', 'internal_identifier',
105 106
            'disabled_redirection',]
106 107
    BOOLEAN_ATTRIBUTES = ['discussion', 'detailed_emails', 'disabled',
107 108
            'only_allow_one', 'enable_tracking_codes', 'confirmation',
......
204 205
                    changed = True
205 206
                    break
206 207

  
208
        if not self.internal_identifier:
209
            self.internal_identifier = self.url_name
210
            changed = True
211

  
207 212
        for f in self.fields or []:
208 213
            changed |= f.migrate()
209 214

  
......
238 243
            actions = sql.do_formdef_tables(self)
239 244
        else:
240 245
            cls = new.classobj(self.url_name.title(), (FormData,),
241
                        {'_names': 'form-%s' % self.url_name,
246
                        {'_names': 'form-%s' % self.internal_identifier,
242 247
                         '_formdef': self})
243 248
            actions = []
244 249
        setattr(sys.modules['formdef'], self.url_name.title(), cls)
......
264 269
        suffix_no = 0
265 270
        while True:
266 271
            try:
267
                formdef = self.get_by_urlname(new_url_name, ignore_migration=True)
272
                obj = self.get_on_index(new_url_name, 'url_name', ignore_migration=True)
268 273
            except KeyError:
269 274
                break
270
            if formdef.id == self.id:
275
            if obj.id == self.id:
271 276
                break
272 277
            suffix_no += 1
273 278
            new_url_name = '%s-%s' % (base_new_url_name, suffix_no)
274 279
        return new_url_name
275 280

  
281
    def get_new_internal_identifier(self):
282
        new_internal_identifier = simplify(self.name)
283
        base_new_internal_identifier = new_internal_identifier
284
        suffix_no = 0
285
        while True:
286
            try:
287
                formdef = self.get_by_urlname(new_internal_identifier, ignore_migration=True)
288
            except KeyError:
289
                break
290
            if formdef.id == self.id:
291
                break
292
            suffix_no += 1
293
            new_internal_identifier = '%s-%s' % (base_new_internal_identifier, suffix_no)
294
        return new_internal_identifier
295

  
276 296
    @classmethod
277 297
    def get_new_id(cls, create=False):
278 298
        keys = cls.keys()
......
303 323
            sql.formdef_wipe()
304 324

  
305 325
    def store(self):
306
        new_url_name = self.get_new_url_name()
307
        if not self.url_name:
308
            self.url_name = new_url_name
309
        if new_url_name != self.url_name:
310
            # title changed, url will be changed only if the formdef is
311
            # currently being imported (self.id is None) or if there are not
312
            # yet any submitted forms
313
            data_class = self.data_class()
314
            if self.id is None or data_class().count() == 0:
315
                self.url_name = new_url_name
326
        if self.url_name is None:
327
            # set url name if it's not yet there
328
            self.url_name = self.get_new_url_name()
329
        new_internal_identifier = self.get_new_internal_identifier()
330
        if not self.internal_identifier:
331
            self.internal_identifier = new_internal_identifier
332
        if new_internal_identifier != self.internal_identifier:
333
            # title changed, internal identifier will be changed only if
334
            # the formdef is currently being imported (self.id is None)
335
            # or if there are not yet any submitted forms
336
            if self.id is None or self.data_class().count() == 0:
337
                self.internal_identifier = new_internal_identifier
316 338
        self.last_modification_time = time.localtime()
317 339
        if get_request() and get_request().user:
318 340
            self.last_modification_user_id = str(get_request().user.id)
......
846 868
        formdef = cls.import_from_xml_tree(tree, charset=charset,
847 869
                include_id=include_id)
848 870

  
871
        if formdef.url_name:
872
            try:
873
                obj = cls.get_on_index(formdef.url_name, 'url_name', ignore_migration=True)
874
            except KeyError:
875
                pass
876
            else:
877
                formdef.url_name = formdef.get_new_url_name()
878

  
849 879
        # fix max_field_id if necessary
850 880
        if formdef.max_field_id is not None:
851 881
            max_field_id = max([lax_int(x.id) for x in formdef.fields])
wcs/sql.py
762 762
def do_global_views(conn, cur):
763 763
    # recreate global views
764 764
    from wcs.formdef import FormDef
765
    view_names = [get_formdef_view_name(x) for x in FormDef.select()]
765
    view_names = [get_formdef_view_name(x) for x in FormDef.select(ignore_migration=True)]
766 766

  
767 767
    cur.execute('''SELECT table_name FROM information_schema.views
768 768
                    WHERE table_schema = 'public'
769
-