使用 DurationField 存储工作时间
Using DurationField to store working hours
我正在做一个小项目,用户可以在其中跟踪他们在合同上花费的时间。每个合同都有规定的工作时间,用户可以按月工作。
现在出现了几个问题:如何将这个工作时间存储在我的 Django 模型中?我最终使用了 Django 1.8 中的 DurationField,但这有它自己的问题,如下所述。我是否应该切换到 IntegerField 并将工作时间存储为分钟并将其转换为模板内的正确格式?然后我需要在用户发送表单后重新转换它以再次以正确的形式存储它。我将如何以及在何处(models.py、forms.py ..?)完成这两个转换?
在使用 DurationField 时我遇到了两个大问题:
- 它总是采用 "hh:mm:ss" 的格式,而我不需要任何秒数来定义工作时间。所以我的 JavaScript TimePicker 不允许我选择秒并将它们归零。这不是我认为最漂亮的解决方案。
- 当指定工作时间超过 24 小时(例如 80 小时/月)的合同时,Django 将 DurationField 值保存为“3 天 8 小时”,但我希望它显示为“80:00”在我的输入字段中。我知道这是正常的 Python timedelta 行为,但有没有办法自定义它?至少对于前端用户来说是这样。
所以我的两个基本问题是:我应该坚持使用 DurationField 并以某种方式解决我面临的问题,还是应该切换到其他字段(如 IntegerField)并自己进行转换,我不确定在哪里开始。
在搁置这个问题一段时间后,我关注了这个博客post:http://charlesleifer.com/blog/writing-custom-field-django/
,想出了一个解决方案
到目前为止,代码可以正常工作。它将工作时间作为整数存储在数据库中,并将其显示为 HH:MM 给用户。
我仍然不确定我是否做对了,或者在某些特殊情况下是否缺少某些东西或者可能是错误的?我无法理解 to_python 和 from_db_value 背后的区别。我还从原始代码中删除了 value_to_string(请参阅博客 post),因为它似乎没有做任何事情。
class WorkingHoursFieldForm(CharField):
"""
Implementation of a CharField to handle validation of data from WorkingHoursField.
"""
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 5
super(WorkingHoursFieldForm, self).__init__(*args, **kwargs)
def clean(self, value):
value = super(CharField, self).clean(value)
# Split submitted duration into list
hour_value = value.split('.')
# If list does not have two values, let us do some more validation
if len(hour_value) != 2:
# In this case the user did not supply the form with the correct format.
# Therefore we are going to assume that he does not care about
# the minutes and we will just append those for him!
if len(hour_value)<2:
value = hour_value[0] + ".00"
# This case should only arise when the format was not correct at all.
else:
raise ValidationError(_('Working hours entered must be in format HH.MM'))
# If the value is in the correct format, check if the total working hours
# exceed 80 hours per month (this equals 288.000 seconds)
if len(hour_value) == 2:
hours, minutes = map(int, value.split('.'))
total_seconds = hours*3600 + minutes*60
if total_seconds > 80 * 3600:
raise ValidationError(_('Contracts may not be longer than 80 hours!'))
return value
class WorkingHoursField(IntegerField):
"""
Creates a custom field so we can store our working hours in contracts.
Working hours are stored as an integer in minutes inside the database.
This field accepts input in the format HH.MM and will display it the same way.
"""
# Get values from database and return them as HH.MM
def from_db_value(self, value, expression, connection, context):
if value is None:
return value
hours, minutes = divmod(value, 60)
return "%02d.%02d" % (hours, minutes)
def to_python(self, value):
if value is None:
return value
if isinstance(value, (int, long)):
return value
# Split into two values and return the duration in minutes!
if isinstance(value, basestring):
hours, minutes = map(int, value.split('.'))
return (hours * 60) + minutes
# I do not know if this is really relevant here?
elif not isinstance(value, datetime.timedelta):
raise ValidationError('Unable to convert %s to timedelta.' % value)
return value
def get_db_prep_value(self, value, connection, prepared):
return value
# This is somehow needed, as otherwise the form will not work correctly!
def formfield(self, form_class=WorkingHoursFieldForm, **kwargs):
defaults = {'help_text': _('Please specify your working hours in the format HH:MM \
(eg. 12:15 - meaning 12 hours and 15 minutes)')}
defaults.update(kwargs)
return form_class(**defaults)
我正在做一个小项目,用户可以在其中跟踪他们在合同上花费的时间。每个合同都有规定的工作时间,用户可以按月工作。
现在出现了几个问题:如何将这个工作时间存储在我的 Django 模型中?我最终使用了 Django 1.8 中的 DurationField,但这有它自己的问题,如下所述。我是否应该切换到 IntegerField 并将工作时间存储为分钟并将其转换为模板内的正确格式?然后我需要在用户发送表单后重新转换它以再次以正确的形式存储它。我将如何以及在何处(models.py、forms.py ..?)完成这两个转换?
在使用 DurationField 时我遇到了两个大问题:
- 它总是采用 "hh:mm:ss" 的格式,而我不需要任何秒数来定义工作时间。所以我的 JavaScript TimePicker 不允许我选择秒并将它们归零。这不是我认为最漂亮的解决方案。
- 当指定工作时间超过 24 小时(例如 80 小时/月)的合同时,Django 将 DurationField 值保存为“3 天 8 小时”,但我希望它显示为“80:00”在我的输入字段中。我知道这是正常的 Python timedelta 行为,但有没有办法自定义它?至少对于前端用户来说是这样。
所以我的两个基本问题是:我应该坚持使用 DurationField 并以某种方式解决我面临的问题,还是应该切换到其他字段(如 IntegerField)并自己进行转换,我不确定在哪里开始。
在搁置这个问题一段时间后,我关注了这个博客post:http://charlesleifer.com/blog/writing-custom-field-django/
,想出了一个解决方案到目前为止,代码可以正常工作。它将工作时间作为整数存储在数据库中,并将其显示为 HH:MM 给用户。 我仍然不确定我是否做对了,或者在某些特殊情况下是否缺少某些东西或者可能是错误的?我无法理解 to_python 和 from_db_value 背后的区别。我还从原始代码中删除了 value_to_string(请参阅博客 post),因为它似乎没有做任何事情。
class WorkingHoursFieldForm(CharField):
"""
Implementation of a CharField to handle validation of data from WorkingHoursField.
"""
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 5
super(WorkingHoursFieldForm, self).__init__(*args, **kwargs)
def clean(self, value):
value = super(CharField, self).clean(value)
# Split submitted duration into list
hour_value = value.split('.')
# If list does not have two values, let us do some more validation
if len(hour_value) != 2:
# In this case the user did not supply the form with the correct format.
# Therefore we are going to assume that he does not care about
# the minutes and we will just append those for him!
if len(hour_value)<2:
value = hour_value[0] + ".00"
# This case should only arise when the format was not correct at all.
else:
raise ValidationError(_('Working hours entered must be in format HH.MM'))
# If the value is in the correct format, check if the total working hours
# exceed 80 hours per month (this equals 288.000 seconds)
if len(hour_value) == 2:
hours, minutes = map(int, value.split('.'))
total_seconds = hours*3600 + minutes*60
if total_seconds > 80 * 3600:
raise ValidationError(_('Contracts may not be longer than 80 hours!'))
return value
class WorkingHoursField(IntegerField):
"""
Creates a custom field so we can store our working hours in contracts.
Working hours are stored as an integer in minutes inside the database.
This field accepts input in the format HH.MM and will display it the same way.
"""
# Get values from database and return them as HH.MM
def from_db_value(self, value, expression, connection, context):
if value is None:
return value
hours, minutes = divmod(value, 60)
return "%02d.%02d" % (hours, minutes)
def to_python(self, value):
if value is None:
return value
if isinstance(value, (int, long)):
return value
# Split into two values and return the duration in minutes!
if isinstance(value, basestring):
hours, minutes = map(int, value.split('.'))
return (hours * 60) + minutes
# I do not know if this is really relevant here?
elif not isinstance(value, datetime.timedelta):
raise ValidationError('Unable to convert %s to timedelta.' % value)
return value
def get_db_prep_value(self, value, connection, prepared):
return value
# This is somehow needed, as otherwise the form will not work correctly!
def formfield(self, form_class=WorkingHoursFieldForm, **kwargs):
defaults = {'help_text': _('Please specify your working hours in the format HH:MM \
(eg. 12:15 - meaning 12 hours and 15 minutes)')}
defaults.update(kwargs)
return form_class(**defaults)