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

senaite.core.z3cform.widgets.queryselect.widget   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 217
Duplicated Lines 27.65 %

Importance

Changes 0
Metric Value
wmc 36
eloc 136
dl 60
loc 217
rs 9.52
c 0
b 0
f 0

19 Methods

Rating   Name   Duplication   Size   Complexity  
A QuerySelectWidget.get_allow_user_value() 0 4 1
A QuerySelectWidget.get_query() 0 2 1
B QuerySelectWidget.get_context() 31 31 6
A QuerySelectWidget.update() 0 3 1
A QuerySelectWidget.get_search_wildcard() 0 2 1
A QuerySelectWidget.get_hide_input_after_select() 0 2 1
A QuerySelectDataConverter.toWidgetValue() 0 12 1
A QuerySelectWidget.get_value_key() 0 2 1
A QuerySelectWidget.get_input_widget_attributes() 0 29 2
A QuerySelectWidget.get_api_url() 0 7 1
A QuerySelectWidget.get_catalog() 0 2 1
A QuerySelectWidget.get_search_index() 0 2 1
A QuerySelectWidget.get_value() 14 14 4
A QuerySelectDataConverter.toFieldValue() 0 6 1
A QuerySelectWidget.get_columns() 0 2 1
A QuerySelectWidget.is_multi_valued() 0 2 1
A QuerySelectWidget.get_limit() 0 2 1
B QuerySelectWidget.attr() 15 15 6
A QuerySelectWidget.get_form() 0 11 3

1 Function

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

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
5
import six
6
7
from bika.lims import api
8
from plone.z3cform.fieldsets.interfaces import IDescriptiveGroup
9
from Products.CMFPlone.utils import base_hasattr
10
from senaite.core.interfaces import ISenaiteFormLayer
11
from senaite.core.z3cform.interfaces import IQuerySelectWidget
12
from z3c.form.browser import widget
13
from z3c.form.converter import TextLinesConverter
14
from z3c.form.interfaces import IDataConverter
15
from z3c.form.interfaces import IFieldWidget
16
from z3c.form.interfaces import ISubForm
17
from z3c.form.widget import FieldWidget
18
from z3c.form.widget import Widget
19
from zope.component import adapter
20
from zope.component import getUtility
21
from zope.component.interfaces import IFactory
22
from zope.interface import implementer
23
from zope.interface import implementer_only
24
from zope.schema.interfaces import IField
25
from zope.schema.interfaces import ISequence
26
27
_marker = object
28
29
30
@adapter(ISequence, IQuerySelectWidget)
31
class QuerySelectDataConverter(TextLinesConverter):
32
    """Converter for multi valued List fields
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(QuerySelectDataConverter, self).toFieldValue(value)
53
54
55
@implementer_only(IQuerySelectWidget)
56
class QuerySelectWidget(widget.HTMLInputWidget, Widget):
57
    """A widget to select one or more items from catalog search
58
    """
59
    klass = u"senaite-queryselect-widget-input"
60
61
    def update(self):
62
        super(QuerySelectWidget, self).update()
63
        widget.addFieldClass(self)
64
65
    def get_form(self):
66
        """Return the current form of the widget
67
        """
68
        form = self.form
69
        # form is a fieldset group
70
        if IDescriptiveGroup.providedBy(form):
71
            form = form.parentForm
72
        # form is a subform (e.g. DataGridFieldObjectSubForm)
73
        if ISubForm.providedBy(form):
74
            form = form.parentForm
75
        return form
76
77 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...
78
        """Get the current context
79
80
        NOTE: If we are in the ++add++ form, `self.context` is the container!
81
              Therefore, we create one here to have access to the methods.
