Passed
Pull Request — 2.x (#1864)
by Ramon
28:02 queued 21:53
created

UIDReferenceWidget.get_value()   A

Complexity

Conditions 4

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 15
rs 9.95
c 0
b 0
f 0
cc 4
nop 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.core.interfaces import ISenaiteFormLayer
11
from senaite.core.schema.interfaces import IUIDReferenceField
12
from senaite.core.z3cform.interfaces import IUIDReferenceWidget
13
from z3c.form import interfaces
14
from z3c.form.browser import widget
15
from senaite.app.supermodel import SuperModel
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.interface import implementer
23
24
DISPLAY_TEMPLATE = "<a href='${url}' _target='blank'>${title} ${uid}</a>"
25
26
_marker = object
27
28
29
@adapter(IUIDReferenceField, interfaces.IWidget)
30
class UIDReferenceDataConverter(TextLinesConverter):
31
    """Converts the raw field data for widget/field usage
32
    """
33
34
    def toWidgetValue(self, value):
35
        """Return the value w/o changes
36
37
        Note:
38
39
        All widget templates use the `get_value` method,
40
        which ensures a list of UIDs.
41
42
        However, `toWidgetValue` is called by `widget.update()` implicitly for
43
        `self.value`, which is then used by the `get_value` method again.
44
        """
45
        return value
46
47
    def toFieldValue(self, value):
48
        """Converts a unicode string to a list of UIDs
49
        """
50
        # remove any blank lines at the end
51
        value = value.rstrip("\r\n")
52
        return super(UIDReferenceDataConverter, self).toFieldValue(value)
53
54
55
@implementer(IUIDReferenceWidget)
56
class UIDReferenceWidget(TextLinesWidget):
57
    """Senaite UID reference widget
58
    """
59
    klass = u"senaite-uidreference-widget"
60
61
    def __init__(self, request, *args, **kw):
62
        super(UIDReferenceWidget, self).__init__(request)
63
        self.request = request
64
65
    def update(self):
66
        super(UIDReferenceWidget, self).update()
67
        widget.addFieldClass(self)
68
69
    def attr(self, name, default=None):
70
        """Get the named attribute of the widget or the field
71
        """
72
        value = getattr(self, name, _marker)
73
        if value is _marker:
74
            return default
75
        if isinstance(value, six.string_types):
76
            if base_hasattr(self.context, value):
77
                attr = getattr(self.context, value)
78
                if callable(attr):
79
                    value = attr()
80
                else:
81
                    value = attr
82
        return value
83
84
    def get_value(self):
85
        """Extract the value from the request or get it from the field
86
        """
87
        # get the processed value from the `update` method
88
        value = self.value
89
        # the value might come from the request, e.g. on object creation
90
        if isinstance(value, six.string_types):
91
            value = IDataConverter(self).toFieldValue(value)
92
        # we handle always lists in the templates
93
        if value is None:
94
            return []
95
        if not isinstance(value, (list, tuple)):
96
            value = [value]
97
        # just to be sure (paranoid)
98
        return [uid for uid in value if api.is_uid(uid)]
99
100
    def get_api_url(self):
101
        portal = api.get_portal()
102
        portal_url = api.get_url(portal)
103
        api_url = "{}/@@API/senaite/v1".format(portal_url)
104
        return api_url
105
106
    def get_display_template(self):
107
        return self.attr("display_template", DISPLAY_TEMPLATE)
108
109
    def get_catalog(self):
110
        return self.attr("catalog", "portal_catalog")
111
112
    def get_query(self):
113
        return self.attr("query", {})
114
115
    def get_columns(self):
116
        return self.attr("columns", [])
117
118
    def get_limit(self):
119
        return self.attr("limit", 25)
120
121
    def is_multi_valued(self):
122
        return getattr(self.field, "multi_valued", False)
123
124
    def get_input_widget_attributes(self):
125
        """Return input widget attributes for the ReactJS component
126
        """
127
        uids = self.get_value()
128
        attributes = {
129
            "data-id": self.id,
130
            "data-name": self.name,
131
            "data-uids": uids,
132
            "data-api_url": self.get_api_url(),
133
            "data-records": dict(zip(uids, map(self.get_obj_info, uids))),
134
            "data-query": self.get_query(),
135
            "data-catalog": self.get_catalog(),
136
            "data-columns": self.get_columns(),
137
            "data-display_template": self.get_display_template(),
138
            "data-limit": self.get_limit(),
139
            "data-multi_valued": self.is_multi_valued(),
140
            "data-disabled": self.disabled or False,
141
            "data-readonly": self.readonly or False,
142
        }
143
144
        # convert all attributes to JSON
145
        for key, value in attributes.items():
146
            attributes[key] = json.dumps(value)
147
148
        return attributes
149
150
    def get_obj_info(self, uid):
151
        """Returns a dictionary with the object info
152
        """
153
        model = SuperModel(uid)
154
        obj_info = model.to_dict()
155
        obj_info["uid"] = uid
156
        obj_info["url"] = api.get_url(model)
157
        return obj_info
158
159
    def render_reference(self, uid):
160
        """Returns a rendered HTML element for the reference
161
        """
162
        template = string.Template(self.get_display_template())
163
        obj_info = self.get_obj_info(uid)
164
        return template.safe_substitute(obj_info)
165
166
167
@adapter(IUIDReferenceField, ISenaiteFormLayer)
168
@implementer(IFieldWidget)
169
def UIDReferenceWidgetFactory(field, request):
170
    """Widget factory for UIDReferenceField
171
    """
172
    return FieldWidget(field, UIDReferenceWidget(request))
173