Projet

Général

Profil

0001-general-add-support-for-a-readonly-marker-in-objects.patch

Frédéric Péters, 17 août 2020 18:30

Télécharger (31,1 ko)

Voir les différences:

Subject: [PATCH] general: add support for a readonly marker in objects
 (#45924)

 wcs/admin/blocks.py                           |  9 +-
 wcs/admin/data_sources.py                     |  6 +-
 wcs/admin/fields.py                           | 14 +++-
 wcs/admin/forms.py                            | 28 +++++--
 wcs/admin/workflows.py                        | 83 ++++++++++---------
 wcs/admin/wscalls.py                          | 12 ++-
 wcs/backoffice/cards.py                       | 10 ++-
 wcs/blocks.py                                 |  1 +
 wcs/data_sources.py                           |  1 +
 wcs/formdef.py                                |  1 +
 wcs/qommon/storage.py                         |  5 ++
 wcs/templates/wcs/backoffice/data-source.html |  2 +
 wcs/wf/form.py                                |  2 +
 wcs/workflows.py                              |  7 ++
 wcs/wscalls.py                                |  1 +
 15 files changed, 120 insertions(+), 62 deletions(-)
wcs/admin/blocks.py
23 23
from wcs.blocks import BlockDef, BlockdefImportError
24 24

  
25 25
from wcs.qommon.form import Form, StringWidget, HtmlWidget, FileWidget
26
from wcs.qommon import _, misc, template
26
from wcs.qommon import _, N_, misc, template
27 27
from wcs.qommon.backoffice.menu import html_top
28 28

  
29 29
from wcs.admin.fields import FieldDefPage, FieldsDirectory
......
43 43
    field_def_page_class = BlockFieldDefPage
44 44
    blacklisted_types = ['page', 'table', 'table-select', 'tablerows', 'ranked-items', 'blocks']
45 45
    support_import = False
46
    readonly_message = N_('This block of fields is readonly.')
46 47

  
47 48
    def __init__(self, section, *args, **kwargs):
48 49
        self.section = section
......
57 58
        r += htmltext('<div id="appbar">')
58 59
        r += htmltext('<h2>%s</h2>') % self.objectdef.name
59 60
        r += htmltext('<span class="actions">')
60
        r += htmltext('<a href="delete" rel="popup">%s</a>') % _('Delete')
61
        if not self.objectdef.is_readonly():
62
            r += htmltext('<a href="delete" rel="popup">%s</a>') % _('Delete')
61 63
        r += htmltext('<a href="export">%s</a>') % _('Export')
62 64
        r += htmltext('<a href="settings" rel="popup">%s</a>') % _('Settings')
63 65
        r += htmltext('</span>')
......
137 139
        form.add(
138 140
            StringWidget, 'digest_template', title=_('Digest Template'), value=self.objectdef.digest_template, size=50
139 141
        )
140
        form.add_submit('submit', _('Submit'))
142
        if not self.objectdef.is_readonly():
143
            form.add_submit('submit', _('Submit'))
141 144
        form.add_submit('cancel', _('Cancel'))
142 145

  
143 146
        if form.get_widget('cancel').parse():
wcs/admin/data_sources.py
120 120
                    title=_('Identifier'),
121 121
                    required=True, advanced=True,
122 122
                    )
123
        form.add_submit('submit', _('Submit'))
123
        if not self.datasource.is_readonly():
124
            form.add_submit('submit', _('Submit'))
124 125
        form.add_submit('cancel', _('Cancel'))
125 126
        return form
126 127

  
......
167 168

  
168 169
    def _q_index(self):
169 170
        html_top('datasources', title=self.datasource.name)
171
        if self.datasource.is_readonly():
172
            get_response().filter['sidebar'] = htmltext(
173
                    '<div class="infonotice"><p>%s</p></div>') % _('This data source is readonly.')
