• 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

55.59
/src/OFS/PropertyManager.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 management
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.SecurityInfo import ClassSecurityInfo
1✔
22
from Acquisition import aq_base
1✔
23
from App.special_dtml import DTMLFile
1✔
24
from ExtensionClass import Base
1✔
25
from OFS.interfaces import IPropertyManager
1✔
26
from OFS.PropertySheets import DefaultPropertySheets
1✔
27
from OFS.PropertySheets import vps
1✔
28
from zExceptions import BadRequest
1✔
29
from zope.interface import implementer
1✔
30
from ZPublisher.Converters import type_converters
1✔
31

32

33
@implementer(IPropertyManager)
1✔
34
class PropertyManager(Base):
1✔
35
    """
36
    The PropertyManager mixin class provides an object with
37
    transparent property management. An object which wants to
38
    have properties should inherit from PropertyManager.
39

40
    An object may specify that it has one or more predefined
41
    properties, by specifying an _properties structure in its
42
    class::
43

44
      _properties=({'id':'title', 'type': 'string', 'mode': 'w'},
45
                   {'id':'color', 'type': 'string', 'mode': 'w'},
46
                   )
47

48
    The _properties structure is a sequence of dictionaries, where
49
    each dictionary represents a predefined property. Note that if a
50
    predefined property is defined in the _properties structure, you
51
    must provide an attribute with that name in your class or instance
52
    that contains the default value of the predefined property.
53

54
    Each entry in the _properties structure must have at least an 'id'
55
    and a 'type' key. The 'id' key contains the name of the property,
56
    and the 'type' key contains a string representing the object's type.
57
    The 'type' string must be one of the values: 'float', 'int', 'long',
58
    'string', 'lines', 'text', 'date', 'tokens', 'selection', or
59
    'multiple section'.
60

61
    For 'selection' and 'multiple selection' properties, there is an
62
    addition item in the property dictionay, 'select_variable' which
63
    provides the name of a property or method which returns a list of
64
    strings from which the selection(s) can be chosen.
65

66
    Each entry in the _properties structure may *optionally* provide a
67
    'mode' key, which specifies the mutability of the property. The 'mode'
68
    string, if present, must contain 0 or more characters from the set
69
    'w','d'.
70

71
    A 'w' present in the mode string indicates that the value of the
72
    property may be changed by the user. A 'd' indicates that the user
73
    can delete the property. An empty mode string indicates that the
74
    property and its value may be shown in property listings, but that
75
    it is read-only and may not be deleted.
76

77
    Entries in the _properties structure which do not have a 'mode' key
78
    are assumed to have the mode 'wd' (writeable and deleteable).
79

80
    To fully support property management, including the system-provided
81
    tabs and user interfaces for working with properties, an object which
82
    inherits from PropertyManager should include the following entry in
83
    its manage_options structure::
84

85
      {'label':'Properties', 'action':'manage_propertiesForm',}
86

87
    to ensure that a 'Properties' tab is displayed in its management
88
    interface. Objects that inherit from PropertyManager should also
89
    include the following entry in its __ac_permissions__ structure::
90

91
      ('Manage properties', ('manage_addProperty',
92
                             'manage_editProperties',
93
                             'manage_delProperties',
94
                             'manage_changeProperties',)),
95
    """
96

97
    security = ClassSecurityInfo()
1✔
98
    security.declareObjectProtected(access_contents_information)
1✔
99
    security.setPermissionDefault(access_contents_information,
1✔
100
                                  ('Anonymous', 'Manager'))
101

102
    manage_options = (
1✔
103
        {
104
            'label': 'Properties',
105
            'action': 'manage_propertiesForm',
106
        },
107
    )
108

109
    security.declareProtected(manage_properties, 'manage_propertiesForm')  # NOQA: D001,E501
1✔
110
    manage_propertiesForm = DTMLFile(
1✔
111
        'dtml/properties', globals(), property_extensible_schema__=1)
112
    security.declareProtected(manage_properties, 'manage_propertyTypeForm')  # NOQA: D001,E501
1✔
113
    manage_propertyTypeForm = DTMLFile('dtml/propertyType', globals())
1✔
114

115
    title = ''
1✔
116
    _properties = (
1✔
117
        {
118
            'id': 'title',
119
            'type': 'string',
120
            'mode': 'wd',
121
        },
122
    )
123
    _reserved_names = ()
1✔
124

125
    __propsets__ = ()
1✔
126
    propertysheets = vps(DefaultPropertySheets)
1✔
127

128
    @security.protected(access_contents_information)
1✔
129
    def valid_property_id(self, id):
1✔
130
        if not id or \
1!
131
           id[:1] == '_' or \
132
           id[:3] == 'aq_' or \
133
           ' ' in id or \
134
           hasattr(aq_base(self), id) or \
135
           html.escape(id, True) != id:
136
            return 0
×
137
        return 1
1✔
138

139
    @security.protected(access_contents_information)
