Code

Ticket #6632: 01-inlines.diff

File 01-inlines.diff, 14.8 KB (added by Petr Marhoun <petr.marhoun@…>, 6 years ago)
Line 
1=== modified file 'django/forms/forms.py'
2--- django/forms/forms.py       2008-10-04 15:17:16 +0000
3+++ django/forms/forms.py       2008-10-04 15:20:05 +0000
4@@ -28,6 +28,7 @@
5         self.fieldsets = getattr(options, 'fieldsets', None)
6         self.fields = getattr(options, 'fields', None)
7         self.exclude = getattr(options, 'exclude', None)
8+        self.inlines = getattr(options, 'inlines', None)
9 
10 def create_declared_fields(cls, attrs):
11     """
12@@ -72,7 +73,8 @@
13     if cls._meta.fieldsets:
14         names = []
15         for fieldset in cls._meta.fieldsets:
16-            names.extend(fieldset['fields'])
17+            if isinstance(fieldset, dict):
18+                names.extend(fieldset['fields'])
19     elif cls._meta.fields:
20         names = cls._meta.fields
21     elif cls._meta.exclude:
22@@ -81,6 +83,12 @@
23         names = cls.base_fields_pool.keys()
24     cls.base_fields = SortedDict([(name, cls.base_fields_pool[name]) for name in names])
25 
26+def create_fieldsets_if_inlines_exist(cls, attrs):
27+    if cls._meta.inlines is not None:
28+        if cls._meta.fieldsets is not None:
29+            raise ImproperlyConfigured("%s cannot have more than one option from fieldsets and inlines." % cls.__name__)
30+        cls._meta.fieldsets = [{'fields': cls.base_fields.keys()}] + list(cls._meta.inlines)
31+
32 class FormMetaclass(type):
33     """
34     Metaclass that converts Field attributes to a dictionary called
35@@ -94,6 +102,7 @@
36         create_declared_fields(new_class, attrs)
37         create_base_fields_pool_from_declared_fields(new_class, attrs)
38         create_base_fields_from_base_fields_pool(new_class, attrs)
39+        create_fieldsets_if_inlines_exist(new_class, attrs)
40         if 'media' not in attrs:
41             new_class.media = media_property(new_class)
42         return new_class
43@@ -115,7 +124,7 @@
44         self.error_class = error_class
45         self.label_suffix = label_suffix
46         self.empty_permitted = empty_permitted
47-        self._errors = None # Stores the errors after clean() has been called.
48+        self._is_valid = None # Stores validation state after full_clean() has been called.
49         self._changed_data = None
50 
51         # The base_fields class attribute is the *class-wide* definition of
52@@ -124,6 +133,15 @@
53         # Instances should always modify self.fields; they should not modify
54         # self.base_fields.
55         self.fields = deepcopy(self.base_fields)
56+        self._construct_inlines()
57+
58+    def _construct_inlines(self):
59+        # This class cannot create any inlines.
60+        self.inlines = []
61+        if self.has_fieldsets():
62+            for fieldset in self._meta.fieldsets:
63+                if not isinstance(fieldset, dict):
64+                    raise ValueError('%s cannot create instance of %s.' % (self.__class__.__name__, fieldset.__name__))
65 
66     def __unicode__(self):
67         return self.as_table()
68@@ -145,16 +163,26 @@
69     
70     def fieldsets(self):
71         if self.has_fieldsets():
72+            fieldset_counter, formset_counter = 0, 0
73             for fieldset in self._meta.fieldsets:
74-                yield {
75-                    'attrs': fieldset.get('attrs', {}),
76-                    'legend': fieldset.get('legend', u''),
77-                    'fields': [self[name] for name in fieldset['fields']],
78-                }
79-   
80+                if isinstance(fieldset, dict):
81+                    yield {
82+                        'order': fieldset_counter, 
83+                        'attrs': fieldset.get('attrs', {}),
84+                        'legend': fieldset.get('legend', u''),
85+                        'fields': [self[name] for name in fieldset['fields']],
86+                    }
87+                    fieldset_counter += 1       
88+                else:
89+                    yield {
90+                        'order': formset_counter,
91+                        'formset': self.inlines[formset_counter]
92+                    }
93+                    formset_counter += 1
94+
95     def _get_errors(self):
96         "Returns an ErrorDict for the data provided for the form"
97-        if self._errors is None:
98+        if self._is_valid is None:
99             self.full_clean()
100         return self._errors
101     errors = property(_get_errors)
102@@ -164,7 +192,9 @@
103         Returns True if the form has no errors. Otherwise, False. If errors are
104         being ignored, returns False.
105         """
106-        return self.is_bound and not bool(self.errors)
107+        if self._is_valid is None:
108+            self.full_clean()
109+        return self._is_valid
110 
111     def add_prefix(self, field_name):
112         """
113@@ -254,11 +284,13 @@
114 
115     def full_clean(self):
116         """
117-        Cleans all of self.data and populates self._errors and
118+        Cleans all of self.data and populates self._is_valid, self._errors and
119         self.cleaned_data.
120         """
121+        self._is_valid = True # Assume the form is valid until proven otherwise.
122         self._errors = ErrorDict()
123         if not self.is_bound: # Stop further processing.
124+            self._is_valid = False
125             return
126         self.cleaned_data = {}
127         # If the form is permitted to be empty, and none of the form data has
128@@ -284,12 +316,20 @@
129                 self._errors[name] = e.messages
130                 if name in self.cleaned_data:
131                     del self.cleaned_data[name]
132+                self._is_valid = False
133         try:
134             self.cleaned_data = self.clean()
135         except ValidationError, e:
136             self._errors[NON_FIELD_ERRORS] = e.messages
137-        if self._errors:
138+            self._is_valid = False
139+        for inline in self.inlines:
140+            inline.full_clean()
141+            if not inline.is_valid():
142+                self._is_valid = False
143+        if not self._is_valid:
144             delattr(self, 'cleaned_data')
145+            for inline in self.inlines:
146+                inline._is_valid = False
147 
148     def clean(self):
149         """
150@@ -337,6 +377,8 @@
151         media = Media()
152         for field in self.fields.values():
153             media = media + field.widget.media
154+        for inline in self.inlines:
155+            media = media + inline.media
156         return media
157     media = property(_get_media)
158 
159@@ -348,6 +390,9 @@
160         for field in self.fields.values():
161             if field.widget.needs_multipart_form:
162                 return True
163+        for inline in self.inlines:
164+            if inline.is_multipart():
165+                return True
166         return False
167 
168 class Form(BaseForm):
169
170=== modified file 'django/forms/formsets.py'
171--- django/forms/formsets.py    2008-10-02 02:46:12 +0000
172+++ django/forms/formsets.py    2008-10-02 23:21:31 +0000
173@@ -38,8 +38,7 @@
174         self.files = files
175         self.initial = initial
176         self.error_class = error_class
177-        self._errors = None
178-        self._non_form_errors = None
179+        self._is_valid = None # Stores validation state after full_clean() has been called.
180         # initialization is different depending on whether we recieved data, initial, or nothing
181         if data or files:
182             self.management_form = ManagementForm(data, auto_id=self.auto_id, prefix=self.prefix)
183@@ -182,15 +181,15 @@
184         form -- i.e., from formset.clean(). Returns an empty ErrorList if there
185         are none.
186         """
187-        if self._non_form_errors is not None:
188-            return self._non_form_errors
189-        return self.error_class()
190+        if self._is_valid is None:
191+            self.full_clean()
192+        return self._non_form_errors
193 
194     def _get_errors(self):
195         """
196         Returns a list of form.errors for every form in self.forms.
197         """
198-        if self._errors is None:
199+        if self._is_valid is None:
200             self.full_clean()
201         return self._errors
202     errors = property(_get_errors)
203@@ -199,31 +198,31 @@
204         """
205         Returns True if form.errors is empty for every form in self.forms.
206         """
207-        if not self.is_bound:
208-            return False
209-        # We loop over every form.errors here rather than short circuiting on the
210-        # first failure to make sure validation gets triggered for every form.
211-        forms_valid = True
212-        for errors in self.errors:
213-            if bool(errors):
214-                forms_valid = False
215-        return forms_valid and not bool(self.non_form_errors())
216+        if self._is_valid is None:
217+            self.full_clean()
218+        return self._is_valid
219 
220     def full_clean(self):
221         """
222         Cleans all of self.data and populates self._errors.
223         """
224+        self._is_valid = True # Assume the form is valid until proven otherwise.
225         self._errors = []
226+        self._non_form_errors = self.error_class()
227         if not self.is_bound: # Stop further processing.
228+            self._is_valid = False
229             return
230         for i in range(0, self._total_form_count):
231             form = self.forms[i]
232             self._errors.append(form.errors)
233+            if form.errors:
234+                self._is_valid = False
235         # Give self.clean() a chance to do cross-form validation.
236         try:
237             self.clean()
238         except ValidationError, e:
239-            self._non_form_errors = e.messages
240+            self._non_form_errors = self.error_class(e.messages)
241+            self._is_valid = False
242 
243     def clean(self):
244         """
245
246=== modified file 'django/forms/models.py'
247--- django/forms/models.py      2008-10-08 17:13:50 +0000
248+++ django/forms/models.py      2008-10-08 17:15:33 +0000
249@@ -11,6 +11,7 @@
250 from util import ValidationError, ErrorList
251 from forms import FormOptions, BaseForm, create_declared_fields
252 from forms import create_base_fields_from_base_fields_pool
253+from forms import create_fieldsets_if_inlines_exist
254 from fields import Field, ChoiceField, IntegerField, EMPTY_VALUES
255 from widgets import Select, SelectMultiple, HiddenInput, MultipleHiddenInput
256 from widgets import media_property
257@@ -60,6 +61,9 @@
258                 continue
259             if f.name in cleaned_data:
260                 f.save_form_data(instance, cleaned_data[f.name])
261+        if not commit:
262+            for inline in form.inlines:
263+                inline.save_m2m()
264     if commit:
265         # If we are committing, save the instance and the m2m data immediately.
266         instance.save()
267@@ -209,6 +213,7 @@
268         create_declared_fields(new_class, attrs)
269         create_base_fields_pool_from_model_fields_and_declared_fields(new_class, attrs)
270         create_base_fields_from_base_fields_pool(new_class, attrs)
271+        create_fieldsets_if_inlines_exist(new_class, attrs)
272         if 'media' not in attrs:
273             new_class.media = media_property(new_class)
274         return new_class
275@@ -233,6 +238,16 @@
276         self.validate_unique()
277         return self.cleaned_data
278 
279+    def _construct_inlines(self):
280+        # This class can create inlines which are subclass of BaseInlineFormSet.
281+        self.inlines = []
282+        if self.has_fieldsets():
283+            for fieldset in self._meta.fieldsets:
284+                if not isinstance(fieldset, dict):
285+                    if not issubclass(fieldset, BaseInlineFormSet):
286+                        raise ValueError('%s cannot create instance of %s.' % (self.__class__.__name__, fieldset.__name__))
287+                    self.inlines.append(fieldset(self.data, self.files, self.instance))
288+
289     def validate_unique(self):
290         from django.db.models.fields import FieldDoesNotExist
291 
292@@ -295,6 +310,7 @@
293                         {'model_name': unicode(model_name),
294                          'field_label': unicode(field_label)}
295                     ])
296+                    self._is_valid = False
297                 # unique_together
298                 else:
299                     field_labels = [self.fields[field_name].label for field_name in unique_check]
300@@ -304,7 +320,8 @@
301                         {'model_name': unicode(model_name),
302                          'field_label': unicode(field_labels)}
303                     )
304-
305+                    self._is_valid = False
306+               
307                 # Mark these fields as needing to be removed from cleaned data
308                 # later.
309                 for field_name in unique_check:
310@@ -329,13 +346,15 @@
311             fail_message = 'created'
312         else:
313             fail_message = 'changed'
314-        return save_instance(self, self.instance, self.fields.keys(), fail_message, commit)
315+        self.saved_instance = save_instance(self, self.instance, self.fields.keys(), fail_message, commit)
316+        self.saved_inline_instances = [inline.save(commit) for inline in self.inlines]
317+        return self.saved_instance
318 
319 class ModelForm(BaseModelForm):
320     __metaclass__ = ModelFormMetaclass
321 
322 def modelform_factory(model, form=ModelForm, fields=None, exclude=None, fieldsets=None,
323-                       formfield_callback=lambda f: f.formfield()):
324+                       inlines=None, formfield_callback=lambda f: f.formfield()):
325     # HACK: we should be able to construct a ModelForm without creating
326     # and passing in a temporary inner class
327     class Meta:
328@@ -344,6 +363,7 @@
329     setattr(Meta, 'fieldsets', fieldsets)
330     setattr(Meta, 'fields', fields)
331     setattr(Meta, 'exclude', exclude)
332+    setattr(Meta, 'inlines', inlines)
333     class_name = model.__name__ + 'Form'
334     return ModelFormMetaclass(class_name, (form,), {'Meta': Meta,
335                               'formfield_callback': formfield_callback})
336@@ -451,12 +471,12 @@
337 def modelformset_factory(model, form=ModelForm, formfield_callback=lambda f: f.formfield(),
338                          formset=BaseModelFormSet,
339                          extra=1, can_delete=False, can_order=False,
340-                         max_num=0, fields=None, exclude=None, fieldsets=None):
341+                         max_num=0, fields=None, exclude=None, fieldsets=None, inlines=None):
342     """
343     Returns a FormSet class for the given Django model class.
344     """
345     form = modelform_factory(model, form=form, fields=fields, exclude=exclude,
346-                             fieldsets=fieldsets,
347+                             fieldsets=fieldsets, inlines=inlines,
348                              formfield_callback=formfield_callback)
349     FormSet = formset_factory(form, formset, extra=extra, max_num=max_num,
350                               can_order=can_order, can_delete=can_delete)
351@@ -546,7 +566,7 @@
352 
353 def inlineformset_factory(parent_model, model, form=ModelForm,
354                           formset=BaseInlineFormSet, fk_name=None,
355-                          fields=None, exclude=None, fieldsets=None,
356+                          fields=None, exclude=None, fieldsets=None, inlines=None,
357                           extra=3, can_order=False, can_delete=True, max_num=0,
358                           formfield_callback=lambda f: f.formfield()):
359     """
360@@ -574,6 +594,7 @@
361         'fieldsets': fieldsets,
362         'fields': fields,
363         'exclude': exclude,
364+        'inlines': inlines,
365         'max_num': max_num,
366     }
367     FormSet = modelformset_factory(model, **kwargs)
368