170 174
        return template.QommonTemplateResponse(
171 175
                templates=['wcs/backoffice/data-source.html'],
172 176
                context={'view': self, 'datasource': self.datasource})
wcs/admin/fields.py
56 56
        self.field.fill_admin_form(form)
57 57
        form.widgets = [x for x in form.widgets
58 58
                if getattr(x, 'name', None) not in self.blacklisted_attributes]
59
        form.add_submit('submit', _('Submit'))
59
        if not self.objectdef.is_readonly():
60
            form.add_submit('submit', _('Submit'))
60 61
        form.add_submit('cancel', _('Cancel'))
61 62
        return form
62 63

  
......
201 202
    blacklisted_types = []
202 203
    page_id = None
203 204
    field_var_prefix = '..._'
205
    readonly_message = N_('The fields are readonly.')
204 206

  
205 207
    support_import = True
206 208

  
......
313 315
                        r += command_icon('pages/%s/' % field.id, 'view',
314 316
                                label = _('Limit display to this page'))
315 317
                    r += command_icon('%s/' % field.id, 'edit')
316
                r += command_icon('%s/duplicate' % field.id, 'duplicate')
317
                r += command_icon('%s/delete' % field.id, 'remove', popup = True)
318
                if not self.objectdef.is_readonly():
319
                    r += command_icon('%s/duplicate' % field.id, 'duplicate')
320
                    r += command_icon('%s/delete' % field.id, 'remove', popup=True)
318 321
                r += htmltext('</p></li>')
319 322
            r += htmltext('</ul>')
320 323

  
321
        get_response().filter['sidebar'] = str(self.get_new_field_form(self.page_id))
324
        if self.objectdef.is_readonly():
325
            get_response().filter['sidebar'] = '<div class="infonotice"><p>%s</p></div>' % _(self.readonly_message)
326
        else:
327
            get_response().filter['sidebar'] = str(self.get_new_field_form(self.page_id))
322 328
        r += self.index_bottom()
323 329
        return r.getvalue()
324 330

  
wcs/admin/forms.py
90 90
            form.add(SingleSelectWidget, 'workflow_id', title=_('Workflow'),
91 91
                    value=formdef.workflow_id,
92 92
                    options=workflows)
93
        form.add_submit('submit', _('Submit'))
93
        if not formdef.is_readonly():
94
            form.add_submit('submit', _('Submit'))
94 95
        form.add_submit('cancel', _('Cancel'))
95 96
        return form
96 97

  
......
136 137
class FieldsDirectory(FieldsDirectory):
137 138
    field_def_page_class = FieldDefPage
138 139
    field_var_prefix = 'form_var_'
140
    readonly_message = N_('This form is readonly.')
139 141

  
140 142
    def index_bottom(self):
141 143
        if hasattr(self.objectdef, str('disabled')) and self.objectdef.disabled:
......
277 279
        return result
278 280

  
279 281
    def handle(self, form, title):
280
        form.add_submit('submit', _('Submit'))
282
        if not self.formdef.is_readonly():
283
            form.add_submit('submit', _('Submit'))
281 284
        form.add_submit('cancel', _('Cancel'))
282 285
        if form.get_widget('cancel').parse():
283 286
            return redirect('..')
......
330 333
        options.extend(get_user_roles())
331 334
        form = Form(enctype='multipart/form-data')
332 335
        form.add(SingleSelectWidget, 'role_id', value=role_id, options=options)
333
        form.add_submit('submit', _('Submit'))
336
        if not self.formdef.is_readonly():
337
            form.add_submit('submit', _('Submit'))
334 338
        form.add_submit('cancel', _('Cancel'))
335 339
        if form.get_widget('cancel').parse():
336 340
            return redirect('.')
......
404 408
        r += htmltext('<div id="appbar">')
405 409
        r += htmltext('<h2>%s</h2>') % self.formdef.name
406 410
        r += htmltext('<span class="actions">')
