• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zopefoundation / Zope / 3956162881

pending completion
3956162881

push

github

Michael Howitz
Update to deprecation warning free releases.

4401 of 7036 branches covered (62.55%)

Branch coverage included in aggregate %.

27161 of 31488 relevant lines covered (86.26%)

0.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

42.07
/src/OFS/PropertySheets.py
1
##############################################################################
2
#
3
# Copyright (c) 2002 Zope Foundation and Contributors.
4
#
5
# This software is subject to the provisions of the Zope Public License,
6
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
7
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
8
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
9
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
10
# FOR A PARTICULAR PURPOSE.
11
#
12
##############################################################################
13
"""Property sheets
1✔
14
"""
15

16
import html
1✔
17

18
from AccessControl.class_init import InitializeClass
1✔
19
from AccessControl.Permissions import access_contents_information
1✔
20
from AccessControl.Permissions import manage_properties
1✔
21
from AccessControl.Permissions import view_management_screens
1✔
22
from AccessControl.SecurityInfo import ClassSecurityInfo
1✔
23
from Acquisition import Implicit
1✔
24
from Acquisition import aq_base
1✔
25
from Acquisition import aq_parent
1✔
26
from App.Management import Tabs
1✔
27
from App.special_dtml import DTMLFile
1✔
28
from ExtensionClass import Base
1✔
29
from OFS.Traversable import Traversable
1✔
30
from Persistence import Persistent
1✔
31
from webdav.PropertySheet import DAVPropertySheetMixin
1✔
32
from zExceptions import BadRequest
1✔
33
from ZPublisher.Converters import type_converters
1✔
34

35

36
class Virtual:
1✔
37
    """A virtual propertysheet stores it's properties in it's instance."""
38

39
    def __init__(self):
1✔
40
        pass
1✔
41

42
    def v_self(self):
1✔
43
        return aq_parent(aq_parent(self))
×
44

45

46
class View(Tabs, Base):
1✔
47
    """A view of an object, typically used for management purposes
48

49
    This class provides bits of management framework needed by propertysheets
50
    to be used as a view on an object.
51
    """
52

53
    def manage_workspace(self, URL1, RESPONSE):
1✔
54
        '''Implement a "management" interface
55
        '''
56
        RESPONSE.redirect(URL1 + '/manage')
×
57

58
    def tpURL(self):
1✔
59
        return self.getId()
×
60

61
    def manage_options(self):
1✔
62
        """Return a manage option data structure for me instance
63
        """
64
        try:
×
65
            request = self.REQUEST
×
66
        except Exception:
×
67
            request = None
×
68
        if request is None:
×
69
            pre = '../../'
×
70
        else:
71
            pre = request['URL']
×
72
            for i in (1, 2, 3):
×
73
                loc = pre.rfind('/')
×
74
                if loc >= 0:
×
75
                    pre = pre[:loc]
×
76
            pre = pre + '/'
×
77

78
        request = []
×
79
        for d in aq_parent(aq_parent(self)).manage_options:
×
80
            path = d['action']
×
81
            option = {
×
82
                'label': d['label'],
83
                'action': pre + path,
84
                'path': '../../' + path,
85
            }
86
            help = d.get('help')
×
87
            if help is not None:
×
88
                option['help'] = help
×
89
            request.append(option)
×
90
        return request
×
91

92
    def tabs_path_info(self, script, path):
1✔
93
        loc = path.rfind('/')
×
94
        if loc >= 0:
×
95
            path = path[:loc]
×
96
            loc = path.rfind('/')
×
97
            if loc >= 0:
×
98
                path = path[:loc]
×
99
        return View.inheritedAttribute('tabs_path_info')(self, script, path)
×
100

101
    def meta_type(self):
1✔
102
        try:
×
103
            return aq_parent(aq_parent(self)).meta_type
×
104
        except Exception:
×
105
            return ''
×
106

107

108
class PropertySheet(Traversable, Persistent, Implicit, DAVPropertySheetMixin):
1✔
109
    """A PropertySheet is a container for a set of related properties and
110
       metadata describing those properties. PropertySheets may or may not
111
       provide a web interface for managing its properties."""
112

113
    _properties = ()
1✔
114
    _extensible = 1
1✔
115

116
    security = ClassSecurityInfo()
1✔
117
    security.declareObjectProtected(access_contents_information)
1✔
118
    security.setPermissionDefault(access_contents_information,
1✔
119
                                  ('Anonymous', 'Manager'))
120

121
    __reserved_ids = ('values', 'items')
1✔
122

