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

zopefoundation / Zope / 6263629025

21 Sep 2023 03:12PM UTC coverage: 82.146% (-0.01%) from 82.159%
6263629025

Pull #1164

github

web-flow
[pre-commit.ci lite] apply automatic fixes
Pull Request #1164: Move all linters to pre-commit.

4353 of 6963 branches covered (0.0%)

Branch coverage included in aggregate %.

487 of 487 new or added lines in 186 files covered. (100.0%)

27394 of 31684 relevant lines covered (86.46%)

0.86 hits per line

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

85.02
/src/OFS/SimpleItem.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
"""This module implements a simple item mix-in for objects that have a very
1✔
14
simple (e.g. one-screen) management interface, like documents, Aqueduct
15
database adapters, etc.
16

17
This module can also be used as a simple template for implementing new
18
item types.
19
"""
20

21
import logging
1✔
22
import re
1✔
23
import sys
1✔
24

25
from AccessControl.class_init import InitializeClass
1✔
26
from AccessControl.Permissions import access_contents_information
1✔
27
from AccessControl.Permissions import view as View
1✔
28
from AccessControl.SecurityInfo import ClassSecurityInfo
1✔
29
from Acquisition import Acquired
1✔
30
from Acquisition import Implicit
1✔
31
from Acquisition import aq_acquire
1✔
32
from Acquisition import aq_base
1✔
33
from Acquisition import aq_inner
1✔
34
from Acquisition import aq_parent
1✔
35
from App.Management import Navigation
1✔
36
from App.Management import Tabs
1✔
37
from App.special_dtml import HTML
1✔
38
from App.special_dtml import DTMLFile
1✔
39
from ComputedAttribute import ComputedAttribute
1✔
40
from DocumentTemplate.html_quote import html_quote
1✔
41
from DocumentTemplate.ustr import ustr
1✔
42
from ExtensionClass import Base
1✔
43
from OFS.CopySupport import CopySource
1✔
44
from OFS.interfaces import IItem
1✔
45
from OFS.interfaces import IItemWithName
1✔
46
from OFS.interfaces import ISimpleItem
1✔
47
from OFS.Lockable import LockableItem
1✔
48
from OFS.owner import Owned
1✔
49
from OFS.role import RoleManager
1✔
50
from OFS.Traversable import Traversable
1✔
51
from Persistence import Persistent
1✔
52
from webdav.Resource import Resource
1✔
53
from zExceptions import Redirect
1✔
54
from zExceptions.ExceptionFormatter import format_exception
1✔
55
from zope.interface import implementer
1✔
56
from ZPublisher.HTTPRequest import default_encoding
1✔
57

58

59
logger = logging.getLogger()
1✔
60

61

62
class PathReprProvider(Base):
1✔
63
    """Provides a representation that includes the physical path.
64

65
    Should be in the MRO before persistent.Persistent as this provides an own
66
    implementation of `__repr__` that includes information about connection and
67
    oid.
68
    """
69

70
    def __repr__(self):
1✔
71
        """Show the physical path of the object and context if available."""
72
        try:
1✔
73
            path = '/'.join(self.getPhysicalPath())
1✔
74
        except Exception:
×
75
            return super().__repr__()
×
76
        context_path = None
1✔
77
        context = aq_parent(self)
1✔
78
        container = aq_parent(aq_inner(self))
1✔
79
        if aq_base(context) is not aq_base(container):
1✔
80
            try:
1✔
81
                context_path = '/'.join(context.getPhysicalPath())
1✔
82
            except Exception:
×
83
                context_path = None
×
84
        res = '<%s' % self.__class__.__name__
1✔
85
        res += ' at %s' % path
1✔
86
        if context_path:
1✔
87
            res += ' used for %s' % context_path
1✔
88
        res += '>'
1✔
89
        return res
1✔
90

91

92
@implementer(IItem)
1✔
93
class Item(
1✔
94
    PathReprProvider,
95
    Base,
96
    Navigation,
97
    Resource,
98
    LockableItem,
99
    CopySource,
100
    Tabs,
101
    Traversable,
102
    Owned
103
):
104
    """A common base class for simple, non-container objects."""
105

106
    zmi_icon = 'far fa-file'
1✔
107
    zmi_show_add_dialog = True
1✔
108

109
    security = ClassSecurityInfo()
1✔
110

111
    isPrincipiaFolderish = 0
1✔
112
    isTopLevelPrincipiaApplicationObject = 0
1✔
113

114
    manage_options = ({'label': 'Interfaces', 'action': 'manage_interfaces'},)
1✔
115

116
    def manage_afterAdd(self, item, container):
1✔
117
        pass
1✔
118
    manage_afterAdd.__five_method__ = True
1✔
119

120
    def manage_beforeDelete(self, item, container):
1✔
121
        pass
1✔
122
    manage_beforeDelete.__five_method__ = True