407
        r += htmltext('<a rel="popup" href="title">%s</a>') % _('change title')
411
        if not self.formdef.is_readonly():
412
            r += htmltext('<a rel="popup" href="title">%s</a>') % _('change title')
408 413
        r += htmltext('</span>')
409 414
        r += htmltext('</div>')
410 415

  
......
604 609

  
605 610
    def get_sidebar(self):
606 611
        r = TemplateIO(html=True)
612
        if self.formdef.is_readonly():
613
            r += htmltext('<div class="infonotice"><p>%s</p></div>') % _('This form is readonly.')
614
            return r.getvalue()
607 615
        r += htmltext('<ul id="sidebar-actions">')
608 616
        r += htmltext('<li><a href="delete" rel="popup">%s</a></li>') % _('Delete')
609 617
        r += htmltext('<li><a href="duplicate">%s</a></li>') % _('Duplicate')
......
658 666
        form.add(SingleSelectWidget, 'category_id',
659 667
                        value=self.formdef.category_id,
660 668
                        options=[(None, '---', '')] + categories)
661
        form.add_submit('submit', _('Submit'))
669
        if not self.formdef.is_readonly():
670
            form.add_submit('submit', _('Submit'))
662 671
        form.add_submit('cancel', _('Cancel'))
663 672
        if form.get_widget('cancel').parse():
664 673
            return redirect('.')
......
697 706
                    title=_('Required authentication contexts'),
698 707
                    value=self.formdef.required_authentication_contexts,
699 708
                    options=list(auth_contexts.items()))
700
        form.add_submit('submit', _('Submit'))
709
        if not self.formdef.is_readonly():
710
            form.add_submit('submit', _('Submit'))
701 711
        form.add_submit('cancel', _('Cancel'))
702 712
        if form.get_widget('cancel').parse():
703 713
            return redirect('.')
......
750 760
        form.add(ValidatedStringWidget, 'url_name', title=_('Identifier in URLs'),
751 761
                size=40, required=True, value=self.formdef.url_name,
752 762
                regex=r'^[a-zA-Z0-9_-]+', **kwargs)
753
        form.add_submit('submit', _('Submit'))
763
        if not self.formdef.is_readonly():
764
            form.add_submit('submit', _('Submit'))
754 765
        form.add_submit('cancel', _('Cancel'))
755 766
        if form.get_widget('cancel').parse():
756 767
            return redirect('.')
......
789 800
        form.add(SingleSelectWidget, 'workflow_id',
790 801
                        value=self.formdef.workflow_id,
791 802
                        options=workflows)
792
        form.add_submit('submit', _('Submit'))
803
        if not self.formdef.is_readonly():
804
            form.add_submit('submit', _('Submit'))
793 805
        form.add_submit('cancel', _('Cancel'))
794 806
        if form.get_widget('cancel').parse():
795 807
            return redirect('.')
wcs/admin/workflows.py
277 277
        form = Form(enctype='multipart/form-data')
278 278
        self.item.fill_admin_form(form)
279 279

  
280
        if not str(self.workflow.id).startswith(str('_')):
280
        if not self.workflow.is_readonly():
281 281
            form.add_submit('submit', _('Submit'))
282 282
        form.add_submit('cancel', _('Cancel'))
283 283

  
......
436 436
        if self.status.get_visibility_restricted_roles():
437 437
            r += htmltext('<div class="bo-block">')
438 438
            r += _('This status is hidden from the user.')
439
            if not str(self.workflow.id).startswith(str('_')):
439
            if not self.workflow.is_readonly():
440 440
                r += ' '
441 441
                r += htmltext('(<a href="display" rel="popup">%s</a>)') % _('change')
442 442
            r += htmltext('</div>')
......
445 445
            r += htmltext('<div class="infonotice">%s</div>') % _('There are not yet any items in this status.')
446 446
        else:
