Passed
Pull Request — 2.x (#1864)
by Ramon
06:04
created

senaite.core.z3cform.widgets.uidreference   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 198
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 28
eloc 122
dl 0
loc 198
rs 10
c 0
b 0
f 0

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