Ticket #2443: durationfield-new.2.diff

File durationfield-new.2.diff, 6.4 KB (added by Adys, 6 years ago)

Fully working DurationField implementation with changes and fixes from Yuri Baburov

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

     
     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

     
    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):
Back to Top