447 447
            r += htmltext('<div class="bo-block">')
448
            if str(self.workflow.id).startswith(str('_')):
448
            if self.workflow.is_readonly():
449 449
                r += htmltext('<ul id="items-list" class="biglist">')
450 450
            else:
451 451
                r += htmltext('<p class="hint">')
......
454 454
                r += htmltext('<ul id="items-list" class="biglist sortable">')
455 455
            for i, item in enumerate(self.status.items):
456 456
                r += htmltext('<li class="biglistitem" id="itemId_%s">') % item.id
457
                if str(self.workflow.id).startswith(str('_')):
458
                    r += item.render_as_line()
457
                if hasattr(item, str('fill_admin_form')):
458
                    r += htmltext('<a href="items/%s/">%s</a>') % (item.id, item.render_as_line())
459 459
                else:
460
                    if hasattr(item, str('fill_admin_form')):
461
                        r += htmltext('<a href="items/%s/">%s</a>') % (item.id, item.render_as_line())
462
                    else:
463
                        r += item.render_as_line()
464
                    r += htmltext('<p class="commands">')
465
                    if hasattr(item, str('fill_admin_form')):
460
                    r += item.render_as_line()
461
                r += htmltext('<p class="commands">')
462
                if not self.workflow.is_readonly():
463
                    if hasattr(item, 'fill_admin_form'):
466 464
                        r += command_icon('items/%s/' % item.id, 'edit')
467 465
                    r += command_icon('items/%s/delete' % item.id, 'remove', popup = True)
468
                    r += htmltext('</p>')
466
                r += htmltext('</p>')
469 467
                r += htmltext('</li>')
470 468
            r += htmltext('</ul>')
471 469
            r += htmltext('</div>') # bo-block
......
509 507
    def get_sidebar(self):
510 508
        get_response().add_javascript(['popup.js', 'jquery.colourpicker.js'])
511 509
        r = TemplateIO(html=True)
512
        if str(self.workflow.id).startswith(str('_')):
510
        if self.workflow.is_default():
513 511
            r += htmltext('<p>')
514 512
            r += _('''This is the default workflow, you cannot edit it but you can
515 513
                 duplicate it to base your own workflow on it.''')
516 514
            r += htmltext('</p>')
515
        elif self.workflow.is_readonly():
516
            r += htmltext('<div class="infonotice"><p>%s</p></div>') % _('This workflow is readonly.')
517 517
        else:
518 518
            r += htmltext('<ul id="sidebar-actions">')
519 519
            r += htmltext('<li><a href="edit" rel="popup">%s</a></li>') % _('Change Status Name')
......
885 885
    support_import = False
886 886
    blacklisted_types = ['page']
887 887
    field_var_prefix = 'form_option_'
888
    readonly_message = N_('This workflow is readonly.')
888 889

  
889 890
    def index_top(self):
890 891
        r = TemplateIO(html=True)
......
908 909
    blacklisted_types = ['page']
909 910
    blacklisted_attributes = ['condition']
910 911
    field_var_prefix = 'form_var_'
912
    readonly_message = N_('This workflow is readonly.')
911 913

  
912 914
    def index_top(self):
913 915
        r = TemplateIO(html=True)
......
1165 1167
        if not self.action.items:
1166 1168
            r += htmltext('<p>%s</p>') % _('There are not yet any items in this action.')
1167 1169
        else:
1168
            if str(self.workflow.id).startswith('_'):
1170
            if self.workflow.is_readonly():
1169 1171
                r += htmltext('<ul id="items-list" class="biglist">')
1170 1172
            else:
1171 1173
                r += htmltext('<p class="items">')
......
1174 1176
                r += htmltext('<ul id="items-list" class="biglist sortable">')
1175 1177
            for i, item in enumerate(self.action.items):
1176 1178
                r += htmltext('<li class="biglistitem" id="itemId_%s">') % item.id
