Passed
Push — 2.x ( 9a1936...d38641 )
by Jordi
08:09
created

DateTimeField.localize()   A

Complexity

Conditions 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 1
nop 3
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 AccessControl import ClassSecurityInfo
22
from App.class_init import InitializeClass
23
from Products.Archetypes.public import DateTimeField as BaseField
24
from Products.Archetypes.Registry import registerField
25
from Products.Archetypes.Registry import registerPropertyType
26
from senaite.core.api import dtime
27
from senaite.core.browser.widgets.datetimewidget import DateTimeWidget
28
from zope.i18n import translate
29
from zope.i18nmessageid import Message
30
31
from bika.lims import _
32
from bika.lims import api
33
34
WIDGET_NOPAST = "datepicker_nopast"
35
WIDGET_NOFUTURE = "datepicker_nofuture"
36
WIDGET_SHOWTIME = "show_time"
37
38
39
class DateTimeField(BaseField):
40
    """An improved DateTime Field. It allows to specify
41
    whether only dates or only times are interesting.
42
43
    This field is ported from Products.ATExtensions
44
    """
45
46
    _properties = BaseField._properties.copy()
47
    _properties.update({
48
        "type": "datetime_ng",
49
        "widget": DateTimeWidget,
50
        "with_time": 1,  # set to False if you want date only objects
51
        "with_date": 1,  # set to False if you want time only objects
52
        })
53
    security = ClassSecurityInfo()
54
55
    def validate(self, value, instance, errors=None, **kwargs):
56
        """Validate passed-in value using all field validators plus the
57
        validators for minimum and maximum date values
58
        Return None if all validations pass; otherwise, return the message of
59
        of the validation failure translated to current language
60
        """
61
        # Rely on the super-class first
62
        error = super(DateTimeField, self).validate(
63
            value, instance, errors=errors, **kwargs)
64
        if error:
65
            return error
66
67
        # Return immediately if we have no value and the field is not required
68
        if not value and not self.required:
69
            return
70
71
        # Validate value is after min date
72
        error = self.validate_min_date(value, instance, errors=errors)
73
        if error:
74
            return error
75
76
        # Validate value is before max date
77
        error = self.validate_max_date(value, instance, errors=errors)
78
        if error:
79
            return error
80
81 View Code Duplication
    def validate_min_date(self, value, instance, errors=None):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
82
        """Validates the passed-in value against the field's minimum date
83
        """
84
        if errors is None:
85
            errors = {}
86
87
        # self.min always returns an offset-naive datetime, but the value
88
        # is offset-aware. We need to add the TZ, otherwise we get a:
89
        #   TypeError: can't compare offset-naive and offset-aware datetimes
90
        if dtime.to_ansi(value) >= dtime.to_ansi(self.min):
91
            return None
92
93
        error = _(
94
            u"error_datetime_before_min",
95
            default=u"${name} is before ${min_date}, please correct.",
96
            mapping={
97
                "name": self.get_label(instance),
98
                "min_date": self.localize(self.min, instance)
99
            }
100
        )
101
102
        field_name = self.getName()
103
        errors[field_name] = translate(error, context=api.get_request())
104
        return errors[field_name]
105
106 View Code Duplication
    def validate_max_date(self, value, instance, errors=None):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
107
        """Validates the passed-in value against the field's maximum date
108
        """
109
        if errors is None:
110
            errors = {}
111
112
        # self.max always returns an offset-naive datetime, but the value
113
        # is offset-aware. We need to add the TZ, otherwise we get a:
114
        #   TypeError: can't compare offset-naive and offset-aware datetimes
115
        if dtime.to_ansi(value) <= dtime.to_ansi(self.max):
116
            return None
117
118
        error = _(
119
            u"error_datetime_after_max",
120
            default=u"${name} is after ${max_date}, please correct.",
121
            mapping={
122
                "name": self.get_label(instance),
123
                "max_date": self.localize(self.max, instance)
124
            }
125
        )
126
127
        field_name = self.getName()
128
        errors[field_name] = translate(error, context=api.get_request())
129
        return errors[field_name]
130
131
    def is_true(self, val):
132
        """Returns whether val evaluates to True
133
        """
134
        val = str(val).strip().lower()
135
        return val in ["y", "yes", "1", "true", "on"]
136
137
    def get_label(self, instance):
138
        """Returns the translated label of this field for the given instance
139
        """
140
        request = api.get_request()
141
        label = self.widget.Label(instance)
142
        if isinstance(label, Message):
143
            return translate(label, context=request)
144
        return label
145
146
    def localize(self, dt, instance):
147
        """Returns the dt to localized time
148
        """
149
        request = api.get_request()
150
        return dtime.to_localized_time(dt, long_format=self.show_time,
151
                                       context=instance, request=request)
152
153
    @property
154
    def min(self):
155
        """Returns the minimum datetime supported by this field
156
        """
157
        no_past = getattr(self.widget, WIDGET_NOPAST, False)
158
        if self.is_true(no_past):
159
            return dtime.datetime.now()
160
        return dtime.datetime.min
161
162
    @property
163
    def max(self):
164
        """Returns the maximum datetime supported for this field
165
        """
166
        no_future = getattr(self.widget, WIDGET_NOFUTURE, False)
167
        if self.is_true(no_future):
168
            return dtime.datetime.now()
169
        return dtime.datetime.max
170
171
    @property
172
    def show_time(self):
173
        """Returns whether the time is displayed by the widget
174
        """
175
        show_time = getattr(self.widget, WIDGET_SHOWTIME, False)
176
        return self.is_true(show_time)
177
178
179
InitializeClass(DateTimeField)
180
181
182
registerField(
183
    DateTimeField,
184
    title="DateTime Field",
185
    description="An improved DateTimeField, which also allows time "
186
                "or date only specifications.")
187
188
189
registerPropertyType("with_time", "boolean", DateTimeField)
190
registerPropertyType("with_date", "boolean", DateTimeField)
191