1✔
140
    def hasProperty(self, id):
1✔
141
        """Return true if object has a property 'id'."""
142
        for p in self._properties:
1!
143
            if id == p['id']:
1✔
144
                return 1
1✔
145
        return 0
×
146

147
    @security.protected(access_contents_information)
1✔
148
    def getProperty(self, id, d=None):
1✔
149
        """Get the property 'id'.
150

151
        Returns the optional second argument or None if no such property is
152
        found.
153
        """
154
        if self.hasProperty(id):
1!
155
            return getattr(self, id)
1✔
156
        return d
×
157

158
    @security.protected(access_contents_information)
1✔
159
    def getPropertyType(self, id):
1✔
160
        """Get the type of property 'id'.
161

162
        Returns None if no such property exists.
163
        """
164
        for md in self._properties:
1!
165
            if md['id'] == id:
1✔
166
                return md.get('type', 'string')
1✔
167
        return None
×
168

169
    def _wrapperCheck(self, object):
1✔
170
        # Raise an error if an object is wrapped.
171
        if hasattr(object, 'aq_base'):
1!
172
            raise ValueError('Invalid property value: wrapped object')
×
173
        return
1✔
174

175
    def _setPropValue(self, id, value):
1✔
176
        self._wrapperCheck(value)
1✔
177
        if type(value) == list:
1✔
178
            value = tuple(value)
1✔
179
        setattr(self, id, value)
1✔
180

181
    def _delPropValue(self, id):
1✔
182
        delattr(self, id)
×
183

184
    def _setProperty(self, id, value, type='string'):
1✔
185
        # for selection and multiple selection properties
186
        # the value argument indicates the select variable
187
        # of the property
188
        self._wrapperCheck(value)
1✔
189
        if not self.valid_property_id(id):
1!
190
            raise BadRequest('Invalid or duplicate property id')
×
191

192
        if type in ('selection', 'multiple selection'):
1!
193
            if not hasattr(self, value):
×
194
                raise BadRequest('No select variable %s' % value)
×
195
            self._properties = self._properties + (
×
196
                {'id': id, 'type': type, 'select_variable': value},)
197
            if type == 'selection':
×
198
                self._setPropValue(id, '')
×
199
            else:
200
                self._setPropValue(id, [])
×
201
        else:
202
            self._properties = self._properties + ({'id': id, 'type': type},)
1✔
203
            self._setPropValue(id, value)
1✔
204

205
    def _updateProperty(self, id, value):
1✔
206
        # Update the value of an existing property. If value
207
        # is a string, an attempt will be made to convert
208
        # the value to the type of the existing property.
209
        self._wrapperCheck(value)
1✔
210
        if not self.hasProperty(id):
1!
211
            raise BadRequest(
×
212
                'The property %s does not exist' % html.escape(id, True))
213
        if isinstance(value, (str, bytes)):
1✔
214
            proptype = self.getPropertyType(id) or 'string'
1✔
215
            if proptype in type_converters:
1!
216
                value = type_converters[proptype](value)
1✔
217
        self._setPropValue(id, value)
1✔
218

219
    def _delProperty(self, id):
1✔
220
        if not self.hasProperty(id):
×
221
            raise ValueError(
×
222
                'The property %s does not exist' % html.escape(id, True))
223
        self._delPropValue(id)
×
224
        self._properties = tuple(i for i in self._properties if i['id'] != id)
×
225

226
    @security.protected(access_contents_information)
1✔
227
    def propertyIds(self):
1✔
228
        """Return a list of property ids."""
229
        return [i['id'] for i in self._properties]
×
230

231
    @security.protected(access_contents_information)
1✔
232
    def propertyValues(self):
1✔
233
        """Return a list of actual property objects."""
234
        return [getattr(self, i['id']) for i in self._properties]
×
235

236
    @security.protected(access_contents_information)
1✔
237
    def propertyItems(self):
1✔
238
        """Return a list of (id,property) tuples."""
239
        return [(i['id'], getattr(self, i['id'])) for i in self._properties]
×
240

241
    def _propertyMap(self):
1✔
242
        """Return a tuple of mappings, giving meta-data for properties."""
243
        return self._properties
1✔
244

245
    @security.protected(access_contents_information)
1✔
246
    def propertyMap(self):
1✔
247
        """Return a tuple of mappings, giving meta-data for properties.
248

249
        Return copies of the real definitions for security.
250
        """
251
        return tuple(dict.copy() for dict in self._propertyMap())
1✔
252

253
    @security.protected(access_contents_information)
1✔
254
    def propertyLabel(self, id):
1✔
255
        """Return a label for the given property id
256
        """
257
        for p in self._properties:
1!
258
            if p['id'] == id:
1!
259
                return p.get('label', id)
1✔
260
        return id
×
261

262
    @security.protected(access_contents_information)
1✔
263
    def propertyDescription(self, id):
1✔
264
        """Return a description for the given property id
265
        """