123
    def property_extensible_schema__(self):
1✔
124
        # Return a flag indicating whether new properties may be
125
        # added or removed.
126
        return self._extensible
1✔
127

128
    def __init__(self, id, md=None):
1✔
129
        # Create a new property set, using the given id and namespace
130
        # string. The namespace string should be usable as an xml name-
131
        # space identifier.
132

133
        if id in self.__reserved_ids:
1!
134
            raise ValueError(
×
135
                "'{}' is a reserved Id (forbidden Ids are: {}) ".format(
136
                    id, self.__reserved_ids))
137

138
        self.id = id
1✔
139
        self._md = md or {}
1✔
140

141
    def getId(self):
1✔
142
        return self.id
1✔
143

144
    @security.protected(access_contents_information)
1✔
145
    def xml_namespace(self):
1✔
146
        """Return a namespace string usable as an xml namespace
147
        for this property set."""
148
        return self._md.get('xmlns', '')
1✔
149

150
    def v_self(self):
1✔
151
        return self
1✔
152

153
    def p_self(self):
1✔
154
        return self.v_self()
1✔
155

156
    def valid_property_id(self, id):
1✔
157
        if not id or id[:1] == '_' or (id[:3] == 'aq_') \
1!
158
           or (' ' in id) or html.escape(id, True) != id:
159
            return 0
×
160
        return 1
1✔
161

162
    @security.protected(access_contents_information)
1✔
163
    def hasProperty(self, id):
1✔
164
        """Return a true value if a property exists with the given id."""
165
        for prop in self._propertyMap():
1!
166
            if id == prop['id']:
1✔
167
                return 1
1✔
168
        return 0
×
169

170
    @security.protected(access_contents_information)
1✔
171
    def getProperty(self, id, default=None):
1✔
172
        # Return the property with the given id, returning the optional
173
        # second argument or None if no such property is found.
174
        if self.hasProperty(id):
1!
175
            return getattr(self.v_self(), id)
1✔
176
        return default
×
177

178
    @security.protected(access_contents_information)
1✔
179
    def getPropertyType(self, id):
1✔
180
        # Get the type of property 'id', returning None if no
181
        # such property exists.
182
        pself = self.p_self()
×
183
        for md in pself._properties:
×
184
            if md['id'] == id:
×
185
                return md.get('type', 'string')
×
186
        return None
×
187

188
    def _wrapperCheck(self, object):
1✔
189
        # Raise an error if an object is wrapped.
190
        if hasattr(object, 'aq_base'):
1!
191
            raise ValueError('Invalid property value: wrapped object')
×
192
        return
1✔
193

194
    def _setProperty(self, id, value, type='string', meta=None):
1✔
195
        # Set a new property with the given id, value and optional type.
196
        # Note that different property sets may support different typing
197
        # systems.
198
        self._wrapperCheck(value)
1✔
199
        if not self.valid_property_id(id):
1!
200
            raise BadRequest(
×
201
                'Invalid property id, %s.' %
202
                html.escape(
203
                    id, True))
204

205
        if not self.property_extensible_schema__():
1!
206
            raise BadRequest(
×
207
                'Properties cannot be added to this property sheet')
208
        pself = self.p_self()
1✔
209
        self = self.v_self()
1✔
210
        if hasattr(aq_base(self), id):
1!
211
            if not (id == 'title' and id not in self.__dict__):
×
212
                raise BadRequest(
×
213
                    'Invalid property id, <em>%s</em>. It is in use.' %
214
                    html.escape(id, True))
215
        if meta is None:
1!
216
            meta = {}
1✔
217
        prop = {'id': id, 'type': type, 'meta': meta}
1✔
218
        pself._properties = pself._properties + (prop,)
1✔
219
        if type in ('selection', 'multiple selection'):
1!
220
            if not value:
×
221
                raise BadRequest(
×
222
                    'The value given for a new selection property '
223
                    'must be a variable name<p>')
224
            prop['select_variable'] = value
×
225
            if type == 'selection':
×
226
                value = None
×
227
            else:
228
                value = []
×
229

230
        if isinstance(value, list):
1✔
231
            value = tuple(value)
1✔
232
        setattr(self, id, value)
1✔
233

234
    def _updateProperty(self, id, value, meta=None):
1✔
235
        # Update the value of an existing property. If value is a string,
236
        # an attempt will be made to convert the value to the type of the
237
        # existing property. If a mapping containing meta-data is passed,
238
        # it will used to _replace_ the properties meta data.
239
        self._wrapperCheck(value)
