Django

Code

Ticket #2443: durationfield-new.2.diff

File durationfield-new.2.diff, 6.4 kB (added by Adys, 10 months ago)

Fully working DurationField implementation with changes and fixes from Yuri Baburov

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

    old new  
     1from django.forms.fields import DurationField as FDurationField 
     2from django.forms.fields import TimeDelta 
     3from django.db.models.fields import Field 
     4from django.core.exceptions import ValidationError 
     5from django.db import connection 
     6 
     7class DurationProxy(object): 
     8    def __init__(self, field): 
     9        self.field_name = field.name 
     10 
     11    def __get__(self, instance=None, owner=None): 
     12        if instance is None: 
     13            raise AttributeError, "%s can only be accessed from %s instances." % (self.field_name, owner.__name__) 
     14        if self.field_name not in instance.__dict__: 
     15            return None 
     16        return instance.__dict__[self.field_name] 
     17 
     18    def __set__(self, instance, value): 
     19        if value and not isinstance(value, TimeDelta): 
     20            value = TimeDelta(microseconds=value) 
     21        instance.__dict__[self.field_name] = value 
     22 
     23class DurationField(Field): 
     24    def __init__(self, *args, **kwargs): 
     25        super(DurationField, self).__init__(*args, **kwargs) 
     26        self.max_digits, self.decimal_places = 20, 6 
     27 
     28    def get_internal_type(self): 
     29        return "DecimalField" 
     30 
     31    def contribute_to_class(self, cls, name): 
     32        super(DurationField, self).contribute_to_class(cls, name) 
     33        setattr(cls, name, DurationProxy(self)) 
     34 
     35    def get_db_prep_save(self, value): 
     36        if value is None: 
     37            return None 
     38        if not isinstance(value, TimeDelta): 
     39            value = TimeDelta(value) 
     40        t = value.days * 24 * 3600 * 1000000 + value.seconds * 1000000 + value.microseconds 
     41        return connection.ops.value_to_db_decimal(t, 20, 0) # max value 86399999999999999999 microseconds 
     42 
     43    def to_python(self, value): 
     44        if isinstance(value, TimeDelta): 
     45            return value 
     46        try: 
     47            return TimeDelta(microseconds=float(value)) 
     48        except (TypeError, ValueError): 
     49            raise ValidationError('The value must be an integer.') 
     50        except OverflowError: 
     51            raise ValidationError('The maximum allowed value is %s' % TimeDelta.max) 
     52 
     53    def formfield(self, form_class=FDurationField, **kwargs): 
     54        return super(DurationField, self).formfield(form_class, **kwargs) 
     55 
  • django/forms/fields.py

    old new  
    2525 
    2626import django.core.exceptions 
    2727from django.utils.translation import ugettext_lazy as _ 
     28from django.utils.datastructures import SortedDict 
    2829from django.utils.encoding import smart_unicode, smart_str 
    2930 
    3031from util import ErrorList, ValidationError 
     
    885886            return datetime.datetime.combine(*data_list) 
    886887        return None 
    887888 
     889class TimeDelta(datetime.timedelta): 
     890    values_in_microseconds = SortedDict(( 
     891        ('y', 31556925993600), # 52.177457 * (7*24*60*60*1000*1000) 
     892        ('m', 2629743832800), # 4.34812141 * (7*24*60*60*1000*1000) 
     893        ('w', 604800000000), # 7*24*60*60*1000*1000 
     894        ('d', 86400000000), # 24*60*60*1000*1000 
     895        ('h', 3600000000), # 60*60*1000*1000 
     896        ('min', 60000000), # 60*1000*1000 
     897        ('s',  1000000), # 1000*1000 
     898        ('ms', 1000), 
     899        ('us', 1), 
     900    )) 
     901 
     902    def __new__(self, *args, **kw): 
     903        assert not args, "Can't accept args" 
     904        usec = kw.get('microseconds') 
     905        if usec and not isinstance(usec, datetime.timedelta): 
     906            kw['microseconds'] = float(usec) 
     907        return datetime.timedelta.__new__(TimeDelta, *args, **kw) 
     908 
     909    def __unicode__(self): 
     910        if not self: 
     911            return u"0" 
     912        vals = [] 
     913        mis = self.days * 24 * 3600 * 1000000 + self.seconds * 1000000 + self.microseconds 
     914        for k in self.values_in_microseconds: 
     915            if mis >= self.values_in_microseconds[k]: 
     916                diff, mis = divmod(mis, self.values_in_microseconds[k]) 
     917                vals.append("%d%s" % (diff, k)) 
     918        return u" ".join(vals) 
     919     
     920class DurationField(Field): 
     921    default_error_messages = { 
     922        'invalid': _(u'Enter a valid duration.'), 
     923        'min_value': _(u'Ensure this self is greater than or equal to %(min)s.'), 
     924        'max_value': _(u'Ensure this self is less than or equal to %(max)s.'), 
     925    } 
     926 
     927    def __init__(self, min_value=None, max_value=None, *args, **kwargs): 
     928        super(DurationField, self).__init__(*args, **kwargs) 
     929        self.min_value, self.max_value = min_value, max_value 
     930 
     931    def to_timedelta(self, value): 
     932        """ 
     933        Takes an Unicode self and converts it to a datetime.timedelta object. 
     934        1y 7m 6w 3d 18h 30min 23s 10ms 150mis =>  
     935         1 year 7 months 6 weeks 3 days 18 hours 30 minutes 23 seconds 10 milliseconds 150 microseconds 
     936         => datetime.timedelta(624, 6155, 805126) 
     937        """ 
     938        if not value or value == '0': 
     939            return TimeDelta(microseconds=0) 
     940 
     941        pairs = [] 
     942        for b in value.lower().split(): 
     943            for index, char in enumerate(b): 
     944                if not char.isdigit(): 
     945                    pairs.append((b[:index], b[index:])) #digits, letters 
     946                    break 
     947        if not pairs: 
     948            raise ValidationError(self.error_messages['invalid']) 
     949 
     950        microseconds = 0 
     951        for digits, chars in pairs: 
     952            if not digits or not chars: 
     953                raise ValidationError(self.error_messages['invalid']) 
     954            microseconds += int(digits) * TimeDelta.values_in_microseconds[chars] 
     955 
     956        return TimeDelta(microseconds=microseconds) 
     957 
     958    def from_timedelta(self, value): 
     959        if not value: 
     960            return u"0" 
     961        return unicode(value) 
     962 
     963    def clean(self, value): 
     964        "Validates max_value and min_value. Returns a datetime.timedelta object." 
     965        value = self.to_timedelta(value) 
     966 
     967        if self.max_value is not None and value > self.max_value: 
     968            raise ValidationError(self.error_messages['max_value'] % {'max': self.max_value}) 
     969 
     970        if self.min_value is not None and value < self.min_value: 
     971            raise ValidationError(self.error_messages['min_value'] % {'min': self.min_value}) 
     972 
     973        return value 
     974 
     975 
    888976ipv4_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}$') 
    889977 
    890978class IPAddressField(RegexField):