1✔
123

124
    def manage_afterClone(self, item):
1✔
125
        pass
1✔
126
    manage_afterClone.__five_method__ = True
1✔
127

128
    # Direct use of the 'id' attribute is deprecated - use getId()
129
    id = ''
1✔
130

131
    @security.public
1✔
132
    def getId(self):
1✔
133
        """Return the id of the object as a string.
134

135
        This method should be used in preference to accessing an id
136
        attribute of an object directly. The getId method is public.
137
        """
138
        name = self.id
1✔
139
        if name is not None:
1!
140
            return name
1✔
141
        return self.__name__
×
142

143
    # Alias id to __name__, which will make tracebacks a good bit nicer:
144
    __name__ = ComputedAttribute(lambda self: self.id)
1✔
145

146
    # Meta type used for selecting all objects of a given type.
147
    meta_type = 'simple item'
1✔
148

149
    # Default title.
150
    title = ''
1✔
151

152
    # Default propertysheet info:
153
    __propsets__ = ()
1✔
154

155
    # Attributes that must be acquired
156
    REQUEST = Acquired
1✔
157

158
    # Allow (reluctantly) access to unprotected attributes
159
    __allow_access_to_unprotected_subobjects__ = 1
1✔
160

161
    def title_or_id(self):
1✔
162
        """Return the title if it is not blank and the id otherwise."""
163
        title = self.title
1✔
164
        if callable(title):
1!
165
            title = title()
×
166
        if title:
1✔
167
            return title
1✔
168
        return self.getId()
1✔
169

170
    def title_and_id(self):
1✔
171
        """Return the title if it is not blank and the id otherwise.
172

173
        If the title is not blank, then the id is included in parens.
174
        """
175
        title = self.title
1✔
176
        if callable(title):
1!
177
            title = title()
×
178
        id = self.getId()
1✔
179
        # Make sure we don't blindly concatenate encoded and unencoded data
180
        if title and not isinstance(id, type(title)):
1✔
181
            if isinstance(id, bytes):
1!
182
                id = id.decode(default_encoding)
1✔
183
            if isinstance(title, bytes):
1!
184
                title = title.decode(default_encoding)
×
185
        return title and f"{title} ({id})" or id
1✔
186

187
    def this(self):
1✔
188
        # Handy way to talk to ourselves in document templates.
189
        return self
1✔
190

191
    def tpURL(self):
1✔
192
        # My URL as used by tree tag
193
        return self.getId()
1✔
194

195
    def tpValues(self):
1✔
196
        # My sub-objects as used by the tree tag
197
        return ()
1✔
198

199
    _manage_editedDialog = DTMLFile('dtml/editedDialog', globals())
1✔
200

201
    def manage_editedDialog(self, REQUEST, **args):
1✔
202
        return self._manage_editedDialog(self, REQUEST, **args)
×
203

204
    def raise_standardErrorMessage(
1✔
205
        self,
206
        client=None,
207
        REQUEST={},
208
        error_type=None,
209
        error_value=None,
210
        tb=None,
211
        error_tb=None,
212
        error_message='',
213
        tagSearch=re.compile(r'[a-zA-Z]>').search,
214
        error_log_url=''
215
    ):
216

217
        try:
1✔
218
            if error_type is None:
1✔
219
                error_type = sys.exc_info()[0]
1✔
220
            if error_value is None:
1✔
221
                error_value = sys.exc_info()[1]
1✔
222

223
            # allow for a few different traceback options
224
            if tb is None and error_tb is None:
1!
225
                tb = sys.exc_info()[2]
1✔
226
            if not isinstance(tb, str) and (error_tb is None):
1!
227
                error_tb = pretty_tb(error_type, error_value, tb)
1✔
228
            elif isinstance(tb, str) and not error_tb:
×
229
                error_tb = tb
×
230

231
            if hasattr(self, '_v_eek'):
1!
232
                # Stop if there is recursion.
233
                raise error_value.with_traceback(tb)
×
234
            self._v_eek = 1
1✔
235

236
            if hasattr(error_type, '__name__'):
1✔
237
                error_name = error_type.__name__
1✔
238
            else:
239
                error_name = 'Unknown'
1✔
240

241
            if not error_message:
1!
242
                try:
1✔
243
                    s = ustr(error_value)
1✔
244
                except Exception:
×
245
                    s = error_value
×
246
                try:
1✔
247
                    match = tagSearch(s)
1✔
248
                except TypeError:
×
249
                    match = None
×
250
                if match is not None:
1✔
251
                    error_message = error_value
1✔
252

253
            if client is None:
1✔
254
                client = self
1✔
255

256
            if not REQUEST:
1!
257
                REQUEST = aq_acquire(self, 'REQUEST')
×
258

259
            try:
1✔
260
                s = aq_acquire(client, 'standard_error_message')
1✔
261

