Code

Ticket #2443: durationfield.6.diff

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

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

Line 
1Index: django/db/models/fields/__init__.py
2===================================================================
3--- django/db/models/fields/__init__.py (revision 6638)
4+++ django/db/models/fields/__init__.py (working copy)
5@@ -686,6 +686,61 @@
6         defaults.update(kwargs)
7         return super(DecimalField, self).formfield(**defaults)
8 
9+class DurationProxy(object):
10+    def __init__(self, field):
11+        self.field_name = field.name
12+
13+    def __get__(self, instance=None, owner=None):
14+        if instance is None:
15+            raise AttributeError, "%s can only be accessed from %s instances." % (self.field_name, owner.__name__)
16+        if self.field_name not in instance.__dict__:
17+            return None
18+        return instance.__dict__[self.field_name]
19+
20+    def __set__(self, instance, value):
21+        if value and not isinstance(value, datetime.timedelta):
22+            value = datetime.timedelta(seconds=float(value))
23+        instance.__dict__[self.field_name] = value
24+
25+class DurationField(Field):
26+    def __init__(self, *args, **kwargs):
27+        super(DurationField, self).__init__(*args, **kwargs)
28+        self.max_digits, self.decimal_places = 20, 6
29+
30+    def get_internal_type(self):
31+        return "DecimalField"
32+
33+    def contribute_to_class(self, cls, name):
34+        super(DurationField, self).contribute_to_class(cls, name)
35+        setattr(cls, name, DurationProxy(self))
36+
37+    def get_db_prep_save(self, value):
38+        if value is None:
39+            return None
40+        return str(value.days * 24 * 3600 + value.seconds + float(value.microseconds) / 1000000)
41+
42+    def to_python(self, value):
43+        if isinstance(value, datetime.timedelta):
44+            return value
45+        try:
46+            return datetime.timedelta(seconds=float(value))
47+        except (TypeError, ValueError):
48+            raise validators.ValidationError('This value must be a real number.')
49+        except OverflowError:
50+            raise validators.ValidationError('The maximum allowed value is %s' % datetime.timedelta.max)
51+
52+    def flatten_data(self, follow, obj=None):
53+        val = self._get_val_from_obj(obj)
54+        if val is None or val is '':
55+            return ''
56+        return {self.name: self.get_db_prep_save(val)}
57+
58+    def formfield(self, form_class=forms.DurationField, **kwargs):
59+        return super(DurationField, self).formfield(form_class, **kwargs)
60+
61+    def get_manipulator_field_objs(self):
62+        return [curry(oldforms.DecimalField, max_digits=self.max_digits, decimal_places=self.decimal_places)]
63+
64 class EmailField(CharField):
65     def __init__(self, *args, **kwargs):
66         kwargs['max_length'] = kwargs.get('max_length', 75)
67Index: django/newforms/fields.py
68===================================================================
69--- django/newforms/fields.py   (revision 6638)
70+++ django/newforms/fields.py   (working copy)
71@@ -20,7 +20,7 @@
72 from django.utils.encoding import StrAndUnicode, smart_unicode
73 
74 from util import ErrorList, ValidationError
75-from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput
76+from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput, DurationWidget
77 
78 
79 __all__ = (
80@@ -31,7 +31,7 @@
81     'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField',
82     'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
83     'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
84-    'SplitDateTimeField', 'IPAddressField',
85+    'SplitDateTimeField', 'IPAddressField', 'DurationField',
86 )
87 
88 # These values, if given to to_python(), will trigger the self.required check.
89@@ -753,3 +753,30 @@
90 
91     def __init__(self, *args, **kwargs):
92         super(IPAddressField, self).__init__(ipv4_re, *args, **kwargs)
93+
94+class DurationField(MultiValueField):
95+    widget = DurationWidget
96+
97+    def __init__(self, *args, **kwargs):
98+        errors = self.default_error_messages.copy()
99+        fields = (
100+            IntegerField(max_value=999999999, min_value=-999999999),
101+            IntegerField(label='Hours', max_value=23, min_value=0),
102+            IntegerField(label='Minutes', max_value=59, min_value=0),
103+            IntegerField(label='Seconds', max_value=59, min_value=0),
104+            IntegerField(label='Microseconds', max_value=999999, min_value=0),
105+        )
106+        super(DurationField, self).__init__(fields, *args, **kwargs)
107+
108+    def compress(self, data_list):
109+        if data_list == [None] * 5:
110+            raise ValidationError(gettext(u'This field is required.'))
111+        if data_list:
112+            return datetime.timedelta(
113+                days=data_list[0] or 0,
114+                hours=data_list[1] or 0,
115+                minutes=data_list[2] or 0,
116+                seconds=data_list[3] or 0,
117+                microseconds=data_list[4] or 0,
118+            )
119+        return None
120Index: django/newforms/widgets.py
121===================================================================
122--- django/newforms/widgets.py  (revision 6638)
123+++ django/newforms/widgets.py  (working copy)
124@@ -22,6 +22,7 @@
125     'FileInput', 'DateTimeInput', 'Textarea', 'CheckboxInput',
126     'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect',
127     'CheckboxSelectMultiple', 'MultiWidget', 'SplitDateTimeWidget',
128+    'DurationWidget',
129 )
130 
131 class Widget(object):
132@@ -450,3 +451,25 @@
133         if value:
134             return [value.date(), value.time().replace(microsecond=0)]
135         return [None, None]
136+
137+class DurationWidget(MultiWidget):
138+    def __init__(self, attrs=None):
139+        attrs = attrs or {}
140+        widgets = (
141+            TextInput(attrs=dict(attrs, size=4, maxlength=10, title='Days')),
142+            TextInput(attrs=dict(attrs, size=1, maxlength=2, title='Hours')),
143+            TextInput(attrs=dict(attrs, size=1, maxlength=2, title='Minutes')),
144+            TextInput(attrs=dict(attrs, size=1, maxlength=2, title='Seconds')),
145+            TextInput(attrs=dict(attrs, size=5, maxlength=6, title='Microseconds')),
146+        )
147+        super(DurationWidget, self).__init__(widgets, attrs)
148+
149+    def decompress(self, value):
150+        if value:
151+            hours, seconds = divmod(value.seconds, 3600)
152+            minutes, seconds = divmod(seconds, 60)
153+            return [value.days, hours, minutes, seconds, value.microseconds]
154+        return [None, None, None, None, None]
155+
156+    def format_output(self, rendered_widgets):
157+        return u'%s days, %s : %s : %s . %s' % tuple(rendered_widgets)
158Index: docs/model-api.txt
159===================================================================
160--- docs/model-api.txt  (revision 6638)
161+++ docs/model-api.txt  (working copy)
162@@ -218,6 +218,17 @@
163 
164 The admin represents this as an ``<input type="text">`` (a single-line input).
165 
166+``DurationField``
167+~~~~~~~~~~~~~~~~~
168+
169+**New in Django development version**
170+
171+A span of time, represented in Python by a ``timedelta`` instance.
172+
173+The admin represents this as an ``<input type="text">`` (a single-line input),
174+with its value representing the number of seconds in the duration. Fractional
175+values are allowed, with a millisecond precision.
176+
177 ``EmailField``
178 ~~~~~~~~~~~~~~
179 
180Index: docs/newforms.txt
181===================================================================
182--- docs/newforms.txt   (revision 6638)
183+++ docs/newforms.txt   (working copy)
184@@ -1275,6 +1275,23 @@
185 permitted in the value, whilst ``decimal_places`` is the maximum number of
186 decimal places permitted.
187 
188+``DurationField``
189+~~~~~~~~~~~~~~~~~
190+
191+**New in Django development version**
192+
193+    * Default widget: ``DurationWidget``
194+    * Empty value: ``None``
195+    * Normalizes to: A Python ``datetime.timedelta`` object
196+    * Validates that the given value is a length of time, such as 24 hours in a
197+      day, 60 minutes in an hour, and 60 seconds in a minute.
198+
199+A span of time, represented in Python by a ``timedelta`` instance.
200+
201+The admin represents this as an ``<input type="text">`` (a single-line input),
202+with its value representing the number of seconds in the duration. Fractional
203+values are allowed, with a millisecond precision.
204+
205 ``EmailField``
206 ~~~~~~~~~~~~~~
207 
208@@ -1618,10 +1635,12 @@
209     ``MultiWidget``               Wrapper around multiple other widgets
210     ``SplitDateTimeWidget``       Wrapper around two ``TextInput`` widgets:
211                                   one for the Date, and one for the Time.
212+    ``DurationWidget``            A set of 5 ``TextInput`` widgets arranged
213+                                  as ``[   ] days, [ ]:[ ]:[ ].[    ]``
214     ============================  ===========================================
215 
216-**New in Django development version:** The ``DateTimeInput`` has been added
217-since the last release.
218+**New in Django development version:** The ``DateTimeInput`` and
219+``DurationWidget`` have been added since the last release.
220 
221 Specifying widgets
222 ------------------