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

zopefoundation / z3c.form / 5104192483

pending completion
5104192483

push

github

web-flow
Config with pure python template (#114)

* Bumped version for breaking release.

* Drop support for Python 2.7, 3.5, 3.6.

* Add support for Python 3.11.

1316 of 1408 branches covered (93.47%)

Branch coverage included in aggregate %.

420 of 420 new or added lines in 39 files covered. (100.0%)

3716 of 3838 relevant lines covered (96.82%)

0.97 hits per line

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

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

16
"""
17
__docformat__ = "reStructuredText"
1✔
18
import binascii
1✔
19
import re
1✔
20
import string
1✔
21
from collections import OrderedDict
1✔
22
from functools import total_ordering
1✔
23

24
import zope.contenttype
1✔
25
import zope.interface
1✔
26
import zope.schema
1✔
27

28
from z3c.form import interfaces
1✔
29
from z3c.form.i18n import MessageFactory as _
1✔
30

31

32
_identifier = re.compile('[A-Za-z][a-zA-Z0-9_]*$')
1✔
33
classTypes = (type,)
1✔
34
_acceptableChars = string.ascii_letters + string.digits + '_-'
1✔
35

36

37
def toUnicode(obj):
1✔
38
    if isinstance(obj, bytes):
1✔
39
        return obj.decode('utf-8', 'ignore')
1✔
40
    return str(obj)
1✔
41

42

43
def toBytes(obj):
1✔
44
    if isinstance(obj, bytes):
1✔
45
        return obj
1✔
46
    if isinstance(obj, str):
1!
47
        return obj.encode('utf-8')
1✔
48
    return str(obj).encode('utf-8')
×
49

50

51
def createId(name):
1✔
52
    """Returns a *native* string as id of the given name."""
53
    if _identifier.match(name):
1✔
54
        return str(name).lower()
1✔
55
    id = binascii.hexlify(name.encode('utf-8'))
1✔
56
    return id.decode()
1✔
57

58

59
@total_ordering
1✔
60
class MinType:
1✔
61
    def __le__(self, other):
1✔
62
        return True
1✔
63

64
    def __eq__(self, other):
1✔
65
        return (self is other)
×
66

67

68
def sortedNone(items):
1✔
69
    Min = MinType()
1✔
70
    return sorted(items, key=lambda x: Min if x is None else x)
1✔
71

72

73
def createCSSId(name):
1✔
74
    return str(''.join([
1✔
75
        (char if char in _acceptableChars else
76
         binascii.hexlify(char.encode('utf-8')).decode())
77
        for char in name]))
78

79

80
def getSpecification(spec, force=False):
1✔
81
    """Get the specification of the given object.
82

83
    If the given object is already a specification acceptable to the component
84
    architecture, it is simply returned. This is true for classes
85
    and specification objects (which includes interfaces).
86

87
    In case of instances, an interface is generated on the fly and tagged onto
88
    the object. Then the interface is returned as the specification.
89
    """
90
    # If the specification is an instance, then we do some magic.
91
    if (force or
1✔
92
        (spec is not None and
93
         not zope.interface.interfaces.ISpecification.providedBy(spec)
94
         and not isinstance(spec, classTypes))):
95

96
        # Step 1: Calculate an interface name
97
        ifaceName = 'IGeneratedForObject_%i' % id(spec)
1✔
98

99
        # Step 2: Find out if we already have such an interface
100
        existingInterfaces = [
1✔
101
            i for i in zope.interface.directlyProvidedBy(spec)
102
            if i.__name__ == ifaceName
103
        ]
104

105
        # Step 3a: Return an existing interface if there is one
106
        if len(existingInterfaces) > 0:
1✔
107
            spec = existingInterfaces[0]
1✔
108
        # Step 3b: Create a new interface if not
109
        else:
110
            iface = zope.interface.interface.InterfaceClass(ifaceName)
1✔
111
            zope.interface.alsoProvides(spec, iface)
1✔
112
            spec = iface
1✔
113
    return spec
1✔
114

115

116
def expandPrefix(prefix):
1✔
117
    """Expand prefix string by adding a trailing period if needed.
118

119
    expandPrefix(p) should be used instead of p+'.' in most contexts.
120
    """
121
    if prefix and not prefix.endswith('.'):
1✔
122
        return prefix + '.'
1✔
123
    return prefix
1✔
124

125

126
def getWidgetById(form, id):
1✔
127
    """Get a widget by it's rendered DOM element id."""
128
    # convert the id to a name
129
    name = id.replace('-', '.')
1✔
130
    prefix = form.prefix + form.widgets.prefix
1✔
131
    if not name.startswith(prefix):
1✔
132
        raise ValueError(f"Name {name!r} must start with prefix {prefix!r}")
1✔
133
    shortName = name[len(prefix):]
1✔
134
    return form.widgets.get(shortName, None)
1✔
135

136

137
def extractContentType(form, id):
1✔
138
    """Extract the content type of the widget with the given id."""
139
    widget = getWidgetById(form, id)
1✔
140
    return zope.contenttype.guess_content_type(widget.filename)[0]
1✔
141

142

143
def extractFileName(form, id, cleanup=True, allowEmptyPostfix=False):
1✔
144
    """Extract the filename of the widget with the given id.
145

146
    Uploads from win/IE need some cleanup because the filename includes also
147
    the path. The option ``cleanup=True`` will do this for you. The option
148
    ``allowEmptyPostfix`` allows to have a filename without extensions. By
149
    default this option is set to ``False`` and will raise a ``ValueError`` if
150
    a filename doesn't contain a extension.
151
    """
152
    widget = getWidgetById(form, id)
1✔
153
    if not allowEmptyPostfix or cleanup:
1!
154
        # We need to strip out the path section even if we do not reomve them
155
        # later, because we just need to check the filename extension.
156
        cleanFileName = widget.filename.split('\\')[-1]
1✔
157
        cleanFileName = cleanFileName.split('/')[-1]
1✔
158
        dottedParts = cleanFileName.split('.')
1✔
159
    if not allowEmptyPostfix:
1✔
160
        if len(dottedParts) <= 1:
1✔
161
            raise ValueError(_('Missing filename extension.'))
1✔
162
    if cleanup:
1✔
163
        return cleanFileName
1✔
164
    return widget.filename
1✔
165

166

167
def changedField(field, value, context=None):
1✔
168
    """Figure if a field's value changed
169

170
    Comparing the value of the context attribute and the given value"""
171
    if context is None:
1✔
172
        context = field.context
1✔
173
    if context is None:
1✔
174
        # IObjectWidget madness
175
        return True
1✔
176
    if zope.schema.interfaces.IObject.providedBy(field):
1✔
177
        return True
1✔
178

179
    # Get the datamanager and get the original value
180
    dm = zope.component.getMultiAdapter(
1✔
181
        (context, field), interfaces.IDataManager)
182
    # now figure value chaged status
183
    # Or we can not get the original value, in which case we can not check
184
    # Or it is an Object, in case we'll never know
185
    if (not dm.canAccess() or dm.query() != value):
1✔
186
        return True
1✔
187
    else:
188
        return False
1✔
189

190

191
def changedWidget(widget, value, field=None, context=None):
1✔
192
    """figure if a widget's value changed
193

194
    Comparing the value of the widget context attribute and the given value"""
195
    if (interfaces.IContextAware.providedBy(widget)
1✔
196
            and not widget.ignoreContext):
197
        # if the widget is context aware, figure if it's field changed
198
        if field is None:
1✔
199
            field = widget.field
1✔
200
        if context is None:
1✔
201
            context = widget.context
1✔
202
        return changedField(field, value, context=context)
1✔
203
    # otherwise we cannot, return 'always changed'
204
    return True
1✔
205

206

207
@zope.interface.implementer(interfaces.IManager)
1✔
208
class Manager(OrderedDict):
1✔
209
    """Non-persistent IManager implementation."""
210

211
    def create_according_to_list(self, d, l_):
1✔
212
        """Arrange elements of d according to sorting of l_."""
213
        # TODO: If we are on Python 3 only reimplement on top of `move_to_end`
214
        self.clear()
1✔
215
        for key in l_:
1✔
216
            if key in d:
1✔
217
                self[key] = d[key]
1✔
218

219
    def __getitem__(self, key):
1✔
220
        if key not in self:
1✔
221
            try:
1✔
222
                return getattr(self, key)
1✔
223
            except AttributeError:
1✔
224
                # make sure an KeyError is raised later
225
                pass
1✔
226
        return super().__getitem__(key)
1✔
227

228

229
@zope.interface.implementer(interfaces.ISelectionManager)
1✔
230
class SelectionManager(Manager):
1✔
231
    """Non-persisents ISelectionManager implementation."""
232

233
    managerInterface = None
1✔
234

235
    def __add__(self, other):
1✔
236
        if not self.managerInterface.providedBy(other):
1✔
237
            return NotImplemented
1✔
238
        return self.__class__(self, other)
1✔
239

240
    def select(self, *names):
1✔
241
        """See interfaces.ISelectionManager"""
242
        return self.__class__(*[self[name] for name in names])
1✔
243

244
    def omit(self, *names):
1✔
245
        """See interfaces.ISelectionManager"""
246
        return self.__class__(
1✔
247
            *[item for name, item in self.items()
248
              if name not in names])
249

250
    def copy(self):
1✔
251
        """See interfaces.ISelectionManager"""
252
        return self.__class__(*self.values())
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