262
                # For backward compatibility, we pass 'error_name' as
263
                # 'error_type' here as historically this has always
264
                # been a string.
265
                kwargs = {
1✔
266
                    'error_type': error_name,
267
                    'error_value': error_value,
268
                    'error_tb': error_tb,
269
                    'error_traceback': error_tb,
270
                    'error_message': error_message,
271
                    'error_log_url': error_log_url,
272
                }
273

274
                if getattr(aq_base(s), 'isDocTemp', 0):
1✔
275
                    v = s(client, REQUEST, **kwargs)
1✔
276
                elif callable(s):
1!
277
                    v = s(**kwargs)
1✔
278
                else:
279
                    v = HTML.__call__(s, client, REQUEST, **kwargs)
×
280
            except Exception:
1✔
281
                logger.error(
1✔
282
                    'Exception while rendering an error message',
283
                    exc_info=True
284
                )
285
                try:
1✔
286
                    strv = repr(error_value)  # quotes tainted strings
1✔
287
                except Exception:
×
288
                    strv = ('<unprintable %s object>' %
×
289
                            str(type(error_value).__name__))
290
                v = strv + (
1✔
291
                    (" (Also, the following error occurred while attempting "
292
                     "to render the standard error message, please see the "
293
                     "event log for full details: %s)") % (
294
                        html_quote(sys.exc_info()[1]),
295
                    ))
296

297
            # If we've been asked to handle errors, just return the rendered
298
            # exception and let the ZPublisher Exception Hook deal with it.
299
            return error_type, v, tb
1✔
300
        finally:
301
            if hasattr(self, '_v_eek'):
1!
302
                del self._v_eek
1✔
303
            tb = None
1!
304

305
    def manage(self, URL1):
1✔
306
        """"""
307
        raise Redirect("%s/manage_main" % URL1)
×
308

309
    # This keeps simple items from acquiring their parents
310
    # objectValues, etc., when used in simple tree tags.
311
    def objectValues(self, spec=None):
1✔
312
        return ()
1✔
313
    objectIds = objectItems = objectValues
1✔
314

315
    def __len__(self):
1✔
316
        return 1
1✔
317

318
    @security.protected(access_contents_information)
1✔
319
    def getParentNode(self):
1✔
320
        """The parent of this node.
321

322
        All nodes except Document DocumentFragment and Attr may have a
323
        parent
324
        """
325
        return getattr(self, '__parent__', None)
×
326

327

328
InitializeClass(Item)
1✔
329

330

331
@implementer(IItemWithName)
1✔
332
class Item_w__name__(Item):
1✔
333
    """Mixin class to support common name/id functions."""
334

335
    def getId(self):
1✔
336
        """Return the id of the object as a string."""
337
        return self.__name__
1✔
338

339
    # Alias (deprecated) `id` to `getId()` (but avoid recursion)
340
    id = ComputedAttribute(
1✔
341
        lambda self: self.getId() if "__name__" in self.__dict__ else "")
342

343
    def title_or_id(self):
1✔
344
        """Return the title if it is not blank and the id otherwise."""
345
        return self.title or self.__name__
1✔
346

347
    def title_and_id(self):
1✔
348
        """Return the title if it is not blank and the id otherwise.
349

350
        If the title is not blank, then the id is included in parens.
351
        """
352
        t = self.title
×
353
        return t and f"{t} ({self.__name__})" or self.__name__
×
354

355
    def _setId(self, id):
1✔
356
        self.__name__ = id
1✔
357

358
    def getPhysicalPath(self):
1✔
359
        # Get the physical path of the object.
360
        #
361
        # Returns a path (an immutable sequence of strings) that can be used
362
        # to access this object again later, for example in a copy/paste
363
        # operation.  getPhysicalRoot() and getPhysicalPath() are designed to
364
        # operate together.
365

366
        path = (self.__name__, )
1✔
367
        p = aq_parent(aq_inner(self))
1✔
368
        if p is not None:
1!
369
            path = p.getPhysicalPath() + path
1✔
370
        return path
1✔
371

372

373
def pretty_tb(t, v, tb, as_html=1):
1✔
374
    tb = format_exception(t, v, tb, as_html=as_html)
1✔
375
    tb = '\n'.join(tb)
1✔
376
    return tb
1✔
377

378

379
@implementer(ISimpleItem)
1✔
380
class SimpleItem(
1✔
381
    Item,
382
    Persistent,
383
    Implicit,
384
    RoleManager,
385
):
386
    """Mix-in class combining the most common set of basic mix-ins."""
387

388
    security = ClassSecurityInfo()
1✔
389
    security.setPermissionDefault(View, ('Manager',))
1✔
390

391
    manage_options = Item.manage_options + (
1✔
392
        {
393
            'label': 'Security',
394
            'action': 'manage_access',
395
        },
396
    )
397

398

399
InitializeClass(SimpleItem)
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