82
        """
83
        schema_iface = self.field.interface
84
        if schema_iface and schema_iface.providedBy(self.context):
85
            return self.context
86
87
        # we might be in a subform, so try first to retrieve the object from
88
        # the base form itself first
89
        form = self.get_form()
90
        portal_type = getattr(form, "portal_type", None)
91
        context = getattr(form, "context", None)
92
        if api.is_object(context):
93
            if api.get_portal_type(context) == portal_type:
94
                return context
95
96
        # Hack alert!
97
        # we are in ++add++ form and have no context!
98
        # Create a temporary object to be able to access class methods
99
        if not portal_type:
100
            portal_type = api.get_portal_type(self.context)
101
        portal_types = api.get_tool("portal_types")
102
        fti = portal_types[portal_type]
103
        factory = getUtility(IFactory, fti.factory)
104
        context = factory("temporary")
105
        # hook into acquisition chain
106
        context = context.__of__(self.context)
107
        return context
108
109 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...
110
        """Get the named attribute of the widget or the field
111
        """
112
        value = getattr(self, name, _marker)
113
        if value is _marker:
114
            return default
115
        if isinstance(value, six.string_types):
116
            context = self.get_context()
117
            if base_hasattr(context, value):
118
                attr = getattr(context, value)
119
                if callable(attr):
120
                    attr = attr()
121
                if attr:
122
                    value = attr
123
        return value
124
125 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...
126
        """Extract the value from the request or get it from the field
127
        """
128
        # get the processed value from the `update` method
129
        value = self.value
130
        # the value might come from the request, e.g. on object creation
131
        if isinstance(value, six.string_types):
132
            value = IDataConverter(self).toFieldValue(value)
133
        # we handle always lists in the templates
134
        if value is None:
135
            return []
136
        if not isinstance(value, (list, tuple)):
137
            value = [value]
138
        return value
139
140
    def get_catalog(self):
141
        return self.attr("catalog", "portal_catalog")
142
143
    def get_query(self):
144
        return self.attr("query", {})
145
146
    def get_search_index(self):
147
        return self.attr("search_index", "")
148
149
    def get_value_key(self):
150
        return self.attr("value_key", "uid")
151
152
    def get_limit(self):
153
        return self.attr("limit", 25)
154
155
    def get_search_wildcard(self):
156
        return self.attr("search_wildcard", True)
157
158
    def get_allow_user_value(self):
159
        """Allow the user to enter a custom value
160
        """
161
        return self.attr("allow_user_value", False)
162
163
    def get_columns(self):
164
        return self.attr("columns", [])
165
166
    def is_multi_valued(self):
167
        return self.attr("multi_valued", False)
168
169
    def get_hide_input_after_select(self):
170
        return self.attr("hide_input_after_select", False)
171
172
    def get_input_widget_attributes(self):
173
        """Return input widget attributes for the ReactJS component
174
        """
175
        values = self.get_value()
176
        attributes = {
177
            "data-id": self.id,
178
            "data-name": self.name,
179
            "data-values": values,
180
            "data-value_key": self.get_value_key(),
181
            "data-api_url": self.get_api_url(),
182
            "data-query": self.get_query(),
183
            "data-catalog": self.get_catalog(),
184
            "data-search_index": self.get_search_index(),
185
            "data-search_wildcard": self.get_search_wildcard(),
186
            "data-allow_user_value": self.get_allow_user_value(),
187
            "data-columns": self.get_columns(),
188
            "data-display_template": None,
189
            "data-limit": self.get_limit(),
190
            "data-multi_valued": self.is_multi_valued(),
191
            "data-disabled": self.disabled or False,
192
            "data-readonly": self.readonly or False,
193
            "data-hide_input_after_select": self.get_hide_input_after_select(),
194
        }
195
196
        # convert all attributes to JSON
197
        for key, value in attributes.items():
198
            attributes[key] = json.dumps(value)
199
200
        return attributes
201
202
    def get_api_url(self):
203
        """JSON API URL to use for this widget
204
        """
205
        portal = api.get_portal()
206
        portal_url = api.get_url(portal)
207
        api_url = "{}/@@API/senaite/v1".format(portal_url)
208
        return api_url
209
210
211
@adapter(IField, ISenaiteFormLayer)
212
@implementer(IFieldWidget)
213
def QuerySelectWidgetFactory(field, request):
214
    """Widget factory
215
    """
216
    return FieldWidget(field, QuerySelectWidget(request))
217