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

zopefoundation / grokcore.formlib / 16861221675

18 Jun 2025 06:33AM UTC coverage: 96.005%. Remained the same
16861221675

push

github

icemac
Back to development: 5.1

92 of 106 branches covered (86.79%)

Branch coverage included in aggregate %.

701 of 720 relevant lines covered (97.36%)

0.97 hits per line

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

89.05
/src/grokcore/formlib/formlib.py
1
##############################################################################
2
#
3
# Copyright (c) 2006-2008 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
"""Custom implementations of formlib helpers
15
"""
16

17
import zope.event
1✔
18
import zope.formlib.form
1✔
19
import zope.interface
1✔
20
import zope.lifecycleevent
1✔
21
from grokcore.content import ObjectEditedEvent
1✔
22
from zope.formlib.interfaces import IInputWidget
1✔
23
from zope.interface.interfaces import IInterface
1✔
24
from zope.schema.interfaces import IField
1✔
25

26

27
class action(zope.formlib.form.action):
1✔
28
    """We override the action decorator we pass in our custom Action.
29
    """
30

31
    def __call__(self, success):
1✔
32
        action = Action(self.label, success=success, **self.options)
1✔
33
        self.actions.append(action)
1✔
34
        return action
1✔
35

36

37
class Action(zope.formlib.form.Action):
1✔
38

39
    def validate(self, data):
1✔
40
        errors = super().validate(data)
1✔
41
        if errors is None:
1!
42
            errors = self.form.validate(self, data)
1✔
43
        errors.extend(ensure_required_fields_have_input(
1✔
44
            self.form.widgets, data))
45
        return errors
1✔
46

47
    def success(self, data):
1✔
48
        if self.success_handler is not None:
1!
49
            return self.success_handler(self.form, **data)
1✔
50

51

52
def ensure_required_fields_have_input(widgets, data):
1✔
53
    errors = []
1✔
54
    for widget in widgets:
1✔
55
        if not IInputWidget.providedBy(widget):
1!
56
            continue
×
57
        if not widget.context.required or widget.hasInput():
1!
58
            continue
1✔
59
        name = widget.context.__name__
×
60
        error = zope.formlib.interfaces.WidgetInputError(
×
61
            name,
62
            widget.label,
63
            zope.schema.interfaces.RequiredMissing(name))
64
        widget._error = error
×
65
        errors.append(error)
×
66
    return errors
1✔
67

68

69
def Fields(*args, **kw):
1✔
70
    fields = []
1✔
71
    for key, value in list(kw.items()):
1✔
72
        if IField.providedBy(value):
1!
73
            value.__name__ = key
1✔
74
            fields.append(value)
1✔
75
            del kw[key]
1✔
76
    fields.sort(key=lambda field: field.order)
1✔
77
    return zope.formlib.form.Fields(*(args + tuple(fields)), **kw)
1✔
78

79

80
def get_auto_fields(context):
1✔
81
    """Get the form fields for context.
82
    """
83
    # for an interface context, we generate them from that interface
84
    if IInterface.providedBy(context):
1✔
85
        return zope.formlib.form.Fields(context)
1✔
86
    # if we have a non-interface context, we're autogenerating them
87
    # from any schemas defined by the context
88
    fields = zope.formlib.form.Fields(*most_specialized_interfaces(context))
1✔
89
    # we pull in this field by default, but we don't want it in our form
90
    fields = fields.omit('__name__')
1✔
91
    return fields
1✔
92

93

94
AutoFields = get_auto_fields
1✔
95

96

97
def most_specialized_interfaces(context):
1✔
98
    """Get interfaces for an object without any duplicates.
99

100
    Interfaces in a declaration for an object may already have been seen
101
    because it is also inherited by another interface. Don't return the
102
    interface twice, as that would result in duplicate names when creating
103
    the form.
104
    """
105
    declaration = zope.interface.implementedBy(context)
1✔
106
    seen = []
1✔
107
    for iface in declaration.flattened():
1✔
108
        if interface_seen(seen, iface):
1✔
109
            continue
1✔
110
        seen.append(iface)
1✔
111
    return seen
1✔
112

113

114
def interface_seen(seen, iface):
1✔
115
    """Return True if interface already is seen.
116
    """
117
    for seen_iface in seen:
1✔
118
        if seen_iface.extends(iface):
1✔
119
            return True
1✔
120
    return False
1✔
121

122

123
def apply_data(context, form_fields, data, adapters=None, update=False):
1✔
124
    """Save form data (``data`` dict) on a ``context`` object.
125

126
    This is a beefed up version of zope.formlib.form.applyChanges().
127
    It allows you to specify whether values should be compared with
128
    the attributes on already existing objects or not, using the
129
    ``update`` parameter.
130

131
    Unlike zope.formlib.form.applyChanges(), it will return a
132
    dictionary of interfaces and their fields that were changed.  This
133
    is necessary to appropriately send IObjectModifiedEvents.
134
    """
135
    if adapters is None:
1!
136
        adapters = {}
×
137

138
    changes = {}
1✔
139

140
    for form_field in form_fields:
1✔
141
        field = form_field.field
1✔
142
        # Adapt context, if necessary
143
        interface = form_field.interface
1✔
144
        adapter = adapters.get(interface)
1✔
145
        if adapter is None:
1✔
146
            if interface is None:
1!
147
                adapter = context
×
148
            else:
149
                adapter = interface(context)
1✔
150
            adapters[interface] = adapter
1✔
151

152
        name = form_field.__name__
1✔
153
        newvalue = data.get(name, form_field)  # using form_field as marker
1✔
154

155
        if update:
1✔
156
            if ((newvalue is not form_field) and
1✔
157
                    (field.get(adapter) != newvalue)):
158
                field.set(adapter, newvalue)
1✔
159
                changes.setdefault(interface, []).append(name)
1✔
160
        else:
161
            if newvalue is not form_field:
1!
162
                field.set(adapter, newvalue)
1✔
163
                changes.setdefault(interface, []).append(name)
1✔
164

165
    return changes
1✔
166

167

168
def apply_data_event(context, form_fields, data, adapters=None, update=False):
1✔
169
    """Like apply_data, but also sends an IObjectModifiedEvent.
170
    """
171
    changes = apply_data(context, form_fields, data, adapters, update)
1✔
172

173
    if changes:
1✔
174
        descriptions = []
1✔
175
        for interface, names in changes.items():
1✔
176
            descriptions.append(
1✔
177
                zope.lifecycleevent.Attributes(interface, *names))
178
        zope.event.notify(ObjectEditedEvent(context, *descriptions))
1✔
179

180
    return changes
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