Passed
Push — 2.x ( f01285...cc9902 )
by Ramon
11:03
created

DatetimeWidget.max()   A

Complexity

Conditions 3

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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