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

zopefoundation / zc.form / 16248976557

21 Oct 2024 07:16AM UTC coverage: 82.759%. Remained the same
16248976557

push

github

icemac
Back to development: 2.2

126 of 180 branches covered (70.0%)

Branch coverage included in aggregate %.

786 of 922 relevant lines covered (85.25%)

0.85 hits per line

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

55.08
/src/zc/form/browser/widgetapi.py
1
##############################################################################
2
#
3
# Copyright (c) 2003-2004 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
"""Alternate base classes for IBrowserWidget implementations.
15

16
The base classes provided here implement the IBrowserWidget API and
17
provide a simpler API that derived classes are expected to implement.
18
"""
19

20
from xml.sax.saxutils import quoteattr
1✔
21

22
from zope.formlib.interfaces import IBrowserWidget
1✔
23
from zope.formlib.interfaces import IInputWidget
1✔
24
from zope.formlib.interfaces import WidgetInputError
1✔
25
from zope.formlib.widget import BrowserWidget
1✔
26
from zope.formlib.widget import InputWidget
1✔
27
from zope.interface import implementer
1✔
28
from zope.schema.interfaces import ValidationError
1✔
29

30
from zc.form.i18n import _
1✔
31

32

33
_msg_missing_single_value_display = _(
1✔
34
    _("widget-missing-single-value-for-display"), "")
35
_msg_missing_multiple_value_display = _(
1✔
36
    _("widget-missing-multiple-value-for-display"), "")
37

38
_msg_missing_single_value_edit = _(
1✔
39
    _("widget-missing-single-value-for-edit"), "(no value)")
40
_msg_missing_multiple_value_edit = _(
1✔
41
    _("widget-missing-multiple-value-for-edit"), "(no value)")
42

43

44
@implementer(IBrowserWidget, IInputWidget)
1✔
45
class BaseWidget(BrowserWidget, InputWidget):
1✔
46
    # Note to previous users of widgetapi:
47
    # .translate -> ._translate; .__prefix -> ._prefix;  NullValue ->
48
    # ._data_marker; .__initial_value and .__calculated_value -> replaced
49
    # with ._data (because zope.formlib.utility.setUpWidget behavior changed
50
    # for the better)
51

52
    _initialized = False
1✔
53
    _error = None
1✔
54
    # set `_display` to True if you are using this for a display widget:
55
    _display = False
1✔
56

57
    # Form management methods.
58
    # Subclasses should not need to override these.
59

60
    def getInputValue(self):
1✔
61
        if not self._initialized:
1!
62
            self._initialize()
1✔
63

64
        if self._error is not None:
1✔
65
            raise self._error
1✔
66

67
        value = self._data
1✔
68
        field = self.context
1✔
69

70
        # allow missing values only for non-required fields
71
        if value == field.missing_value and not field.required:
1✔
72
            return value
1✔
73

74
        # value must be valid per the field contraints
75
        try:
1✔
76
            field.validate(value)
1✔
77
        except ValidationError as v:
1✔
78
            self._error = WidgetInputError(
1✔
79
                self.context.__name__, self.context.title, v)
80
            raise self._error
1✔
81
        return value
1✔
82

83
    def hasInput(self):
1✔
84
        if self._display:
1!
85
            return False
×
86
        marker_name = self.name + "-marker"
1✔
87
        return marker_name in self.request.form
1✔
88

89
    def _initialize(self):
1✔
90
        self._initialized = True
1✔
91
        self.initialize()
1✔
92
        if not self._renderedValueSet():
1✔
93
            if self.hasInput():
1✔
94
                self._data = self.loadValueFromRequest()
1✔
95
            else:  # self._data is self._data_marker but no input in request
96
                self._data = self.context.default
1✔
97

98
    def applyChanges(self, content):
1✔
99
        field = self.context
×
100
        value = self.getInputValue()
×
101
        change = field.query(content, self) != value
×
102
        if change:
×
103
            field.set(content, value)
×
104
            # Dynamic fields may change during a set, so re-get their value;
105
            # this is a larger Zope3 problem which is solved here for now.
106
            self._data = field.get(content)
×
107
        return change
×
108

109
    # Rendering methods:
110
    # (These should not need to be overridden.)
111

112
    def __call__(self):
