Ticket #2443: durationfield-new.diff

File durationfield-new.diff, 6.7 KB (added by Jerome Leclanche, 17 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