Passed
Push — 2.x ( 6a1f51...1afad9 )
by Jordi
05:47
created

senaite.core.z3cform.widgets.uidreference   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 209
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 30
eloc 129
dl 0
loc 209
rs 10
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A UIDReferenceWidget.get_api_url() 0 5 1
A UIDReferenceWidget.get_query() 0 2 1
A UIDReferenceWidget.get_input_widget_attributes() 0 25 2
A UIDReferenceWidget.attr() 0 15 5
A UIDReferenceWidget.get_obj_info() 0 8 1
A UIDReferenceWidget.render_reference() 0 6 1
A UIDReferenceWidget.__init__() 0 3 1
A UIDReferenceWidget.update() 0 3 1
A UIDReferenceWidget.get_catalog() 0 2 1
A UIDReferenceWidget.get_limit() 0 2 1
A UIDReferenceWidget.is_multi_valued() 0 2 1
A UIDReferenceDataConverter.toFieldValue() 0 6 1
A UIDReferenceDataConverter.toWidgetValue() 0 12 1
A UIDReferenceWidget.get_value() 0 15 4
A UIDReferenceWidget.get_form() 0 8 2
A UIDReferenceWidget.get_context() 0 22 3
A UIDReferenceWidget.get_display_template() 0 2 1
A UIDReferenceWidget.get_columns() 0 2 1

1 Function

Rating   Name   Duplication   Size   Complexity  
A UIDReferenceWidgetFactory() 0 6 1
1
# -*- coding: utf-8 -*-
2
3
import json
4
import string
5
6
import six
7
8
from bika.lims import api
9
from Products.CMFPlone.utils import base_hasattr
10
from senaite.app.supermodel import SuperModel
11
from senaite.core.interfaces import ISenaiteFormLayer
12
from senaite.core.schema.interfaces import IUIDReferenceField
13
from senaite.core.z3cform.interfaces import IUIDReferenceWidget
14
from plone.z3cform.fieldsets.interfaces import IDescriptiveGroup
15
from z3c.form import interfaces
16
from z3c.form.browser import widget
17
from z3c.form.browser.textlines import TextLinesWidget
18
from z3c.form.converter import TextLinesConverter
19
from z3c.form.interfaces import IDataConverter
20
from z3c.form.interfaces import IFieldWidget
21
from z3c.form.widget import FieldWidget
22
from zope.component import adapter
23
from zope.component import getUtility
24
from zope.component.interfaces import IFactory
25
from zope.interface import implementer
26
27
DISPLAY_TEMPLATE = "<a href='${url}' _target='blank'>${title}</a>"
28
29
_marker = object
30
31
32
@adapter(IUIDReferenceField, interfaces.IWidget)
33
class UIDReferenceDataConverter(TextLinesConverter):
34
    """Converts the raw field data for widget/field usage
35
    """
36
37
    def toWidgetValue(self, value):
38
        """Return the value w/o changes
39
40
        Note:
41
42
        All widget templates use the `get_value` method,
43
        which ensures a list of UIDs.
44
45
        However, `toWidgetValue` is called by `widget.update()` implicitly for
46
        `self.value`, which is then used by the `get_value` method again.
47
        """
48
        return value
49
50
    def toFieldValue(self, value):
51
        """Converts a unicode string to a list of UIDs
52
        """
53
        # remove any blank lines at the end
54
        value = value.rstrip("\r\n")
55
        return super(UIDReferenceDataConverter, self).toFieldValue(value)
56
57
58
@implementer(IUIDReferenceWidget)
59
class UIDReferenceWidget(TextLinesWidget):
60
    """Senaite UID reference widget
61
    """
62
    klass = u"senaite-uidreference-widget"
63
64
    def __init__(self, request, *args, **kw):
65
        super(UIDReferenceWidget, self).__init__(request)
66
        self.request = request
67
68
    def update(self):
69
        super(UIDReferenceWidget, self).update()
70
        widget.addFieldClass(self)
71
72
    def get_form(self):
73
        """Return the current form of the widget
74
        """
75
        form = self.form
76
        # form is a fieldset group
77
        if IDescriptiveGroup.providedBy(form):
78
            form = form.parentForm
79
        return form
80
81
    def get_context(self):