1✔
240
        if not self.hasProperty(id):
1!
241
            raise BadRequest('The property %s does not exist.' %
×
242
                             html.escape(id, True))
243
        propinfo = self.propertyInfo(id)
1✔
244
        if 'w' not in propinfo.get('mode', 'wd'):
1!
245
            raise BadRequest('%s cannot be changed.' % html.escape(id, True))
×
246
        if isinstance(value, (str, bytes)):
1✔
247
            proptype = propinfo.get('type', 'string')
1✔
248
            if proptype in type_converters:
1!
249
                value = type_converters[proptype](value)
1✔
250
        if meta is not None:
1!
251
            props = []
×
252
            pself = self.p_self()
×
253
            for prop in pself._properties:
×
254
                if prop['id'] == id:
×
255
                    prop['meta'] = meta
×
256
                props.append(prop)
×
257
            pself._properties = tuple(props)
×
258

259
        if type(value) == list:
1!
260
            value = tuple(value)
1✔
261
        setattr(self.v_self(), id, value)
1✔
262

263
    def _delProperty(self, id):
1✔
264
        # Delete the property with the given id. If a property with the
265
        # given id does not exist, a ValueError is raised.
266
        if not self.hasProperty(id):
×
267
            raise BadRequest('The property %s does not exist.' %
×
268
                             html.escape(id, True))
269
        vself = self.v_self()
×
270
        if hasattr(vself, '_reserved_names'):
×
271
            nd = vself._reserved_names
×
272
        else:
273
            nd = ()
×
274
        if ('d' not in self.propertyInfo(id).get('mode', 'wd')) or (id in nd):
×
275
            raise BadRequest('%s cannot be deleted.' % html.escape(id, True))
×
276
        delattr(vself, id)
×
277
        pself = self.p_self()
×
278
        pself._properties = tuple(
×
279
            [i for i in pself._properties if i['id'] != id])
280

281
    @security.protected(access_contents_information)
1✔
282
    def propertyIds(self):
1✔
283
        """Return a list of property ids."""
284
        return [i['id'] for i in self._propertyMap()]
×
285

286
    @security.protected(access_contents_information)
1✔
287
    def propertyValues(self):
1✔
288
        """Return a list of property values."""
289
        return [self.getProperty(i['id']) for i in self._propertyMap()]
×
290

291
    @security.protected(access_contents_information)
1✔
292
    def propertyItems(self):
1✔
293
        """Return a list of (id, property) tuples."""
294
        return [(i['id'], self.getProperty(i['id']))
×
295
                for i in self._propertyMap()]
296

297
    @security.protected(access_contents_information)
1✔
298
    def propertyInfo(self, id):
1✔
299
        """Return a mapping containing property meta-data."""
300
        for p in self._propertyMap():
1!
301
            if p['id'] == id:
1!
302
                return p
1✔
303
        raise ValueError(
×
304
            'The property %s does not exist.' %
305
            html.escape(
306
                id, True))
307

308
    def _propertyMap(self):
1✔
309
        """Return a tuple of mappings, giving meta-data for properties."""
310
        return self.p_self()._properties
1✔
311

312
    @security.protected(access_contents_information)
1✔
313
    def propertyMap(self):
1✔
314
        """Returns a secure copy of the property definitions."""
315
        return tuple(dict.copy() for dict in self._propertyMap())
×
316

317
    def _propdict(self):
1✔
318
        dict = {}
1✔
319
        for p in self._propertyMap():
1✔
320
            dict[p['id']] = p
1✔
321
        return dict
1✔
322

323
    manage = DTMLFile('dtml/properties', globals())
1✔
324

325
    @security.protected(manage_properties)
1✔
326
    def manage_propertiesForm(self, URL1, RESPONSE):
1✔
327
        " "
328
        RESPONSE.redirect(URL1 + '/manage')
×
329

330
    @security.protected(manage_properties)
1✔
331
    def manage_addProperty(self, id, value, type, REQUEST=None):
1✔
332
        """Add a new property via the web. Sets a new property with
333
        the given id, type, and value."""
334
        if type in type_converters:
1!
335
            value = type_converters[type](value)
1✔
336
        self._setProperty(id, value, type)
1✔
337
        if REQUEST is not None:
1!
338
            return self.manage(self, REQUEST)
×
339

340
    @security.protected(manage_properties)
1✔
341
    def manage_editProperties(self, REQUEST):
1✔
342
        """Edit object properties via the web."""
343
        for prop in self._propertyMap():
×
344
            name = prop['id']
×
345
            if 'w' in prop.get('mode', 'wd'):