1177
                if str(self.workflow.id).startswith('_'):
1179
                if self.workflow.is_readonly():
1178 1180
                    r += item.render_as_line()
1179 1181
                else:
1180 1182
                    if hasattr(item, 'fill_admin_form'):
......
1259 1261
    def get_sidebar(self):
1260 1262
        get_response().add_javascript(['popup.js', 'jquery.colourpicker.js'])
1261 1263
        r = TemplateIO(html=True)
1262
        if str(self.workflow.id).startswith('_'):
1264
        if self.workflow.is_default():
1263 1265
            r += htmltext('<p>')
1264 1266
            r += _('''This is the default workflow, you cannot edit it but you can
1265 1267
                 duplicate it to base your own workflow on it.''')
......
1394 1396
            'svg-pan-zoom.js', 'jquery.colourpicker.js'])
1395 1397
        r += htmltext('<div id="appbar">')
1396 1398
        r += htmltext('<h2>%s</h2>') % self.workflow.name
1397
        if not str(self.workflow.id).startswith('_'):
1399
        if not self.workflow.is_readonly():
1398 1400
            r += htmltext('<span class="actions">')
1399 1401
            r += htmltext('<a rel="popup" href="edit">%s</a>') % _('change title')
1400 1402
            r += htmltext('</span>')
......
1411 1413
        if not self.workflow.possible_status:
1412 1414
            r += htmltext('<p>%s</p>') % _('There are not yet any status defined in this workflow.')
1413 1415
        else:
1414
            if not str(self.workflow.id).startswith(str('_')):
1416
            if not self.workflow.is_readonly():
1415 1417
                r += htmltext('<p class="hint">')
1416 1418
                r += _('Use drag and drop with the handles to reorder status.')
1417 1419
                r += htmltext('</p>')
......
1438 1440
        r += htmltext('<div class="splitcontent-right">')
1439 1441
        r += htmltext('<div class="bo-block">')
1440 1442
        r += htmltext('<h3>%s') % _('Workflow Functions')
1441
        if not str(self.workflow.id).startswith('_'):
1443
        if not self.workflow.is_readonly():
1442 1444
            r += htmltext(' <span class="change">(<a rel="popup" href="functions/new">%s</a>)</span>') % _('add function')
1443 1445
        r += htmltext('</h3>')
1444 1446
        r += htmltext('<ul id="roles-list" class="biglist">')
......
1446 1448
        workflow_roles.sort(key=lambda x: '' if x[0] == '_receiver' else misc.simplify(x[1]))
1447 1449
        for key, label in workflow_roles:
1448 1450
            r += htmltext('<li class="biglistitem">')
1449
            if not str(self.workflow.id).startswith('_'):
1451
            if not self.workflow.is_readonly():
1450 1452
                r += htmltext('<a rel="popup" href="functions/%s">%s</a>') % (key[1:], label)
1451 1453
            else:
1452 1454
                r += htmltext('<a>%s</a>') % label
......
1454 1456
        r += htmltext('</ul>')
1455 1457
        r += htmltext('</div>')
1456 1458

  
1457
        if not str(self.workflow.id).startswith('_'):
1459
        if not self.workflow.is_default():
1458 1460
            r += htmltext('<div class="bo-block">')
1459 1461
            r += htmltext('<h3>%s') % _('Workflow Variables')
1460
            r += htmltext(' <span class="change">(<a href="variables/">%s</a>)</span></h3>') % _('change')
1462
            if not self.workflow.is_readonly():
1463
                r += htmltext(' <span class="change">(<a href="variables/">%s</a>)</span>') % _('change')
1464
            r += htmltext('</h3>')
1461 1465
            if self.workflow.variables_formdef:
1462 1466
                r += htmltext('<ul class="biglist">')
1463 1467
                for field in self.workflow.variables_formdef.fields:
......
1470 1474
                r += htmltext('</ul>')
