Code

Ticket #2443: durationfield.patch

File durationfield.patch, 9.8 KB (added by Adys, 4 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)