×
346
                value = REQUEST.get(name, '')
×
347
                self._updateProperty(name, value)
×
348
        message = 'Your changes have been saved.'
×
349
        return self.manage(self, REQUEST, manage_tabs_message=message)
×
350

351
    @security.protected(manage_properties)
1✔
352
    def manage_changeProperties(self, REQUEST=None, **kw):
1✔
353
        """Change existing object properties.
354

355
        Change object properties by passing either a REQUEST object or
356
        name=value parameters
357
        """
358
        if REQUEST is None:
×
359
            props = {}
×
360
        else:
361
            props = REQUEST
×
362
        if kw:
×
363
            for name, value in kw.items():
×
364
                props[name] = value
×
365
        propdict = self._propdict()
×
366
        for name, value in props.items():
×
367
            if self.hasProperty(name):
×
368
                if 'w' not in propdict[name].get('mode', 'wd'):
×
369
                    raise BadRequest('%s cannot be changed' %
×
370
                                     html.escape(name, True))
371
                self._updateProperty(name, value)
×
372
        message = 'Your changes have been saved.'
×
373
        return self.manage(self, REQUEST, manage_tabs_message=message)
×
374

375
    @security.protected(manage_properties)
1✔
376
    def manage_delProperties(self, ids=None, REQUEST=None):
1✔
377
        """Delete one or more properties specified by 'ids'."""
378
        if REQUEST:
×
379
            # Bugfix for properties named "ids" (casey)
380
            if ids == self.getProperty('ids', None):
×
381
                ids = None
×
382
            ids = REQUEST.get('_ids', ids)
×
383
        if ids is None:
×
384
            raise BadRequest('No property specified')
×
385
        for id in ids:
×
386
            self._delProperty(id)
×
387
        if REQUEST is not None:
×
388
            return self.manage(self, REQUEST)
×
389

390

391
InitializeClass(PropertySheet)
1✔
392

393

394
class DefaultProperties(Virtual, PropertySheet, View):
1✔
395
    """The default property set mimics the behavior of old-style Zope
396
       properties -- it stores its property values in the instance of
397
       its owner."""
398

399
    id = 'default'
1✔
400
    _md = {'xmlns': 'http://www.zope.org/propsets/default'}
1✔
401

402

403
InitializeClass(DefaultProperties)
1✔
404

405

406
# import cycles
407
from webdav.PropertySheets import DAVProperties  # NOQA: E402 isort:skip
1✔
408

409

410
class PropertySheets(Traversable, Implicit, Tabs):
1✔
411
    """A tricky container to keep property sets from polluting
412
       an object's direct attribute namespace."""
413

414
    id = 'propertysheets'
1✔
415

416
    security = ClassSecurityInfo()
1✔
417
    security.declareObjectProtected(access_contents_information)
1✔
418
    security.setPermissionDefault(access_contents_information,
1✔
419
                                  ('Anonymous', 'Manager'))
420

421
    # optionally to be overridden by derived classes
422
    PropertySheetClass = PropertySheet
1✔
423

424
    webdav = DAVProperties()
1✔
425

426
    def _get_defaults(self):
1✔
427
        return (self.webdav,)
×
428

429
    def __propsets__(self):
1✔
430
        propsets = aq_parent(self).__propsets__
1✔
431
        __traceback_info__ = propsets, type(propsets)
1✔
432
        return self._get_defaults() + propsets
1✔
433

434
    def __bobo_traverse__(self, REQUEST, name=None):
1✔
435
        for propset in self.__propsets__():
×
436
            if propset.getId() == name:
×
437
                return propset.__of__(self)
×
438
        return getattr(self, name)
×
439

440
    def __getitem__(self, n):
1✔
441
        return self.__propsets__()[n].__of__(self)
×
442

443
    @security.protected(access_contents_information)
1✔
444
    def values(self):
1✔
445
        propsets = self.__propsets__()
×
446
        return [n.__of__(self) for n in propsets]
×
447

448
    @security.protected(access_contents_information)
1✔
449
    def items(self):
1✔
450
        propsets = self.__propsets__()
×
451
        r = []
×
452
        for n in propsets:
×
453
            if hasattr(n, 'id'):
×
454
                id = n.id
×
455
            else:
456
                id = ''
×
457
            r.append((id, n.__of__(self)))
×
458

459
        return r
×
460

461
    @security.protected(access_contents_information)
1✔
462
    def get(self, name, default=None):
1✔
463
        for propset in self.__propsets__():
1✔
464
            if propset.id == name or \