1✔
113
        if not self._initialized:
1✔
114
            self._initialize()
1✔
115
        marker = self._get_marker()
1✔
116
        return marker + self.render(self._data)
1✔
117

118
    def hidden(self):
1✔
119
        if not self._initialized:
×
120
            self._initialize()
×
121
        marker = self._get_marker()
×
122
        return marker + self.renderHidden(self._data)
×
123

124
    def _get_marker(self):
1✔
125
        marker_name = self.name + "-marker"
1✔
126
        return "<input type='hidden' name='%s' value='x' />\n" % marker_name
1✔
127

128
    # API for subclasses to implement:
129

130
    def initialize(self):
1✔
131
        """Initialize internal data structures needed by the widget.
132

133
        This method should not load values from the request.
134

135
        Derived classes should call the base class initialize() before
136
        performing specialized initialization.  This requirement is
137
        waived for classes which inherit directly from, and *only*
138
        from, BaseWidget.
139
        """
140

141
    def loadValueFromRequest(self):
1✔
142
        """Load the value from data in the request."""
143
        raise NotImplementedError(
144
            "BaseWidget subclasses must implement loadValueFromRequest()")
145

146
    def render(self, value):
1✔
147
        raise NotImplementedError(
148
            "BaseWidget subclasses must implement render()")
149

150
    def renderHidden(self, value):
1✔
151
        """Render a hidden widget"""
152

153

154
class BaseVocabularyWidget(BaseWidget):
1✔
155

156
    query = None
1✔
157
    queryview = None
1✔
158

159
    def __init__(self, field, vocabulary, request):
1✔
160
        """Initialize the widget."""
161
        # only allow this to happen for a bound field
162
        assert field.context is not None
×
163
        self.vocabulary = vocabulary
×
164
        super().__init__(field, request)
×
165

166
    # Helpers used by the vocabulary widget machinery;
167
    # these should not be overridden.
168

169
    def setQuery(self, query, queryview):
1✔
170
        assert self.query is None
×
171
        assert self.queryview is None
×
172
        assert query is not None
×
173
        assert queryview is not None
×
174
        self.query = query
×
175
        self.queryview = queryview
×
176

177
        # Use of a hyphen to form the name for the query widget
178
        # ensures that it won't clash with anything else, since
179
        # field names are normally Python identifiers.
180
        queryview.setName(self.name + "-query")
×
181

182
    def initialize(self):
1✔
183
        """Make sure the query view has a chance to initialize itself."""
184
        if self.queryview is not None:
×
185
            self.queryview.initialize()
×
186

187
    def loadValueFromRequest(self):
1✔
188
        """Load the value from data in the request.
189

190
        If self.queryview is not None, this method is responsible for
191
        calling the query view's performAction() method with the value
192
        loaded, and returning the result::
193

194
            value = ...load value from request data...
195
            if self.queryview is not None:
196
                value = self.queryview.performAction(value)
197
            return value
198
        """
199
        return super().loadValueFromRequest()
×
200

201
    # Convenience method:
202

203
    def convertTokensToValues(self, tokens):
1✔
204
        """Convert a list of tokens to a list of values.
205

206
        If an invalid token is encountered, WidgetInputError is raised.
207
        """
208
        L = []
×
209
        for token in tokens:
×
210
            try:
×
211
                term = self.vocabulary.getTermByToken(token)
×
212
            except LookupError:
×
213
                raise WidgetInputError(
×
214
                    self.context.__name__,
215
                    self.context.title,
216
                    "token %r not found in vocabulary" % token)
217
            else:
218
                L.append(term.value)
×
219
        return L
×
220

221

222
class BaseVocabularyDisplay(BaseVocabularyWidget):
1✔
223

224
    _display = True
1✔
225

226
    def render(self, value):
1✔
227
        if value in (self._data_marker, None):
×
228
            # missing single value
229
            return self.translate(_msg_missing_single_value_display)
×
230
        else:
231
            return self.renderTerm(self.vocabulary.getTerm(value))
×
232

233
    def renderTerm(self, term):
1✔
234
        """Return textual presentation for term."""
235
        raise NotImplementedError("BaseVocabularyMultiDisplay subclasses"
236
                                  " must implement renderTerm()")
237

238
    def _get_marker(self):
1✔
239
        return ""
