Code

Ticket #6162: modelform-init-2.diff

File modelform-init-2.diff, 17.2 KB (added by ubernostrum, 6 years ago)

Updated patch

Line 
1Index: django/newforms/models.py
2===================================================================
3--- django/newforms/models.py   (revision 6898)
4+++ django/newforms/models.py   (working copy)
5@@ -262,11 +262,17 @@
6         return type.__new__(cls, name, bases, attrs)
7 
8 class BaseModelForm(BaseForm):
9-    def __init__(self, instance, data=None, files=None, auto_id='id_%s', prefix=None,
10-                 initial=None, error_class=ErrorList, label_suffix=':'):
11-        self.instance = instance
12+    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
13+                 initial=None, error_class=ErrorList, label_suffix=':', instance=None):
14         opts = self._meta
15-        object_data = model_to_dict(instance, opts.fields, opts.exclude)
16+        if instance is None and opts.model is None:
17+            raise ImproperlyConfigured("ModelForm must either be bound to a model class or receive a model instance in __init__()")
18+        if instance is None:
19+           self.instance = opts.model()
20+           object_data = {}
21+        else:
22+            self.instance = instance
23+            object_data = model_to_dict(instance, opts.fields, opts.exclude)
24         # if initial was provided, it should override the values from instance
25         if initial is not None:
26             object_data.update(initial)
27Index: tests/modeltests/model_forms/models.py
28===================================================================
29--- tests/modeltests/model_forms/models.py      (revision 6898)
30+++ tests/modeltests/model_forms/models.py      (working copy)
31@@ -29,6 +29,13 @@
32     def __unicode__(self):
33         return self.name
34 
35+class Editor(models.Model):
36+    name = models.CharField(max_length=50, help_text='Use both first and last names.')
37+    categories = models.ManyToManyField(Category)
38+
39+    def __unicode__(self):
40+        return self.name
41+
42 class Article(models.Model):
43     headline = models.CharField(max_length=50)
44     slug = models.SlugField()
45@@ -155,9 +162,18 @@
46 ...
47 ImproperlyConfigured: BadForm's base classes define more than one model.
48 
49+Require either a model bound in Meta or an instance passed in on __init__().
50 
51-# Old form_for_x tests #######################################################
52+>>> class NoModelForm(ModelForm):
53+...     pass
54 
55+>>> f = NoModelForm()
56+Traceback (most recent call last):
57+...
58+ImproperlyConfigured: ModelForm must either be bound to a model class or receive a model instance in __init__()
59+
60+# Test valid ModelForms #######################################################
61+
62 >>> from django.newforms import ModelForm, CharField
63 >>> import datetime
64 
65@@ -167,7 +183,7 @@
66 >>> class CategoryForm(ModelForm):
67 ...     class Meta:
68 ...         model = Category
69->>> f = CategoryForm(Category())
70+>>> f = CategoryForm()
71 >>> print f
72 <tr><th><label for="id_name">Name:</label></th><td><input id="id_name" type="text" name="name" maxlength="20" /></td></tr>
73 <tr><th><label for="id_slug">Slug:</label></th><td><input id="id_slug" type="text" name="slug" maxlength="20" /></td></tr>
74@@ -179,13 +195,13 @@
75 >>> print f['name']
76 <input id="id_name" type="text" name="name" maxlength="20" />
77 
78->>> f = CategoryForm(Category(), auto_id=False)
79+>>> f = CategoryForm(auto_id=False)
80 >>> print f.as_ul()
81 <li>Name: <input type="text" name="name" maxlength="20" /></li>
82 <li>Slug: <input type="text" name="slug" maxlength="20" /></li>
83 <li>The URL: <input type="text" name="url" maxlength="40" /></li>
84 
85->>> f = CategoryForm(Category(), {'name': 'Entertainment', 'slug': 'entertainment', 'url': 'entertainment'})
86+>>> f = CategoryForm({'name': 'Entertainment', 'slug': 'entertainment', 'url': 'entertainment'})
87 >>> f.is_valid()
88 True
89 >>> f.cleaned_data
90@@ -196,7 +212,7 @@
91 >>> Category.objects.all()
92 [<Category: Entertainment>]
93 
94->>> f = CategoryForm(Category(), {'name': "It's a test", 'slug': 'its-test', 'url': 'test'})
95+>>> f = CategoryForm({'name': "It's a test", 'slug': 'its-test', 'url': 'test'})
96 >>> f.is_valid()
97 True
98 >>> f.cleaned_data
99@@ -210,7 +226,7 @@
100 If you call save() with commit=False, then it will return an object that
101 hasn't yet been saved to the database. In this case, it's up to you to call
102 save() on the resulting model instance.
103->>> f = CategoryForm(Category(), {'name': 'Third test', 'slug': 'third-test', 'url': 'third'})
104+>>> f = CategoryForm({'name': 'Third test', 'slug': 'third-test', 'url': 'third'})
105 >>> f.is_valid()
106 True
107 >>> f.cleaned_data
108@@ -225,7 +241,7 @@
109 [<Category: Entertainment>, <Category: It's a test>, <Category: Third test>]
110 
111 If you call save() with invalid data, you'll get a ValueError.
112->>> f = CategoryForm(Category(), {'name': '', 'slug': '', 'url': 'foo'})
113+>>> f = CategoryForm({'name': '', 'slug': '', 'url': 'foo'})
114 >>> f.errors
115 {'name': [u'This field is required.'], 'slug': [u'This field is required.']}
116 >>> f.cleaned_data
117@@ -236,7 +252,7 @@
118 Traceback (most recent call last):
119 ...
120 ValueError: The Category could not be created because the data didn't validate.
121->>> f = CategoryForm(Category(), {'name': '', 'slug': '', 'url': 'foo'})
122+>>> f = CategoryForm({'name': '', 'slug': '', 'url': 'foo'})
123 >>> f.save()
124 Traceback (most recent call last):
125 ...
126@@ -253,7 +269,7 @@
127 >>> class ArticleForm(ModelForm):
128 ...     class Meta:
129 ...         model = Article
130->>> f = ArticleForm(Article(), auto_id=False)
131+>>> f = ArticleForm(auto_id=False)
132 >>> print f
133 <tr><th>Headline:</th><td><input type="text" name="headline" maxlength="50" /></td></tr>
134 <tr><th>Slug:</th><td><input type="text" name="slug" maxlength="50" /></td></tr>
135@@ -286,19 +302,19 @@
136 ...     class Meta:
137 ...         model = Article
138 ...         fields = ('headline','pub_date')
139->>> f = PartialArticleForm(Article(), auto_id=False)
140+>>> f = PartialArticleForm(auto_id=False)
141 >>> print f
142 <tr><th>Headline:</th><td><input type="text" name="headline" maxlength="50" /></td></tr>
143 <tr><th>Pub date:</th><td><input type="text" name="pub_date" /></td></tr>
144 
145-Use form_for_instance to create a Form from a model instance. The difference
146-between this Form and one created via form_for_model is that the object's
147+Pass the "instance" keyword argument to create a Form from a model instance. The
148+difference between this Form and one created wthout the"instance" argument is that the object's
149 current values are inserted as 'initial' data in each Field.
150 >>> w = Writer.objects.get(name='Mike Royko')
151 >>> class RoykoForm(ModelForm):
152 ...     class Meta:
153 ...         model = Writer
154->>> f = RoykoForm(w, auto_id=False)
155+>>> f = RoykoForm(auto_id=False, instance=w)
156 >>> print f
157 <tr><th>Name:</th><td><input type="text" name="name" value="Mike Royko" maxlength="50" /><br />Use both first and last names.</td></tr>
158 
159@@ -309,7 +325,7 @@
160 >>> class TestArticleForm(ModelForm):
161 ...     class Meta:
162 ...         model = Article
163->>> f = TestArticleForm(art, auto_id=False)
164+>>> f = TestArticleForm(instance=art, auto_id=False)
165 >>> print f.as_ul()
166 <li>Headline: <input type="text" name="headline" value="Test article" maxlength="50" /></li>
167 <li>Slug: <input type="text" name="slug" value="test-article" maxlength="50" /></li>
168@@ -331,7 +347,7 @@
169 <option value="2">It&#39;s a test</option>
170 <option value="3">Third test</option>
171 </select>  Hold down "Control", or "Command" on a Mac, to select more than one.</li>
172->>> f = TestArticleForm(art, {'headline': u'Test headline', 'slug': 'test-headline', 'pub_date': u'1984-02-06', 'writer': u'1', 'article': 'Hello.'})
173+>>> f = TestArticleForm({'headline': u'Test headline', 'slug': 'test-headline', 'pub_date': u'1984-02-06', 'writer': u'1', 'article': 'Hello.'}, instance=art)
174 >>> f.is_valid()
175 True
176 >>> test_art = f.save()
177@@ -347,7 +363,7 @@
178 ...     class Meta:
179 ...         model = Article
180 ...         fields=('headline', 'slug', 'pub_date')
181->>> f = PartialArticleForm(art, {'headline': u'New headline', 'slug': 'new-headline', 'pub_date': u'1988-01-04'}, auto_id=False)
182+>>> f = PartialArticleForm({'headline': u'New headline', 'slug': 'new-headline', 'pub_date': u'1988-01-04'}, auto_id=False, instance=art)
183 >>> print f.as_ul()
184 <li>Headline: <input type="text" name="headline" value="New headline" maxlength="50" /></li>
185 <li>Slug: <input type="text" name="slug" value="new-headline" maxlength="50" /></li>
186@@ -370,7 +386,7 @@
187 >>> class TestArticleForm(ModelForm):
188 ...     class Meta:
189 ...         model = Article
190->>> f = TestArticleForm(new_art, auto_id=False)
191+>>> f = TestArticleForm(auto_id=False, instance=new_art)
192 >>> print f.as_ul()
193 <li>Headline: <input type="text" name="headline" value="New headline" maxlength="50" /></li>
194 <li>Slug: <input type="text" name="slug" value="new-headline" maxlength="50" /></li>
195@@ -393,8 +409,8 @@
196 <option value="3">Third test</option>
197 </select>  Hold down "Control", or "Command" on a Mac, to select more than one.</li>
198 
199->>> f = TestArticleForm(new_art, {'headline': u'New headline', 'slug': u'new-headline', 'pub_date': u'1988-01-04',
200-...     'writer': u'1', 'article': u'Hello.', 'categories': [u'1', u'2']})
201+>>> f = TestArticleForm({'headline': u'New headline', 'slug': u'new-headline', 'pub_date': u'1988-01-04',
202+...     'writer': u'1', 'article': u'Hello.', 'categories': [u'1', u'2']}, instance=new_art)
203 >>> new_art = f.save()
204 >>> new_art.id
205 1
206@@ -403,8 +419,8 @@
207 [<Category: Entertainment>, <Category: It's a test>]
208 
209 Now, submit form data with no categories. This deletes the existing categories.
210->>> f = TestArticleForm(new_art, {'headline': u'New headline', 'slug': u'new-headline', 'pub_date': u'1988-01-04',
211-...     'writer': u'1', 'article': u'Hello.'})
212+>>> f = TestArticleForm({'headline': u'New headline', 'slug': u'new-headline', 'pub_date': u'1988-01-04',
213+...     'writer': u'1', 'article': u'Hello.'}, instance=new_art)
214 >>> new_art = f.save()
215 >>> new_art.id
216 1
217@@ -416,7 +432,7 @@
218 >>> class ArticleForm(ModelForm):
219 ...     class Meta:
220 ...         model = Article
221->>> f = ArticleForm(Article(), {'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01',
222+>>> f = ArticleForm({'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01',
223 ...     'writer': u'1', 'article': u'Test.', 'categories': [u'1', u'2']})
224 >>> new_art = f.save()
225 >>> new_art.id
226@@ -429,7 +445,7 @@
227 >>> class ArticleForm(ModelForm):
228 ...     class Meta:
229 ...         model = Article
230->>> f = ArticleForm(Article(), {'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01',
231+>>> f = ArticleForm({'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01',
232 ...     'writer': u'1', 'article': u'Test.'})
233 >>> new_art = f.save()
234 >>> new_art.id
235@@ -443,7 +459,7 @@
236 >>> class ArticleForm(ModelForm):
237 ...     class Meta:
238 ...         model = Article
239->>> f = ArticleForm(Article(), {'headline': u'The walrus was Paul', 'slug': 'walrus-was-paul', 'pub_date': u'1967-11-01',
240+>>> f = ArticleForm({'headline': u'The walrus was Paul', 'slug': 'walrus-was-paul', 'pub_date': u'1967-11-01',
241 ...     'writer': u'1', 'article': u'Test.', 'categories': [u'1', u'2']})
242 >>> new_art = f.save(commit=False)
243 
244@@ -474,7 +490,7 @@
245 <Category: Third test>
246 >>> cat.id
247 3
248->>> form = ShortCategory(cat, {'name': 'Third', 'slug': 'third', 'url': '3rd'})
249+>>> form = ShortCategory({'name': 'Third', 'slug': 'third', 'url': '3rd'}, instance=cat)
250 >>> form.save()
251 <Category: Third>
252 >>> Category.objects.get(id=3)
253@@ -486,7 +502,7 @@
254 >>> class ArticleForm(ModelForm):
255 ...     class Meta:
256 ...         model = Article
257->>> f = ArticleForm(Article(), auto_id=False)
258+>>> f = ArticleForm(auto_id=False)
259 >>> print f.as_ul()
260 <li>Headline: <input type="text" name="headline" maxlength="50" /></li>
261 <li>Slug: <input type="text" name="slug" maxlength="50" /></li>
262@@ -536,6 +552,23 @@
263 <option value="4">Fourth</option>
264 </select>  Hold down "Control", or "Command" on a Mac, to select more than one.</li>
265 
266+By not binding the form to a specific model, and by explicitly
267+specifying a list of fields, it's possible to use a ModelForm with any
268+model which has fields of those names. Doing so requires that an
269+instance be passed in on form instantiation.
270+
271+>>> class WriterOrEditorForm(ModelForm):
272+...     name = forms.CharField(max_length=50)
273+>>> w = Writer()
274+>>> f = WriterOrEditorForm(instance=w)
275+>>> print f.as_ul()
276+<li><label for="id_name">Name:</label> <input id="id_name" type="text" name="name" maxlength="50" /></li>
277+>>> e = Editor(name="Horace Greeley")
278+>>> f = WriterOrEditorForm(instance=e)
279+>>> print f.as_ul()
280+<li><label for="id_name">Name:</label> <input id="id_name" type="text" name="name" value="Horace Greeley" maxlength="50" /></li>
281+
282+
283 # ModelChoiceField ############################################################
284 
285 >>> from django.newforms import ModelChoiceField, ModelMultipleChoiceField
286@@ -690,7 +723,7 @@
287 >>> class PhoneNumberForm(ModelForm):
288 ...     class Meta:
289 ...         model = PhoneNumber
290->>> f = PhoneNumberForm(PhoneNumber(), {'phone': '(312) 555-1212', 'description': 'Assistance'})
291+>>> f = PhoneNumberForm({'phone': '(312) 555-1212', 'description': 'Assistance'})
292 >>> f.is_valid()
293 True
294 >>> f.cleaned_data
295Index: docs/modelforms.txt
296===================================================================
297--- docs/modelforms.txt (revision 6898)
298+++ docs/modelforms.txt (working copy)
299@@ -24,12 +24,11 @@
300     ...         model = Article
301 
302     # Creating a form to add an article.
303-    >>> article = Article()
304-    >>> form = ArticleForm(article)
305+    >>> form = ArticleForm()
306 
307     # Creating a form to change an existing article.
308     >>> article = Article.objects.get(pk=1)
309-    >>> form = ArticleForm(article)
310+    >>> form = ArticleForm(instance=article)
311 
312 Field types
313 -----------
314@@ -166,18 +165,23 @@
315 The ``save()`` method
316 ---------------------
317 
318-Every form produced by ``ModelForm`` also has a ``save()`` method. This
319-method creates and saves a database object from the data bound to the form.
320-A subclass of ``ModelForm`` also requires a model instance as the first
321-arument to its constructor. For example::
322+Every form produced by ``ModelForm`` also has a ``save()``
323+method. This method creates and saves a database object from the data
324+bound to the form. A subclass of ``ModelForm`` can accept an existing
325+model instance as the keyword argument ``instance``; if this is
326+supplied, ``save()`` will update that instance. If it's not supplied,
327+``save()`` will create a new instance of the specified model::
328 
329     # Create a form instance from POST data.
330-    >>> a = Article()
331-    >>> f = ArticleForm(a, request.POST)
332+    >>> f = ArticleForm(request.POST)
333 
334     # Save a new Article object from the form's data.
335     >>> new_article = f.save()
336 
337+    # Create a form to edit an existing Article.
338+    >>> a = Article.objects.get(pk=1)
339+    >>> f = ArticleForm(instance=a)
340+
341 Note that ``save()`` will raise a ``ValueError`` if the data in the form
342 doesn't validate -- i.e., ``if form.errors``.
343 
344@@ -201,8 +205,7 @@
345 ``save_m2m()`` to save the many-to-many form data. For example::
346 
347     # Create a form instance with POST data.
348-    >>> a = Author()
349-    >>> f = AuthorForm(a, request.POST)
350+    >>> f = AuthorForm(request.POST)
351 
352     # Create, but don't save the new author instance.
353     >>> new_author = f.save(commit=False)
354@@ -222,8 +225,7 @@
355 For example::
356 
357     # Create a form instance with POST data.
358-    >>> a = Author()
359-    >>> f = AuthorForm(a, request.POST)
360+    >>> f = AuthorForm(request.POST)
361 
362     # Create and save the new author instance. There's no need to do anything else.
363     >>> new_author = f.save()
364@@ -277,7 +279,7 @@
365     manually set anyextra required fields::
366     
367         instance = Instance(required_field='value')
368-        form = InstanceForm(instance, request.POST)
369+        form = InstanceForm(request.POST, instance=instance)
370         new_instance = form.save()
371 
372         instance = form.save(commit=False)
373@@ -289,6 +291,44 @@
374 
375 .. _section on saving forms: `The save() method`_
376 
377+Using a form with any of multiple models
378+----------------------------------------
379+
380+If you have several models which share a common set of fields, and
381+want to expose just those fields for editing, you can define a
382+``ModelForm`` subclass *without* specifying the model, and list only
383+the shared fields to be displayed.
384+
385+For example, if your application has multiple models which represent
386+different types of people, each with a "name" field, you could define
387+a ``ModelForm`` subclass like so::
388+
389+    from django import newforms as forms
390+
391+    class PersonNameForm(forms.ModelForm):
392+        name = forms.CharField(max_length=50)
393+
394+You could then pass any instance of any model which has a "name" field
395+as the ``instance`` keyword argument when creating an instance of the
396+form.
397+
398+Note that when a ``ModelForm`` is used like this, two important
399+restrictions apply:
400+
401+1. Only the fields which are explicitly declared on the form will be
402+   displayed.
403+
404+2. It is your responsibility to supply values for any other required
405+   fields before saving the model instance, either by filling them in
406+   on the instance before passing it to the form, or by using the
407+   ``commit=False`` argument to ``save()`` and then filling in the
408+   fields before actually saving the instance to the databae.
409+       
410+Defining a ``ModelForm`` subclass without specifying a model, and then
411+attempting to instantiate it without passing a model instance as the
412+``instance`` keyword argument, is an error.
413+
414+
415 Overriding the default field types
416 ----------------------------------
417