-
-
Notifications
You must be signed in to change notification settings - Fork 152
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rely on fields when validating submitted values on sample creation #2307
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
c322eb5
Validate non-future and non-past dates on add sample submit
xispa 902482f
Changelog
xispa 9823c4c
Remove the property date_2months (no longer exists in 2.x)
xispa 3963363
Make linter happy
xispa 716af15
Do use the field validator instead
xispa 344bbed
Add min and max validator for datetime
xispa 11e5f1e
Changelog
xispa b22ea15
Ensure the value to validate is the output of the widget's process_form
xispa 6e91f9a
Use ansi to compare dates (timestamp is since epoch 1970)
xispa ef27116
Added doctest
xispa fc55ad4
Store the processed value (by the widget) as the valid value
xispa 2e394c8
Better comment and remove unnecessary code
xispa File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,7 +23,17 @@ | |
from Products.Archetypes.public import DateTimeField as BaseField | ||
from Products.Archetypes.Registry import registerField | ||
from Products.Archetypes.Registry import registerPropertyType | ||
from senaite.core.api import dtime | ||
from senaite.core.browser.widgets.datetimewidget import DateTimeWidget | ||
from zope.i18n import translate | ||
from zope.i18nmessageid import Message | ||
|
||
from bika.lims import _ | ||
from bika.lims import api | ||
|
||
WIDGET_NOPAST = "datepicker_nopast" | ||
WIDGET_NOFUTURE = "datepicker_nofuture" | ||
WIDGET_SHOWTIME = "show_time" | ||
|
||
|
||
class DateTimeField(BaseField): | ||
|
@@ -42,6 +52,125 @@ class DateTimeField(BaseField): | |
}) | ||
security = ClassSecurityInfo() | ||
|
||
def validate(self, value, instance, errors=None, **kwargs): | ||
"""Validate passed-in value using all field validators plus the | ||
validators for minimum and maximum date values | ||
Return None if all validations pass; otherwise, return the message of | ||
of the validation failure translated to current language | ||
""" | ||
# Rely on the super-class first | ||
error = super(DateTimeField, self).validate( | ||
value, instance, errors=errors, **kwargs) | ||
if error: | ||
return error | ||
|
||
# Validate value is after min date | ||
error = self.validate_min_date(value, instance, errors=errors) | ||
if error: | ||
return error | ||
|
||
# Validate value is before max date | ||
error = self.validate_max_date(value, instance, errors=errors) | ||
if error: | ||
return error | ||
|
||
def validate_min_date(self, value, instance, errors=None): | ||
"""Validates the passed-in value against the field's minimum date | ||
""" | ||
if errors is None: | ||
errors = {} | ||
|
||
# self.min always returns an offset-naive datetime, but the value | ||
# is offset-aware. We need to add the TZ, otherwise we get a: | ||
# TypeError: can't compare offset-naive and offset-aware datetimes | ||
if dtime.to_ansi(value) >= dtime.to_ansi(self.min): | ||
return None | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, ANSI times can be compared as strings, then it's fine like this. |
||
|
||
error = _( | ||
u"error_datetime_before_min", | ||
default=u"${name} is before ${min_date}, please correct.", | ||
mapping={ | ||
"name": self.get_label(instance), | ||
"min_date": self.localize(self.min, instance) | ||
} | ||
) | ||
|
||
field_name = self.getName() | ||
errors[field_name] = translate(error, context=api.get_request()) | ||
return errors[field_name] | ||
|
||
def validate_max_date(self, value, instance, errors=None): | ||
"""Validates the passed-in value against the field's maximum date | ||
""" | ||
if errors is None: | ||
errors = {} | ||
|
||
# self.max always returns an offset-naive datetime, but the value | ||
# is offset-aware. We need to add the TZ, otherwise we get a: | ||
# TypeError: can't compare offset-naive and offset-aware datetimes | ||
if dtime.to_ansi(value) <= dtime.to_ansi(self.max): | ||
return None | ||
|
||
error = _( | ||
u"error_datetime_after_max", | ||
default=u"${name} is after ${max_date}, please correct.", | ||
mapping={ | ||
"name": self.get_label(instance), | ||
"max_date": self.localize(self.max, instance) | ||
} | ||
) | ||
|
||
field_name = self.getName() | ||
errors[field_name] = translate(error, context=api.get_request()) | ||
return errors[field_name] | ||
|
||
def is_true(self, val): | ||
"""Returns whether val evaluates to True | ||
""" | ||
val = str(val).strip().lower() | ||
return val in ["y", "yes", "1", "true", "on"] | ||
|
||
def get_label(self, instance): | ||
"""Returns the translated label of this field for the given instance | ||
""" | ||
request = api.get_request() | ||
label = self.widget.Label(instance) | ||
if isinstance(label, Message): | ||
return translate(label, context=request) | ||
return label | ||
|
||
def localize(self, dt, instance): | ||
"""Returns the dt to localized time | ||
""" | ||
request = api.get_request() | ||
return dtime.to_localized_time(dt, long_format=self.show_time, | ||
context=instance, request=request) | ||
|
||
@property | ||
def min(self): | ||
"""Returns the minimum datetime supported by this field | ||
""" | ||
no_past = getattr(self.widget, WIDGET_NOPAST, False) | ||
if self.is_true(no_past): | ||
return dtime.datetime.now() | ||
return dtime.datetime.min | ||
|
||
@property | ||
def max(self): | ||
"""Returns the maximum datetime supported for this field | ||
""" | ||
no_future = getattr(self.widget, WIDGET_NOFUTURE, False) | ||
if self.is_true(no_future): | ||
return dtime.datetime.now() | ||
return dtime.datetime.max | ||
|
||
@property | ||
def show_time(self): | ||
"""Returns whether the time is displayed by the widget | ||
""" | ||
show_time = getattr(self.widget, WIDGET_SHOWTIME, False) | ||
return self.is_true(show_time) | ||
|
||
|
||
InitializeClass(DateTimeField) | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -555,3 +555,67 @@ string (YYYYMMDD[HHMMSS] | |
|
||
>>> dtime.to_DT(dt) is None | ||
True | ||
|
||
We can also the other way round conversion. Simply giving a date in ant valid | ||
string format: | ||
|
||
>>> dt = "1989-12-01" | ||
>>> dtime.to_ansi(dt, show_time=False) | ||
'19891201' | ||
|
||
>>> dtime.to_ansi(dt, show_time=True) | ||
'19891201000000' | ||
|
||
>>> dt = "19891201" | ||
>>> dtime.to_ansi(dt, show_time=False) | ||
'19891201' | ||
|
||
>>> dtime.to_ansi(dt, show_time=True) | ||
'19891201000000' | ||
|
||
Or using datetime or DateTime as the input parameter: | ||
|
||
>>> dt = "19891201131405" | ||
>>> dt = dtime.ansi_to_dt(dt) | ||
>>> dtime.to_ansi(dt, show_time=False) | ||
'19891201' | ||
|
||
>>> dtime.to_ansi(dt, show_time=True) | ||
'19891201131405' | ||
|
||
>>> DT = dtime.to_DT(dt) | ||
>>> dtime.to_ansi(DT, show_time=False) | ||
'19891201' | ||
|
||
>>> dtime.to_ansi(DT, show_time=True) | ||
'19891201131405' | ||
|
||
We even suport dates that are long before epoch: | ||
|
||
>>> min_date = dtime.datetime.min | ||
>>> min_date | ||
datetime.datetime(1, 1, 1, 0, 0) | ||
|
||
>>> dtime.to_ansi(min_date) | ||
'00010101000000' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, got it. Therefore you format the date manually:
|
||
|
||
Or long after epoch: | ||
|
||
>>> max_date = dtime.datetime.max | ||
>>> max_date | ||
datetime.datetime(9999, 12, 31, 23, 59, 59, 999999) | ||
|
||
>>> dtime.to_ansi(max_date) | ||
'99991231235959' | ||
|
||
Still, invalid dates return None: | ||
|
||
>>> # Month 13 | ||
>>> dt = "17891301132505" | ||
>>> dtime.to_ansi(dt) is None | ||
True | ||
|
||
>>> # Month 2, day 30 | ||
>>> dt = "20030230123408" | ||
>>> dtime.to_ansi(dt) is None | ||
True |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better use
strftime
from thedatetime
module: