Ticket #2443: durationfield.6.diff

File durationfield.6.diff, 8.9 KB (added by Gulopine, 8 years ago)

Fixed a problem with creating objects by hand, and removed a couple debug lines

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

     
    686686        defaults.update(kwargs)
    687687        return super(DecimalField, self).formfield(**defaults)
    688688
     689class DurationProxy(object):
     690    def __init__(self, field):
     691        self.field_name = field.name
     692
     693    def __get__(self, instance=None, owner=None):
     694        if instance is None:
     695            raise AttributeError, "%s can only be accessed from %s instances." % (self.field_name, owner.__name__)
     696        if self.field_name not in instance.__dict__:
     697            return None
     698        return instance.__dict__[self.field_name]
     699
     700    def __set__(self, instance, value):
     701        if value and not isinstance(value, datetime.timedelta):
     702            value = datetime.timedelta(seconds=float(value))
     703        instance.__dict__[self.field_name] = value
     704
     705class DurationField(Field):
     706    def __init__(self, *args, **kwargs):
     707        super(DurationField, self).__init__(*args, **kwargs)
     708        self.max_digits, self.decimal_places = 20, 6
     709
     710    def get_internal_type(self):
     711        return "DecimalField"
     712
     713    def contribute_to_class(self, cls, name):
     714        super(DurationField, self).contribute_to_class(cls, name)
     715        setattr(cls, name, DurationProxy(self))
     716
     717    def get_db_prep_save(self, value):
     718        if value is None:
     719            return None
     720        return str(value.days * 24 * 3600 + value.seconds + float(value.microseconds) / 1000000)
     721
     722    def to_python(self, value):
     723        if isinstance(value, datetime.timedelta):
     724            return value
     725        try:
     726            return datetime.timedelta(seconds=float(value))
     727        except (TypeError, ValueError):
     728            raise validators.ValidationError('This value must be a real number.')
     729        except OverflowError:
     730            raise validators.ValidationError('The maximum allowed value is %s' % datetime.timedelta.max)
     731
     732    def flatten_data(self, follow, obj=None):
     733        val = self._get_val_from_obj(obj)
     734        if val is None or val is '':
     735            return ''
     736        return {self.name: self.get_db_prep_save(val)}
     737
     738    def formfield(self, form_class=forms.DurationField, **kwargs):
     739        return super(DurationField, self).formfield(form_class, **kwargs)
     740
     741    def get_manipulator_field_objs(self):
     742        return [curry(oldforms.DecimalField, max_digits=self.max_digits, decimal_places=self.decimal_places)]
     743
    689744class EmailField(CharField):
    690745    def __init__(self, *args, **kwargs):
    691746        kwargs['max_length'] = kwargs.get('max_length', 75)
  • django/newforms/fields.py

     
    2020from django.utils.encoding import StrAndUnicode, smart_unicode
    2121
    2222from util import ErrorList, ValidationError
    23 from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput
     23from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput, DurationWidget
    2424
    2525
    2626__all__ = (
     
    3131    'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField',
    3232    'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
    3333    'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
    34     'SplitDateTimeField', 'IPAddressField',
     34    'SplitDateTimeField', 'IPAddressField', 'DurationField',
    3535)
    3636
    3737# These values, if given to to_python(), will trigger the self.required check.
     
    753753
    754754    def __init__(self, *args, **kwargs):
    755755        super(IPAddressField, self).__init__(ipv4_re, *args, **kwargs)
     756
     757class DurationField(MultiValueField):
     758    widget = DurationWidget
     759
     760    def __init__(self, *args, **kwargs):
     761        errors = self.default_error_messages.copy()
     762        fields = (
     763            IntegerField(max_value=999999999, min_value=-999999999),
     764            IntegerField(label='Hours', max_value=23, min_value=0),
     765            IntegerField(label='Minutes', max_value=59, min_value=0),
     766            IntegerField(label='Seconds', max_value=59, min_value=0),
     767            IntegerField(label='Microseconds', max_value=999999, min_value=0),
     768        )
     769        super(DurationField, self).__init__(fields, *args, **kwargs)
     770
     771    def compress(self, data_list):
     772        if data_list == [None] * 5:
     773            raise ValidationError(gettext(u'This field is required.'))
     774        if data_list:
     775            return datetime.timedelta(
     776                days=data_list[0] or 0,
     777                hours=data_list[1] or 0,
     778                minutes=data_list[2] or 0,
     779                seconds=data_list[3] or 0,
     780                microseconds=data_list[4] or 0,
     781            )
     782        return None
  • django/newforms/widgets.py

     
    2222    'FileInput', 'DateTimeInput', 'Textarea', 'CheckboxInput',
    2323    'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect',
    2424    'CheckboxSelectMultiple', 'MultiWidget', 'SplitDateTimeWidget',
     25    'DurationWidget',
    2526)
    2627
    2728class Widget(object):
     
    450451        if value:
    451452            return [value.date(), value.time().replace(microsecond=0)]
    452453        return [None, None]
     454
     455class DurationWidget(MultiWidget):
     456    def __init__(self, attrs=None):
     457        attrs = attrs or {}
     458        widgets = (
     459            TextInput(attrs=dict(attrs, size=4, maxlength=10, title='Days')),
     460            TextInput(attrs=dict(attrs, size=1, maxlength=2, title='Hours')),
     461            TextInput(attrs=dict(attrs, size=1, maxlength=2, title='Minutes')),
     462            TextInput(attrs=dict(attrs, size=1, maxlength=2, title='Seconds')),
     463            TextInput(attrs=dict(attrs, size=5, maxlength=6, title='Microseconds')),
     464        )
     465        super(DurationWidget, self).__init__(widgets, attrs)
     466
     467    def decompress(self, value):
     468        if value:
     469            hours, seconds = divmod(value.seconds, 3600)
     470            minutes, seconds = divmod(seconds, 60)
     471            return [value.days, hours, minutes, seconds, value.microseconds]
     472        return [None, None, None, None, None]
     473
     474    def format_output(self, rendered_widgets):
     475        return u'%s days, %s : %s : %s . %s' % tuple(rendered_widgets)
  • docs/model-api.txt

     
    218218
    219219The admin represents this as an ``<input type="text">`` (a single-line input).
    220220
     221``DurationField``
     222~~~~~~~~~~~~~~~~~
     223
     224**New in Django development version**
     225
     226A span of time, represented in Python by a ``timedelta`` instance.
     227
     228The admin represents this as an ``<input type="text">`` (a single-line input),
     229with its value representing the number of seconds in the duration. Fractional
     230values are allowed, with a millisecond precision.
     231
    221232``EmailField``
    222233~~~~~~~~~~~~~~
    223234
  • docs/newforms.txt

     
    12751275permitted in the value, whilst ``decimal_places`` is the maximum number of
    12761276decimal places permitted.
    12771277
     1278``DurationField``
     1279~~~~~~~~~~~~~~~~~
     1280
     1281**New in Django development version**
     1282
     1283    * Default widget: ``DurationWidget``
     1284    * Empty value: ``None``
     1285    * Normalizes to: A Python ``datetime.timedelta`` object
     1286    * Validates that the given value is a length of time, such as 24 hours in a
     1287      day, 60 minutes in an hour, and 60 seconds in a minute.
     1288
     1289A span of time, represented in Python by a ``timedelta`` instance.
     1290
     1291The admin represents this as an ``<input type="text">`` (a single-line input),
     1292with its value representing the number of seconds in the duration. Fractional
     1293values are allowed, with a millisecond precision.
     1294
    12781295``EmailField``
    12791296~~~~~~~~~~~~~~
    12801297
     
    16181635    ``MultiWidget``               Wrapper around multiple other widgets
    16191636    ``SplitDateTimeWidget``       Wrapper around two ``TextInput`` widgets:
    16201637                                  one for the Date, and one for the Time.
     1638    ``DurationWidget``            A set of 5 ``TextInput`` widgets arranged
     1639                                  as ``[   ] days, [ ]:[ ]:[ ].[    ]``
    16211640    ============================  ===========================================
    16221641
    1623 **New in Django development version:** The ``DateTimeInput`` has been added
    1624 since the last release.
     1642**New in Django development version:** The ``DateTimeInput`` and
     1643``DurationWidget`` have been added since the last release.
    16251644
    16261645Specifying widgets
    16271646------------------
Back to Top