82
        """Get the current context
83
84
        NOTE: If we are in the ++add++ form, `self.context` is the container!
85
              Therefore, we create one here to have access to the methods.
86
        """
87
        schema_iface = self.field.interface
88
        if schema_iface and schema_iface.providedBy(self.context):
89
            return self.context
90
91
        # Hack alert!
92
        # we are in ++add++ form and have no context!
93
        # Create a temporary object to be able to access class methods
94
        form = self.get_form()
95
        portal_type = form.portal_type
96
        portal_types = api.get_tool("portal_types")
97
        fti = portal_types[portal_type]
98
        factory = getUtility(IFactory, fti.factory)
99
        context = factory("temporary")
100
        # hook into acquisition chain
101
        context = context.__of__(self.context)
102
        return context
103
104
    def attr(self, name, default=None):
105
        """Get the named attribute of the widget or the field
106
        """
107
        value = getattr(self, name, _marker)
108
        if value is _marker:
109
            return default
110
        if isinstance(value, six.string_types):
111
            context = self.get_context()
112
            if base_hasattr(context, value):
113
                attr = getattr(context, value)
114
                if callable(attr):
115
                    value = attr()
116
                else:
117
                    value = attr
118
        return value
119
120
    def get_value(self):
121
        """Extract the value from the request or get it from the field
122
        """
123
        # get the processed value from the `update` method
124
        value = self.value
125
        # the value might come from the request, e.g. on object creation
126
        if isinstance(value, six.string_types):
127
            value = IDataConverter(self).toFieldValue(value)
128
        # we handle always lists in the templates
129
        if value is None:
130
            return []
131
        if not isinstance(value, (list, tuple)):
132
            value = [value]
133
        # just to be sure (paranoid)
134
        return [uid for uid in value if api.is_uid(uid)]
135
136
    def get_api_url(self):
137
        portal = api.get_portal()
138
        portal_url = api.get_url(portal)
139
        api_url = "{}/@@API/senaite/v1".format(portal_url)
140
        return api_url
141
142
    def get_display_template(self):
143
        return self.attr("display_template", DISPLAY_TEMPLATE)
144
145
    def get_catalog(self):
146
        return self.attr("catalog", "portal_catalog")
147
148
    def get_query(self):
149
        return self.attr("query", {})
150
151
    def get_columns(self):
152
        return self.attr("columns", [])
153
154
    def get_limit(self):
155
        return self.attr("limit", 25)
156
157
    def is_multi_valued(self):
158
        return getattr(self.field, "multi_valued", False)
159
160
    def get_input_widget_attributes(self):
161
        """Return input widget attributes for the ReactJS component
162
        """
163
        uids = self.get_value()
164
        attributes = {
165
            "data-id": self.id,
166
            "data-name": self.name,
167
            "data-uids": uids,
168
            "data-api_url": self.get_api_url(),
169
            "data-records": dict(zip(uids, map(self.get_obj_info, uids))),
170
            "data-query": self.get_query(),
171
            "data-catalog": self.get_catalog(),
172
            "data-columns": self.get_columns(),
173
            "data-display_template": self.get_display_template(),
174
            "data-limit": self.get_limit(),
175
            "data-multi_valued": self.is_multi_valued(),
176
            "data-disabled": self.disabled or False,
177
            "data-readonly": self.readonly or False,
178
        }
179
180
        # convert all attributes to JSON
181
        for key, value in attributes.items():
182
            attributes[key] = json.dumps(value)
183
184
        return attributes
185
186
    def get_obj_info(self, uid):
187
        """Returns a dictionary with the object info
188
        """
189
        model = SuperModel(uid)
190
        obj_info = model.to_dict()
191
        obj_info["uid"] = uid
192
        obj_info["url"] = api.get_url(model)
193
        return obj_info
194
195
    def render_reference(self, uid):
196
        """Returns a rendered HTML element for the reference
197
        """
198
        template = string.Template(self.get_display_template())
199
        obj_info = self.get_obj_info(uid)
200
        return template.safe_substitute(obj_info)
201
202
203
@adapter(IUIDReferenceField, ISenaiteFormLayer)
204
@implementer(IFieldWidget)
205
def UIDReferenceWidgetFactory(field, request):
206
    """Widget factory for UIDReferenceField
207
    """
208
    return FieldWidget(field, UIDReferenceWidget(request))
209