266
        for p in self._properties:
1!
267
            if p['id'] == id:
1!
268
                return p.get('description', '')
1✔
269
        return id
×
270

271
    @security.protected(access_contents_information)
1✔
272
    def propdict(self):
1✔
273
        dict = {}
1✔
274
        for p in self._properties:
1✔
275
            dict[p['id']] = p
1✔
276
        return dict
1✔
277

278
    @security.protected(manage_properties)
1✔
279
    def manage_addProperty(self, id, value, type, REQUEST=None):
1✔
280
        """Add a new property via the web.
281

282
        Sets a new property with the given id, type, and value.
283
        """
284
        if type in type_converters:
1!
285
            value = type_converters[type](value)
1✔
286
        self._setProperty(id.strip(), value, type)
1✔
287
        if REQUEST is not None:
1!
288
            return self.manage_propertiesForm(self, REQUEST)
×
289

290
    @security.protected(manage_properties)
1✔
291
    def manage_editProperties(self, REQUEST):
1✔
292
        """Edit object properties via the web.
293

294
        The purpose of this method is to change all property values,
295
        even those not listed in REQUEST; otherwise checkboxes that
296
        get turned off will be ignored.  Use manage_changeProperties()
297
        instead for most situations.
298
        """
299
        for prop in self._propertyMap():
×
300
            name = prop['id']
×
301
            if 'w' in prop.get('mode', 'wd'):
×
302
                if prop['type'] == 'multiple selection':
×
303
                    value = REQUEST.form.get(name, [])
×
304
                else:
305
                    value = REQUEST.form.get(name, '')
×
306
                self._updateProperty(name, value)
×
307
        if REQUEST:
×
308
            message = "Saved changes."
×
309
            return self.manage_propertiesForm(
×
310
                self,
311
                REQUEST,
312
                manage_tabs_message=message,
313
            )
314

315
    @security.protected(manage_properties)
1✔
316
    def manage_changeProperties(self, REQUEST=None, **kw):
1✔
317
        """Change existing object properties.
318

319
        Change object properties by passing either a REQUEST object or
320
        name=value parameters
321
        """
322
        if REQUEST is None:
1!
323
            props = {}
1✔
324
        elif isinstance(REQUEST, dict):
×
325
            props = REQUEST
×
326
        else:
327
            props = REQUEST.form
×
328
        if kw:
1!
329
            for name, value in kw.items():
1✔
330
                props[name] = value
1✔
331
        propdict = self.propdict()
1✔
332
        for name, value in props.items():
1✔
333
            if self.hasProperty(name):
1!
334
                if 'w' not in propdict[name].get('mode', 'wd'):
1!
335
                    raise BadRequest(
×
336
                        '%s cannot be changed' % html.escape(name, True))
337
                self._updateProperty(name, value)
1✔
338

339
        if REQUEST:
1!
340
            message = "Saved changes."
×
341
            return self.manage_propertiesForm(
×
342
                self,
343
                REQUEST,
344
                manage_tabs_message=message
345
            )
346

347
    @security.protected(manage_properties)
1✔
348
    def manage_changePropertyTypes(self, old_ids, props, REQUEST=None):
1✔
349
        """Replace one set of properties with another.
350

351
        Delete all properties that have ids in old_ids, then add a
352
        property for each item in props.  Each item has a new_id,
353
        new_value, and new_type.  The type of new_value should match
354
        new_type.
355
        """
356
        err = self.manage_delProperties(old_ids)
×
357
        if err:
×
358
            if REQUEST is not None:
×
359
                return err
×
360
            return
×
361
        for prop in props:
×
362
            self._setProperty(prop.new_id, prop.new_value, prop.new_type)
×
363
        if REQUEST is not None:
×
364
            return self.manage_propertiesForm(self, REQUEST)
×
365

366
    @security.protected(manage_properties)
1✔
367
    def manage_delProperties(self, ids=None, REQUEST=None):
1✔
368
        """Delete one or more properties specified by 'ids'."""
369
        if REQUEST:
×
370
            # Bugfix for property named "ids" (Casey)
371
            if ids == self.getProperty('ids', None):
×
372
                ids = None
×
373
            ids = REQUEST.get('_ids', ids)
×
374
        if ids is None:
×
375
            raise BadRequest('No property specified.')
×
376
        propdict = self.propdict()
×
377
        nd = self._reserved_names
×
378
        for id in ids:
×
379
            if not hasattr(aq_base(self), id):
×
380
                raise BadRequest(
×
381
                    'The property <em>%s</em> '
382
                    'does not exist' % html.escape(id, True))
383
            if ('d' not in propdict[id].get('mode', 'wd')) or (id in nd):
×
384
                raise BadRequest('Cannot delete %s' % id)
×
385
            self._delProperty(id)
×
386

387
        if REQUEST is not None:
×
388
            return self.manage_propertiesForm(self, REQUEST)
×
389

390

391
InitializeClass(PropertyManager)
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