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

zopefoundation / five.formlib / 16399733854

15 Feb 2025 02:34PM UTC coverage: 83.302%. Remained the same
16399733854

push

github

icemac
Back to development: 5.1

49 of 92 branches covered (53.26%)

Branch coverage included in aggregate %.

390 of 435 relevant lines covered (89.66%)

0.9 hits per line

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

80.23
/src/five/formlib/__init__.py
1
##############################################################################
2
#
3
# Copyright (c) 2004, 2005 Zope Corporation 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
"""Add and edit views
15
"""
16
import sys
1✔
17
from datetime import datetime
1✔
18

19
import transaction
1✔
20
from Products.Five.browser import BrowserView
1✔
21
from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
1✔
22
from zope.app.form.browser.submit import Update
1✔
23
from zope.app.form.utility import getWidgetsData
1✔
24
from zope.app.form.utility import setUpEditWidgets
1✔
25
from zope.event import notify
1✔
26
from zope.formlib.interfaces import IInputWidget
1✔
27
from zope.formlib.interfaces import WidgetsError
1✔
28
from zope.formlib.utility import applyWidgetsChanges
1✔
29
from zope.formlib.utility import setUpWidgets
1✔
30
from zope.i18nmessageid import MessageFactory
1✔
31
from zope.lifecycleevent import Attributes
1✔
32
from zope.lifecycleevent import ObjectCreatedEvent
1✔
33
from zope.lifecycleevent import ObjectModifiedEvent
1✔
34
from zope.location import LocationProxy
1✔
35
from zope.location.interfaces import ILocation
1✔
36
from zope.schema import getFieldNamesInOrder
1✔
37
from zope.schema.interfaces import ValidationError
1✔
38

39

40
_ = MessageFactory('zope')
1✔
41

42

43
class EditView(BrowserView):
1✔
44
    """Simple edit-view base class
45

46
    Subclasses should provide a schema attribute defining the schema
47
    to be edited.
48
    """
49

50
    errors = ()
1✔
51
    update_status = None
1✔
52
    label = ''
1✔
53
    charsets = None
1✔
54

55
    # Fall-back field names computes from schema
56
    fieldNames = property(lambda self: getFieldNamesInOrder(self.schema))
1✔
57
    # Fall-back template
58
    generated_form = ZopeTwoPageTemplateFile('edit.pt')
1✔
59

60
    def __init__(self, context, request):
1✔
61
        BrowserView.__init__(self, context, request)
1✔
62
        self._setUpWidgets()
1✔
63

64
    def _setUpWidgets(self):
1✔
65
        adapted = self.schema(self.context)
1✔
66
        if adapted is not self.context:
1!
67
            if not ILocation.providedBy(adapted):
×
68
                adapted = LocationProxy(adapted)
×
69
            adapted.__parent__ = self.context
×
70
        self.adapted = adapted
1✔
71
        setUpEditWidgets(self, self.schema, source=self.adapted,
1✔
72
                         names=self.fieldNames)
73

74
    def setPrefix(self, prefix):
1✔
75
        for widget in self.widgets():
×
76
            widget.setPrefix(prefix)
×
77

78
    def widgets(self):
1✔
79
        return [getattr(self, name + '_widget')
1✔
80
                for name in self.fieldNames]
81

82
    def changed(self):
1✔
83
        # This method is overridden to execute logic *after* changes
84
        # have been made.
85
        pass
1✔
86

87
    def update(self):
1✔
88
        if self.update_status is not None:
1✔
89
            # We've been called before. Just return the status we previously
90
            # computed.
91
            return self.update_status
1✔
92

93
        status = ''
1✔
94

95
        content = self.adapted
1✔
96

97
        if Update in self.request.form.keys():
1✔
98
            changed = False
1✔
99
            try:
1✔
100
                changed = applyWidgetsChanges(
1✔
101
                    self, self.schema, target=content, names=self.fieldNames)
102
                # We should not generate events when an adapter is used.
103
                # That's the adapter's job.  We need to unwrap the objects to
104
                # compare them, as they are wrapped differently.
105
                # Additionally, we can't use Acquisition.aq_base() because
106
                # it strangely returns different objects for these two even
107
                # when they are identical.  In particular
108
                # aq_base(self.adapted) != self.adapted.aq_base :-(
109
                if changed and getattr(self.context, 'aq_base', self.context)\
1!
110
                        is getattr(self.adapted, 'aq_base', self.adapted):
