Ticket #2443: durationfield.patch

File durationfield.patch, 9.8 KB (added by Adys, 7 years ago)

Updated patch using the new BigIntegerField as backend

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

     
     1# -*- coding: utf-8 -*-
     2from datetime import timedelta
     3from django.forms.fields import DurationField as FDurationField
     4from django.db.models.fields import Field
     5from django.core.exceptions import ValidationError
     6from django.db import connection
     7from django.utils import timestring
     8
     9class DurationField(Field):
     10    def __init__(self, *args, **kwargs):
     11        super(DurationField, self).__init__(*args, **kwargs)
     12        self.max_digits, self.decimal_places = 20, 6
     13
     14    def get_internal_type(self):
     15        return "BigIntegerField"
     16
     17    def get_db_prep_save(self, value):
     18        if value is None:
     19            return None # db NULL
     20        if isinstance(value, int) or isinstance(value, long):
     21            value = timedelta(microseconds=value)
     22        return value.days * 24 * 3600 * 1000000 + value.seconds * 1000000 + value.microseconds
     23
     24    def to_python(self, value):
     25        return value
     26
     27    def formfield(self, form_class=FDurationField, **kwargs):
     28        return super(DurationField, self).formfield(form_class, **kwargs)
  • django/forms/fields.py

     
     1# -*- coding: utf-8 -*-
    12"""
    23Field classes.
    34"""
     
    1718from django.core.exceptions import ValidationError
    1819from django.core import validators
    1920import django.utils.copycompat as copy
     21from django.utils.datastructures import SortedDict
    2022from django.utils.translation import ugettext_lazy as _
    2123from django.utils.encoding import smart_unicode, smart_str
    2224from django.utils.formats import get_format
    2325from django.utils.functional import lazy
     26from django.utils.timestring import to_timedelta
    2427
    2528# Provide this import for backwards compatibility.
    2629from django.core.validators import EMPTY_VALUES
    2730
    2831from util import ErrorList
    2932from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, \
    30         FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, \
     33       DurationInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, \
    3134        DateInput, DateTimeInput, TimeInput, SplitDateTimeWidget, SplitHiddenDateTimeWidget
    3235
    3336__all__ = (
     
    841844            return datetime.datetime.combine(*data_list)
    842845        return None
    843846
     847 
     848class DurationField(Field):
     849    widget = DurationInput
     850    default_error_messages = {
     851        'invalid': u'Enter a valid duration.',
     852        'max_value': _(u'Ensure this value is less than or equal to %(limit_value)s.'),
     853        'min_value': _(u'Ensure this value is greater than or equal to %(limit_value)s.'),
     854    }
    844855
     856    def __init__(self, min_value=None, max_value=None, *args, **kwargs):
     857        super(DurationField, self).__init__(*args, **kwargs)
     858
     859        if max_value is not None:
     860            self.validators.append(validators.MaxValueValidator(max_value))
     861        if min_value is not None:
     862            self.validators.append(validators.MinValueValidator(min_value))
     863
     864    def clean(self, value):
     865        """
     866        Validates max_value and min_value.
     867        Returns a datetime.timedelta object.
     868        """
     869        try:
     870            return to_timedelta(value)
     871        except ValueError, e:
     872            raise ValidationError(e)
     873
     874        if self.max_value is not None and value > self.max_value:
     875            raise ValidationError(self.error_messages['max_value'] % {'max': self.max_value})
     876
     877        if self.min_value is not None and value < self.min_value:
     878            raise ValidationError(self.error_messages['min_value'] % {'min': self.min_value})
     879
     880        return value
     881
     882    def to_python(self, value):
     883        try:
     884            return to_timedelta(value)
     885        except ValueError, e:
     886            raise ValidationError(e)
     887
     888
    845889class IPAddressField(CharField):
    846890    default_error_messages = {
    847891        'invalid': _(u'Enter a valid IPv4 address.'),
  • django/forms/widgets.py

     
     1# -*- coding: utf-8 -*-
    12"""
    23HTML Widget classes
    34"""
     
    1011from django.utils.translation import ugettext
    1112from django.utils.encoding import StrAndUnicode, force_unicode
    1213from django.utils.safestring import mark_safe
     14from django.utils.timestring import from_timedelta
    1315from django.utils import datetime_safe, formats
    14 from datetime import time
     16from datetime import time, timedelta
    1517from util import flatatt
    1618from urlparse import urljoin
    1719
     
    593595            option_value = force_unicode(option_value)
    594596            rendered_cb = cb.render(name, option_value)
    595597            option_label = conditional_escape(force_unicode(option_label))
    596             output.append(u'<li><label%s>%s %s</label></li>' % (label_for, rendered_cb, option_label))
     598            output.append(u'<li>%(cb)s<label%(for)s>%(label)s</label></li>' % {"for": label_for, "label": option_label, "cb": rendered_cb})
    597599        output.append(u'</ul>')
    598600        return mark_safe(u'\n'.join(output))
    599601
     
    699701            media = media + w.media
    700702        return media
    701703    media = property(_get_media)
     704 
     705class DurationInput(TextInput):
     706    def render(self, name, value, attrs=None):
     707        if value is None: value = ''
     708        final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
     709        if value != '':
     710            # Only add the 'value' attribute if a value is non-empty.
     711            if isinstance(value, int) or isinstance(value, long): # Database backends serving different types
     712                value = from_timedelta(timedelta(microseconds=value))
     713            final_attrs['value'] = force_unicode(formats.localize_input(value))
     714        return mark_safe(u'<input%s />' % flatatt(final_attrs))
    702715
    703716class SplitDateTimeWidget(MultiWidget):
    704717    """
  • django/shortcuts/__init__.py

     
     1# -*- coding: utf-8 -*-
    12"""
    23This module collects helper functions and classes that "span" multiple levels
    34of MVC. In other words, these functions/classes introduce controlled coupling
     
    2324    """
    2425    Returns an HttpResponseRedirect to the apropriate URL for the arguments
    2526    passed.
    26    
     27
    2728    The arguments could be:
    28    
     29
    2930        * A model: the model's `get_absolute_url()` function will be called.
    30    
     31
    3132        * A view name, possibly with arguments: `urlresolvers.reverse()` will
    3233          be used to reverse-resolve the name.
    33          
     34
    3435        * A URL, which will be used as-is for the redirect location.
    35        
     36
    3637    By default issues a temporary redirect; pass permanent=True to issue a
    3738    permanent redirect
    3839    """
     
    4041        redirect_class = HttpResponsePermanentRedirect
    4142    else:
    4243        redirect_class = HttpResponseRedirect
    43    
     44
    4445    # If it's a model, use get_absolute_url()
    4546    if hasattr(to, 'get_absolute_url'):
    4647        return redirect_class(to.get_absolute_url())
    47    
     48
    4849    # Next try a reverse URL resolution.
    4950    try:
    5051        return redirect_class(urlresolvers.reverse(to, args=args, kwargs=kwargs))
     
    5556        # If this doesn't "feel" like a URL, re-raise.
    5657        if '/' not in to and '.' not in to:
    5758            raise
    58        
     59
    5960    # Finally, fall back and assume it's a URL
    6061    return redirect_class(to)
    6162
  • django/utils/timestring.py

     
     1# -*- coding: utf-8 -*-
     2"""
     3Utility functions to convert back and forth between a timestring and timedelta.
     4  1y 7m 6w 3d 18h 30min 23s 10ms 150mis
     5    => 1 year 7 months 6 weeks 3 days 18 hours 30 minutes 23 seconds 10 milliseconds 150 microseconds
     6    => datetime.timedelta(624, 6155, 805126)
     7"""
     8
     9from datetime import timedelta
     10from django.utils.datastructures import SortedDict
     11
     12values_in_microseconds = SortedDict((
     13    # Uncomment the following two lines for year and month support
     14    # ('y', 31556925993600), # 52.177457 * (7*24*60*60*1000*1000)
     15    # ('m', 2629743832800), # 4.34812141 * (7*24*60*60*1000*1000)
     16    ('w', 604800000000), # 7*24*60*60*1000*1000
     17    ('d', 86400000000), # 24*60*60*1000*1000
     18    ('h', 3600000000), # 60*60*1000*1000
     19    ('min', 60000000), # 60*1000*1000
     20    ('s',  1000000), # 1000*1000
     21    ('ms', 1000),
     22    ('us', 1),
     23))
     24
     25def to_timedelta(value):
     26    chunks = []
     27    for b in value.lower().split():
     28        for index, char in enumerate(b):
     29            if not char.isdigit():
     30                chunks.append((b[:index], b[index:])) # digits, letters
     31                break
     32
     33    microseconds = 0
     34    for digits, chars in chunks:
     35        if not digits or not chars in values_in_microseconds:
     36            raise ValueError('Incorrect timestring pair')
     37        microseconds += int(digits) * values_in_microseconds[chars]
     38
     39    return timedelta(microseconds=microseconds)
     40
     41def from_timedelta(value):
     42    if not value:
     43        return u'0s'
     44
     45    if not isinstance(value, timedelta):
     46        raise ValueError('to_timestring argument must be a datetime.timedelta instance')
     47
     48    chunks = []
     49    microseconds = value.days * 24 * 3600 * 1000000 + value.seconds * 1000000 + value.microseconds
     50    for k in values_in_microseconds:
     51        if microseconds >= values_in_microseconds[k]:
     52            diff, microseconds = divmod(microseconds, values_in_microseconds[k])
     53            chunks.append('%d%s' % (diff, k))
     54    return u' '.join(chunks)
Back to Top