Passed
Push — 2.x ( dfb5c5...7c9886 )
by Jordi
11:10
created

senaite.core.z3cform.widgets.uidreference.widget   A

Complexity

Total Complexity 37

Size/Duplication

Total Lines 238
Duplicated Lines 25.63 %

Importance

Changes 0
Metric Value
wmc 37
eloc 150
dl 61
loc 238
rs 9.44
c 0
b 0
f 0

1 Function

Rating   Name   Duplication   Size   Complexity  
A UIDReferenceWidgetFactory() 0 6 1

20 Methods

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

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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