1471 1475
            r += htmltext('</div>')
1472 1476

  
1473
        if not str(self.workflow.id).startswith('_'):
1477
        if not self.workflow.is_default():
1474 1478
            r += htmltext('<div class="bo-block">')
1475 1479
            r += htmltext('<h3>%s') % _('Global Actions')
1476
            if not str(self.workflow.id).startswith('_'):
1480
            if not self.workflow.is_readonly():
1477 1481
                r += htmltext(' <span class="change">(<a rel="popup" href="global-actions/new">%s</a>)</span>') % _('add global action')
1478 1482
            r += htmltext('</h3>')
1479 1483

  
1480
            if not str(self.workflow.id).startswith('_'):
1484
            if not self.workflow.is_readonly():
1481 1485
                r += htmltext('<ul id="status-list" class="biglist sortable" '
1482 1486
                              'data-order-function="update_actions_order">')
1483 1487
            else:
......
1485 1489

  
1486 1490
            for action in (self.workflow.global_actions or []):
1487 1491
                r += htmltext('<li class="biglistitem" id="itemId_%s">' % action.id)
1488
                if not str(self.workflow.id).startswith('_'):
1489
                    r += htmltext('<a href="global-actions/%s/">%s</a>') % (
1492
                r += htmltext('<a href="global-actions/%s/">%s</a>') % (
1490 1493
                            action.id, action.name)
1491
                else:
1492
                    r += htmltext('<a>%s</a>') % action.name
1493 1494
                r += htmltext('</li>')
1494 1495
            r += htmltext('</ul>')
1495 1496
            r += htmltext('</div>')
1496 1497

  
1497
        if not str(self.workflow.id).startswith('_'):
1498
        if not self.workflow.is_default():
1498 1499
            r += htmltext('<div class="bo-block">')
1499 1500
            r += htmltext('<h3>%s') % _('Criticality Levels')
1500
            if not str(self.workflow.id).startswith('_'):
1501
            if not self.workflow.is_readonly():
1501 1502
                r += htmltext(' <span class="change">'
1502 1503
                              '(<a rel="popup" href="criticality-levels/new">'
1503 1504
                              '%s</a>)</span>') % _('add criticality level')
......
1509 1510
                if level.colour:
1510 1511
                    style = 'style="border-left-color: #%s"' % level.colour
1511 1512
                r += htmltext('<li class="biglistitem" id="itemId_%s" %s>' % (level.id, style))
1512
                if not str(self.workflow.id).startswith('_'):
1513
                    r += htmltext('<a rel="popup" href="criticality-levels/%s">%s</a>') % (
1513
                r += htmltext('<a rel="popup" href="criticality-levels/%s">%s</a>') % (
1514 1514
                            level.id, level.name)
1515
                else:
1516
                    r += htmltext('<a>%s</a>') % level.name
1517 1515
                r += htmltext('</li>')
1518 1516
            r += htmltext('</ul>')
1519 1517
            r += htmltext('</div>')
1520 1518

  
1521
        if not str(self.workflow.id).startswith('_'):
1519
        if not self.workflow.is_default():
1522 1520
            r += htmltext('<div class="bo-block">')
1523 1521
            r += htmltext('<h3>%s') % _('Backoffice Fields')
1524
            r += htmltext(' <span class="change">(<a href="backoffice-fields/">%s</a>)</span></h3>') % _('change')
1522
            if not self.workflow.is_readonly():
1523
                r += htmltext(' <span class="change">(<a href="backoffice-fields/">%s</a>)</span></h3>') % _('change')
1525 1524
            if self.workflow.backoffice_fields_formdef:
1526 1525
                r += htmltext('<ul class="biglist">')
1527 1526
                for field in self.workflow.backoffice_fields_formdef.fields:
......
1558 1557

  
1559 1558
    def get_sidebar(self):
1560 1559
        r = TemplateIO(html=True)
1561
        if str(self.workflow.id).startswith(str('_')):
1560

  
1561
        if self.workflow.is_default():
1562 1562
            r += htmltext('<p>')
1563 1563
            r += _('''This is the default workflow, you cannot edit it but you can
1564 1564
                 duplicate it to base your own workflow on it.''')
1565 1565
            r += htmltext('</p>')
1566
        elif self.workflow.is_readonly():
1567
            r += htmltext('<div class="infonotice"><p>%s</p></div>') % _('This workflow is readonly.')
1568
            return r.getvalue()
1566 1569

  
1567 1570
        r += htmltext('<ul id="sidebar-actions">')
1568
        if not str(self.workflow.id).startswith(str('_')):
1571
        if not self.workflow.is_readonly():
1569 1572
            r += htmltext('<li><a href="delete" rel="popup">%s</a></li>') % _('Delete')
1570 1573
        r += htmltext('<li><a href="duplicate">%s</a></li>') % _('Duplicate')
1571 1574
        r += htmltext('<li><a href="export">%s</a></li>') % _('Export')
1572 1575
        r += htmltext('</ul>')
1573
        if not str(self.workflow.id).startswith(str('_')):
1576
        if not self.workflow.is_readonly():
1574 1577
            r += self.get_new_status_form()
1575 1578
        r += LoggedErrorsDirectory.errors_block(workflow_id=self.workflow.id)
1576 1579
        return r.getvalue()
wcs/admin/wscalls.py
50 50
                value=self.wscall.request,
51 51
                title=_('Request'), required=True)
52 52

  
53
        form.add_submit('submit', _('Submit'))
53
        if not self.wscall.is_readonly():
54
            form.add_submit('submit', _('Submit'))
54 55
        form.add_submit('cancel', _('Cancel'))
55 56
        return form
56 57

  
......
94 95
        html_top('wscalls', title=self.wscall.name)
95 96
        r = TemplateIO(html=True)
96 97

  
98
        if self.wscall.is_readonly():
99
            get_response().filter['sidebar'] = htmltext(
100
                    '<div class="infonotice"><p>%s</p></div>') % _('This webservice call is readonly.')
101

  
97 102
        r += htmltext('<div id="appbar">')
98 103
        r += htmltext('<h2>%s - ') % _('Webservice Call')
99 104
        r += self.wscall.name
100 105
        r += htmltext('</h2>')
101 106
        r += htmltext('<span class="actions">')
102
        r += htmltext('<a href="delete" rel="popup">%s</a>') % _('Delete')
103
        r += htmltext('<a href="edit">%s</a>') % _('Edit')
107
        if not self.wscall.is_readonly():
108
            r += htmltext('<a href="delete" rel="popup">%s</a>') % _('Delete')
109
            r += htmltext('<a href="edit">%s</a>') % _('Edit')
104 110
        r += htmltext('</span>')
105 111
        r += htmltext('</div>')
106 112

  
wcs/backoffice/cards.py
66 66

  
67 67
        r += htmltext('<div id="appbar">')
68 68
        r += htmltext('<h2>%s</h2>') % self.formdef.name
69
        r += htmltext('<span class="actions">')
70
        r += htmltext('<a rel="popup" href="title">%s</a>') % _('change title')
71
        r += htmltext('</span>')
69
        if not self.formdef.is_readonly():
70
            r += htmltext('<span class="actions">')
71
            r += htmltext('<a rel="popup" href="title">%s</a>') % _('change title')
72
            r += htmltext('</span>')
72 73
        r += htmltext('</div>')
73 74

  
74 75
        r += utils.last_modification_block(obj=self.formdef)
......
173 174

  
174 175
    def get_sidebar(self):
175 176
        r = TemplateIO(html=True)
177
        if self.formdef.is_readonly():
178
            r += htmltext('<div class="infonotice"><p>%s</p></div>') % _('This card model is readonly.')
179
            return r.getvalue()
176 180
        r += htmltext('<ul id="sidebar-actions">')
177 181
        r += htmltext('<li><a href="delete" rel="popup">%s</a></li>') % _('Delete')
178 182
        r += htmltext('<li><a href="duplicate">%s</a></li>') % _('Duplicate')
wcs/blocks.py
57 57
        self.fields = []
58 58

  
59 59
    def store(self):
60
        assert not self.is_readonly()
60 61
        if self.slug is None:
61 62
            # set slug if it's not yet there
62 63
            self.slug = self.get_new_slug()
wcs/data_sources.py
354 354
            self.store()
355 355

  
356 356
    def store(self):
357
        assert not self.is_readonly()
357 358
        if self.slug is None:
358 359
            # set slug if it's not yet there
359 360
            self.slug = self.get_new_slug()
wcs/formdef.py
366 366
        sql.formdef_wipe()
367 367

  
368 368
    def store(self):
369
        assert not self.is_readonly()
369 370
        if self.url_name is None:
370 371
            # set url name if it's not yet there
371 372
            self.url_name = self.get_new_url_name()
wcs/qommon/storage.py
292 292
    def __init__(self, id = None):
293 293
        self.id = id
294 294

  
295
    def is_readonly(self):
296
        return getattr(self, 'readonly', False)
297

  
295 298
    @classmethod
296 299
    def get_table_name(cls):
297 300
        return cls._names
......
594 597
        return pickle.dumps(object, protocol=2)
595 598

  
596 599
    def store(self, async_op=False):
600
        assert not self.is_readonly()
597 601
        objects_dir = self.get_objects_dir()
598 602
        new_object = False
599 603
        if self._filename:
......
810 814
        os.unlink(os.path.join(objects_dir, fix_key(id)))
811 815

  
812 816
    def remove_self(self):
817
        assert not self.is_readonly()
813 818
        self.remove_object(self.id)
814 819

  
815 820
    @classmethod
wcs/templates/wcs/backoffice/data-source.html
5 5
<h2>{% trans "Data Source" %} - {{ datasource.name }}</h2>
6 6
<span class="actions">
7 7
  <a href="export">{% trans "Export" %}</a>
8
  {% if not datasource.is_readonly %}
8 9
  <a href="delete" rel="popup">{% trans "Delete" %}</a>
9 10
  <a href="edit">{% trans "Edit" %}</a>
11
  {% endif %}
10 12
</span>
11 13
</div>
12 14

  
wcs/wf/form.py
155 155
        if component == 'fields':
156 156
            if not self.formdef:
157 157
                self.formdef = WorkflowFormFieldsFormDef(item=self)
158
            if workflow.is_readonly():
159
                self.formdef.readonly = True
158 160
            fields_directory = WorkflowFormFieldsDirectory(self.formdef)
159 161
            if self.varname:
160 162
                fields_directory.field_var_prefix = '%s_var_' % self.varname
wcs/workflows.py
348 348
            self.store()
349 349

  
350 350
    def store(self):
351
        assert not self.is_readonly()
351 352
        must_update = False
352 353
        if self.id:
353 354
            old_self = self.get(self.id, ignore_errors=True, ignore_migration=True)
......
849 850

  
850 851
        return workflow
851 852

  
853
    def is_default(self):
854
        return str(self.id).startswith(str('_'))
855

  
856
    def is_readonly(self):
857
        return self.is_default() or super().is_readonly()
858

  
852 859
    def formdefs(self, **kwargs):
853 860
        return list(FormDef.select(lambda x: x.workflow_id == self.id, **kwargs))
854 861

  
wcs/wscalls.py
176 176
        return request
177 177

  
178 178
    def store(self):
179
        assert not self.is_readonly()
179 180
        if self.slug is None:
180 181
            # set slug if it's not yet there
181 182
            self.slug = self.get_new_slug()
182
-