|
1 """ |
|
2 36. Generating HTML forms from models |
|
3 |
|
4 Django provides shortcuts for creating Form objects from a model class and a |
|
5 model instance. |
|
6 |
|
7 The function django.newforms.form_for_model() takes a model class and returns |
|
8 a Form that is tied to the model. This Form works just like any other Form, |
|
9 with one additional method: save(). The save() method creates an instance |
|
10 of the model and returns that newly created instance. It saves the instance to |
|
11 the database if save(commit=True), which is default. If you pass |
|
12 commit=False, then you'll get the object without committing the changes to the |
|
13 database. |
|
14 |
|
15 The function django.newforms.form_for_instance() takes a model instance and |
|
16 returns a Form that is tied to the instance. This form works just like any |
|
17 other Form, with one additional method: save(). The save() |
|
18 method updates the model instance. It also takes a commit=True parameter. |
|
19 |
|
20 The function django.newforms.save_instance() takes a bound form instance and a |
|
21 model instance and saves the form's clean_data into the instance. It also takes |
|
22 a commit=True parameter. |
|
23 """ |
|
24 |
|
25 from django.db import models |
|
26 |
|
27 class Category(models.Model): |
|
28 name = models.CharField(maxlength=20) |
|
29 url = models.CharField('The URL', maxlength=40) |
|
30 |
|
31 def __str__(self): |
|
32 return self.name |
|
33 |
|
34 class Writer(models.Model): |
|
35 name = models.CharField(maxlength=50, help_text='Use both first and last names.') |
|
36 |
|
37 def __str__(self): |
|
38 return self.name |
|
39 |
|
40 class Article(models.Model): |
|
41 headline = models.CharField(maxlength=50) |
|
42 pub_date = models.DateField() |
|
43 created = models.DateField(editable=False) |
|
44 writer = models.ForeignKey(Writer) |
|
45 article = models.TextField() |
|
46 categories = models.ManyToManyField(Category, blank=True) |
|
47 |
|
48 def save(self): |
|
49 import datetime |
|
50 if not self.id: |
|
51 self.created = datetime.date.today() |
|
52 return super(Article, self).save() |
|
53 |
|
54 def __str__(self): |
|
55 return self.headline |
|
56 |
|
57 class PhoneNumber(models.Model): |
|
58 phone = models.PhoneNumberField() |
|
59 description = models.CharField(maxlength=20) |
|
60 |
|
61 def __str__(self): |
|
62 return self.phone |
|
63 |
|
64 __test__ = {'API_TESTS': """ |
|
65 >>> from django.newforms import form_for_model, form_for_instance, save_instance, BaseForm, Form, CharField |
|
66 >>> import datetime |
|
67 |
|
68 >>> Category.objects.all() |
|
69 [] |
|
70 |
|
71 >>> CategoryForm = form_for_model(Category) |
|
72 >>> f = CategoryForm() |
|
73 >>> print f |
|
74 <tr><th><label for="id_name">Name:</label></th><td><input id="id_name" type="text" name="name" maxlength="20" /></td></tr> |
|
75 <tr><th><label for="id_url">The URL:</label></th><td><input id="id_url" type="text" name="url" maxlength="40" /></td></tr> |
|
76 >>> print f.as_ul() |
|
77 <li><label for="id_name">Name:</label> <input id="id_name" type="text" name="name" maxlength="20" /></li> |
|
78 <li><label for="id_url">The URL:</label> <input id="id_url" type="text" name="url" maxlength="40" /></li> |
|
79 >>> print f['name'] |
|
80 <input id="id_name" type="text" name="name" maxlength="20" /> |
|
81 |
|
82 >>> f = CategoryForm(auto_id=False) |
|
83 >>> print f.as_ul() |
|
84 <li>Name: <input type="text" name="name" maxlength="20" /></li> |
|
85 <li>The URL: <input type="text" name="url" maxlength="40" /></li> |
|
86 |
|
87 >>> f = CategoryForm({'name': 'Entertainment', 'url': 'entertainment'}) |
|
88 >>> f.is_valid() |
|
89 True |
|
90 >>> f.clean_data |
|
91 {'url': u'entertainment', 'name': u'Entertainment'} |
|
92 >>> obj = f.save() |
|
93 >>> obj |
|
94 <Category: Entertainment> |
|
95 >>> Category.objects.all() |
|
96 [<Category: Entertainment>] |
|
97 |
|
98 >>> f = CategoryForm({'name': "It's a test", 'url': 'test'}) |
|
99 >>> f.is_valid() |
|
100 True |
|
101 >>> f.clean_data |
|
102 {'url': u'test', 'name': u"It's a test"} |
|
103 >>> obj = f.save() |
|
104 >>> obj |
|
105 <Category: It's a test> |
|
106 >>> Category.objects.all() |
|
107 [<Category: Entertainment>, <Category: It's a test>] |
|
108 |
|
109 If you call save() with commit=False, then it will return an object that |
|
110 hasn't yet been saved to the database. In this case, it's up to you to call |
|
111 save() on the resulting model instance. |
|
112 >>> f = CategoryForm({'name': 'Third test', 'url': 'third'}) |
|
113 >>> f.is_valid() |
|
114 True |
|
115 >>> f.clean_data |
|
116 {'url': u'third', 'name': u'Third test'} |
|
117 >>> obj = f.save(commit=False) |
|
118 >>> obj |
|
119 <Category: Third test> |
|
120 >>> Category.objects.all() |
|
121 [<Category: Entertainment>, <Category: It's a test>] |
|
122 >>> obj.save() |
|
123 >>> Category.objects.all() |
|
124 [<Category: Entertainment>, <Category: It's a test>, <Category: Third test>] |
|
125 |
|
126 If you call save() with invalid data, you'll get a ValueError. |
|
127 >>> f = CategoryForm({'name': '', 'url': 'foo'}) |
|
128 >>> f.errors |
|
129 {'name': [u'This field is required.']} |
|
130 >>> f.clean_data |
|
131 Traceback (most recent call last): |
|
132 ... |
|
133 AttributeError: 'CategoryForm' object has no attribute 'clean_data' |
|
134 >>> f.save() |
|
135 Traceback (most recent call last): |
|
136 ... |
|
137 ValueError: The Category could not be created because the data didn't validate. |
|
138 >>> f = CategoryForm({'name': '', 'url': 'foo'}) |
|
139 >>> f.save() |
|
140 Traceback (most recent call last): |
|
141 ... |
|
142 ValueError: The Category could not be created because the data didn't validate. |
|
143 |
|
144 Create a couple of Writers. |
|
145 >>> w = Writer(name='Mike Royko') |
|
146 >>> w.save() |
|
147 >>> w = Writer(name='Bob Woodward') |
|
148 >>> w.save() |
|
149 |
|
150 ManyToManyFields are represented by a MultipleChoiceField, and ForeignKeys are |
|
151 represented by a ChoiceField. |
|
152 >>> ArticleForm = form_for_model(Article) |
|
153 >>> f = ArticleForm(auto_id=False) |
|
154 >>> print f |
|
155 <tr><th>Headline:</th><td><input type="text" name="headline" maxlength="50" /></td></tr> |
|
156 <tr><th>Pub date:</th><td><input type="text" name="pub_date" /></td></tr> |
|
157 <tr><th>Writer:</th><td><select name="writer"> |
|
158 <option value="" selected="selected">---------</option> |
|
159 <option value="1">Mike Royko</option> |
|
160 <option value="2">Bob Woodward</option> |
|
161 </select></td></tr> |
|
162 <tr><th>Article:</th><td><textarea name="article"></textarea></td></tr> |
|
163 <tr><th>Categories:</th><td><select multiple="multiple" name="categories"> |
|
164 <option value="1">Entertainment</option> |
|
165 <option value="2">It's a test</option> |
|
166 <option value="3">Third test</option> |
|
167 </select><br /> Hold down "Control", or "Command" on a Mac, to select more than one.</td></tr> |
|
168 |
|
169 You can pass a custom Form class to form_for_model. Make sure it's a |
|
170 subclass of BaseForm, not Form. |
|
171 >>> class CustomForm(BaseForm): |
|
172 ... def say_hello(self): |
|
173 ... print 'hello' |
|
174 >>> CategoryForm = form_for_model(Category, form=CustomForm) |
|
175 >>> f = CategoryForm() |
|
176 >>> f.say_hello() |
|
177 hello |
|
178 |
|
179 Use form_for_instance to create a Form from a model instance. The difference |
|
180 between this Form and one created via form_for_model is that the object's |
|
181 current values are inserted as 'initial' data in each Field. |
|
182 >>> w = Writer.objects.get(name='Mike Royko') |
|
183 >>> RoykoForm = form_for_instance(w) |
|
184 >>> f = RoykoForm(auto_id=False) |
|
185 >>> print f |
|
186 <tr><th>Name:</th><td><input type="text" name="name" value="Mike Royko" maxlength="50" /><br />Use both first and last names.</td></tr> |
|
187 |
|
188 >>> art = Article(headline='Test article', pub_date=datetime.date(1988, 1, 4), writer=w, article='Hello.') |
|
189 >>> art.save() |
|
190 >>> art.id |
|
191 1 |
|
192 >>> TestArticleForm = form_for_instance(art) |
|
193 >>> f = TestArticleForm(auto_id=False) |
|
194 >>> print f.as_ul() |
|
195 <li>Headline: <input type="text" name="headline" value="Test article" maxlength="50" /></li> |
|
196 <li>Pub date: <input type="text" name="pub_date" value="1988-01-04" /></li> |
|
197 <li>Writer: <select name="writer"> |
|
198 <option value="">---------</option> |
|
199 <option value="1" selected="selected">Mike Royko</option> |
|
200 <option value="2">Bob Woodward</option> |
|
201 </select></li> |
|
202 <li>Article: <textarea name="article">Hello.</textarea></li> |
|
203 <li>Categories: <select multiple="multiple" name="categories"> |
|
204 <option value="1">Entertainment</option> |
|
205 <option value="2">It's a test</option> |
|
206 <option value="3">Third test</option> |
|
207 </select> Hold down "Control", or "Command" on a Mac, to select more than one.</li> |
|
208 >>> f = TestArticleForm({'headline': u'New headline', 'pub_date': u'1988-01-04', 'writer': u'1', 'article': 'Hello.'}) |
|
209 >>> f.is_valid() |
|
210 True |
|
211 >>> new_art = f.save() |
|
212 >>> new_art.id |
|
213 1 |
|
214 >>> new_art = Article.objects.get(id=1) |
|
215 >>> new_art.headline |
|
216 'New headline' |
|
217 |
|
218 Add some categories and test the many-to-many form output. |
|
219 >>> new_art.categories.all() |
|
220 [] |
|
221 >>> new_art.categories.add(Category.objects.get(name='Entertainment')) |
|
222 >>> new_art.categories.all() |
|
223 [<Category: Entertainment>] |
|
224 >>> TestArticleForm = form_for_instance(new_art) |
|
225 >>> f = TestArticleForm(auto_id=False) |
|
226 >>> print f.as_ul() |
|
227 <li>Headline: <input type="text" name="headline" value="New headline" maxlength="50" /></li> |
|
228 <li>Pub date: <input type="text" name="pub_date" value="1988-01-04" /></li> |
|
229 <li>Writer: <select name="writer"> |
|
230 <option value="">---------</option> |
|
231 <option value="1" selected="selected">Mike Royko</option> |
|
232 <option value="2">Bob Woodward</option> |
|
233 </select></li> |
|
234 <li>Article: <textarea name="article">Hello.</textarea></li> |
|
235 <li>Categories: <select multiple="multiple" name="categories"> |
|
236 <option value="1" selected="selected">Entertainment</option> |
|
237 <option value="2">It's a test</option> |
|
238 <option value="3">Third test</option> |
|
239 </select> Hold down "Control", or "Command" on a Mac, to select more than one.</li> |
|
240 |
|
241 >>> f = TestArticleForm({'headline': u'New headline', 'pub_date': u'1988-01-04', |
|
242 ... 'writer': u'1', 'article': u'Hello.', 'categories': [u'1', u'2']}) |
|
243 >>> new_art = f.save() |
|
244 >>> new_art.id |
|
245 1 |
|
246 >>> new_art = Article.objects.get(id=1) |
|
247 >>> new_art.categories.all() |
|
248 [<Category: Entertainment>, <Category: It's a test>] |
|
249 |
|
250 Now, submit form data with no categories. This deletes the existing categories. |
|
251 >>> f = TestArticleForm({'headline': u'New headline', 'pub_date': u'1988-01-04', |
|
252 ... 'writer': u'1', 'article': u'Hello.'}) |
|
253 >>> new_art = f.save() |
|
254 >>> new_art.id |
|
255 1 |
|
256 >>> new_art = Article.objects.get(id=1) |
|
257 >>> new_art.categories.all() |
|
258 [] |
|
259 |
|
260 Create a new article, with categories, via the form. |
|
261 >>> ArticleForm = form_for_model(Article) |
|
262 >>> f = ArticleForm({'headline': u'The walrus was Paul', 'pub_date': u'1967-11-01', |
|
263 ... 'writer': u'1', 'article': u'Test.', 'categories': [u'1', u'2']}) |
|
264 >>> new_art = f.save() |
|
265 >>> new_art.id |
|
266 2 |
|
267 >>> new_art = Article.objects.get(id=2) |
|
268 >>> new_art.categories.all() |
|
269 [<Category: Entertainment>, <Category: It's a test>] |
|
270 |
|
271 Create a new article, with no categories, via the form. |
|
272 >>> ArticleForm = form_for_model(Article) |
|
273 >>> f = ArticleForm({'headline': u'The walrus was Paul', 'pub_date': u'1967-11-01', |
|
274 ... 'writer': u'1', 'article': u'Test.'}) |
|
275 >>> new_art = f.save() |
|
276 >>> new_art.id |
|
277 3 |
|
278 >>> new_art = Article.objects.get(id=3) |
|
279 >>> new_art.categories.all() |
|
280 [] |
|
281 |
|
282 Here, we define a custom Form. Because it happens to have the same fields as |
|
283 the Category model, we can use save_instance() to apply its changes to an |
|
284 existing Category instance. |
|
285 >>> class ShortCategory(Form): |
|
286 ... name = CharField(max_length=5) |
|
287 ... url = CharField(max_length=3) |
|
288 >>> cat = Category.objects.get(name='Third test') |
|
289 >>> cat |
|
290 <Category: Third test> |
|
291 >>> cat.id |
|
292 3 |
|
293 >>> sc = ShortCategory({'name': 'Third', 'url': '3rd'}) |
|
294 >>> save_instance(sc, cat) |
|
295 <Category: Third> |
|
296 >>> Category.objects.get(id=3) |
|
297 <Category: Third> |
|
298 |
|
299 Here, we demonstrate that choices for a ForeignKey ChoiceField are determined |
|
300 at runtime, based on the data in the database when the form is displayed, not |
|
301 the data in the database when the form is instantiated. |
|
302 >>> ArticleForm = form_for_model(Article) |
|
303 >>> f = ArticleForm(auto_id=False) |
|
304 >>> print f.as_ul() |
|
305 <li>Headline: <input type="text" name="headline" maxlength="50" /></li> |
|
306 <li>Pub date: <input type="text" name="pub_date" /></li> |
|
307 <li>Writer: <select name="writer"> |
|
308 <option value="" selected="selected">---------</option> |
|
309 <option value="1">Mike Royko</option> |
|
310 <option value="2">Bob Woodward</option> |
|
311 </select></li> |
|
312 <li>Article: <textarea name="article"></textarea></li> |
|
313 <li>Categories: <select multiple="multiple" name="categories"> |
|
314 <option value="1">Entertainment</option> |
|
315 <option value="2">It's a test</option> |
|
316 <option value="3">Third</option> |
|
317 </select> Hold down "Control", or "Command" on a Mac, to select more than one.</li> |
|
318 >>> Category.objects.create(name='Fourth', url='4th') |
|
319 <Category: Fourth> |
|
320 >>> Writer.objects.create(name='Carl Bernstein') |
|
321 <Writer: Carl Bernstein> |
|
322 >>> print f.as_ul() |
|
323 <li>Headline: <input type="text" name="headline" maxlength="50" /></li> |
|
324 <li>Pub date: <input type="text" name="pub_date" /></li> |
|
325 <li>Writer: <select name="writer"> |
|
326 <option value="" selected="selected">---------</option> |
|
327 <option value="1">Mike Royko</option> |
|
328 <option value="2">Bob Woodward</option> |
|
329 <option value="3">Carl Bernstein</option> |
|
330 </select></li> |
|
331 <li>Article: <textarea name="article"></textarea></li> |
|
332 <li>Categories: <select multiple="multiple" name="categories"> |
|
333 <option value="1">Entertainment</option> |
|
334 <option value="2">It's a test</option> |
|
335 <option value="3">Third</option> |
|
336 <option value="4">Fourth</option> |
|
337 </select> Hold down "Control", or "Command" on a Mac, to select more than one.</li> |
|
338 |
|
339 # ModelChoiceField ############################################################ |
|
340 |
|
341 >>> from django.newforms import ModelChoiceField, ModelMultipleChoiceField |
|
342 |
|
343 >>> f = ModelChoiceField(Category.objects.all()) |
|
344 >>> f.clean('') |
|
345 Traceback (most recent call last): |
|
346 ... |
|
347 ValidationError: [u'This field is required.'] |
|
348 >>> f.clean(None) |
|
349 Traceback (most recent call last): |
|
350 ... |
|
351 ValidationError: [u'This field is required.'] |
|
352 >>> f.clean(0) |
|
353 Traceback (most recent call last): |
|
354 ... |
|
355 ValidationError: [u'Select a valid choice. That choice is not one of the available choices.'] |
|
356 >>> f.clean(3) |
|
357 <Category: Third> |
|
358 >>> f.clean(2) |
|
359 <Category: It's a test> |
|
360 |
|
361 # Add a Category object *after* the ModelChoiceField has already been |
|
362 # instantiated. This proves clean() checks the database during clean() rather |
|
363 # than caching it at time of instantiation. |
|
364 >>> Category.objects.create(name='Fifth', url='5th') |
|
365 <Category: Fifth> |
|
366 >>> f.clean(5) |
|
367 <Category: Fifth> |
|
368 |
|
369 # Delete a Category object *after* the ModelChoiceField has already been |
|
370 # instantiated. This proves clean() checks the database during clean() rather |
|
371 # than caching it at time of instantiation. |
|
372 >>> Category.objects.get(url='5th').delete() |
|
373 >>> f.clean(5) |
|
374 Traceback (most recent call last): |
|
375 ... |
|
376 ValidationError: [u'Select a valid choice. That choice is not one of the available choices.'] |
|
377 |
|
378 >>> f = ModelChoiceField(Category.objects.filter(pk=1), required=False) |
|
379 >>> print f.clean('') |
|
380 None |
|
381 >>> f.clean('') |
|
382 >>> f.clean('1') |
|
383 <Category: Entertainment> |
|
384 >>> f.clean('100') |
|
385 Traceback (most recent call last): |
|
386 ... |
|
387 ValidationError: [u'Select a valid choice. That choice is not one of the available choices.'] |
|
388 |
|
389 # ModelMultipleChoiceField #################################################### |
|
390 |
|
391 >>> f = ModelMultipleChoiceField(Category.objects.all()) |
|
392 >>> f.clean(None) |
|
393 Traceback (most recent call last): |
|
394 ... |
|
395 ValidationError: [u'This field is required.'] |
|
396 >>> f.clean([]) |
|
397 Traceback (most recent call last): |
|
398 ... |
|
399 ValidationError: [u'This field is required.'] |
|
400 >>> f.clean([1]) |
|
401 [<Category: Entertainment>] |
|
402 >>> f.clean([2]) |
|
403 [<Category: It's a test>] |
|
404 >>> f.clean(['1']) |
|
405 [<Category: Entertainment>] |
|
406 >>> f.clean(['1', '2']) |
|
407 [<Category: Entertainment>, <Category: It's a test>] |
|
408 >>> f.clean([1, '2']) |
|
409 [<Category: Entertainment>, <Category: It's a test>] |
|
410 >>> f.clean((1, '2')) |
|
411 [<Category: Entertainment>, <Category: It's a test>] |
|
412 >>> f.clean(['100']) |
|
413 Traceback (most recent call last): |
|
414 ... |
|
415 ValidationError: [u'Select a valid choice. 100 is not one of the available choices.'] |
|
416 >>> f.clean('hello') |
|
417 Traceback (most recent call last): |
|
418 ... |
|
419 ValidationError: [u'Enter a list of values.'] |
|
420 |
|
421 # Add a Category object *after* the ModelChoiceField has already been |
|
422 # instantiated. This proves clean() checks the database during clean() rather |
|
423 # than caching it at time of instantiation. |
|
424 >>> Category.objects.create(id=6, name='Sixth', url='6th') |
|
425 <Category: Sixth> |
|
426 >>> f.clean([6]) |
|
427 [<Category: Sixth>] |
|
428 |
|
429 # Delete a Category object *after* the ModelChoiceField has already been |
|
430 # instantiated. This proves clean() checks the database during clean() rather |
|
431 # than caching it at time of instantiation. |
|
432 >>> Category.objects.get(url='6th').delete() |
|
433 >>> f.clean([6]) |
|
434 Traceback (most recent call last): |
|
435 ... |
|
436 ValidationError: [u'Select a valid choice. 6 is not one of the available choices.'] |
|
437 |
|
438 >>> f = ModelMultipleChoiceField(Category.objects.all(), required=False) |
|
439 >>> f.clean([]) |
|
440 [] |
|
441 >>> f.clean(()) |
|
442 [] |
|
443 >>> f.clean(['10']) |
|
444 Traceback (most recent call last): |
|
445 ... |
|
446 ValidationError: [u'Select a valid choice. 10 is not one of the available choices.'] |
|
447 >>> f.clean(['3', '10']) |
|
448 Traceback (most recent call last): |
|
449 ... |
|
450 ValidationError: [u'Select a valid choice. 10 is not one of the available choices.'] |
|
451 >>> f.clean(['1', '10']) |
|
452 Traceback (most recent call last): |
|
453 ... |
|
454 ValidationError: [u'Select a valid choice. 10 is not one of the available choices.'] |
|
455 |
|
456 # PhoneNumberField ############################################################ |
|
457 |
|
458 >>> PhoneNumberForm = form_for_model(PhoneNumber) |
|
459 >>> f = PhoneNumberForm({'phone': '(312) 555-1212', 'description': 'Assistance'}) |
|
460 >>> f.is_valid() |
|
461 True |
|
462 >>> f.clean_data |
|
463 {'phone': u'312-555-1212', 'description': u'Assistance'} |
|
464 """} |