1✔
465
               getattr(propset, 'xml_namespace', object)() == name:
466
                return propset.__of__(self)
1✔
467
        return default
1✔
468

469
    @security.protected(manage_properties)
1✔
470
    def manage_addPropertySheet(self, id, ns, REQUEST=None):
1✔
471
        """ """
472
        md = {'xmlns': ns}
×
473
        ps = self.PropertySheetClass(id, md)
×
474
        self.addPropertySheet(ps)
×
475
        if REQUEST is None:
×
476
            return ps
×
477
        ps = self.get(id)
×
478
        REQUEST.RESPONSE.redirect('%s/manage' % ps.absolute_url())
×
479

480
    @security.protected(manage_properties)
1✔
481
    def addPropertySheet(self, propset):
1✔
482
        propsets = aq_parent(self).__propsets__
×
483
        propsets = propsets + (propset,)
×
484
        aq_parent(self).__propsets__ = propsets
×
485

486
    @security.protected(manage_properties)
1✔
487
    def delPropertySheet(self, name):
1✔
488
        result = []
×
489
        for propset in aq_parent(self).__propsets__:
×
490
            if propset.getId() != name and propset.xml_namespace() != name:
×
491
                result.append(propset)
×
492
        aq_parent(self).__propsets__ = tuple(result)
×
493

494
    def isDeletable(self, name):
1✔
495
        '''currently, we say that *name* is deletable when it is not a
496
        default sheet. Later, we may further restrict deletability
497
        based on an instance attribute.'''
498
        ps = self.get(name)
×
499
        if ps is None:
×
500
            return 0
×
501
        if ps in self._get_defaults():
×
502
            return 0
×
503
        return 1
×
504

505
    def manage_delPropertySheets(self, ids=(), REQUEST=None):
1✔
506
        '''delete all sheets identified by *ids*.'''
507
        for id in ids:
×
508
            if not self.isDeletable(id):
×
509
                raise BadRequest(
×
510
                    'attempt to delete undeletable property sheet: ' + id)
511
            self.delPropertySheet(id)
×
512
        if REQUEST is not None:
×
513
            REQUEST.RESPONSE.redirect('%s/manage' % self.absolute_url())
×
514

515
    def __len__(self):
1✔
516
        return len(self.__propsets__())
×
517

518
    def getId(self):
1✔
519
        return self.id
×
520

521
    security.declareProtected(view_management_screens, 'manage')  # NOQA: D001
1✔
522
    manage = DTMLFile('dtml/propertysheets', globals())
1✔
523

524
    def manage_options(self):
1✔
525
        """Return a manage option data structure for me instance
526
        """
527
        try:
×
528
            request = self.REQUEST
×
529
        except Exception:
×
530
            request = None
×
531
        if request is None:
×
532
            pre = '../'
×
533
        else:
534
            pre = request['URLPATH0']
×
535
            for i in (1, 2):
×
536
                loc = pre.rfind('/')
×
537
                if loc >= 0:
×
538
                    pre = pre[:loc]
×
539
            pre = pre + '/'
×
540

541
        request = []
×
542
        for d in aq_parent(self).manage_options:
×
543
            request.append({'label': d['label'], 'action': pre + d['action']})
×
544
        return request
×
545

546
    def tabs_path_info(self, script, path):
1✔
547
        loc = path.rfind('/')
×
548
        if loc >= 0:
×
549
            path = path[:loc]
×
550
        return PropertySheets.inheritedAttribute('tabs_path_info')(
×
551
            self, script, path)
552

553

554
InitializeClass(PropertySheets)
1✔
555

556

557
class DefaultPropertySheets(PropertySheets):
1✔
558
    """A PropertySheets container that contains a default property
559
       sheet for compatibility with the arbitrary property mgmt
560
       design of Zope PropertyManagers."""
561

562
    default = DefaultProperties()
1✔
563
    webdav = DAVProperties()
1✔
564

565
    def _get_defaults(self):
1✔
566
        return (self.default, self.webdav)
1✔
567

568

569
InitializeClass(DefaultPropertySheets)
1✔
570

571

572
class vps(Base):
1✔
573
    """Virtual Propertysheets
574

575
    The vps object implements a "computed attribute" - it ensures
576
    that a PropertySheets instance is returned when the propertysheets
577
    attribute of a PropertyManager is accessed.
578
    """
579

580
    def __init__(self, c=PropertySheets):
1✔
581
        self.c = c
1✔
582

583
    def __of__(self, parent):
1✔
584
        return self.c().__of__(parent)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc