Ticket #2443: durationfield.7.diff

File durationfield.7.diff, 6.9 KB (added by Jerome Leclanche, 16 years ago)

Updated patch for current trunk, without docs (no idea how much they've changed)

  • django/db/models/fields/__init__.py

     
    636636        defaults.update(kwargs)
    637637        return super(DecimalField, self).formfield(**defaults)
    638638
     639class DurationProxy(object):
     640    def __init__(self, field):
     641        self.field_name = field.name
     642
     643    def __get__(self, instance=None, owner=None):
     644        if instance is None:
     645            raise AttributeError, "%s can only be accessed from %s instances." % (self.field_name, owner.__name__)
     646        if self.field_name not in instance.__dict__:
     647            return None
     648        return instance.__dict__[self.field_name]
     649
     650    def __set__(self, instance, value):
     651        if value and not isinstance(value, datetime.timedelta):
     652            value = datetime.timedelta(seconds=float(value))
     653        instance.__dict__[self.field_name] = value
     654
     655class DurationField(Field):
     656    def __init__(self, *args, **kwargs):
     657        super(DurationField, self).__init__(*args, **kwargs)
     658        self.max_digits, self.decimal_places = 20, 6
     659
     660    def get_internal_type(self):
     661        return "DecimalField"
     662
     663    def contribute_to_class(self, cls, name):
     664        super(DurationField, self).contribute_to_class(cls, name)
     665        setattr(cls, name, DurationProxy(self))
     666
     667    def get_db_prep_save(self, value):
     668        if value is None:
     669            return None
     670        return str(value.days * 24 * 3600 + value.seconds + float(value.microseconds) / 1000000)
     671
     672    def to_python(self, value):
     673        if isinstance(value, datetime.timedelta):
     674            return value
     675        try:
     676            return datetime.timedelta(seconds=float(value))
     677        except (TypeError, ValueError):
     678            raise validators.ValidationError('This value must be a real number.')
     679        except OverflowError:
     680            raise validators.ValidationError('The maximum allowed value is %s' % datetime.timedelta.max)
     681
     682    def flatten_data(self, follow, obj=None):
     683        val = self._get_val_from_obj(obj)
     684        if val is None or val is '':
     685            return ''
     686        return {self.name: self.get_db_prep_save(val)}
     687
     688    def formfield(self, form_class=forms.DurationField, **kwargs):
     689        return super(DurationField, self).formfield(form_class, **kwargs)
     690
     691    def get_manipulator_field_objs(self):
     692        return [curry(oldforms.DecimalField, max_digits=self.max_digits, decimal_places=self.decimal_places)]
     693
    639694class EmailField(CharField):
    640695    def __init__(self, *args, **kwargs):
    641696        kwargs['max_length'] = kwargs.get('max_length', 75)
  • django/forms/fields.py

     
    2828from django.utils.encoding import smart_unicode, smart_str
    2929
    3030from util import ErrorList, ValidationError
    31 from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateInput, DateTimeInput, TimeInput, SplitDateTimeWidget, SplitHiddenDateTimeWidget
     31from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateInput, DateTimeInput, TimeInput, DurationWidget, SplitDateTimeWidget, SplitHiddenDateTimeWidget
    3232from django.core.files.uploadedfile import SimpleUploadedFile as UploadedFile
    3333
    3434__all__ = (
     
    4040    'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
    4141    'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
    4242    'SplitDateTimeField', 'IPAddressField', 'FilePathField', 'SlugField',
    43     'TypedChoiceField'
     43    'TypedChoiceField', 'DurationField',
    4444)
    4545
    4646# These values, if given to to_python(), will trigger the self.required check.
     
    884884            return datetime.datetime.combine(*data_list)
    885885        return None
    886886
     887class DurationField(MultiValueField):
     888    widget = DurationWidget
     889
     890    def __init__(self, *args, **kwargs):
     891        errors = self.default_error_messages.copy()
     892        fields = (
     893            IntegerField(max_value=999999999, min_value=-999999999),
     894            IntegerField(label='Hours', max_value=23, min_value=0),
     895            IntegerField(label='Minutes', max_value=59, min_value=0),
     896            IntegerField(label='Seconds', max_value=59, min_value=0),
     897            IntegerField(label='Microseconds', max_value=999999, min_value=0),
     898        )
     899        super(DurationField, self).__init__(fields, *args, **kwargs)
     900
     901    def compress(self, data_list):
     902        if data_list == [None] * 5:
     903            raise ValidationError(gettext(u'This field is required.'))
     904        if data_list:
     905            return datetime.timedelta(
     906                days=data_list[0] or 0,
     907                hours=data_list[1] or 0,
     908                minutes=data_list[2] or 0,
     909                seconds=data_list[3] or 0,
     910                microseconds=data_list[4] or 0,
     911            )
     912        return None
     913
    887914ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$')
    888915
    889916class IPAddressField(RegexField):
  • django/forms/widgets.py

     
    2626    'FileInput', 'DateInput', 'DateTimeInput', 'TimeInput', 'Textarea', 'CheckboxInput',
    2727    'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect',
    2828    'CheckboxSelectMultiple', 'MultiWidget',
    29     'SplitDateTimeWidget',
     29    'SplitDateTimeWidget', 'DurationWidget',
    3030)
    3131
    3232MEDIA_TYPES = ('css','js')
     
    706706    def __init__(self, attrs=None):
    707707        widgets = (HiddenInput(attrs=attrs), HiddenInput(attrs=attrs))
    708708        super(SplitDateTimeWidget, self).__init__(widgets, attrs)
     709
     710class DurationWidget(MultiWidget):
     711    def __init__(self, attrs=None):
     712        attrs = attrs or {}
     713        widgets = (
     714            TextInput(attrs=dict(attrs, size=4, maxlength=10, title='Days')),
     715            TextInput(attrs=dict(attrs, size=1, maxlength=2, title='Hours')),
     716            TextInput(attrs=dict(attrs, size=1, maxlength=2, title='Minutes')),
     717            TextInput(attrs=dict(attrs, size=1, maxlength=2, title='Seconds')),
     718            TextInput(attrs=dict(attrs, size=5, maxlength=6, title='Microseconds')),
     719        )
     720        super(DurationWidget, self).__init__(widgets, attrs)
     721
     722    def decompress(self, value):
     723        if value:
     724            hours, seconds = divmod(value.seconds, 3600)
     725            minutes, seconds = divmod(seconds, 60)
     726            return [value.days, hours, minutes, seconds, value.microseconds]
     727        return [None, None, None, None, None]
     728
     729    def format_output(self, rendered_widgets):
     730        return u'%s days, %s : %s : %s . %s' % tuple(rendered_widgets)
Back to Top