Passed
Push — 2.x ( 2d6cc3...84f108 )
by Jordi
06:01
created

senaite.core.z3cform.widgets.datetimewidget   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 244
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 32
eloc 127
dl 0
loc 244
rs 9.84
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
A DatetimeWidget.__init__() 0 3 1
A DatetimeDataValidator.validate() 0 6 1
A DatetimeDataConverter.toFieldValue() 0 12 1
A DatetimeDataConverter.toWidgetValue() 0 23 3
A DatetimeWidget.update() 0 7 1
A DatetimeWidget.get_display_value() 0 9 2
A DatetimeWidget.to_datetime() 0 10 1
A DatetimeWidget.portal() 0 5 1
A DatetimeWidget.portal_url() 0 5 1
A DatetimeWidget.get_date() 0 9 2
A DatetimeWidget.to_localized_time() 0 8 2
A DatetimeWidget.date_now() 0 8 2
A DatetimeWidget.attrs() 0 12 3
A DatetimeWidget.get_time() 0 9 2

2 Functions

Rating   Name   Duplication   Size   Complexity  
B to_datetime() 0 28 8
A DatetimeWidgetFactory() 0 6 1
1
# -*- coding: utf-8 -*-
2
3
from datetime import datetime
4
from datetime import timedelta
5
6
from bika.lims import api
7
from senaite.core.api import dtime
8
from senaite.core.interfaces import ISenaiteFormLayer
9
from senaite.core.schema.interfaces import IDatetimeField
10
from senaite.core.z3cform.interfaces import IDatetimeWidget
11
from z3c.form import interfaces
12
from z3c.form.browser import widget
13
from z3c.form.browser.widget import HTMLInputWidget
14
from z3c.form.converter import BaseDataConverter
15
from z3c.form.interfaces import IDataManager
16
from z3c.form.interfaces import IFieldWidget
17
from z3c.form.validator import SimpleFieldValidator
18
from z3c.form.widget import FieldWidget
19
from z3c.form.widget import Widget
20
from zope.component import adapter
21
from zope.component import queryMultiAdapter
22
from zope.interface import Interface
23
from zope.interface import implementer
24
25
HOUR_FORMAT = "%H:%M"
26
DATE_FORMAT = "%Y-%m-%d"
27
28
DATE_ONLY = ("{value.year:}-{value.month:02}-{value.day:02}")
29
30
DATE_AND_TIME = ("{value.year:}-{value.month:02}-{value.day:02} "
31
                 "{value.hour:02}:{value.minute:02}")
32
33
34
@adapter(
35
    Interface,           # context
36
    ISenaiteFormLayer,   # request
37
    interfaces.IForm,    # form
38
    IDatetimeField,      # field
39
    interfaces.IWidget,  # widget
40
)
41
class DatetimeDataValidator(SimpleFieldValidator):
42
    """Datetime validator
43
44
    Adapter looked up by `z3c.form.field.extract()` when storing a new value
45
    """
46
    def validate(self, value, force=True):
47
        # we always force to avoid comparison of datetime objects, which might
48
        # eventually raise this error `TypeError`:
49
        #
50
        # TypeError: can't compare offset-naive and offset-aware datetimes
51
        return super(DatetimeDataValidator, self).validate(value, force=True)
52
53
54
@adapter(IDatetimeField, interfaces.IWidget)
55
class DatetimeDataConverter(BaseDataConverter):
56
    """Converts the value between the field and the widget
57
    """
58
    def toWidgetValue(self, value):
59
        """Converts from field value to widget.
60
61
        This value will be set to the hidden field and does not reflect the
62
        date that is visible to the user!
63
64
        called by `z3c.form.widget.update`
65
66
        Bases:
67
            `plone.app.z3cform.converters.DatetimeWidgetConverter`
68
            `z3c.form.converter.DatetimeDataConverter`
69
70
        :param value: Field value.
71
        :type value: datetime
72
73
        :returns: Datetime in format `Y-m-d H:M`
74
        :rtype: string
75
        """
76
        if value is None:
77
            return u""
78
        if getattr(self.widget, "show_time", None) is False:
79
            return DATE_ONLY.format(value=value)
80
        return DATE_AND_TIME.format(value=value)
81
82
    def toFieldValue(self, value):
83
        """Converts from widget (date string) value to field value (datetime)
84
85
        :param value: Date string inserted by datetime widget.
86
        :type value: string
87
88
        :returns: `datetime.datetime` object.
89
        :rtype: datetime
90
        """
91
        default = getattr(self.field, "missing_value", None)
92
        timezone = self.widget.default_timezone or dtime.get_os_timezone()
