Ticket #2443: durationfield-new.diff

File durationfield-new.diff, 6.7 KB (added by Jerome Leclanche, 15 years ago)

New DurationField implementation (try 3)

  • 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(microseconds=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        if not isinstance(value, datetime.timedelta):
     671            value = datetime.timedelta(microseconds=value)
     672        return connection.ops.value_to_db_decimal(value.days * 24 * 3600 * 1000000 + value.seconds * 1000000 + value.microseconds, 20, 0) # max value 86399999999999999999 microseconds
     673
     674    def to_python(self, value):
     675        if isinstance(value, datetime.timedelta):
     676            return value
     677        try:
     678            return datetime.timedelta(microseconds=float(value))
     679        except (TypeError, ValueError):
     680            raise exceptions.ValidationError('The value must be an integer.')
     681        except OverflowError:
     682            raise exceptions.ValidationError('The maximum allowed value is %s' % datetime.timedelta.max)
     683
     684    def formfield(self, form_class=forms.DurationField, **kwargs):
     685        return super(DurationField, self).formfield(form_class, **kwargs)
     686
    639687class EmailField(CharField):
    640688    def __init__(self, *args, **kwargs):
    641689        kwargs['max_length'] = kwargs.get('max_length', 75)
  • django/forms/fields.py

     
    2626import django.core.exceptions
    2727from django.utils.translation import ugettext_lazy as _
    2828from django.utils.encoding import smart_unicode, smart_str
     29from django.utils.datastructures import SortedDict
    2930
    3031from util import ErrorList, ValidationError
    3132from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateInput, DateTimeInput, TimeInput, SplitDateTimeWidget, SplitHiddenDateTimeWidget
     
    4041    'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
    4142    'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
    4243    'SplitDateTimeField', 'IPAddressField', 'FilePathField', 'SlugField',
    43     'TypedChoiceField'
     44    'TypedChoiceField', 'DurationField',
    4445)
    4546
    4647# These values, if given to to_python(), will trigger the self.required check.
     
    885886            return datetime.datetime.combine(*data_list)
    886887        return None
    887888
     889class DurationField(Field):
     890    default_error_messages = {
     891        'invalid': _(u'Enter a valid duration.'),
     892        'min_value': _(u'Ensure this value is greater than or equal to %(min)s.'),
     893        'max_value': _(u'Ensure this value is less than or equal to %(max)s.'),
     894    }
     895    values_in_microseconds = SortedDict((
     896        ('y', 31556925993600), # 52.177457 * (7*24*60*60*1000*1000)
     897        ('m', 2629743828768), # 4.34812141 * (7*24*60*60*1000*1000)
     898        ('w', 604800000000), # 7*24*60*60*1000*1000
     899        ('d', 86400000000), # 24*60*60*1000*1000
     900        ('h', 3600000000), # 60*60*1000*1000
     901        ('min', 60000000), # 60*1000*1000
     902        ('s', 1000000), # 1000*1000
     903        ('ms', 1000),
     904        ('mis', 1),
     905    ))
     906
     907    def __init__(self, min_value=None, max_value=None, *args, **kwargs):
     908        super(DurationField, self).__init__(*args, **kwargs)
     909        self.min_value, self.max_value = min_value, max_value
     910
     911    def to_timedelta(self, value):
     912        """
     913        Takes an Unicode value and converts it to a datetime.timedelta object.
     914        1y 7m 6w 3d 18h 30min 23s 10ms 150mis =>
     915         1 year 7 months 6 weeks 3 days 18 hours 30 minutes 23 seconds 10 milliseconds 150 microseconds
     916         => datetime.timedelta(624, 6155, 805126)
     917        """
     918        if not value:
     919            return datetime.timedelta(microseconds=0)
     920
     921        pairs = []
     922        for b in value.lower().split():
     923            for index, char in enumerate(b):
     924                if not char.isdigit():
     925                    pairs.append((b[:index], b[index:])) #digits, letters
     926                    break
     927        if not pairs:
     928            raise ValidationError(self.error_messages['invalid'])
     929
     930        microseconds = 0
     931        for digits, chars in pairs:
     932            if not digits or not chars:
     933                raise ValidationError(self.error_messages['invalid'])
     934            microseconds += int(digits) * self.values_in_microseconds[chars]
     935
     936        return datetime.timedelta(microseconds=microseconds)
     937
     938    def from_timedelta(self, value):
     939        if not value:
     940            return u"0 sec"
     941        vals = []
     942        mis = value.days * 24 * 3600 * 1000000 + value.seconds * 1000000 + value.microseconds
     943        for k in self.values_in_microseconds:
     944            if mis >= self.values_in_microseconds[k]:
     945                diff, mis = divmod(mis, self.values_in_microseconds[k])
     946                vals.append("%d%s" % (diff, k))
     947        return u" ".join(vals)
     948
     949    def clean(self, value):
     950        "Validates max_value and min_value. Returns a datetime.timedelta object."
     951        value = self.to_timedelta(value)
     952
     953        if self.max_value is not None and value > self.max_value:
     954            raise ValidationError(self.error_messages['max_value'] % {'max': self.max_value})
     955
     956        if self.min_value is not None and value < self.min_value:
     957            raise ValidationError(self.error_messages['min_value'] % {'min': self.min_value})
     958
     959        return value
     960
     961
    888962ipv4_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}$')
    889963
    890964class IPAddressField(RegexField):
Back to Top