Code

Ticket #6405: combofield_multivaluefield_pass_initial_trough.diff

File combofield_multivaluefield_pass_initial_trough.diff, 9.2 KB (added by Øyvind Saltvik <oyvind@…>, 6 years ago)

a new approach using isinstance and a mixin

Line 
1Index: django/newforms/fields.py
2===================================================================
3--- django/newforms/fields.py   (revision 7022)
4+++ django/newforms/fields.py   (working copy)
5@@ -413,6 +413,9 @@
6     # It's OK if Django settings aren't configured.
7     URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)'
8 
9+class FieldWithInitial(object):
10+    pass
11+
12 class UploadedFile(StrAndUnicode):
13     "A wrapper for files uploaded in a FileField"
14     def __init__(self, filename, content):
15@@ -426,7 +429,7 @@
16         """
17         return self.filename
18 
19-class FileField(Field):
20+class FileField(Field, FieldWithInitial):
21     widget = FileInput
22     default_error_messages = {
23         'invalid': _(u"No file was submitted. Check the encoding type on the form."),
24@@ -440,7 +443,7 @@
25     def clean(self, data, initial=None):
26         super(FileField, self).clean(initial or data)
27         if not self.required and data in EMPTY_VALUES:
28-            return None
29+            return initial or None
30         elif not data and initial:
31             return initial
32         try:
33@@ -615,7 +618,7 @@
34                 raise ValidationError(self.error_messages['invalid_choice'] % {'value': val})
35         return new_value
36 
37-class ComboField(Field):
38+class ComboField(Field, FieldWithInitial):
39     """
40     A Field whose clean() method calls multiple Field clean() methods.
41     """
42@@ -628,17 +631,20 @@
43             f.required = False
44         self.fields = fields
45 
46-    def clean(self, value):
47+    def clean(self, value, initial=None):
48         """
49         Validates the given value against all of self.fields, which is a
50         list of Field instances.
51         """
52-        super(ComboField, self).clean(value)
53+        super(ComboField, self).clean(initial or value)
54         for field in self.fields:
55-            value = field.clean(value)
56+            if isinstance(field, FieldWithInitial):
57+                value = field.clean(value, initial)
58+            else:
59+                value = field.clean(value)
60         return value
61 
62-class MultiValueField(Field):
63+class MultiValueField(Field, FieldWithInitial):
64     """
65     A Field that aggregates the logic of multiple Fields.
66 
67@@ -668,7 +674,7 @@
68             f.required = False
69         self.fields = fields
70 
71-    def clean(self, value):
72+    def clean(self, value, initial=None):
73         """
74         Validates every value in the given list. A value is validated against
75         the corresponding Field in self.fields.
76@@ -677,10 +683,27 @@
77         fields=(DateField(), TimeField()), clean() would call
78         DateField.clean(value[0]) and TimeField.clean(value[1]).
79         """
80-        clean_data = []
81+        clean_data, value_list, initial_list, takes_initial_list = [], [], [], []
82+        value, initial = value or [], initial or []
83         errors = ErrorList()
84         if not value or isinstance(value, (list, tuple)):
85-            if not value or not [v for v in value if v not in EMPTY_VALUES]:
86+            for i, field in enumerate(self.fields):
87+                try:
88+                    field_value = value[i]
89+                except IndexError:
90+                    field_value = None
91+                value_list.append(field_value)
92+                try:
93+                    field_initial = initial[i]
94+                except IndexError:
95+                    field_initial = None
96+                initial_list.append(field_initial)
97+                takes_initial_list.append(isinstance(field, FieldWithInitial))
98+
99+            if not value or not [(v, i, t) for v, i, t in \
100+                zip(value_list, initial_list, takes_initial_list) \
101+                if v not in EMPTY_VALUES or t and i not in EMPTY_VALUES]:
102+
103                 if self.required:
104                     raise ValidationError(self.error_messages['required'])
105                 else:
106@@ -688,14 +711,17 @@
107         else:
108             raise ValidationError(self.error_messages['invalid'])
109         for i, field in enumerate(self.fields):
110+            field_value = value_list[i]
111+            field_initial = initial_list[i]
112+            takes_initial = takes_initial_list[i]
113+            if self.required and not ( (takes_initial and field_initial not in EMPTY_VALUES) or
114+                (field_value not in EMPTY_VALUES) ):
115+                 raise ValidationError(self.error_messages['required'])
116             try:
117-                field_value = value[i]
118-            except IndexError:
119-                field_value = None
120-            if self.required and field_value in EMPTY_VALUES:
121-                raise ValidationError(self.error_messages['required'])
122-            try:
123-                clean_data.append(field.clean(field_value))
124+                if takes_initial:
125+                    clean_data.append(field.clean(field_value, field_initial))
126+                else:
127+                    clean_data.append(field.clean(field_value))
128             except ValidationError, e:
129                 # Collect all validation errors in a single list, which we'll
130                 # raise at the end of clean(), rather than raising a single
131@@ -703,9 +729,9 @@
132                 errors.extend(e.messages)
133         if errors:
134             raise ValidationError(errors)
135-        return self.compress(clean_data)
136+        return self.compress(clean_data, initial_list)
137 
138-    def compress(self, data_list):
139+    def compress(self, data_list, initial_list=[]):
140         """
141         Returns a single value for the given list of values. The values can be
142         assumed to be valid.
143@@ -732,7 +758,7 @@
144         )
145         super(SplitDateTimeField, self).__init__(fields, *args, **kwargs)
146 
147-    def compress(self, data_list):
148+    def compress(self, data_list, initial_list=[]):
149         if data_list:
150             # Raise a validation error if time or date is empty
151             # (possible if SplitDateTimeField has required=False).
152Index: django/newforms/forms.py
153===================================================================
154--- django/newforms/forms.py    (revision 7022)
155+++ django/newforms/forms.py    (working copy)
156@@ -9,7 +9,7 @@
157 from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode
158 from django.utils.safestring import mark_safe
159 
160-from fields import Field, FileField
161+from fields import Field, FieldWithInitial
162 from widgets import TextInput, Textarea
163 from util import flatatt, ErrorDict, ErrorList, ValidationError
164 
165@@ -182,7 +182,7 @@
166             # widgets split data over several HTML fields.
167             value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
168             try:
169-                if isinstance(field, FileField):
170+                if isinstance(field, FieldWithInitial):
171                     initial = self.initial.get(name, field.initial)
172                     value = field.clean(value, initial)
173                 else:
174Index: tests/regressiontests/forms/extra.py
175===================================================================
176--- tests/regressiontests/forms/extra.py        (revision 7022)
177+++ tests/regressiontests/forms/extra.py        (working copy)
178@@ -209,7 +209,7 @@
179 ...         )
180 ...         super(ComplexField, self).__init__(fields, required, widget, label, initial)
181 ...
182-...     def compress(self, data_list):
183+...     def compress(self, data_list, initial_list=[]):
184 ...         if data_list:
185 ...             return '%s,%s,%s' % (data_list[0],''.join(data_list[1]),data_list[2])
186 ...         return None
187Index: tests/regressiontests/forms/fields.py
188===================================================================
189--- tests/regressiontests/forms/fields.py       (revision 7022)
190+++ tests/regressiontests/forms/fields.py       (working copy)
191@@ -1132,7 +1132,44 @@
192 u''
193 >>> f.clean(None)
194 u''
195+>>> f = ComboField(fields=[FileField(), CharField(max_length=20)])
196+>>> f.clean('data', 'some_file.txt')
197+Traceback (most recent call last):
198+...   
199+ValidationError: [u'No file was submitted. Check the encoding type on the form.']
200+>>> f.clean('', 'some_file.txt')
201+u'some_file.txt'
202+>>> f.clean('', None)
203+Traceback (most recent call last):
204+...
205+ValidationError: [u'This field is required.']
206+>>> f.clean({'filename': 'filename.txt', 'content':'something'})
207+u'filename.txt'
208+>>> f.clean({'filename': 'too_long_filename_for_this_combofield_here.txt', 'content':'something'})
209+Traceback (most recent call last):
210+...
211+ValidationError: [u'Ensure this value has at most 20 characters (it has 46).']
212 
213+# MultiValueField ##########################################################
214+
215+>>> f = MultiValueField(fields=[FileField(), CharField(max_length=20)])
216+>>> f.clean([{'filename': 'some_file.txt', 'content':'something'}, 'Some text'])
217+Traceback (most recent call last):
218+...
219+NotImplementedError: Subclasses must implement this method.
220+>>> f.clean([{'filename': 'some_file.txt', 'content':'something'}, 'alotoftext_more_than_20_chars'])
221+Traceback (most recent call last):
222+...
223+ValidationError: [u'Ensure this value has at most 20 characters (it has 29).']
224+>>> f.clean([None, None])
225+Traceback (most recent call last):
226+...
227+ValidationError: [u'This field is required.']
228+>>> f.clean([None, 'Some text'], ['some_file.txt', None])
229+Traceback (most recent call last):
230+...
231+NotImplementedError: Subclasses must implement this method.
232+
233 # SplitDateTimeField ##########################################################
234 
235 >>> f = SplitDateTimeField()