93
        return to_datetime(value, timezone=timezone, default=default)
94
95
96
def to_datetime(value, timezone=None, default=None):
97
    """Convert the value to a datetime object
98
99
    Code originally taken from here:
100
    `plone.app.z3cform.converters.DatetimeWidgetConverter.toFieldValue`
101
102
    :param value: Value inserted by datetime widget.
103
    :type value: string
104
    """
105
    if isinstance(value, datetime):
106
        return value
107
    if not value:
108
        return default
109
    tmp = value.split(" ")
110
    if not tmp[0]:
111
        return default
112
    value = tmp[0].split("-")
113
    if len(tmp) == 2 and ":" in tmp[1]:
114
        value += tmp[1].split(":")
115
    else:
116
        value += ["00", "00"]
117
118
    ret = datetime(*map(int, value))
119
    if timezone:
120
        if callable(timezone):
121
            timezone = timezone()
122
        ret = dtime.to_zone(ret, timezone)
123
    return ret
124
125
126
@implementer(IDatetimeWidget)
127
class DatetimeWidget(HTMLInputWidget, Widget):
128
    """Senaite date and time widget
129
    """
130
    klass = u"senaite-datetime-widget"
131
    # Olson DB/pytz timezone identifier or a callback
132
    # returning such an identifier.
133
    default_timezone = None
134
    # enable/disable time component
135
    show_time = True
136
    # disable past dates in the date picker
137
    datepicker_nopast = False
138
    # disable future dates in the date picker
139
    datepicker_nofuture = False
140
141
    def __init__(self, request, *args, **kw):
142
        super(DatetimeWidget, self).__init__(request)
143
        self.request = request
144
145
    def update(self):
146
        """Computes self.value for the widget templates
147
148
        see z3c.form.widget.Widget
149
        """
150
        super(DatetimeWidget, self).update()
151
        widget.addFieldClass(self)
152
153
    def to_localized_time(self, time, long_format=None, time_only=None):
154
        """Convert time to localized time
155
        """
156
        dt = self.to_datetime(time)
157
        ts = api.get_tool("translation_service")
158
        long_format = True if self.show_time else False
159
        return ts.ulocalized_time(
160
            dt, long_format, time_only, self.context, domain="senaite.core")
161
162
    def get_display_value(self):
163
        """Returns the localized date value
164
        """
165
        value = self.value
166
        dm = queryMultiAdapter((self.context, self.field), IDataManager)
167
        if dm:
168
            # extract the object from the database
169
            value = dm.query()
170
        return self.to_localized_time(value)
171
172
    def to_datetime(self, value):
173
        """convert date string to datetime object with tz
174
175
        :param value: date or datetime
176
        :type value: string or datetime object
177
        :returns: datetime object
178
        """
179
        default = getattr(self.field, "missing_value", None)
180
        timezone = self.default_timezone
181
        return to_datetime(value, timezone=timezone, default=default)
182
183
    def get_date(self, value):
184
        """Return only the date part of the value
185
186
        :returns: date string
187
        """
188
        dt = self.to_datetime(value)
189
        if not dt:
190
            return u""
191
        return dt.strftime(DATE_FORMAT)
192
193
    def get_time(self, value):
194
        """Return only the time part of the value
195
196
        :returns: time string
197
        """
198
        dt = self.to_datetime(value)
199
        if not dt:
200
            return u""
201
        return dt.strftime(HOUR_FORMAT)
202
203
    def date_now(self, offset=0):
204
        """Get the current date without time component
205
        """
206
        ts = datetime.now().strftime(DATE_FORMAT)
207
        dt = datetime.strptime(ts, DATE_FORMAT)
208
        if offset:
209
            dt = dt + timedelta(offset)
210
        return dt
211
212
    def attrs(self):
213
        """Return the template attributes for the date field
214
215
        :returns: dictionary of HTML attributes
216
        """
217
        attrs = {}
218
        today = self.date_now().strftime(DATE_FORMAT)
219
        if self.datepicker_nofuture:
220
            attrs["max"] = today
221
        if self.datepicker_nopast:
222
            attrs["min"] = today
223
        return attrs
224
225
    @property
226
    def portal(self):
227
        """Return the portal object
228
        """
229
        return api.get_portal()
230
231
    @property
232
    def portal_url(self):
233
        """Return the portal object URL
234
        """
235
        return api.get_url(self.portal)
236
237
238
@adapter(IDatetimeField, ISenaiteFormLayer)
239
@implementer(IFieldWidget)
240
def DatetimeWidgetFactory(field, request):
241
    """Widget factory for datetime field
242
    """
243
    return FieldWidget(field, DatetimeWidget(request))
244