×
240

241

242
class BaseVocabularyMultiDisplay(BaseVocabularyDisplay):
1✔
243
    """Base class for display widgets of multi-valued vocabulary fields."""
244

245
    def render(self, value):
1✔
246
        if not value:
×
247
            # missing multiple value
248
            return self.translate(_msg_missing_multiple_value_display)
×
249
        else:
250
            pattern = ("<li>%s\n"
×
251
                       "    <input type='hidden' name=%s value=%s /></li>")
252
            vocabulary = self.vocabulary
×
253
            L = []
×
254
            name = quoteattr(self.name)
×
255
            for v in value:
×
256
                term = vocabulary.getTerm(v)
×
257
                L.append(pattern % (self.renderTerm(term), name,
×
258
                                    quoteattr(term.token)))
259
            return ("<%s class=%s id=%s>\n%s\n</%s>"
×
260
                    % (self.containerElementType,
261
                       quoteattr(self.containerCssClass),
262
                       quoteattr(self.name),
263
                       "\n".join(L),
264
                       self.containerElementType))
265

266
    containerCssClass = "values"
1✔
267

268

269
class BaseVocabularyBagDisplay(BaseVocabularyMultiDisplay):
1✔
270
    """Base class for display widgets of unordered multi-valued
271
    vocabulary fields."""
272

273
    containerElementType = "ul"
1✔
274

275

276
class BaseVocabularyListDisplay(BaseVocabularyMultiDisplay):
1✔
277
    """Base class for display widgets of ordered multi-valued
278
    vocabulary fields."""
279

280
    containerElementType = "ol"
1✔
281

282

283
class BaseQueryView:
1✔
284

285
    name = None
1✔
286
    widget = None
1✔
287
    _initialized = False
1✔
288

289
    def __init__(self, context, request):
1✔
290
        self.context = context
×
291
        self.request = request
×
292

293
    # Methods called by the vocabulary widget construction machinery;
294
    # subclasses should not need to override these.
295

296
    def setName(self, name):
1✔
297
        assert not self._initialized
×
298
        assert not name.endswith(".")
×
299
        assert self.name is None
×
300
        self.name = name
×
301

302
    def setWidget(self, widget):
1✔
303
        assert not self._initialized
×
304
        assert self.widget is None
×
305
        assert widget is not None
×
306
        self.widget = widget
×
307

308
    # Methods which may be overriden by subclasses:
309

310
    def initialize(self):
1✔
311
        """Initialization which does not require reading the request.
312

313
        Derived classes should call the base class initialize() before
314
        performing specialized initialization.
315
        """
316
        # Should loading from the request happen here?
317
        assert self.name is not None
×
318
        assert self.widget is not None
×
319
        assert not self._initialized
×
320
        self._initialized = True
×
321

322
    def renderResults(self, value):
1✔
323
        """Render query results if we have any, otherwise return an
324
        empty string.
325
        """
326
        results = self.getResults()
×
327
        if results is None:
×
328
            return ""
×
329
        else:
330
            return self.renderQueryResults(results, value)
×
331

332
    # Methods which should be overriden by subclasses:
333

334
    def performAction(self, value):
1✔
335
        """Perform any modifications to the value based on user actions.
336

337
        This method should be overriden if the query view provides any
338
        actions which can modify the value of the field.
339
        """
340
        return value
×
341

342
    # Methods which must be overriden by subclasses:
343

344
    def getResults(self):
1✔
345
        """Perform the query, or return None.
346

347
        The return value should be None if there is no query to
348
        execute, or an object that can be rendered as a set of results
349
        by renderQueryResults().
350

351
        If the query results in an empty set of results, some value
352
        other than None should be used to represent the results so
353
        that renderQueryResults() can provide a helpful message.
354
        """
355
        raise NotImplementedError(
356
            "BaseQueryView subclasses must implement getResults()")
357

358
    def renderInput(self):
1✔
359
        """Render the input area of the query view."""
360
        raise NotImplementedError(
361
            "BaseQueryView subclasses must implement renderInput()")
362

363
    def renderQueryResults(self, results, value):
1✔
364
        """Render the results returned by getResults()."""
365
        raise NotImplementedError(
366
            "BaseQueryView subclasses must implement renderQueryResults()")
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