Passed
Pull Request — 2.x (#1903)
by Ramon
05:07
created

to_datetime()   B

Complexity

Conditions 7

Size

Total Lines 27
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 17
dl 0
loc 27
rs 8
c 0
b 0
f 0
cc 7
nop 3
1
# -*- coding: utf-8 -*-
2
3
from datetime import datetime
4
from datetime import timedelta
5
6
import pytz
7
from bika.lims import api
8
from bika.lims import senaiteMessageFactory as _
9
from plone.app.event.base import default_timezone as current_timezone
10
from Products.CMFPlone.utils import safe_callable
11
from senaite.core.interfaces import ISenaiteFormLayer
12
from senaite.core.schema.interfaces import IDatetimeField
13
from senaite.core.z3cform.interfaces import IDatetimeWidget
14
from z3c.form import interfaces
15
from z3c.form.browser import widget
16
from z3c.form.browser.widget import HTMLInputWidget
17
from z3c.form.converter import BaseDataConverter
18
from z3c.form.interfaces import IFieldWidget
19
from z3c.form.validator import SimpleFieldValidator
20
from z3c.form.widget import FieldWidget
21
from z3c.form.widget import Widget
22
from zope.component import adapter
23
from zope.interface import Interface
24
from zope.interface import implementer
25
26
HOUR_FORMAT = "%H:%M"
27
DATE_FORMAT = "%Y-%m-%d"
28
29
DATE_ONLY = ("{value.year:}-{value.month:02}-{value.day:02}")
30
31
DATE_AND_TIME = ("{value.year:}-{value.month:02}-{value.day:02} "
32
                 "{value.hour:02}:{value.minute:02}")
33
34
35
@adapter(
36
    Interface,           # context
37
    ISenaiteFormLayer,   # request
38
    interfaces.IForm,    # form
39
    IDatetimeField,      # field
40
    interfaces.IWidget,  # widget
41
)
42
class DatetimeDataValidator(SimpleFieldValidator):
43
    """Datetime validator
44
45
    Adapter looked up by `z3c.form.field.extract()` when storing a new value
46
    """
47
    def validate(self, value, force=False):
48
        if isinstance(value, datetime):
49
            if value.tzinfo is None:
50
                return _("No timezone found for date %r" % value)
51
        return super(DatetimeDataValidator, self).validate(value, force)
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
        called by `z3c.form.widget.update`
62
63
        Bases:
64
            `plone.app.z3cform.converters.DatetimeWidgetConverter`
65
            `z3c.form.converter.DatetimeDataConverter`
66
67
        :param value: Field value.
68
        :type value: datetime
69
70
        :returns: Datetime in format `Y-m-d H:M`
71
        :rtype: string
72
        """
73
        if value is self.field.missing_value:
74
            return u""
75
        if getattr(self.widget, "show_time", None) is False:
76
            return DATE_ONLY.format(value=value)
77
        return DATE_AND_TIME.format(value=value)
78
79
    def toFieldValue(self, value):
80
        """Converts from widget value to field.
81
82
        :param value: Value inserted by datetime widget.
83
        :type value: string
84
85
        :returns: `datetime.datetime` object.
86
        :rtype: datetime
87
        """
88
        default = self.field.missing_value
89
        timezone = self.widget.get_timezone()
90
        return to_datetime(value, timezone=timezone, default=default)
91
92
93
def to_datetime(value, timezone=None, default=None):
94
    """Convert the value to a datetime object
95
96
    Code originally taken from here:
97
    `plone.app.z3cform.converters.DatetimeWidgetConverter.toFieldValue`
98
99
    :param value: Value inserted by datetime widget.
100
    :type value: string
101
    """
102
    if isinstance(value, datetime):
103
        return value
104
    if not value:
105
        return default
106
    tmp = value.split(" ")
107
    if not tmp[0]:
108
        return default
109
    value = tmp[0].split("-")
110
    if len(tmp) == 2 and ":" in tmp[1]:
111
        value += tmp[1].split(":")
112
    else:
113
        value += ["00", "00"]
114
115
    ret = datetime(*map(int, value))
116
    if timezone:
117
        tzinfo = pytz.timezone(timezone)
118
        ret = tzinfo.localize(ret)
119
    return ret
120
121
122
@implementer(IDatetimeWidget)
123
class DatetimeWidget(HTMLInputWidget, Widget):
124
    """Senaite date and time widget
125
    """
126
    klass = u"senaite-datetime-widget"
127
    # Olson DB/pytz timezone identifier or a callback
128
    # returning such an identifier.
129
    default_timezone = None
130
    # enable/disable time component
131
    show_time = True
132
    # disable past dates in the date picker
133
    datepicker_nopast = False
134
    # disable future dates in the date picker
135
    datepicker_nofuture = False
136
137
    def __init__(self, request, *args, **kw):
138
        super(DatetimeWidget, self).__init__(request)
139
        self.request = request
140
141
    def update(self):
142
        """Computes self.value for the widget templates
143
144
        see z3c.form.widget.Widget
145
        """
146
        super(DatetimeWidget, self).update()
147
        widget.addFieldClass(self)
148
149
    def get_timezone(self):
150
        """Return the current timezone
151
        """
152
        default_zone = self.default_timezone
153
        timezone = default_zone(self.context)\
154
            if safe_callable(default_zone) else default_zone
155
        if not timezone:
156
            # return computed timezone
157
            timezone = current_timezone(context=self.context, as_tzinfo=False)
158
        return timezone
159
160
    def to_localized_time(self, time, long_format=None, time_only=None):
161
        """Convert time to localized time
162
        """
163
        ts = api.get_tool("translation_service")
164
        long_format = True if self.show_time else False
165
        return ts.ulocalized_time(
166
            time, long_format, time_only, self.context, domain="senaite.core")
167
168
    def to_local_date(self, value, length="short"):
169
        """Converts value to localized date
170
171
        Used in the display template to show a localized version of the date
172
173
        :param value: date or datetime
174
        :type value: string or datetime object
175
        :returns: localized date string
176
        """
177
        dt = self.to_datetime(value)
178
        if not dt:
179
            return ""
180
        df = "dateTime" if self.show_time else "date"
181
        formatter = self.request.locale.dates.getFormatter(df, length)
182
        return formatter.format(dt)
183
184
    def to_datetime(self, value):
185
        """convert date string to datetime object with tz
186
187
        :param value: date or datetime
188
        :type value: string or datetime object
189
        :returns: datetime object
190
        """
191
        default = self.field.missing_value
192
        timezone = self.get_timezone()
193
        return to_datetime(value, timezone=timezone, default=default)
194
195
    def get_date(self, value):
196
        """Return only the date part of the value
197
198
        :returns: date string
199
        """
200
        dt = self.to_datetime(value)
201
        if not dt:
202
            return u""
203
        return dt.strftime(DATE_FORMAT)
204
205
    def get_time(self, value):
206
        """Return only the time part of the value
207
208
        :returns: time string
209
        """
210
        dt = self.to_datetime(value)
211
        if not dt:
212
            return u""
213
        return dt.strftime(HOUR_FORMAT)
214
215
    def date_now(self, offset=0):
216
        """Get the current date without time component
217
        """
218
        ts = datetime.now().strftime(DATE_FORMAT)
219
        dt = datetime.strptime(ts, DATE_FORMAT)
220
        if offset:
221
            dt = dt + timedelta(offset)
222
        return dt
223
224
    def get_max(self):
225
        """Return the max allowed date in the future
226
227
        :returns: date string
228
        """
229
        now = self.date_now()
230
        return now.strftime(DATE_FORMAT)
231
232
    def get_min(self):
233
        """Return the min allowed date in the past
234
235
        :returns: date string
236
        """
237
        now = self.date_now()
238
        return now.strftime(DATE_FORMAT)
239
240
    def attrs(self):
241
        """Return the template attributes for the date field
242
243
        :returns: dictionary of HTML attributes
244
        """
245
        attrs = {}
246
        if self.datepicker_nofuture:
247
            attrs["max"] = self.get_max()
248
        if self.datepicker_nopast:
249
            attrs["min"] = self.get_min()
250
        return attrs
251
252
    @property
253
    def portal(self):
254
        """Return the portal object
255
        """
256
        return api.get_portal()
257
258
    @property
259
    def portal_url(self):
260
        """Return the portal object URL
261
        """
262
        return api.get_url(self.portal)
263
264
265
@adapter(IDatetimeField, ISenaiteFormLayer)
266
@implementer(IFieldWidget)
267
def DatetimeWidgetFactory(field, request):
268
    """Widget factory for datetime field
269
    """
270
    return FieldWidget(field, DatetimeWidget(request))
271