Passed
Pull Request — 2.x (#1960)
by Jordi
09:44 queued 03:35
created

senaite.core.z3cform.widgets.address   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 194
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 22
eloc 92
dl 0
loc 194
rs 10
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A AddressWidget.get_address_format() 0 13 2
A AddressWidget.get_geographical_hierarchy() 0 41 3
A AddressWidget.get_countries_names() 0 4 2
A AddressWidget.get_districts() 0 9 2
A AddressWidget.get_formatted_address() 0 8 2
A AddressDataConverter.toFieldValue() 0 4 1
A AddressWidget.get_input_widget_attributes() 0 21 2
A AddressDataConverter.to_dict() 0 4 2
A AddressDataConverter.toWidgetValue() 0 4 1
A AddressWidget.get_states() 0 8 2
A AddressDataConverter.to_list_of_dicts() 0 5 2

1 Function

Rating   Name   Duplication   Size   Complexity  
A AddressWidgetFactory() 0 6 1
1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of SENAITE.CORE.
4
#
5
# SENAITE.CORE is free software: you can redistribute it and/or modify it under
6
# the terms of the GNU General Public License as published by the Free Software
7
# Foundation, version 2.
8
#
9
# This program is distributed in the hope that it will be useful, but WITHOUT
10
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12
# details.
13
#
14
# You should have received a copy of the GNU General Public License along with
15
# this program; if not, write to the Free Software Foundation, Inc., 51
16
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
#
18
# Copyright 2018-2022 by it's authors.
19
# Some rights reserved, see README and LICENSE.
20
21
import json
22
from senaite.core.interfaces import ISenaiteFormLayer
23
from senaite.core.locales import get_countries
24
from senaite.core.locales import get_districts
25
from senaite.core.locales import get_states
26
from senaite.core.schema.interfaces import IAddressField
27
from senaite.core.z3cform.interfaces import IAddressWidget
28
from z3c.form.browser.widget import HTMLFormElement
29
from z3c.form.converter import FieldDataConverter
30
from z3c.form.interfaces import IFieldWidget
31
from z3c.form.interfaces import IWidget
32
from z3c.form.widget import FieldWidget
33
from z3c.form.widget import Widget
34
from zope.component import adapter
35
from zope.interface import implementer
36
37
from bika.lims import api
38
39
40
@adapter(IAddressField, IWidget)
41
class AddressDataConverter(FieldDataConverter):
42
    """Value conversion between field and widget
43
    """
44
    def to_list_of_dicts(self, value):
45
        if not isinstance(value, list):
46
            value = [value]
47
        value = filter(None, value)
48
        return map(self.to_dict, value)
49
50
    def to_dict(self, value):
51
        if not isinstance(value, dict):
52
            return {}
53
        return value
54
55
    def toWidgetValue(self, value):
56
        """Returns the field value with encoded string
57
        """
58
        return self.to_list_of_dicts(value)
59
60
    def toFieldValue(self, value):
61
        """Converts from widget value to safe_unicode
62
        """
63
        return self.to_list_of_dicts(value)
64
65
66
@implementer(IAddressWidget)
67
class AddressWidget(HTMLFormElement, Widget):
68
    """SENAITE Address Widget
69
    """
70
    klass = u"senaite-address-widget"
71
72
    # Address html format for display. Wildcards accepted: {type}, {address},
73
    # {zip}, {district}, {city}, {state}, {country}. Use '<br/>' for newlines
74
    address_format = ""
75
76
    def get_address_format(self):
77
        """Returns the format for displaying the address
78
        """
79
        if self.address_format:
80
            return self.address_format
81
        lines = [
82
            "<strong>{type}</strong>",
83
            "{address}",
84
            "{zip} {district} {city}",
85
            "{state}",
86
            "{country}"
87
        ]
88
        return "<br/>".join(lines)
89
90
    def get_formatted_address(self, address):
91
        """Returns the address formatted in html
92
        """
93
        address_format = self.get_address_format()
94
        lines = address_format.split("<br/>")
95
        values = map(lambda line: line.format(**address), lines)
96
        values = filter(None, values)
97
        return "<br/>".join(values)
98
99
    def get_countries_names(self):
100
        """Returns the list of names of available countries
101
        """
102
        return map(lambda c: c.name, get_countries())
103
104
    def get_geographical_hierarchy(self):
105
        """Returns a dict with geographical information as follows:
106
107
            {<country_name_1>: {
108
                <state_name_1>: [
109
                    <district_name_1>,
110
                    ...
111
                    <district_name_n>,
112
                ],
113
                ...
114
                <state_name_n>: [..]
115
                },
116
            <country_name_2>: {
117
                ...
118
            }
119
120
        Available states and districts for each country are only considered for
121
        those countries selected in current addresses
122
        """
123
124
        # Initialize a dict with countries names as keys and None as values
125
        countries = self.get_countries_names()
126
        countries = dict.fromkeys(countries)
127
128
        # Fill the dict with geo information for selected countries only
129
        for item in self.value:
130
            country = item.get("country")
131
132
            # Initialize a dict with states names as keys and None as values
133
            states = self.get_states(country)
134
            states = map(lambda st: st[1], states)
135
            states = dict.fromkeys(states)
136
137
            # Fill the dict with geo information for selected state only
138
            state = item.get("state")
139
            states[state] = self.get_districts(country, state)
140
141
            # Set the value to the geomap
142
            countries[country] = states
143
144
        return countries
145
146
    def get_states(self, country):
147
        """Returns the states for the country with the given name or iso
148
149
        :param country: name or iso of the country
150
        :return: a list of tuples as (<state_code>, <state_name>)
151
        """
152
        states = get_states(country)
153
        return map(lambda state: (state.code, state.name), states)
154
155
    def get_districts(self, country, state):
156
        """Returns the districts for the country and state given
157
158
        :param country: name or iso of the country
159
        :param state: name or code of the state
160
        :return: a list of districts
161
        """
162
        districts = get_districts(country, state)
163
        return map(lambda dis: dis.name, districts)
164
165
    def get_input_widget_attributes(self):
166
        """Return input widget attributes for the ReactJS component
167
        """
168
        attributes = {
169
            "data-id": self.id,
170
            "data-uid": api.get_uid(self.context),
171
            "data-geography": self.get_geographical_hierarchy(),
172
            "data-name": self.name,
173
            "data-portal_url": api.get_url(api.get_portal()),
174
            "data-items": self.value,
175
            "data-title": self.title,
176
            "data-class": self.klass,
177
            "data-style": self.style,
178
            "data-disabled": self.disabled or False,
179
        }
180
181
        # convert all attributes to JSON
182
        for key, value in attributes.items():
183
            attributes[key] = json.dumps(value)
184
185
        return attributes
186
187
188
@adapter(IAddressField, ISenaiteFormLayer)
189
@implementer(IFieldWidget)
190
def AddressWidgetFactory(field, request):
191
    """Widget factory for Address Widget
192
    """
193
    return FieldWidget(field, AddressWidget(request))
194