111
                    description = Attributes(self.schema, *self.fieldNames)
1✔
112
                    notify(ObjectModifiedEvent(content, description))
1✔
113
            except WidgetsError as errors:
1✔
114
                self.errors = errors
1✔
115
                status = _("An error occurred.")
1✔
116
                transaction.abort()
1✔
117
            else:
118
                setUpEditWidgets(self, self.schema, source=self.adapted,
1✔
119
                                 ignoreStickyValues=True,
120
                                 names=self.fieldNames)
121
                if changed:
1!
122
                    self.changed()
1✔
123
                    formatter = self.request.locale.dates.getFormatter(
1✔
124
                        'dateTime', 'medium')
125
                    status = _("Updated on ${date_time}",
1✔
126
                               mapping={'date_time':
127
                                        formatter.format(datetime.utcnow())})
128

129
        self.update_status = status
1✔
130
        return status
1✔
131

132

133
class AddView(EditView):
1✔
134
    """Simple edit-view base class.
135

136
    Subclasses should provide a schema attribute defining the schema
137
    to be edited.
138
    """
139

140
    def _setUpWidgets(self):
1✔
141
        setUpWidgets(self, self.schema, IInputWidget, names=self.fieldNames)
1✔
142

143
    def update(self):
1✔
144
        if self.update_status is not None:
1!
145
            # We've been called before. Just return the previous result.
146
            return self.update_status
×
147

148
        if Update in self.request.form:
1✔
149

150
            self.update_status = ''
1✔
151
            try:
1✔
152
                data = getWidgetsData(self, self.schema, names=self.fieldNames)
1✔
153
                self.createAndAdd(data)
1✔
154
            except WidgetsError as errors:
1✔
155
                self.errors = errors
1✔
156
                self.update_status = _("An error occurred.")
1✔
157
                return self.update_status
1✔
158

159
            self.request.response.redirect(self.nextURL())
1✔
160

161
        return self.update_status
1✔
162

163
    def create(self, *args, **kw):
1✔
164
        """Do the actual instantiation."""
165
        # hack to please typical Zope 2 factories, which expect id and title
166
        # Any sane schema will use a unicode title, and may fail on a
167
        # non-unicode one.
168
        args = ('tmp_id', 'Temporary title') + args
1✔
169
        return self._factory(*args, **kw)
1✔
170

171
    def createAndAdd(self, data):
1✔
172
        """Add the desired object using the data in the data argument.
173

174
        The data argument is a dictionary with the data entered in the form.
175
        """
176

177
        args = []
1✔
178
        if self._arguments:
1!
179
            for name in self._arguments:
×
180
                args.append(data[name])
×
181

182
        kw = {}
1✔
183
        if self._keyword_arguments:
1!
184
            for name in self._keyword_arguments:
×
185
                if name in data:
×
186
                    kw[str(name)] = data[name]
×
187

188
        content = self.create(*args, **kw)
1✔
189
        adapted = self.schema(content)
1✔
190

191
        errors = []
1✔
192

193
        if self._set_before_add:
1✔
194
            for name in self._set_before_add:
1✔
195
                if name in data:
1✔
196
                    field = self.schema[name]
1✔
197
                    try:
1✔
198
                        field.set(adapted, data[name])
1✔
199
                    except ValidationError:
×
200
                        errors.append(sys.exc_info()[1])
×
201

202
        if errors:
1!
203
            raise WidgetsError(*errors)
×
204

205
        notify(ObjectCreatedEvent(content))
1✔
206

207
        content = self.add(content)
1✔
208
        adapted = self.schema(content)
1✔
209

210
        if self._set_after_add:
1✔
211
            for name in self._set_after_add:
1✔
212
                if name in data:
1✔
213
                    field = self.schema[name]
1✔
214
                    try:
1✔
215
                        field.set(adapted, data[name])
1✔
216
                    except ValidationError:
×
217
                        errors.append(sys.exc_info()[1])
×
218
            # We have modified the object, so we need to publish an
219
            # object-modified event:
220
            description = Attributes(self.schema, *self._set_after_add)
1✔
221
            notify(ObjectModifiedEvent(content, description))
1✔
222

223
        if errors:
1!
224
            raise WidgetsError(*errors)
×
225

226
        return content
1✔
227

228
    def add(self, content):
1✔
229
        return self.context.add(content)
1✔
230

231
    def nextURL(self):
1✔
232
        return self.context.nextURL()
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