Passed
Push — 2.x ( f01285...cc9902 )
by Ramon
11:03
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_SHOWTIME = "show_time"
35
36
37
class DateTimeField(BaseField):
38
    """An improved DateTime Field. It allows to specify
39
    whether only dates or only times are interesting.
40
41
    This field is ported from Products.ATExtensions
42
    """
43
44
    _properties = BaseField._properties.copy()
45
    _properties.update({
46
        "type": "datetime_ng",
47
        "widget": DateTimeWidget,
48
        "min": dtime.datetime.min,
49
        "max": dtime.datetime.max,
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.get_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
        min_date = self.get_min(instance)
91
        if dtime.to_ansi(value) >= dtime.to_ansi(min_date):
92
            return None
93
94
        error = _(
95
            u"error_datetime_before_min",
96
            default=u"${name} is before ${min_date}, please correct.",
97
            mapping={
98
                "name": self.get_label(instance),
99
                "min_date": self.localize(min_date, instance)
100
            }
101
        )
102
103
        field_name = self.getName()
104
        errors[field_name] = translate(error, context=api.get_request())
105
        return errors[field_name]
106
107 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...
108
        """Validates the passed-in value against the field's maximum date
109
        """
110
        if errors is None:
111
            errors = {}
112
113
        # self.get_max always returns an offset-naive datetime, but the value
114
        # is offset-aware. We need to add the TZ, otherwise we get a:
115
        #   TypeError: can't compare offset-naive and offset-aware datetimes
116
        max_date = self.get_max(instance)
117
        if dtime.to_ansi(value) <= dtime.to_ansi(max_date):
118
            return None
119
120
        error = _(
121
            u"error_datetime_after_max",
122
            default=u"${name} is after ${max_date}, please correct.",
123
            mapping={
124
                "name": self.get_label(instance),
125
                "max_date": self.localize(max_date, instance)
126
            }
127
        )
128
129
        field_name = self.getName()
130
        errors[field_name] = translate(error, context=api.get_request())
131
        return errors[field_name]
132
133
    def is_true(self, val):
134
        """Returns whether val evaluates to True
135
        """
136
        val = str(val).strip().lower()
137
        return val in ["y", "yes", "1", "true", "on"]
138
139
    def get_label(self, instance):
140
        """Returns the translated label of this field for the given instance
141
        """
142
        request = api.get_request()
143
        label = self.widget.Label(instance)
144
        if isinstance(label, Message):
145
            return translate(label, context=request)
146
        return label
147
148
    def localize(self, dt, instance):
149
        """Returns the dt to localized time
150
        """
151
        request = api.get_request()
152
        return dtime.to_localized_time(dt, long_format=self.show_time,
153
                                       context=instance, request=request)
154
155
    def get_min(self, instance):
156
        """Returns the minimum datetime supported by this field and instance
157
        """
158
        min_date = self.resolve_date(self.min, instance)
159
        return min_date or dtime.datetime.min
160
161
    def get_max(self, instance):
162
        """Returns the maximum datetime supported for this field and instance
163
        """
164
        max_date = self.resolve_date(self.max, instance)
165
        return max_date or dtime.datetime.max
166
167
    def resolve_date(self, thing, instance):
168
        """Resolves the thing passed in to a DateTime object or None
169
        """
170
        if not thing:
171
            return None
172
173
        date = api.to_date(thing)
174
        if api.is_date(date):
175
            return date
176
177
        if thing in ["current", "now"]:
178
            return dtime.datetime.now()
179
180
        # maybe a callable
181
        if callable(thing):
182
            value = thing()
183
            return api.to_date(value)
184
185
        # maybe an instance attribute
186
        if hasattr(instance, thing):
187
            value = getattr(instance, thing)
188
            if callable(value):
189
                value = value()
190
            return api.to_date(value)
191
192
        # maybe an instance fieldname
193
        if api.is_string(thing):
194
            fields = api.get_fields(instance)
195
            field = fields.get(thing)
196
            if field:
197
                value = field.get(instance)
198
                return api.to_date(value)
199
200
        return None
201
202
    @property
203
    def show_time(self):
204
        """Returns whether the time is displayed by the widget
205
        """
206
        show_time = getattr(self.widget, WIDGET_SHOWTIME, False)
207
        return self.is_true(show_time)
208
209
210
InitializeClass(DateTimeField)
211
212
213
registerField(
214
    DateTimeField,
215
    title="DateTime Field",
216
    description="An improved DateTimeField, which also allows time "
217
                "or date only specifications.")
218
219
220
registerPropertyType("with_time", "boolean", DateTimeField)
221
registerPropertyType("with_date", "boolean", DateTimeField)
222
registerPropertyType("min", "string", DateTimeField)
223
registerPropertyType("max", "string", DateTimeField)
224