yafowil.widget.array 2.0.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2203 @@
1
+ from node.utils import UNSET
2
+ from odict import odict
3
+ from yafowil.base import ExtractionError
4
+ from yafowil.base import factory
5
+ from yafowil.compat import IS_PY2
6
+ from yafowil.tests import fxml
7
+ from yafowil.tests import YafowilTestCase
8
+ import os
9
+
10
+
11
+ if not IS_PY2:
12
+ from importlib import reload
13
+
14
+
15
+ def np(path):
16
+ return path.replace('/', os.path.sep)
17
+
18
+
19
+ class TestArrayWidget(YafowilTestCase):
20
+
21
+ def setUp(self):
22
+ super(TestArrayWidget, self).setUp()
23
+ from yafowil.widget import array
24
+ from yafowil.widget.array import widget
25
+ reload(widget)
26
+ array.register()
27
+
28
+ def test_array_with_missing_entry_definition(self):
29
+ form = factory(
30
+ 'form',
31
+ name='myform',
32
+ props={'action': 'myaction'})
33
+ form['myarray'] = factory(
34
+ 'array',
35
+ props={'label': 'My Array'})
36
+ with self.assertRaises(Exception) as arc:
37
+ form()
38
+ msg = 'Empty array widget defined'
39
+ self.assertEqual(str(arc.exception), msg)
40
+
41
+ def test_empty_array(self):
42
+ # Create empty array widget
43
+ form = factory(
44
+ 'form',
45
+ name='myform',
46
+ props={'action': 'myaction'})
47
+ form['myarray'] = factory(
48
+ 'array',
49
+ props={'label': 'My Array'})
50
+ form['myarray']['myfield'] = factory(
51
+ 'field:label:text',
52
+ props={'label': 'My Field'})
53
+
54
+ self.assertEqual(form.treerepr().split('\n'), [
55
+ "<class 'yafowil.base.Widget'>: myform",
56
+ " <class 'yafowil.base.Widget'>: myarray",
57
+ " <class 'yafowil.base.Widget'>: table",
58
+ " <class 'yafowil.base.Widget'>: head",
59
+ " <class 'yafowil.base.Widget'>: row",
60
+ " <class 'yafowil.base.Widget'>: label",
61
+ " <class 'yafowil.base.Widget'>: actions",
62
+ " <class 'yafowil.base.Widget'>: body",
63
+ " <class 'yafowil.base.Widget'>: myfield",
64
+ ""
65
+ ])
66
+
67
+ self.checkOutput("""
68
+ <form action="myaction" enctype="multipart/form-data"
69
+ id="form-myform" method="post" novalidate="novalidate">
70
+ <div class="array card array-add array-remove array-sort"
71
+ id="array-myform-myarray">
72
+ <table>
73
+ <thead>
74
+ <tr>
75
+ <th>My Array</th>
76
+ <th class="head_actions">
77
+ <div class="array_actions">
78
+ <a class="array_row_add" href="#">
79
+ <span class="icon-plus-sign"> </span>
80
+ </a>
81
+ </div>
82
+ </th>
83
+ </tr>
84
+ </thead>
85
+ <tbody/>
86
+ </table>
87
+ <div class="arraytemplate">
88
+ <div class="field" id="field-myform-myarray-TEMPLATE">
89
+ <label for="input-myform-myarray-TEMPLATE">My Field</label>
90
+ <input class="text" id="input-myform-myarray-TEMPLATE"
91
+ name="myform.myarray.TEMPLATE" type="text" value=""/>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ </form>
96
+ """, fxml(form()))
97
+
98
+ def test_empty_array_with_add_action_disabled(self):
99
+ form = factory(
100
+ 'form',
101
+ name='myform',
102
+ props={'action': 'myaction'})
103
+ form['myarray'] = factory(
104
+ 'array',
105
+ props={
106
+ 'label': 'My Array',
107
+ 'add': False,
108
+ })
109
+ form['myarray']['myfield'] = factory(
110
+ 'field:label:text',
111
+ props={'label': 'My Field'})
112
+
113
+ self.assertEqual(form.treerepr().split('\n'), [
114
+ "<class 'yafowil.base.Widget'>: myform",
115
+ " <class 'yafowil.base.Widget'>: myarray",
116
+ " <class 'yafowil.base.Widget'>: table",
117
+ " <class 'yafowil.base.Widget'>: head",
118
+ " <class 'yafowil.base.Widget'>: row",
119
+ " <class 'yafowil.base.Widget'>: label",
120
+ " <class 'yafowil.base.Widget'>: actions",
121
+ " <class 'yafowil.base.Widget'>: body",
122
+ " <class 'yafowil.base.Widget'>: myfield",
123
+ ""
124
+ ])
125
+
126
+ self.checkOutput("""
127
+ <form action="myaction" enctype="multipart/form-data" id="form-myform"
128
+ method="post" novalidate="novalidate">
129
+ <div class="array card array-remove array-sort" id="array-myform-myarray">
130
+ <table>
131
+ <thead>
132
+ <tr>
133
+ <th>My Array</th>
134
+ <th class="head_actions">
135
+ <div class="array_actions"/>
136
+ </th>
137
+ </tr>
138
+ </thead>
139
+ <tbody/>
140
+ </table>
141
+ </div>
142
+ </form>
143
+ """, fxml(form()))
144
+
145
+ def test_array_with_overwritten_class(self):
146
+ # Create array widget with overwritten class property. If CSS class
147
+ # 'array' missing, it gets added transparently
148
+ form = factory(
149
+ 'form',
150
+ name='myform',
151
+ props={'action': 'myaction'})
152
+ form['myarray'] = factory(
153
+ 'array',
154
+ props={
155
+ 'label': 'My Array',
156
+ 'class': 'specialclass',
157
+ })
158
+ form['myarray']['myfield'] = factory(
159
+ 'field:label:text',
160
+ props={'label': 'My Field'})
161
+ self.checkOutput("""
162
+ <form action="myaction" enctype="multipart/form-data" id="form-myform"
163
+ method="post" novalidate="novalidate">
164
+ <div class="array specialclass array-add array-remove array-sort"
165
+ id="array-myform-myarray">
166
+ <table>
167
+ ...
168
+ </table>
169
+ <div class="arraytemplate">
170
+ ...
171
+ </div>
172
+ </div>
173
+ </form>
174
+ """, fxml(form()))
175
+
176
+ def test_display_mode(self):
177
+ # Display mode is not implemented yet
178
+ form = factory(
179
+ 'form',
180
+ name='myform',
181
+ props={'action': 'myaction'})
182
+ form['myarray'] = factory(
183
+ 'array',
184
+ value=['1', '2'],
185
+ props={
186
+ 'label': 'My Array',
187
+ 'class': 'specialclass',
188
+ },
189
+ mode='display')
190
+ form['myarray']['myfield'] = factory(
191
+ 'field:label:text',
192
+ props={'label': 'My Field'})
193
+ self.checkOutput("""
194
+ <form action="myaction" enctype="multipart/form-data" id="form-myform"
195
+ method="post" novalidate="novalidate">
196
+ <table>
197
+ <thead>
198
+ <tr>
199
+ <th>My Array</th>
200
+ </tr>
201
+ </thead>
202
+ <tbody>
203
+ <tr>
204
+ <td class="widget">
205
+ <div class="field" id="field-myform-myarray-0">
206
+ <label>My Field</label>
207
+ <div class="display-text" id="display-myform-myarray-0">1</div>
208
+ </div>
209
+ </td>
210
+ </tr>
211
+ <tr>
212
+ <td class="widget">
213
+ <div class="field" id="field-myform-myarray-1">
214
+ <label>My Field</label>
215
+ <div class="display-text" id="display-myform-myarray-1">2</div>
216
+ </div>
217
+ </td>
218
+ </tr>
219
+ </tbody>
220
+ </table>
221
+ </form>
222
+ """, fxml(form()))
223
+
224
+ def test_empty_static_array(self):
225
+ form = factory(
226
+ 'form',
227
+ name='myform',
228
+ props={'action': 'myaction'})
229
+ form['myarray'] = factory(
230
+ 'array',
231
+ props={
232
+ 'label': 'My Array',
233
+ 'static': True,
234
+ })
235
+ form['myarray']['myfield'] = factory(
236
+ 'field:label:text',
237
+ props={'label': 'My Field'})
238
+
239
+ self.assertEqual(form.treerepr().split('\n'), [
240
+ "<class 'yafowil.base.Widget'>: myform",
241
+ " <class 'yafowil.base.Widget'>: myarray",
242
+ " <class 'yafowil.base.Widget'>: table",
243
+ " <class 'yafowil.base.Widget'>: head",
244
+ " <class 'yafowil.base.Widget'>: row",
245
+ " <class 'yafowil.base.Widget'>: label",
246
+ " <class 'yafowil.base.Widget'>: body",
247
+ " <class 'yafowil.base.Widget'>: myfield",
248
+ ""
249
+ ])
250
+
251
+ self.checkOutput("""
252
+ <form action="myaction" enctype="multipart/form-data" id="form-myform"
253
+ method="post" novalidate="novalidate">
254
+ <div class="array card array-static" id="array-myform-myarray">
255
+ <table>
256
+ <thead>
257
+ <tr>
258
+ <th>My Array</th>
259
+ </tr>
260
+ </thead>
261
+ <tbody/>
262
+ </table>
263
+ </div>
264
+ </form>
265
+ """, fxml(form()))
266
+
267
+ def test_empty_array_with_compound_as_template(self):
268
+ # Create empty array widget with compound as template widget. If
269
+ # compound is used as array template, this must not be structural
270
+ form = factory(
271
+ 'form',
272
+ name='myform',
273
+ props={'action': 'myaction'})
274
+ form['myarray'] = factory(
275
+ 'array',
276
+ props={'label': 'My Compound Array'})
277
+ form['myarray']['mycompound'] = factory(
278
+ 'compound',
279
+ props={'structural': True})
280
+
281
+ with self.assertRaises(Exception) as arc:
282
+ form()
283
+ msg = 'Compound templates for arrays must not be structural.'
284
+ self.assertEqual(str(arc.exception), msg)
285
+
286
+ # Now with valid compound template
287
+ form['myarray'] = factory(
288
+ 'array',
289
+ props={'label': 'My Compound Array'})
290
+ form['myarray']['mycompound'] = factory('compound')
291
+ form['myarray']['mycompound']['f1'] = factory(
292
+ 'field:label:text',
293
+ props={'label': 'F1'})
294
+ form['myarray']['mycompound']['f2'] = factory(
295
+ 'field:label:text',
296
+ props={'label': 'F2'})
297
+
298
+ self.assertEqual(form.treerepr().split('\n'), [
299
+ "<class 'yafowil.base.Widget'>: myform",
300
+ " <class 'yafowil.base.Widget'>: myarray",
301
+ " <class 'yafowil.base.Widget'>: table",
302
+ " <class 'yafowil.base.Widget'>: head",
303
+ " <class 'yafowil.base.Widget'>: row",
304
+ " <class 'yafowil.base.Widget'>: label",
305
+ " <class 'yafowil.base.Widget'>: actions",
306
+ " <class 'yafowil.base.Widget'>: body",
307
+ " <class 'yafowil.base.Widget'>: mycompound",
308
+ " <class 'yafowil.base.Widget'>: f1",
309
+ " <class 'yafowil.base.Widget'>: f2",
310
+ ""
311
+ ])
312
+
313
+ self.checkOutput("""
314
+ <form action="myaction" enctype="multipart/form-data" id="form-myform"
315
+ method="post" novalidate="novalidate">
316
+ <div class="array card array-add array-remove array-sort"
317
+ id="array-myform-myarray">
318
+ <table>
319
+ ...
320
+ </table>
321
+ <div class="arraytemplate">
322
+ <div class="field" id="field-myform-myarray-TEMPLATE-f1">
323
+ <label for="input-myform-myarray-TEMPLATE-f1">F1</label>
324
+ <input class="text" id="input-myform-myarray-TEMPLATE-f1"
325
+ name="myform.myarray.TEMPLATE.f1" type="text" value=""/>
326
+ </div>
327
+ <div class="field" id="field-myform-myarray-TEMPLATE-f2">
328
+ <label for="input-myform-myarray-TEMPLATE-f2">F2</label>
329
+ <input class="text" id="input-myform-myarray-TEMPLATE-f2"
330
+ name="myform.myarray.TEMPLATE.f2" type="text" value=""/>
331
+ </div>
332
+ </div>
333
+ </div>
334
+ </form>
335
+ """, fxml(form()))
336
+
337
+ def test_empty_array_with_array_as_template(self):
338
+ form = factory(
339
+ 'form',
340
+ name='myform',
341
+ props={'action': 'myaction'})
342
+ form['myarrayarray'] = factory(
343
+ 'array',
344
+ props={'label': 'My Array Array'})
345
+ form['myarrayarray']['myarray'] = factory(
346
+ 'array',
347
+ props={'label': 'My Array'})
348
+ form['myarrayarray']['myarray']['myfield'] = factory(
349
+ 'field:label:text',
350
+ props={'label': 'My Field'})
351
+
352
+ self.assertEqual(form.treerepr().split('\n'), [
353
+ "<class 'yafowil.base.Widget'>: myform",
354
+ " <class 'yafowil.base.Widget'>: myarrayarray",
355
+ " <class 'yafowil.base.Widget'>: table",
356
+ " <class 'yafowil.base.Widget'>: head",
357
+ " <class 'yafowil.base.Widget'>: row",
358
+ " <class 'yafowil.base.Widget'>: label",
359
+ " <class 'yafowil.base.Widget'>: actions",
360
+ " <class 'yafowil.base.Widget'>: body",
361
+ " <class 'yafowil.base.Widget'>: myarray",
362
+ " <class 'yafowil.base.Widget'>: table",
363
+ " <class 'yafowil.base.Widget'>: head",
364
+ " <class 'yafowil.base.Widget'>: row",
365
+ " <class 'yafowil.base.Widget'>: label",
366
+ " <class 'yafowil.base.Widget'>: actions",
367
+ " <class 'yafowil.base.Widget'>: body",
368
+ " <class 'yafowil.base.Widget'>: myfield",
369
+ ""
370
+ ])
371
+
372
+ self.checkOutput("""
373
+ <form action="myaction" enctype="multipart/form-data" id="form-myform"
374
+ method="post" novalidate="novalidate">
375
+ <div class="array card array-add array-remove array-sort"
376
+ id="array-myform-myarrayarray">
377
+ <table>
378
+ ...
379
+ </table>
380
+ <div class="arraytemplate">
381
+ <div class="array card array-add array-remove array-sort"
382
+ id="array-myform-myarrayarray-TEMPLATE">
383
+ <table>
384
+ ...
385
+ </table>
386
+ <div class="arraytemplate">
387
+ <div class="field"
388
+ id="field-myform-myarrayarray-TEMPLATE-TEMPLATE">
389
+ <label for="input-myform-myarrayarray-TEMPLATE-TEMPLATE">My Field</label>
390
+ <input class="text" id="input-myform-myarrayarray-TEMPLATE-TEMPLATE"
391
+ name="myform.myarrayarray.TEMPLATE.TEMPLATE"
392
+ type="text" value=""/>
393
+ </div>
394
+ </div>
395
+ </div>
396
+ </div>
397
+ </div>
398
+ </form>
399
+ """, fxml(form()))
400
+
401
+ def test_array_with_invalid_preset_value(self):
402
+ form = factory(
403
+ 'form',
404
+ name='myform',
405
+ props={'action': 'myaction'})
406
+ form['myarray'] = factory(
407
+ 'array',
408
+ value=object(),
409
+ props={'label': 'My Array'})
410
+ form['myarray']['myfield'] = factory(
411
+ 'field:label:text',
412
+ props={'label': 'My Field'})
413
+
414
+ with self.assertRaises(ValueError) as arc:
415
+ form()
416
+ msg = (
417
+ "Expected list or dict as value. Got '<type 'object'>'"
418
+ if IS_PY2
419
+ else "Expected list or dict as value. Got '<class 'object'>'"
420
+ )
421
+ self.assertEqual(str(arc.exception), msg)
422
+
423
+ def test_array_with_preset_values_disable_add_action(self):
424
+ # Value as list. Disable ``add``
425
+ form = factory(
426
+ 'form',
427
+ name='myform',
428
+ props={'action': 'myaction'})
429
+ form['myarray'] = factory(
430
+ 'array',
431
+ value=['1', '2'],
432
+ props={
433
+ 'label': 'My Array',
434
+ 'add': False,
435
+ })
436
+ form['myarray']['myfield'] = factory(
437
+ 'field:label:text',
438
+ props={'label': 'My Field'})
439
+
440
+ self.checkOutput("""
441
+ <form action="myaction" enctype="multipart/form-data" id="form-myform"
442
+ method="post" novalidate="novalidate">
443
+ <div class="array card array-remove array-sort" id="array-myform-myarray">
444
+ <table>
445
+ <thead>
446
+ <tr>
447
+ <th>My Array</th>
448
+ <th class="head_actions">
449
+ <div class="array_actions"/>
450
+ </th>
451
+ </tr>
452
+ </thead>
453
+ <tbody>
454
+ <tr>
455
+ <td class="widget">
456
+ <div class="field" id="field-myform-myarray-0">
457
+ <label for="input-myform-myarray-0">My Field</label>
458
+ <input class="text" id="input-myform-myarray-0"
459
+ name="myform.myarray.0" type="text" value="1"/>
460
+ </div>
461
+ </td>
462
+ <td class="actions">
463
+ <div class="array_actions">
464
+ <a class="array_row_remove" href="#">
465
+ <span class="icon-minus-sign"> </span>
466
+ </a>
467
+ <a class="array_row_up" href="#">
468
+ <span class="icon-circle-arrow-up"> </span>
469
+ </a>
470
+ <a class="array_row_down" href="#">
471
+ <span class="icon-circle-arrow-down"> </span>
472
+ </a>
473
+ </div>
474
+ </td>
475
+ </tr>
476
+ <tr>
477
+ ...
478
+ </tr>
479
+ </tbody>
480
+ </table>
481
+ </div>
482
+ </form>
483
+ """, fxml(form()))
484
+
485
+ def test_array_with_preset_values_disable_sort_actions(self):
486
+ # Value as list. Disable ``sort``
487
+ form = factory(
488
+ 'form',
489
+ name='myform',
490
+ props={'action': 'myaction'})
491
+ form['myarray'] = factory(
492
+ 'array',
493
+ value=['1', '2'],
494
+ props={
495
+ 'label': 'My Array',
496
+ 'sort': False,
497
+ })
498
+ form['myarray']['myfield'] = factory(
499
+ 'field:label:text',
500
+ props={'label': 'My Field'})
501
+
502
+ self.checkOutput("""
503
+ <form action="myaction" enctype="multipart/form-data" id="form-myform"
504
+ method="post" novalidate="novalidate">
505
+ <div class="array card array-add array-remove" id="array-myform-myarray">
506
+ <table>
507
+ <thead>
508
+ <tr>
509
+ <th>My Array</th>
510
+ <th class="head_actions">
511
+ <div class="array_actions">
512
+ ...
513
+ </div>
514
+ </th>
515
+ </tr>
516
+ </thead>
517
+ <tbody>
518
+ <tr>
519
+ <td class="widget">
520
+ <div class="field" id="field-myform-myarray-0">
521
+ <label for="input-myform-myarray-0">My Field</label>
522
+ <input class="text" id="input-myform-myarray-0"
523
+ name="myform.myarray.0" type="text" value="1"/>
524
+ </div>
525
+ </td>
526
+ <td class="actions">
527
+ <div class="array_actions">
528
+ <a class="array_row_add" href="#">
529
+ <span class="icon-plus-sign"> </span>
530
+ </a>
531
+ <a class="array_row_remove" href="#">
532
+ <span class="icon-minus-sign"> </span>
533
+ </a>
534
+ </div>
535
+ </td>
536
+ </tr>
537
+ ...
538
+ </tbody>
539
+ </table>
540
+ <div class="arraytemplate">
541
+ ...
542
+ </div>
543
+ </div>
544
+ </form>
545
+ """, fxml(form()))
546
+
547
+ def test_array_with_preset_values_disable_all_actions(self):
548
+ # Value as list. All actions disabled. Actions col still rendered
549
+ form = factory(
550
+ 'form',
551
+ name='myform',
552
+ props={'action': 'myaction'})
553
+ form['myarray'] = factory(
554
+ 'array',
555
+ value=['1', '2'],
556
+ props={
557
+ 'label': 'My Array',
558
+ 'add': False,
559
+ 'remove': False,
560
+ 'sort': False,
561
+ })
562
+ form['myarray']['myfield'] = factory(
563
+ 'field:label:text',
564
+ props={'label': 'My Field'})
565
+
566
+ self.checkOutput("""
567
+ <form action="myaction" enctype="multipart/form-data" id="form-myform"
568
+ method="post" novalidate="novalidate">
569
+ <div class="array card" id="array-myform-myarray">
570
+ <table>
571
+ <thead>
572
+ <tr>
573
+ <th>My Array</th>
574
+ <th class="head_actions">
575
+ <div class="array_actions"/>
576
+ </th>
577
+ </tr>
578
+ </thead>
579
+ <tbody>
580
+ <tr>
581
+ <td class="widget">
582
+ <div class="field" id="field-myform-myarray-0">
583
+ <label for="input-myform-myarray-0">My Field</label>
584
+ <input class="text" id="input-myform-myarray-0"
585
+ name="myform.myarray.0" type="text" value="1"/>
586
+ </div>
587
+ </td>
588
+ <td class="actions">
589
+ <div class="array_actions"/>
590
+ </td>
591
+ </tr>
592
+ ...
593
+ </tbody>
594
+ </table>
595
+ </div>
596
+ </form>
597
+ """, fxml(form()))
598
+
599
+ def test_static_array_with_preset_values(self):
600
+ # Value as list. Set ``static`` property to ``True``. Actions col is
601
+ # skipped
602
+ form = factory(
603
+ 'form',
604
+ name='myform',
605
+ props={'action': 'myaction'})
606
+ form['myarray'] = factory(
607
+ 'array',
608
+ value=['1', '2'],
609
+ props={
610
+ 'label': 'My Array',
611
+ 'static': True,
612
+ })
613
+ form['myarray']['myfield'] = factory(
614
+ 'field:label:text',
615
+ props={'label': 'My Field'})
616
+
617
+ self.assertEqual(form.treerepr().split('\n'), [
618
+ "<class 'yafowil.base.Widget'>: myform",
619
+ " <class 'yafowil.base.Widget'>: myarray",
620
+ " <class 'yafowil.base.Widget'>: table",
621
+ " <class 'yafowil.base.Widget'>: head",
622
+ " <class 'yafowil.base.Widget'>: row",
623
+ " <class 'yafowil.base.Widget'>: label",
624
+ " <class 'yafowil.base.Widget'>: body",
625
+ " <class 'yafowil.base.Widget'>: myfield",
626
+ ""
627
+ ])
628
+
629
+ self.checkOutput("""
630
+ <form action="myaction" enctype="multipart/form-data" id="form-myform"
631
+ method="post" novalidate="novalidate">
632
+ <div class="array card array-static" id="array-myform-myarray">
633
+ <table>
634
+ <thead>
635
+ <tr>
636
+ <th>My Array</th>
637
+ </tr>
638
+ </thead>
639
+ <tbody>
640
+ <tr>
641
+ <td class="widget">
642
+ <div class="field" id="field-myform-myarray-0">
643
+ <label for="input-myform-myarray-0">My Field</label>
644
+ <input class="text" id="input-myform-myarray-0"
645
+ name="myform.myarray.0" type="text" value="1"/>
646
+ </div>
647
+ </td>
648
+ </tr>
649
+ <tr>
650
+ <td class="widget">
651
+ <div class="field" id="field-myform-myarray-1">
652
+ <label for="input-myform-myarray-1">My Field</label>
653
+ <input class="text" id="input-myform-myarray-1"
654
+ name="myform.myarray.1" type="text" value="2"/>
655
+ </div>
656
+ </td>
657
+ </tr>
658
+ </tbody>
659
+ </table>
660
+ </div>
661
+ </form>
662
+ """, fxml(form()))
663
+
664
+ def test_array_with_preset_value_as_list(self):
665
+ form = factory(
666
+ 'form',
667
+ name='myform',
668
+ props={'action': 'myaction'})
669
+ form['myarray'] = factory(
670
+ 'array',
671
+ value=['1', '2'],
672
+ props={'label': 'My Array'})
673
+ form['myarray']['myfield'] = factory(
674
+ 'field:label:text',
675
+ props={'label': 'My Field'})
676
+
677
+ self.checkOutput("""
678
+ <form action="myaction" enctype="multipart/form-data" id="form-myform"
679
+ method="post" novalidate="novalidate">
680
+ <div class="array card array-add array-remove array-sort"
681
+ id="array-myform-myarray">
682
+ <table>
683
+ <thead>
684
+ ...
685
+ </thead>
686
+ <tbody>
687
+ <tr>
688
+ <td class="widget">
689
+ <div class="field" id="field-myform-myarray-0">
690
+ <label for="input-myform-myarray-0">My Field</label>
691
+ <input class="text" id="input-myform-myarray-0"
692
+ name="myform.myarray.0" type="text" value="1"/>
693
+ </div>
694
+ </td>
695
+ <td class="actions">
696
+ <div class="array_actions">
697
+ <a class="array_row_add" href="#">
698
+ <span class="icon-plus-sign"> </span>
699
+ </a>
700
+ <a class="array_row_remove" href="#">
701
+ <span class="icon-minus-sign"> </span>
702
+ </a>
703
+ <a class="array_row_up" href="#">
704
+ <span class="icon-circle-arrow-up"> </span>
705
+ </a>
706
+ <a class="array_row_down" href="#">
707
+ <span class="icon-circle-arrow-down"> </span>
708
+ </a>
709
+ </div>
710
+ </td>
711
+ </tr>
712
+ <tr>
713
+ <td class="widget">
714
+ <div class="field" id="field-myform-myarray-1">
715
+ <label for="input-myform-myarray-1">My Field</label>
716
+ <input class="text" id="input-myform-myarray-1"
717
+ name="myform.myarray.1" type="text" value="2"/>
718
+ </div>
719
+ </td>
720
+ <td class="actions">
721
+ ...
722
+ </td>
723
+ </tr>
724
+ </tbody>
725
+ </table>
726
+ <div class="arraytemplate">
727
+ ...
728
+ </div>
729
+ </div>
730
+ </form>
731
+ """, fxml(form()))
732
+
733
+ def test_array_with_preset_value_as_dict_invalid(self):
734
+ # Value as dict, must contain indices as keys
735
+ form = factory(
736
+ 'form',
737
+ name='myform',
738
+ props={'action': 'myaction'})
739
+ value = odict()
740
+ value['a'] = '1'
741
+ form['myarray'] = factory(
742
+ 'array',
743
+ value=value,
744
+ props={'label': 'My Array'})
745
+ form['myarray']['myfield'] = factory(
746
+ 'field:label:text',
747
+ props={'label': 'My Field'})
748
+
749
+ with self.assertRaises(Exception) as arc:
750
+ form()
751
+ msg = "Array value error. invalid literal for int() with base 10: 'a'"
752
+ self.assertEqual(str(arc.exception), msg)
753
+
754
+ def test_array_with_preset_value_as_dict(self):
755
+ # Valid dict value
756
+ form = factory(
757
+ 'form',
758
+ name='myform',
759
+ props={'action': 'myaction'})
760
+ value = odict()
761
+ value['0'] = '1'
762
+ value['1'] = '2'
763
+ form['myarray'] = factory(
764
+ 'array',
765
+ value=value,
766
+ props={'label': 'My Array'})
767
+ form['myarray']['myfield'] = factory(
768
+ 'field:label:text',
769
+ props={'label': 'My Field'})
770
+
771
+ self.checkOutput("""
772
+ <form action="myaction" enctype="multipart/form-data" id="form-myform"
773
+ method="post" novalidate="novalidate">
774
+ <div class="array card array-add array-remove array-sort"
775
+ id="array-myform-myarray">
776
+ <table>
777
+ <thead>
778
+ ...
779
+ </thead>
780
+ <tbody>
781
+ <tr>
782
+ <td class="widget">
783
+ <div class="field" id="field-myform-myarray-0">
784
+ <label for="input-myform-myarray-0">My Field</label>
785
+ <input class="text" id="input-myform-myarray-0"
786
+ name="myform.myarray.0" type="text" value="1"/>
787
+ </div>
788
+ </td>
789
+ <td class="actions">
790
+ ...
791
+ </td>
792
+ </tr>
793
+ <tr>
794
+ ...
795
+ </tr>
796
+ </tbody>
797
+ </table>
798
+ <div class="arraytemplate">
799
+ ...
800
+ </div>
801
+ </div>
802
+ </form>
803
+ """, fxml(form()))
804
+
805
+ def test_array_with_compounds_default_values(self):
806
+ form = factory(
807
+ 'form',
808
+ name='myform',
809
+ props={'action': 'myaction'})
810
+ form['myarray'] = factory(
811
+ 'array',
812
+ value=[
813
+ {
814
+ 'f1': 'Value 1.1 F1',
815
+ 'f2': 'Value 1.2 F2',
816
+ },
817
+ {
818
+ 'f1': 'Value 2.1 F1',
819
+ 'f2': 'Value 2.2 F2',
820
+ }
821
+ ],
822
+ props={'label': 'My Compound Array'})
823
+ form['myarray']['mycompound'] = factory('compound')
824
+ form['myarray']['mycompound']['f1'] = factory(
825
+ 'field:label:text',
826
+ props={'label': 'F1'})
827
+ form['myarray']['mycompound']['f2'] = factory(
828
+ 'field:label:text',
829
+ props={'label': 'F2'})
830
+
831
+ self.checkOutput("""
832
+ <form action="myaction" enctype="multipart/form-data" id="form-myform"
833
+ method="post" novalidate="novalidate">
834
+ <div class="array card array-add array-remove array-sort"
835
+ id="array-myform-myarray">
836
+ <table>
837
+ <thead>
838
+ ...
839
+ </thead>
840
+ <tbody>
841
+ <tr>
842
+ <td class="widget">
843
+ <div class="field" id="field-myform-myarray-0-f1">
844
+ <label for="input-myform-myarray-0-f1">F1</label>
845
+ <input class="text" id="input-myform-myarray-0-f1"
846
+ name="myform.myarray.0.f1" type="text"
847
+ value="Value 1.1 F1"/>
848
+ </div>
849
+ <div class="field" id="field-myform-myarray-0-f2">
850
+ <label for="input-myform-myarray-0-f2">F2</label>
851
+ <input class="text" id="input-myform-myarray-0-f2"
852
+ name="myform.myarray.0.f2" type="text"
853
+ value="Value 1.2 F2"/>
854
+ </div>
855
+ </td>
856
+ <td class="actions">
857
+ ...
858
+ </td>
859
+ </tr>
860
+ <tr>
861
+ <td class="widget">
862
+ <div class="field" id="field-myform-myarray-1-f1">
863
+ <label for="input-myform-myarray-1-f1">F1</label>
864
+ <input class="text" id="input-myform-myarray-1-f1"
865
+ name="myform.myarray.1.f1" type="text"
866
+ value="Value 2.1 F1"/>
867
+ </div>
868
+ <div class="field" id="field-myform-myarray-1-f2">
869
+ <label for="input-myform-myarray-1-f2">F2</label>
870
+ <input class="text" id="input-myform-myarray-1-f2"
871
+ name="myform.myarray.1.f2" type="text"
872
+ value="Value 2.2 F2"/>
873
+ </div>
874
+ </td>
875
+ <td class="actions">
876
+ ...
877
+ </td>
878
+ </tr>
879
+ </tbody>
880
+ </table>
881
+ <div class="arraytemplate">
882
+ ...
883
+ </div>
884
+ </div>
885
+ </form>
886
+ """, fxml(form()))
887
+
888
+ def test_array_with_array_default_values_set(self):
889
+ # default values as list
890
+ form = factory(
891
+ 'form',
892
+ name='myform',
893
+ props={'action': 'myaction'})
894
+ form['myarray'] = factory(
895
+ 'array',
896
+ value=[
897
+ ['1', '2'],
898
+ ['4', '5'],
899
+ ],
900
+ props={'label': 'My Array Array'})
901
+ form['myarray']['subarray'] = factory(
902
+ 'array',
903
+ props={'label': 'Subrray'})
904
+ form['myarray']['subarray']['myfield'] = factory(
905
+ 'field:label:text',
906
+ props={'label': 'My Field'})
907
+ rendered = form()
908
+ self.checkOutput("""
909
+ <form action="myaction" enctype="multipart/form-data" id="form-myform"
910
+ method="post" novalidate="novalidate">
911
+ <div class="array card array-add array-remove array-sort"
912
+ id="array-myform-myarray">
913
+ <table>
914
+ <thead>
915
+ ...
916
+ </thead>
917
+ <tbody>
918
+ <tr>
919
+ <td class="widget">
920
+ <div class="array card array-add array-remove array-sort"
921
+ id="array-myform-myarray-0">
922
+ <table>
923
+ <thead>
924
+ <tr>
925
+ <th>Subrray</th>
926
+ ...
927
+ </tr>
928
+ </thead>
929
+ <tbody>
930
+ <tr>
931
+ <td class="widget">
932
+ <div class="field" id="field-myform-myarray-0-0">
933
+ <label for="input-myform-myarray-0-0">My Field</label>
934
+ <input class="text" id="input-myform-myarray-0-0"
935
+ name="myform.myarray.0.0" type="text"
936
+ value="1"/>
937
+ </div>
938
+ </td>
939
+ <td class="actions">
940
+ ...
941
+ </td>
942
+ </tr>
943
+ <tr>
944
+ <td class="widget">
945
+ <div class="field" id="field-myform-myarray-0-1">
946
+ <label for="input-myform-myarray-0-1">My Field</label>
947
+ <input class="text" id="input-myform-myarray-0-1"
948
+ name="myform.myarray.0.1" type="text"
949
+ value="2"/>
950
+ </div>
951
+ </td>
952
+ <td class="actions">
953
+ ...
954
+ </td>
955
+ </tr>
956
+ </tbody>
957
+ </table>
958
+ <div class="arraytemplate">
959
+ <div class="field" id="field-myform-myarray-0-TEMPLATE">
960
+ <label for="input-myform-myarray-0-TEMPLATE">My Field</label>
961
+ <input class="text"
962
+ id="input-myform-myarray-0-TEMPLATE"
963
+ name="myform.myarray.0.TEMPLATE" type="text"
964
+ value=""/>
965
+ </div>
966
+ </div>
967
+ </div>
968
+ </td>
969
+ <td class="actions">
970
+ ...
971
+ </td>
972
+ </tr>
973
+ <tr>
974
+ <td class="widget">
975
+ <div class="array card array-add array-remove array-sort"
976
+ id="array-myform-myarray-1">
977
+ ...
978
+ </div>
979
+ </td>
980
+ <td class="actions">
981
+ ...
982
+ </td>
983
+ </tr>
984
+ </tbody>
985
+ </table>
986
+ <div class="arraytemplate">
987
+ <div class="array card array-add array-remove array-sort"
988
+ id="array-myform-myarray-TEMPLATE">
989
+ <table>
990
+ <thead>
991
+ <tr>
992
+ <th>Subrray</th>
993
+ ...
994
+ </tr>
995
+ </thead>
996
+ <tbody/>
997
+ </table>
998
+ <div class="arraytemplate">
999
+ ...
1000
+ </div>
1001
+ </div>
1002
+ </div>
1003
+ </div>
1004
+ </form>
1005
+ """, fxml(rendered))
1006
+
1007
+ # default values as dict
1008
+ form['myarray'] = factory(
1009
+ 'array',
1010
+ value={
1011
+ '0': {'0': '1', '1': '2'},
1012
+ '1': {'0': '4', '1': '5'},
1013
+ },
1014
+ props={'label': 'My Array Array'})
1015
+ form['myarray']['subarray'] = factory(
1016
+ 'array',
1017
+ props={'label': 'Subrray'})
1018
+ form['myarray']['subarray']['myfield'] = factory(
1019
+ 'field:label:text',
1020
+ props={'label': 'My Field'})
1021
+ self.assertEqual(form(), rendered)
1022
+
1023
+ # default values mixed
1024
+ form['myarray'] = factory(
1025
+ 'array',
1026
+ value={
1027
+ '0': ['1', '2'],
1028
+ '1': ['4', '5'],
1029
+ },
1030
+ props={'label': 'My Array Array'})
1031
+ form['myarray']['subarray'] = factory(
1032
+ 'array',
1033
+ props={'label': 'Subrray'})
1034
+ form['myarray']['subarray']['myfield'] = factory(
1035
+ 'field:label:text',
1036
+ props={'label': 'My Field'})
1037
+ self.assertEqual(form(), rendered)
1038
+
1039
+ form['myarray'] = factory(
1040
+ 'array',
1041
+ value=[
1042
+ {'0': '1', '1': '2'},
1043
+ {'0': '4', '1': '5'},
1044
+ ],
1045
+ props={'label': 'My Array Array'})
1046
+ form['myarray']['subarray'] = factory(
1047
+ 'array',
1048
+ props={'label': 'Subrray'})
1049
+ form['myarray']['subarray']['myfield'] = factory(
1050
+ 'field:label:text',
1051
+ props={'label': 'My Field'})
1052
+ self.assertEqual(form(), rendered)
1053
+
1054
+ def test_array_with_array_with_compound_default_values_set(self):
1055
+ # default values as list
1056
+ form = factory(
1057
+ 'form',
1058
+ name='myform',
1059
+ props={'action': 'myaction'})
1060
+ form['myarray'] = factory(
1061
+ 'array',
1062
+ value=[
1063
+ [
1064
+ {
1065
+ 'f1': 'Value 0.0 F1',
1066
+ 'f2': 'Value 0.0 F2',
1067
+ },
1068
+ {
1069
+ 'f1': 'Value 0.1 F1',
1070
+ 'f2': 'Value 0.1 F2',
1071
+ },
1072
+ ],
1073
+ ],
1074
+ props={'label': 'My Compound Array'})
1075
+ form['myarray']['subarray'] = factory(
1076
+ 'array',
1077
+ props={'label': 'Subarray'})
1078
+ form['myarray']['subarray']['compoundinsub'] = factory('compound')
1079
+ form['myarray']['subarray']['compoundinsub']['f1'] = factory(
1080
+ 'field:label:text',
1081
+ props={'label': 'F1'})
1082
+ form['myarray']['subarray']['compoundinsub']['f2'] = factory(
1083
+ 'field:label:text',
1084
+ props={'label': 'F2'})
1085
+
1086
+ self.assertEqual(form.treerepr().split('\n'), [
1087
+ "<class 'yafowil.base.Widget'>: myform",
1088
+ " <class 'yafowil.base.Widget'>: myarray",
1089
+ " <class 'yafowil.base.Widget'>: table",
1090
+ " <class 'yafowil.base.Widget'>: head",
1091
+ " <class 'yafowil.base.Widget'>: row",
1092
+ " <class 'yafowil.base.Widget'>: label",
1093
+ " <class 'yafowil.base.Widget'>: actions",
1094
+ " <class 'yafowil.base.Widget'>: body",
1095
+ " <class 'yafowil.base.Widget'>: subarray",
1096
+ " <class 'yafowil.base.Widget'>: table",
1097
+ " <class 'yafowil.base.Widget'>: head",
1098
+ " <class 'yafowil.base.Widget'>: row",
1099
+ " <class 'yafowil.base.Widget'>: label",
1100
+ " <class 'yafowil.base.Widget'>: actions",
1101
+ " <class 'yafowil.base.Widget'>: body",
1102
+ " <class 'yafowil.base.Widget'>: compoundinsub",
1103
+ " <class 'yafowil.base.Widget'>: f1",
1104
+ " <class 'yafowil.base.Widget'>: f2",
1105
+ ""
1106
+ ])
1107
+
1108
+ rendered = form()
1109
+ self.checkOutput("""
1110
+ <form action="myaction" enctype="multipart/form-data" id="form-myform"
1111
+ method="post" novalidate="novalidate">
1112
+ <div class="array card array-add array-remove array-sort"
1113
+ id="array-myform-myarray">
1114
+ <table>
1115
+ <thead>
1116
+ <tr>
1117
+ <th>My Compound Array</th>
1118
+ ...
1119
+ </tr>
1120
+ </thead>
1121
+ <tbody>
1122
+ <tr>
1123
+ <td class="widget">
1124
+ <div class="array card array-add array-remove array-sort"
1125
+ id="array-myform-myarray-0">
1126
+ <table>
1127
+ <thead>
1128
+ <tr>
1129
+ <th>Subarray</th>
1130
+ ...
1131
+ </tr>
1132
+ </thead>
1133
+ <tbody>
1134
+ <tr>
1135
+ <td class="widget">
1136
+ <div class="field" id="field-myform-myarray-0-0-f1">
1137
+ <label for="input-myform-myarray-0-0-f1">F1</label>
1138
+ <input class="text" id="input-myform-myarray-0-0-f1"
1139
+ name="myform.myarray.0.0.f1" type="text"
1140
+ value="Value 0.0 F1"/>
1141
+ </div>
1142
+ <div class="field" id="field-myform-myarray-0-0-f2">
1143
+ <label for="input-myform-myarray-0-0-f2">F2</label>
1144
+ <input class="text" id="input-myform-myarray-0-0-f2"
1145
+ name="myform.myarray.0.0.f2" type="text"
1146
+ value="Value 0.0 F2"/>
1147
+ </div>
1148
+ </td>
1149
+ <td class="actions">
1150
+ ...
1151
+ </td>
1152
+ </tr>
1153
+ <tr>
1154
+ <td class="widget">
1155
+ <div class="field" id="field-myform-myarray-0-1-f1">
1156
+ <label for="input-myform-myarray-0-1-f1">F1</label>
1157
+ <input class="text" id="input-myform-myarray-0-1-f1"
1158
+ name="myform.myarray.0.1.f1" type="text"
1159
+ value="Value 0.1 F1"/>
1160
+ </div>
1161
+ <div class="field" id="field-myform-myarray-0-1-f2">
1162
+ <label for="input-myform-myarray-0-1-f2">F2</label>
1163
+ <input class="text" id="input-myform-myarray-0-1-f2"
1164
+ name="myform.myarray.0.1.f2" type="text"
1165
+ value="Value 0.1 F2"/>
1166
+ </div>
1167
+ </td>
1168
+ <td class="actions">
1169
+ ...
1170
+ </td>
1171
+ </tr>
1172
+ </tbody>
1173
+ </table>
1174
+ <div class="arraytemplate">
1175
+ ...
1176
+ </div>
1177
+ </div>
1178
+ </td>
1179
+ <td class="actions">
1180
+ ...
1181
+ </td>
1182
+ </tr>
1183
+ </tbody>
1184
+ </table>
1185
+ <div class="arraytemplate">
1186
+ <div class="array card array-add array-remove array-sort"
1187
+ id="array-myform-myarray-TEMPLATE">
1188
+ <table>
1189
+ <thead>
1190
+ <tr>
1191
+ <th>Subarray</th>
1192
+ ...
1193
+ </tr>
1194
+ </thead>
1195
+ <tbody/>
1196
+ </table>
1197
+ <div class="arraytemplate">
1198
+ <div class="field" id="field-myform-myarray-TEMPLATE-TEMPLATE-f1">
1199
+ <label for="input-myform-myarray-TEMPLATE-TEMPLATE-f1">F1</label>
1200
+ <input class="text" id="input-myform-myarray-TEMPLATE-TEMPLATE-f1"
1201
+ name="myform.myarray.TEMPLATE.TEMPLATE.f1"
1202
+ type="text" value=""/>
1203
+ </div>
1204
+ <div class="field" id="field-myform-myarray-TEMPLATE-TEMPLATE-f2">
1205
+ <label for="input-myform-myarray-TEMPLATE-TEMPLATE-f2">F2</label>
1206
+ <input class="text" id="input-myform-myarray-TEMPLATE-TEMPLATE-f2"
1207
+ name="myform.myarray.TEMPLATE.TEMPLATE.f2" type="text"
1208
+ value=""/>
1209
+ </div>
1210
+ </div>
1211
+ </div>
1212
+ </div>
1213
+ </div>
1214
+ </form>
1215
+ """, fxml(rendered))
1216
+
1217
+ # default values as dict
1218
+ form['myarray'] = factory(
1219
+ 'array',
1220
+ value={
1221
+ '0': {
1222
+ '0': {
1223
+ 'f1': 'Value 0.0 F1',
1224
+ 'f2': 'Value 0.0 F2',
1225
+ },
1226
+ '1': {
1227
+ 'f1': 'Value 0.1 F1',
1228
+ 'f2': 'Value 0.1 F2',
1229
+ },
1230
+ },
1231
+ },
1232
+ props={'label': 'My Compound Array'})
1233
+ form['myarray']['subarray'] = factory(
1234
+ 'array',
1235
+ props={'label': 'Subarray'})
1236
+ form['myarray']['subarray']['mycompound'] = factory('compound')
1237
+ form['myarray']['subarray']['mycompound']['f1'] = factory(
1238
+ 'field:label:text',
1239
+ props={'label': 'F1'})
1240
+ form['myarray']['subarray']['mycompound']['f2'] = factory(
1241
+ 'field:label:text',
1242
+ props={'label': 'F2'})
1243
+ self.assertEqual(rendered, form())
1244
+
1245
+ # default values mixed
1246
+ form['myarray'] = factory(
1247
+ 'array',
1248
+ value=[
1249
+ {
1250
+ '0': {
1251
+ 'f1': 'Value 0.0 F1',
1252
+ 'f2': 'Value 0.0 F2',
1253
+ },
1254
+ '1': {
1255
+ 'f1': 'Value 0.1 F1',
1256
+ 'f2': 'Value 0.1 F2',
1257
+ },
1258
+ },
1259
+ ],
1260
+ props={'label': 'My Compound Array'})
1261
+ form['myarray']['subarray'] = factory(
1262
+ 'array',
1263
+ props={'label': 'Subarray'})
1264
+ form['myarray']['subarray']['mycompound'] = factory('compound')
1265
+ form['myarray']['subarray']['mycompound']['f1'] = factory(
1266
+ 'field:label:text',
1267
+ props={'label': 'F1'})
1268
+ form['myarray']['subarray']['mycompound']['f2'] = factory(
1269
+ 'field:label:text',
1270
+ props={'label': 'F2'})
1271
+ self.assertEqual(rendered, form())
1272
+
1273
+ def test_array_with_single_fields_extraction(self):
1274
+ # without preset values
1275
+ form = factory(
1276
+ 'form',
1277
+ name='myform',
1278
+ props={'action': 'myaction'})
1279
+ form['myarray'] = factory(
1280
+ 'array',
1281
+ props={'label': 'My Array'})
1282
+ form['myarray']['myfield'] = factory(
1283
+ 'field:label:text',
1284
+ props={'label': 'My Field'})
1285
+ request = {
1286
+ 'myform.myarray.0': '1',
1287
+ 'myform.myarray.1': '2',
1288
+ 'myform.myarray.2': '3',
1289
+ 'myform.myarray.3': '4',
1290
+ }
1291
+ data = form.extract(request=request)
1292
+ self.assertEqual(
1293
+ [data.name, data.value, data.extracted, data.errors],
1294
+ ['myform', UNSET, odict([('myarray', ['1', '2', '3', '4'])]), []]
1295
+ )
1296
+ arrd = data['myarray']
1297
+ self.assertEqual(len(arrd), 4)
1298
+ self.assertEqual(
1299
+ [arrd.name, arrd.value, arrd.extracted, arrd.errors],
1300
+ ['myarray', UNSET, ['1', '2', '3', '4'], []]
1301
+ )
1302
+ arrd0 = data['myarray']['0']
1303
+ self.assertEqual(
1304
+ [arrd0.name, arrd0.value, arrd0.extracted, arrd0.errors],
1305
+ ['0', UNSET, '1', []]
1306
+ )
1307
+ arrd1 = data['myarray']['1']
1308
+ self.assertEqual(
1309
+ [arrd1.name, arrd1.value, arrd1.extracted, arrd1.errors],
1310
+ ['1', UNSET, '2', []]
1311
+ )
1312
+ arrd2 = data['myarray']['2']
1313
+ self.assertEqual(
1314
+ [arrd2.name, arrd2.value, arrd2.extracted, arrd2.errors],
1315
+ ['2', UNSET, '3', []]
1316
+ )
1317
+ arrd3 = data['myarray']['3']
1318
+ self.assertEqual(
1319
+ [arrd3.name, arrd3.value, arrd3.extracted, arrd3.errors],
1320
+ ['3', UNSET, '4', []]
1321
+ )
1322
+
1323
+ # with preset values
1324
+ form['myarray'] = factory(
1325
+ 'array',
1326
+ value=['4', '3', '2', '1'],
1327
+ props={'label': 'My Array'})
1328
+ form['myarray']['myfield'] = factory(
1329
+ 'field:label:text',
1330
+ props={'label': 'My Field'})
1331
+ request = {
1332
+ 'myform.myarray.0': '41',
1333
+ 'myform.myarray.1': '31',
1334
+ 'myform.myarray.2': '21',
1335
+ 'myform.myarray.3': '11',
1336
+ }
1337
+ data = form.extract(request=request)
1338
+ self.assertEqual(
1339
+ [data.name, data.value, data.extracted, data.errors],
1340
+ ['myform', UNSET, odict([('myarray', ['41', '31', '21', '11'])]), []]
1341
+ )
1342
+ arrd = data['myarray']
1343
+ self.assertEqual(len(arrd), 4)
1344
+ self.assertEqual(
1345
+ [arrd.name, arrd.value, arrd.extracted, arrd.errors],
1346
+ ['myarray', ['4', '3', '2', '1'], ['41', '31', '21', '11'], []]
1347
+ )
1348
+ arrd0 = data['myarray']['0']
1349
+ self.assertEqual(
1350
+ [arrd0.name, arrd0.value, arrd0.extracted, arrd0.errors],
1351
+ ['0', UNSET, '41', []]
1352
+ )
1353
+ arrd1 = data['myarray']['1']
1354
+ self.assertEqual(
1355
+ [arrd1.name, arrd1.value, arrd1.extracted, arrd1.errors],
1356
+ ['1', UNSET, '31', []]
1357
+ )
1358
+ arrd2 = data['myarray']['2']
1359
+ self.assertEqual(
1360
+ [arrd2.name, arrd2.value, arrd2.extracted, arrd2.errors],
1361
+ ['2', UNSET, '21', []]
1362
+ )
1363
+ arrd3 = data['myarray']['3']
1364
+ self.assertEqual(
1365
+ [arrd3.name, arrd3.value, arrd3.extracted, arrd3.errors],
1366
+ ['3', UNSET, '11', []]
1367
+ )
1368
+
1369
+ # entries increased in UI
1370
+ request = {
1371
+ 'myform.myarray.0': 'a',
1372
+ 'myform.myarray.1': 'b',
1373
+ 'myform.myarray.2': 'c',
1374
+ 'myform.myarray.3': 'd',
1375
+ 'myform.myarray.4': 'e',
1376
+ }
1377
+ data = form.extract(request=request)
1378
+ self.assertEqual(
1379
+ [data.name, data.value, data.extracted, data.errors],
1380
+ ['myform', UNSET, odict([('myarray', ['a', 'b', 'c', 'd', 'e'])]), []]
1381
+ )
1382
+ arrd = data['myarray']
1383
+ self.assertEqual(len(arrd), 5)
1384
+ self.assertEqual(
1385
+ [arrd.name, arrd.value, arrd.extracted, arrd.errors],
1386
+ ['myarray', ['4', '3', '2', '1'], ['a', 'b', 'c', 'd', 'e'], []]
1387
+ )
1388
+ arrd0 = data['myarray']['0']
1389
+ self.assertEqual(
1390
+ [arrd0.name, arrd0.value, arrd0.extracted, arrd0.errors],
1391
+ ['0', UNSET, 'a', []]
1392
+ )
1393
+ arrd1 = data['myarray']['1']
1394
+ self.assertEqual(
1395
+ [arrd1.name, arrd1.value, arrd1.extracted, arrd1.errors],
1396
+ ['1', UNSET, 'b', []]
1397
+ )
1398
+ arrd2 = data['myarray']['2']
1399
+ self.assertEqual(
1400
+ [arrd2.name, arrd2.value, arrd2.extracted, arrd2.errors],
1401
+ ['2', UNSET, 'c', []]
1402
+ )
1403
+ arrd3 = data['myarray']['3']
1404
+ self.assertEqual(
1405
+ [arrd3.name, arrd3.value, arrd3.extracted, arrd3.errors],
1406
+ ['3', UNSET, 'd', []]
1407
+ )
1408
+ arrd4 = data['myarray']['4']
1409
+ self.assertEqual(
1410
+ [arrd4.name, arrd4.value, arrd4.extracted, arrd4.errors],
1411
+ ['4', UNSET, 'e', []]
1412
+ )
1413
+
1414
+ # entries decreased in UI
1415
+ request = {
1416
+ 'myform.myarray.0': 'x',
1417
+ 'myform.myarray.1': 'y',
1418
+ 'myform.myarray.2': 'z',
1419
+ }
1420
+ data = form.extract(request=request)
1421
+ self.assertEqual(
1422
+ [data.name, data.value, data.extracted, data.errors],
1423
+ ['myform', UNSET, odict([('myarray', ['x', 'y', 'z'])]), []]
1424
+ )
1425
+ arrd = data['myarray']
1426
+ self.assertEqual(len(arrd), 3)
1427
+ self.assertEqual(
1428
+ [arrd.name, arrd.value, arrd.extracted, arrd.errors],
1429
+ ['myarray', ['4', '3', '2', '1'], ['x', 'y', 'z'], []]
1430
+ )
1431
+ arrd0 = data['myarray']['0']
1432
+ self.assertEqual(
1433
+ [arrd0.name, arrd0.value, arrd0.extracted, arrd0.errors],
1434
+ ['0', UNSET, 'x', []]
1435
+ )
1436
+ arrd1 = data['myarray']['1']
1437
+ self.assertEqual(
1438
+ [arrd1.name, arrd1.value, arrd1.extracted, arrd1.errors],
1439
+ ['1', UNSET, 'y', []]
1440
+ )
1441
+ arrd2 = data['myarray']['2']
1442
+ self.assertEqual(
1443
+ [arrd2.name, arrd2.value, arrd2.extracted, arrd2.errors],
1444
+ ['2', UNSET, 'z', []]
1445
+ )
1446
+
1447
+ def test_array_required_extraction(self):
1448
+ form = factory(
1449
+ 'form',
1450
+ name='myform',
1451
+ props={'action': 'myaction'})
1452
+ form['myarray'] = factory(
1453
+ 'error:array',
1454
+ value=['4', '3', '2', '1'],
1455
+ props={
1456
+ 'label': 'My Array',
1457
+ 'required': 'Array is required',
1458
+ })
1459
+ form['myarray']['myfield'] = factory(
1460
+ 'field:label:text',
1461
+ props={'label': 'My Field'})
1462
+ request = {}
1463
+ data = form.extract(request=request)
1464
+ self.assertEqual(
1465
+ [data.name, data.value, data.extracted, data.errors],
1466
+ ['myform', UNSET, odict([('myarray', [])]), []]
1467
+ )
1468
+ arrd = data['myarray']
1469
+ self.assertEqual(len(arrd), 0)
1470
+ self.assertEqual(
1471
+ [arrd.name, arrd.value, arrd.extracted, arrd.errors],
1472
+ [
1473
+ 'myarray',
1474
+ ['4', '3', '2', '1'],
1475
+ [],
1476
+ [ExtractionError('Array is required')]
1477
+ ]
1478
+ )
1479
+
1480
+ self.checkOutput("""
1481
+ <form action="myaction" enctype="multipart/form-data" id="form-myform" method="post" novalidate="novalidate">
1482
+ <div class="error">
1483
+ <div class="errormessage">Array is required</div>
1484
+ <div class="array card error array-add array-remove array-sort" id="array-myform-myarray">
1485
+ ...
1486
+ </div>
1487
+ </div>
1488
+ </form>
1489
+ """, fxml(form(data=data)))
1490
+
1491
+ def test_array_field_required_extraction(self):
1492
+ form = factory(
1493
+ 'form',
1494
+ name='myform',
1495
+ props={'action': 'myaction'})
1496
+ form['myarray'] = factory(
1497
+ 'array',
1498
+ props={'label': 'My Array'})
1499
+ form['myarray']['myfield'] = factory(
1500
+ 'field:label:error:text',
1501
+ props={
1502
+ 'label': 'My Field',
1503
+ 'required': 'My Field is required',
1504
+ })
1505
+ request = {
1506
+ 'myform.myarray.0': '0',
1507
+ 'myform.myarray.1': '',
1508
+ }
1509
+
1510
+ data = form.extract(request=request)
1511
+ self.assertEqual(
1512
+ [data.name, data.value, data.extracted, data.errors],
1513
+ ['myform', UNSET, odict([('myarray', ['0', ''])]), []]
1514
+ )
1515
+ arrd = data['myarray']
1516
+ self.assertEqual(
1517
+ [arrd.name, arrd.value, arrd.extracted, arrd.errors],
1518
+ ['myarray', UNSET, ['0', ''], []]
1519
+ )
1520
+ arrd0 = data['myarray']['0']
1521
+ self.assertEqual(
1522
+ [arrd0.name, arrd0.value, arrd0.extracted, arrd0.errors],
1523
+ ['0', UNSET, '0', []]
1524
+ )
1525
+ arrd1 = data['myarray']['1']
1526
+ self.assertEqual(
1527
+ [arrd1.name, arrd1.value, arrd1.extracted, arrd1.errors],
1528
+ ['1', UNSET, '', [ExtractionError('My Field is required')]]
1529
+ )
1530
+
1531
+ self.checkOutput("""
1532
+ <form action="myaction" enctype="multipart/form-data" id="form-myform"
1533
+ method="post" novalidate="novalidate">
1534
+ <div class="array card array-add array-remove array-sort"
1535
+ id="array-myform-myarray">
1536
+ <table>
1537
+ ...
1538
+ <tbody>
1539
+ <tr>
1540
+ ...
1541
+ </tr>
1542
+ <tr>
1543
+ <td class="widget">
1544
+ <div class="field" id="field-myform-myarray-1">
1545
+ <label for="input-myform-myarray-1">My Field</label>
1546
+ <div class="error">
1547
+ <div class="errormessage">My Field is required</div>
1548
+ <input class="required text"
1549
+ id="input-myform-myarray-1"
1550
+ name="myform.myarray.1"
1551
+ required="required"
1552
+ type="text"
1553
+ value=""/>
1554
+ </div>
1555
+ </div>
1556
+ </td>
1557
+ <td class="actions">
1558
+ ...
1559
+ </td>
1560
+ </tr>
1561
+ </tbody>
1562
+ </table>
1563
+ <div class="arraytemplate">
1564
+ ...
1565
+ </div>
1566
+ </div>
1567
+ </form>
1568
+ """, fxml(form(data)))
1569
+
1570
+ def test_array_with_compound_fields_extraction(self):
1571
+ form = factory(
1572
+ 'form',
1573
+ name='myform',
1574
+ props={'action': 'myaction'})
1575
+ form['myarray'] = factory(
1576
+ 'array',
1577
+ props={'label': 'My Compound Array'})
1578
+ form['myarray']['mycompound'] = factory('compound')
1579
+ form['myarray']['mycompound']['f1'] = factory(
1580
+ 'field:label:text',
1581
+ props={'label': 'F1'})
1582
+ form['myarray']['mycompound']['f2'] = factory(
1583
+ 'field:label:text',
1584
+ props={'label': 'F2'})
1585
+ request = {
1586
+ 'myform.myarray.0.f1': '1',
1587
+ 'myform.myarray.0.f2': '2',
1588
+ 'myform.myarray.1.f1': '3',
1589
+ 'myform.myarray.1.f2': '4',
1590
+ }
1591
+ data = form.extract(request=request)
1592
+ extracted = odict([
1593
+ ('myarray', [
1594
+ odict([('f1', '1'), ('f2', '2')]),
1595
+ odict([('f1', '3'), ('f2', '4')])
1596
+ ])
1597
+ ])
1598
+ self.assertEqual(
1599
+ [data.name, data.value, data.extracted, data.errors],
1600
+ ['myform', UNSET, extracted, []]
1601
+ )
1602
+ arrd = data['myarray']
1603
+ self.assertEqual(len(arrd), 2)
1604
+ extracted = [
1605
+ odict([('f1', '1'), ('f2', '2')]),
1606
+ odict([('f1', '3'), ('f2', '4')])
1607
+ ]
1608
+ self.assertEqual(
1609
+ [arrd.name, arrd.value, arrd.extracted, arrd.errors],
1610
+ ['myarray', UNSET, extracted, []]
1611
+ )
1612
+ c0 = data['myarray']['0']
1613
+ self.assertEqual(len(c0), 2)
1614
+ self.assertEqual(
1615
+ [c0.name, c0.value, c0.extracted, c0.errors],
1616
+ ['0', UNSET, odict([('f1', '1'), ('f2', '2')]), []]
1617
+ )
1618
+ c0f1 = data['myarray']['0']['f1']
1619
+ self.assertEqual(
1620
+ [c0f1.name, c0f1.value, c0f1.extracted, c0f1.errors],
1621
+ ['f1', UNSET, '1', []]
1622
+ )
1623
+ c0f2 = data['myarray']['0']['f2']
1624
+ self.assertEqual(
1625
+ [c0f2.name, c0f2.value, c0f2.extracted, c0f2.errors],
1626
+ ['f2', UNSET, '2', []]
1627
+ )
1628
+ c1 = data['myarray']['1']
1629
+ self.assertEqual(len(c1), 2)
1630
+ self.assertEqual(
1631
+ [c1.name, c1.value, c1.extracted, c1.errors],
1632
+ ['1', UNSET, odict([('f1', '3'), ('f2', '4')]), []]
1633
+ )
1634
+ c1f1 = data['myarray']['1']['f1']
1635
+ self.assertEqual(
1636
+ [c1f1.name, c1f1.value, c1f1.extracted, c1f1.errors],
1637
+ ['f1', UNSET, '3', []]
1638
+ )
1639
+ c1f2 = data['myarray']['1']['f2']
1640
+ self.assertEqual(
1641
+ [c1f2.name, c1f2.value, c1f2.extracted, c1f2.errors],
1642
+ ['f2', UNSET, '4', []]
1643
+ )
1644
+
1645
+ def test_array_in_array_with_single_fields_extraction(self):
1646
+ form = factory(
1647
+ 'form',
1648
+ name='myform',
1649
+ props={'action': 'myaction'})
1650
+ form['myarray'] = factory(
1651
+ 'array',
1652
+ value=[
1653
+ ['1', '2'],
1654
+ ['4', '5'],
1655
+ ],
1656
+ props={'label': 'My Array Array'})
1657
+ form['myarray']['subarray'] = factory(
1658
+ 'array',
1659
+ props={'label': 'Subrray'})
1660
+ form['myarray']['subarray']['myfield'] = factory(
1661
+ 'field:label:text',
1662
+ props={'label': 'My Field'})
1663
+ request = {
1664
+ 'myform.myarray.0.0': '1',
1665
+ 'myform.myarray.0.1': '2',
1666
+ 'myform.myarray.1.0': '3',
1667
+ 'myform.myarray.1.1': '4',
1668
+ }
1669
+ data = form.extract(request=request)
1670
+ extracted = odict([('myarray', [['1', '2'], ['3', '4']])])
1671
+ self.assertEqual(
1672
+ [data.name, data.value, data.extracted, data.errors],
1673
+ ['myform', UNSET, extracted, []]
1674
+ )
1675
+ arrd = data['myarray']
1676
+ self.assertEqual(len(arrd), 2)
1677
+ value = [['1', '2'], ['4', '5']]
1678
+ extracted = [['1', '2'], ['3', '4']]
1679
+ self.assertEqual(
1680
+ [arrd.name, arrd.value, arrd.extracted, arrd.errors],
1681
+ ['myarray', value, extracted, []]
1682
+ )
1683
+ a0 = data['myarray']['0']
1684
+ self.assertEqual(len(a0), 2)
1685
+ self.assertEqual(
1686
+ [a0.name, a0.value, a0.extracted, a0.errors],
1687
+ ['0', UNSET, ['1', '2'], []]
1688
+ )
1689
+ a0_0 = data['myarray']['0']['0']
1690
+ self.assertEqual(
1691
+ [a0_0.name, a0_0.value, a0_0.extracted, a0_0.errors],
1692
+ ['0', UNSET, '1', []]
1693
+ )
1694
+ a0_1 = data['myarray']['0']['1']
1695
+ self.assertEqual(
1696
+ [a0_1.name, a0_1.value, a0_1.extracted, a0_1.errors],
1697
+ ['1', UNSET, '2', []]
1698
+ )
1699
+ a1 = data['myarray']['1']
1700
+ self.assertEqual(len(a1), 2)
1701
+ self.assertEqual(
1702
+ [a1.name, a1.value, a1.extracted, a1.errors],
1703
+ ['1', UNSET, ['3', '4'], []]
1704
+ )
1705
+ a1_0 = data['myarray']['1']['0']
1706
+ self.assertEqual(
1707
+ [a1_0.name, a1_0.value, a1_0.extracted, a1_0.errors],
1708
+ ['0', UNSET, '3', []]
1709
+ )
1710
+ a1_1 = data['myarray']['1']['1']
1711
+ self.assertEqual(
1712
+ [a1_1.name, a1_1.value, a1_1.extracted, a1_1.errors],
1713
+ ['1', UNSET, '4', []]
1714
+ )
1715
+
1716
+ def test_array_in_array_with_compound_fields_extraction(self):
1717
+ form = factory(
1718
+ 'form',
1719
+ name='myform',
1720
+ props={'action': 'myaction'})
1721
+ form['myarray'] = factory(
1722
+ 'array',
1723
+ props={'label': 'My Compound Array'})
1724
+ form['myarray']['subarray'] = factory(
1725
+ 'array',
1726
+ props={'label': 'Subarray'})
1727
+ form['myarray']['subarray']['mycompound'] = factory('compound')
1728
+ form['myarray']['subarray']['mycompound']['f1'] = factory(
1729
+ 'field:label:text',
1730
+ props={'label': 'F1'})
1731
+ form['myarray']['subarray']['mycompound']['f2'] = factory(
1732
+ 'field:label:text',
1733
+ props={'label': 'F2'})
1734
+ request = {
1735
+ 'myform.myarray.0.0.f1': '1',
1736
+ 'myform.myarray.0.0.f2': '2',
1737
+ 'myform.myarray.1.0.f1': '3',
1738
+ 'myform.myarray.1.0.f2': '4',
1739
+ 'myform.myarray.1.1.f1': '5',
1740
+ 'myform.myarray.1.1.f2': '6',
1741
+ }
1742
+ data = form.extract(request=request)
1743
+ extracted = odict([
1744
+ ('myarray', [
1745
+ [
1746
+ odict([('f1', '1'), ('f2', '2')])
1747
+ ], [
1748
+ odict([('f1', '3'), ('f2', '4')]),
1749
+ odict([('f1', '5'), ('f2', '6')])
1750
+ ]
1751
+ ])
1752
+ ])
1753
+ self.assertEqual(
1754
+ [data.name, data.value, data.extracted, data.errors],
1755
+ ['myform', UNSET, extracted, []]
1756
+ )
1757
+ arrd0 = data['myarray']
1758
+ self.assertEqual(len(arrd0), 2)
1759
+ extracted = [
1760
+ [
1761
+ odict([('f1', '1'), ('f2', '2')])
1762
+ ], [
1763
+ odict([('f1', '3'), ('f2', '4')]),
1764
+ odict([('f1', '5'), ('f2', '6')])
1765
+ ]
1766
+ ]
1767
+ self.assertEqual(
1768
+ [arrd0.name, arrd0.value, arrd0.extracted, arrd0.errors],
1769
+ ['myarray', UNSET, extracted, []]
1770
+ )
1771
+ a0 = data['myarray']['0']
1772
+ self.assertEqual(len(a0), 1)
1773
+ extracted = [odict([('f1', '1'), ('f2', '2')])]
1774
+ self.assertEqual(
1775
+ [a0.name, a0.value, a0.extracted, a0.errors],
1776
+ ['0', UNSET, extracted, []]
1777
+ )
1778
+ a0_0 = data['myarray']['0']['0']
1779
+ self.assertEqual(len(a0_0), 2)
1780
+ extracted = odict([('f1', '1'), ('f2', '2')])
1781
+ self.assertEqual(
1782
+ [a0_0.name, a0_0.value, a0_0.extracted, a0_0.errors],
1783
+ ['0', UNSET, extracted, []]
1784
+ )
1785
+ a0_0f1 = data['myarray']['0']['0']['f1']
1786
+ self.assertEqual(
1787
+ [a0_0f1.name, a0_0f1.value, a0_0f1.extracted, a0_0f1.errors],
1788
+ ['f1', UNSET, '1', []]
1789
+ )
1790
+ a0_0f2 = data['myarray']['0']['0']['f2']
1791
+ self.assertEqual(
1792
+ [a0_0f2.name, a0_0f2.value, a0_0f2.extracted, a0_0f2.errors],
1793
+ ['f2', UNSET, '2', []]
1794
+ )
1795
+ a1 = data['myarray']['1']
1796
+ self.assertEqual(len(a1), 2)
1797
+ extracted = [
1798
+ odict([('f1', '3'), ('f2', '4')]),
1799
+ odict([('f1', '5'), ('f2', '6')])
1800
+ ]
1801
+ self.assertEqual(
1802
+ [a1.name, a1.value, a1.extracted, a1.errors],
1803
+ ['1', UNSET, extracted, []]
1804
+ )
1805
+ a1_0 = data['myarray']['1']['0']
1806
+ self.assertEqual(len(a1_0), 2)
1807
+ extracted = odict([('f1', '3'), ('f2', '4')])
1808
+ self.assertEqual(
1809
+ [a1_0.name, a1_0.value, a1_0.extracted, a1_0.errors],
1810
+ ['0', UNSET, extracted, []]
1811
+ )
1812
+ a1_0f1 = data['myarray']['1']['0']['f1']
1813
+ self.assertEqual(
1814
+ [a1_0f1.name, a1_0f1.value, a1_0f1.extracted, a1_0f1.errors],
1815
+ ['f1', UNSET, '3', []]
1816
+ )
1817
+ a1_0f2 = data['myarray']['1']['0']['f2']
1818
+ self.assertEqual(
1819
+ [a1_0f2.name, a1_0f2.value, a1_0f2.extracted, a1_0f2.errors],
1820
+ ['f2', UNSET, '4', []]
1821
+ )
1822
+ a1_1 = data['myarray']['1']['1']
1823
+ self.assertEqual(len(a1_1), 2)
1824
+ extracted = odict([('f1', '5'), ('f2', '6')])
1825
+ self.assertEqual(
1826
+ [a1_1.name, a1_1.value, a1_1.extracted, a1_1.errors],
1827
+ ['1', UNSET, extracted, []]
1828
+ )
1829
+ a1_1f1 = data['myarray']['1']['1']['f1']
1830
+ self.assertEqual(
1831
+ [a1_1f1.name, a1_1f1.value, a1_1f1.extracted, a1_1f1.errors],
1832
+ ['f1', UNSET, '5', []]
1833
+ )
1834
+ a1_1f2 = data['myarray']['1']['1']['f2']
1835
+ self.assertEqual(
1836
+ [a1_1f2.name, a1_1f2.value, a1_1f2.extracted, a1_1f2.errors],
1837
+ ['f2', UNSET, '6', []]
1838
+ )
1839
+
1840
+ def test_array_hidden_proxy_for_display_mode_children(self):
1841
+ # ``yafowil.widget.array`` differs in value extraction when rerendering
1842
+ # forms. Normally the value gets fetched from the getter if not found
1843
+ # on request. Since it's hard to reference the origin value for array
1844
+ # entries if not found on request - you have possibly a mutable array
1845
+ # containing componds with some fields disabled or in display mode - a
1846
+ # hidden field is added for such widgets in the tree on the fly in
1847
+ # order to rerender forms correctly
1848
+ form = factory(
1849
+ 'form',
1850
+ name='myform',
1851
+ props={'action': 'myaction'})
1852
+ form['myarray'] = factory(
1853
+ 'array',
1854
+ value=[{'f1': 'foo1', 'f2': 'foo2'}],
1855
+ props={'label': 'My Compound Array with display children'})
1856
+ form['myarray']['mycompound'] = factory('compound')
1857
+ form['myarray']['mycompound']['f1'] = factory(
1858
+ 'field:label:text',
1859
+ props={'label': 'F1'},
1860
+ mode='display')
1861
+ form['myarray']['mycompound']['f2'] = factory(
1862
+ 'field:label:text',
1863
+ props={'label': 'F2', 'disabled': 'disabled'})
1864
+ self.checkOutput("""
1865
+ <form action="myaction" enctype="multipart/form-data" id="form-myform"
1866
+ method="post" novalidate="novalidate">
1867
+ <div class="array card array-add array-remove array-sort"
1868
+ id="array-myform-myarray">
1869
+ <table>
1870
+ ...
1871
+ <td class="widget">
1872
+ <input id="input-myform-myarray-0-f1"
1873
+ name="myform.myarray.0.f1"
1874
+ type="hidden" value="foo1"/>
1875
+ <div class="field" id="field-myform-myarray-0-f1">
1876
+ <label>F1</label>
1877
+ <div class="display-text"
1878
+ id="display-myform-myarray-0-f1">foo1</div>
1879
+ </div>
1880
+ <input id="input-myform-myarray-0-f2"
1881
+ name="myform.myarray.0.f2"
1882
+ type="hidden" value="foo2"/>
1883
+ <div class="field" id="field-myform-myarray-0-f2">
1884
+ <label for="input-myform-myarray-0-f2">F2</label>
1885
+ <input class="text" disabled="disabled"
1886
+ id="input-myform-myarray-0-f2"
1887
+ name="myform.myarray.0.f2"
1888
+ type="text" value="foo2"/>
1889
+ </div>
1890
+ </td>
1891
+ ...
1892
+ </table>
1893
+ ...
1894
+ </form>
1895
+ """, fxml(form()))
1896
+
1897
+ def test_callable_array_label(self):
1898
+ form = factory(
1899
+ 'form',
1900
+ name='myform',
1901
+ props={'action': 'myaction'})
1902
+ form['myarray'] = factory(
1903
+ 'array',
1904
+ props={'label': lambda: 'Callable label'})
1905
+ form['myarray']['f1'] = factory(
1906
+ 'field:label:text',
1907
+ props={'label': 'F1'},
1908
+ mode='display')
1909
+ self.checkOutput("""
1910
+ <form action="myaction" enctype="multipart/form-data" id="form-myform"
1911
+ method="post" novalidate="novalidate">
1912
+ <div class="array card array-add array-remove array-sort"
1913
+ id="array-myform-myarray">
1914
+ <table>
1915
+ <thead>
1916
+ <tr>
1917
+ <th>Callable label</th>
1918
+ ...
1919
+ </form>
1920
+ """, fxml(form()))
1921
+
1922
+ def test_3_dimensional_array(self):
1923
+ form = factory(
1924
+ 'form',
1925
+ name='myform',
1926
+ props={'action': 'myaction'})
1927
+ arr_1 = form['array_1'] = factory(
1928
+ 'array',
1929
+ value=[
1930
+ [
1931
+ ['1'],
1932
+ ],
1933
+ [
1934
+ ['2'],
1935
+ ],
1936
+ ],
1937
+ props={
1938
+ 'label': 'Array 1',
1939
+ })
1940
+ arr_2 = arr_1['array_2'] = factory(
1941
+ 'array',
1942
+ props={
1943
+ 'label': 'Array 2',
1944
+ })
1945
+ arr_3 = arr_2['array_3'] = factory(
1946
+ 'array',
1947
+ props={
1948
+ 'label': 'Array 3',
1949
+ })
1950
+ arr_3['textfield'] = factory(
1951
+ 'field:error:label:text',
1952
+ props={
1953
+ 'label': 'Text Field',
1954
+ 'required': 'Text Field is required',
1955
+ })
1956
+
1957
+ self.assertEqual(form.treerepr().split('\n'), [
1958
+ "<class 'yafowil.base.Widget'>: myform",
1959
+ " <class 'yafowil.base.Widget'>: array_1",
1960
+ " <class 'yafowil.base.Widget'>: table",
1961
+ " <class 'yafowil.base.Widget'>: head",
1962
+ " <class 'yafowil.base.Widget'>: row",
1963
+ " <class 'yafowil.base.Widget'>: label",
1964
+ " <class 'yafowil.base.Widget'>: actions",
1965
+ " <class 'yafowil.base.Widget'>: body",
1966
+ " <class 'yafowil.base.Widget'>: array_2",
1967
+ " <class 'yafowil.base.Widget'>: table",
1968
+ " <class 'yafowil.base.Widget'>: head",
1969
+ " <class 'yafowil.base.Widget'>: row",
1970
+ " <class 'yafowil.base.Widget'>: label",
1971
+ " <class 'yafowil.base.Widget'>: actions",
1972
+ " <class 'yafowil.base.Widget'>: body",
1973
+ " <class 'yafowil.base.Widget'>: array_3",
1974
+ " <class 'yafowil.base.Widget'>: table",
1975
+ " <class 'yafowil.base.Widget'>: head",
1976
+ " <class 'yafowil.base.Widget'>: row",
1977
+ " <class 'yafowil.base.Widget'>: label",
1978
+ " <class 'yafowil.base.Widget'>: actions",
1979
+ " <class 'yafowil.base.Widget'>: body",
1980
+ " <class 'yafowil.base.Widget'>: textfield",
1981
+ ""
1982
+ ])
1983
+
1984
+ self.checkOutput("""
1985
+ <form action="myaction" enctype="multipart/form-data" id="form-myform"
1986
+ method="post" novalidate="novalidate">
1987
+ <div class="array card array-add array-remove array-sort"
1988
+ id="array-myform-array_1">
1989
+ <table>
1990
+ <thead>
1991
+ <tr>
1992
+ <th>Array 1</th>
1993
+ ...
1994
+ </tr>
1995
+ </thead>
1996
+ <tbody>
1997
+ <tr>
1998
+ <td class="widget">
1999
+ <div class="array card array-add array-remove array-sort"
2000
+ id="array-myform-array_1-0">
2001
+ <table>
2002
+ <thead>
2003
+ <tr>
2004
+ <th>Array 2</th>
2005
+ ...
2006
+ </tr>
2007
+ </thead>
2008
+ <tbody>
2009
+ <tr>
2010
+ <td class="widget">
2011
+ <div class="array card array-add array-remove array-sort"
2012
+ id="array-myform-array_1-0-0">
2013
+ <table>
2014
+ <thead>
2015
+ <tr>
2016
+ <th>Array 3</th>
2017
+ ...
2018
+ </tr>
2019
+ </thead>
2020
+ <tbody>
2021
+ <tr>
2022
+ <td class="widget">
2023
+ <div class="field" id="field-myform-array_1-0-0-0">
2024
+ <label for="input-myform-array_1-0-0-0">Text Field</label>
2025
+ <input class="required text"
2026
+ id="input-myform-array_1-0-0-0"
2027
+ name="myform.array_1.0.0.0"
2028
+ required="required"
2029
+ type="text" value="1"/>
2030
+ </div>
2031
+ </td>
2032
+ <td class="actions">
2033
+ ...
2034
+ </td>
2035
+ </tr>
2036
+ </tbody>
2037
+ </table>
2038
+ <div class="arraytemplate">
2039
+ <div class="field" id="field-myform-array_1-0-0-TEMPLATE">
2040
+ <label for="input-myform-array_1-0-0-TEMPLATE">Text Field</label>
2041
+ <input class="required text"
2042
+ id="input-myform-array_1-0-0-TEMPLATE"
2043
+ name="myform.array_1.0.0.TEMPLATE"
2044
+ required="required"
2045
+ type="text" value=""/>
2046
+ </div>
2047
+ </div>
2048
+ </div>
2049
+ </td>
2050
+ <td class="actions">
2051
+ ...
2052
+ </td>
2053
+ </tr>
2054
+ </tbody>
2055
+ </table>
2056
+ <div class="arraytemplate">
2057
+ <div class="array card array-add array-remove array-sort" id="array-myform-array_1-0-TEMPLATE">
2058
+ <table>
2059
+ <thead>
2060
+ <tr>
2061
+ <th>Array 3</th>
2062
+ ...
2063
+ </tr>
2064
+ </thead>
2065
+ <tbody/>
2066
+ </table>
2067
+ <div class="arraytemplate">
2068
+ <div class="field"
2069
+ id="field-myform-array_1-0-TEMPLATE-TEMPLATE">
2070
+ <label for="input-myform-array_1-0-TEMPLATE-TEMPLATE">Text Field</label>
2071
+ <input class="required text"
2072
+ id="input-myform-array_1-0-TEMPLATE-TEMPLATE"
2073
+ name="myform.array_1.0.TEMPLATE.TEMPLATE"
2074
+ required="required"
2075
+ type="text" value=""/>
2076
+ </div>
2077
+ </div>
2078
+ </div>
2079
+ </div>
2080
+ </div>
2081
+ </td>
2082
+ <td class="actions">
2083
+ ...
2084
+ </td>
2085
+ </tr>
2086
+ <tr>
2087
+ ...
2088
+ </tr>
2089
+ </tbody>
2090
+ </table>
2091
+ <div class="arraytemplate">
2092
+ <div class="array card array-add array-remove array-sort"
2093
+ id="array-myform-array_1-TEMPLATE">
2094
+ <table>
2095
+ <thead>
2096
+ <tr>
2097
+ <th>Array 2</th>
2098
+ ...
2099
+ </tr>
2100
+ </thead>
2101
+ <tbody/>
2102
+ </table>
2103
+ <div class="arraytemplate">
2104
+ <div class="array card array-add array-remove array-sort"
2105
+ id="array-myform-array_1-TEMPLATE-TEMPLATE">
2106
+ <table>
2107
+ <thead>
2108
+ <tr>
2109
+ <th>Array 3</th>
2110
+ ...
2111
+ </tr>
2112
+ </thead>
2113
+ <tbody/>
2114
+ </table>
2115
+ <div class="arraytemplate">
2116
+ <div class="field" id="field-myform-array_1-TEMPLATE-TEMPLATE-TEMPLATE">
2117
+ <label for="input-myform-array_1-TEMPLATE-TEMPLATE-TEMPLATE">Text Field</label>
2118
+ <input class="required text"
2119
+ id="input-myform-array_1-TEMPLATE-TEMPLATE-TEMPLATE"
2120
+ name="myform.array_1.TEMPLATE.TEMPLATE.TEMPLATE"
2121
+ required="required" type="text" value=""/>
2122
+ </div>
2123
+ </div>
2124
+ </div>
2125
+ </div>
2126
+ </div>
2127
+ </div>
2128
+ </div>
2129
+ </form>
2130
+ """, fxml(form()))
2131
+
2132
+ def test_resources(self):
2133
+ factory.theme = 'default'
2134
+ resources = factory.get_resources('yafowil.widget.array')
2135
+ self.assertTrue(resources.directory.endswith(np('/array/resources')))
2136
+ self.assertEqual(resources.name, 'yafowil.widget.array')
2137
+ self.assertEqual(resources.path, 'yafowil-array')
2138
+
2139
+ scripts = resources.scripts
2140
+ self.assertEqual(len(scripts), 1)
2141
+
2142
+ self.assertTrue(scripts[0].directory.endswith(np('/array/resources/default')))
2143
+ self.assertEqual(scripts[0].path, 'yafowil-array/default')
2144
+ self.assertEqual(scripts[0].file_name, 'widget.min.js')
2145
+ self.assertTrue(os.path.exists(scripts[0].file_path))
2146
+
2147
+ styles = resources.styles
2148
+ self.assertEqual(len(styles), 1)
2149
+
2150
+ self.assertTrue(
2151
+ styles[0].directory.endswith(np('/array/resources/default'))
2152
+ )
2153
+ self.assertEqual(styles[0].path, 'yafowil-array/default')
2154
+ self.assertEqual(styles[0].file_name, 'widget.min.css')
2155
+ self.assertTrue(os.path.exists(styles[0].file_path))
2156
+
2157
+ factory.theme = 'bootstrap3'
2158
+ resources = factory.get_resources('yafowil.widget.array')
2159
+ self.assertTrue(resources.directory.endswith(np('/array/resources')))
2160
+ self.assertEqual(resources.name, 'yafowil.widget.array')
2161
+ self.assertEqual(resources.path, 'yafowil-array')
2162
+
2163
+ scripts = resources.scripts
2164
+ self.assertEqual(len(scripts), 1)
2165
+
2166
+ self.assertTrue(scripts[0].directory.endswith(np('/array/resources/default')))
2167
+ self.assertEqual(scripts[0].path, 'yafowil-array/default')
2168
+ self.assertEqual(scripts[0].file_name, 'widget.min.js')
2169
+ self.assertTrue(os.path.exists(scripts[0].file_path))
2170
+
2171
+ styles = resources.styles
2172
+ self.assertEqual(len(styles), 1)
2173
+
2174
+ self.assertTrue(
2175
+ styles[0].directory.endswith(np('/array/resources/bootstrap'))
2176
+ )
2177
+ self.assertEqual(styles[0].path, 'yafowil-array/bootstrap')
2178
+ self.assertEqual(styles[0].file_name, 'widget.min.css')
2179
+ self.assertTrue(os.path.exists(styles[0].file_path))
2180
+
2181
+ factory.theme = 'plone5'
2182
+ resources = factory.get_resources('yafowil.widget.array')
2183
+ self.assertTrue(resources.directory.endswith(np('/array/resources')))
2184
+ self.assertEqual(resources.name, 'yafowil.widget.array')
2185
+ self.assertEqual(resources.path, 'yafowil-array')
2186
+
2187
+ scripts = resources.scripts
2188
+ self.assertEqual(len(scripts), 1)
2189
+
2190
+ self.assertTrue(scripts[0].directory.endswith(np('/array/resources/default')))
2191
+ self.assertEqual(scripts[0].path, 'yafowil-array/default')
2192
+ self.assertEqual(scripts[0].file_name, 'widget.min.js')
2193
+ self.assertTrue(os.path.exists(scripts[0].file_path))
2194
+
2195
+ styles = resources.styles
2196
+ self.assertEqual(len(styles), 1)
2197
+
2198
+ self.assertTrue(
2199
+ styles[0].directory.endswith(np('/array/resources/plone5'))
2200
+ )
2201
+ self.assertEqual(styles[0].path, 'yafowil-array/plone5')
2202
+ self.assertEqual(styles[0].file_name, 'widget.min.css')
2203
+ self.assertTrue(os.path.exists(styles[0].file_path))