使用DurationField存储工作时间

时间:2015-10-07 10:01:58

标签: django-models django-forms

我正在开展一个小项目,用户可以跟踪他们在合同上花费的时间。每份合同都有一个明确的所需工作时间,用户可以每月工作。

现在出现了几个问题:如何将这个工作时间存储在我的Django模型中?我最终使用了Django 1.8中的DurationField,但是它带有它自己的问题,如下所述。我是否可以切换到IntegerField并将工作时间存储为分钟并将其转换为模板内的正确格式?然后我需要在用户发送表单后再重新转换它,以便再次以正确的形式存储它。我最终会如何以及在哪里(models.py,forms.py ...?)进行这两次转换?

使用DurationField时,我遇到了两个大问题:

  1. 它总是采用“hh:mm:ss”格式,而工作时间定义则不需要任何秒数。所以我的JavaScript TimePicker不允许我选择秒并让它们归零。这不是我认为最美丽的解决方案。
  2. 当指定超过24小时工作时间的合同(比如80 /月)时,Django会将DurationField值保存为“3天,8小时”,但我希望它在我的内部显示为“80:00”输入字段。我知道这是正常的Python timedelta行为,但有没有办法自定义它?至少仅适用于前端用户。
  3. 所以我的基本两个问题是:我应该坚持使用DurationField并以某种方式解决我面临的问题,或者我应该切换到其他字段如IntegerField并自己进行转换,我不知道从哪里开始。

1 个答案:

答案 0 :(得分:0)

在将这个问题暂时搁置一段时间之后,我在关注此博客文章后想出了一个解决方案:http://charlesleifer.com/blog/writing-custom-field-django/

到目前为止,代码的工作方式正如我所希望的那样。它将工作时间作为整数存储在数据库中,并将其显示为HH:MM给用户。 我还不确定我是否做得正确,或者在某些特殊情况下是否遗漏或错误?我无法绕过to_python和from_db_value背后的差异。我还从原始代码中删除了value_to_string(请参阅博客文章),因为它似乎没有做任何事情。

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)