Django

Code

root/django/trunk/tests/modeltests/model_forms/models.py

Revision 8854, 40.4 kB (checked in by jacob, 3 days ago)

Fixed #8795: unique_together validation no longer fails on model forms that exclude fields included in the check. Thanks, Alex Gaynor.

  • Property svn:eol-style set to native
Line 
1 """
2 XX. Generating HTML forms from models
3
4 This is mostly just a reworking of the ``form_for_model``/``form_for_instance``
5 tests to use ``ModelForm``. As such, the text may not make sense in all cases,
6 and the examples are probably a poor fit for the ``ModelForm`` syntax. In other
7 words, most of these tests should be rewritten.
8 """
9
10 import os
11 import tempfile
12
13 from django.db import models
14 from django.core.files.storage import FileSystemStorage
15
16 # Python 2.3 doesn't have sorted()
17 try:
18     sorted
19 except NameError:
20     from django.utils.itercompat import sorted
21
22 temp_storage = FileSystemStorage(tempfile.gettempdir())
23
24 ARTICLE_STATUS = (
25     (1, 'Draft'),
26     (2, 'Pending'),
27     (3, 'Live'),
28 )
29
30 ARTICLE_STATUS_CHAR = (
31     ('d', 'Draft'),
32     ('p', 'Pending'),
33     ('l', 'Live'),
34 )
35
36 class Category(models.Model):
37     name = models.CharField(max_length=20)
38     slug = models.SlugField(max_length=20)
39     url = models.CharField('The URL', max_length=40)
40
41     def __unicode__(self):
42         return self.name
43
44 class Writer(models.Model):
45     name = models.CharField(max_length=50, help_text='Use both first and last names.')
46
47     def __unicode__(self):
48         return self.name
49
50 class Article(models.Model):
51     headline = models.CharField(max_length=50)
52     slug = models.SlugField()
53     pub_date = models.DateField()
54     created = models.DateField(editable=False)
55     writer = models.ForeignKey(Writer)
56     article = models.TextField()
57     categories = models.ManyToManyField(Category, blank=True)
58     status = models.PositiveIntegerField(choices=ARTICLE_STATUS, blank=True, null=True)
59
60     def save(self):
61         import datetime
62         if not self.id:
63             self.created = datetime.date.today()
64         return super(Article, self).save()
65
66     def __unicode__(self):
67         return self.headline
68
69 class ImprovedArticle(models.Model):
70     article = models.OneToOneField(Article)
71
72 class ImprovedArticleWithParentLink(models.Model):
73     article = models.OneToOneField(Article, parent_link=True)
74
75 class BetterWriter(Writer):
76     pass
77
78 class WriterProfile(models.Model):
79     writer = models.OneToOneField(Writer, primary_key=True)
80     age = models.PositiveIntegerField()
81
82     def __unicode__(self):
83         return "%s is %s" % (self.writer, self.age)
84
85 from django.contrib.localflavor.us.models import PhoneNumberField
86 class PhoneNumber(models.Model):
87     phone = PhoneNumberField()
88     description = models.CharField(max_length=20)
89
90     def __unicode__(self):
91         return self.phone
92
93 class TextFile(models.Model):
94     description = models.CharField(max_length=20)
95     file = models.FileField(storage=temp_storage, upload_to='tests')
96
97     def __unicode__(self):
98         return self.description
99
100 class ImageFile(models.Model):
101     description = models.CharField(max_length=20)
102     try:
103         # If PIL is available, try testing PIL.
104         # Checking for the existence of Image is enough for CPython, but
105         # for PyPy, you need to check for the underlying modules
106         # If PIL is not available, this test is equivalent to TextFile above.
107         from PIL import Image, _imaging
108         image = models.ImageField(storage=temp_storage, upload_to='tests')
109     except ImportError:
110         image = models.FileField(storage=temp_storage, upload_to='tests')
111
112     def __unicode__(self):
113         return self.description
114
115 class CommaSeparatedInteger(models.Model):
116     field = models.CommaSeparatedIntegerField(max_length=20)
117
118     def __unicode__(self):
119         return self.field
120
121 class Product(models.Model):
122     slug = models.SlugField(unique=True)
123
124     def __unicode__(self):
125         return self.slug
126
127 class Price(models.Model):
128     price = models.DecimalField(max_digits=10, decimal_places=2)
129     quantity = models.PositiveIntegerField()
130
131     def __unicode__(self):
132         return u"%s for %s" % (self.quantity, self.price)
133
134     class Meta:
135         unique_together = (('price', 'quantity'),)
136
137 class ArticleStatus(models.Model):
138     status = models.CharField(max_length=2, choices=ARTICLE_STATUS_CHAR, blank=True, null=True)
139
140 class Inventory(models.Model):
141    barcode = models.PositiveIntegerField(unique=True)
142    parent = models.ForeignKey('self', to_field='barcode', blank=True, null=True)
143    name = models.CharField(blank=False, max_length=20)
144
145    def __unicode__(self):
146       return self.name
147      
148 __test__ = {'API_TESTS': """
149 >>> from django import forms
150 >>> from django.forms.models import ModelForm, model_to_dict
151 >>> from django.core.files.uploadedfile import SimpleUploadedFile
152
153 The bare bones, absolutely nothing custom, basic case.
154
155 >>> class CategoryForm(ModelForm):
156 ...     class Meta:
157 ...         model = Category
158 >>> CategoryForm.base_fields.keys()
159 ['name', 'slug', 'url']
160
161
162 Extra fields.
163
164 >>> class CategoryForm(ModelForm):
165 ...     some_extra_field = forms.BooleanField()
166 ...
167 ...     class Meta:
168 ...         model = Category
169
170 >>> CategoryForm.base_fields.keys()
171 ['name', 'slug', 'url', 'some_extra_field']
172
173
174 Replacing a field.
175
176 >>> class CategoryForm(ModelForm):
177 ...     url = forms.BooleanField()
178 ...
179 ...     class Meta:
180 ...         model = Category
181
182 >>> CategoryForm.base_fields['url'].__class__
183 <class 'django.forms.fields.BooleanField'>
184
185
186 Using 'fields'.
187
188 >>> class CategoryForm(ModelForm):
189 ...
190 ...     class Meta:
191 ...         model = Category
192 ...         fields = ['url']
193
194 >>> CategoryForm.base_fields.keys()
195 ['url']
196
197
198 Using 'exclude'
199
200 >>> class CategoryForm(ModelForm):
201 ...
202 ...     class Meta:
203 ...         model = Category
204 ...         exclude = ['url']
205
206 >>> CategoryForm.base_fields.keys()
207 ['name', 'slug']
208
209
210 Using 'fields' *and* 'exclude'. Not sure why you'd want to do this, but uh,
211 "be liberal in what you accept" and all.
212
213 >>> class CategoryForm(ModelForm):
214 ...
215 ...     class Meta:
216 ...         model = Category
217 ...         fields = ['name', 'url']
218 ...         exclude = ['url']
219
220 >>> CategoryForm.base_fields.keys()
221 ['name']
222
223 Don't allow more than one 'model' definition in the inheritance hierarchy.
224 Technically, it would generate a valid form, but the fact that the resulting
225 save method won't deal with multiple objects is likely to trip up people not
226 familiar with the mechanics.
227
228 >>> class CategoryForm(ModelForm):
229 ...     class Meta:
230 ...         model = Category
231
232 >>> class OddForm(CategoryForm):
233 ...     class Meta:
234 ...         model = Article
235
236 OddForm is now an Article-related thing, because BadForm.Meta overrides
237 CategoryForm.Meta.
238 >>> OddForm.base_fields.keys()
239 ['headline', 'slug', 'pub_date', 'writer', 'article', 'status', 'categories']
240
241 >>> class ArticleForm(ModelForm):
242 ...     class Meta:
243 ...         model = Article
244
245 First class with a Meta class wins.
246
247 >>> class BadForm(ArticleForm, CategoryForm):
248 ...     pass
249 >>> OddForm.base_fields.keys()
250 ['headline', 'slug', 'pub_date', 'writer', 'article', 'status', 'categories']
251
252 Subclassing without specifying a Meta on the class will use the parent's Meta
253 (or the first parent in the MRO if there are multiple parent classes).
254
255 >>> class CategoryForm(ModelForm):
256 ...     class Meta:
257 ...         model = Category
258 >>> class SubCategoryForm(CategoryForm):
259 ...     pass
260 >>> SubCategoryForm.base_fields.keys()
261 ['name', 'slug', 'url']
262
263 We can also subclass the Meta inner class to change the fields list.
264
265 >>> class CategoryForm(ModelForm):
266 ...     checkbox = forms.BooleanField()
267 ...
268 ...     class Meta:
269 ...         model = Category
270 >>> class SubCategoryForm(CategoryForm):
271 ...     class Meta(CategoryForm.Meta):
272 ...         exclude = ['url']
273
274 >>> print SubCategoryForm()
275 <tr><th><label for="id_name">Name:</label></th><td><input id="id_name" type="text" name="name" maxlength="20" /></td></tr>
276 <tr><th><label for="id_slug">Slug:</label></th><td><input id="id_slug" type="text" name="slug" maxlength="20" /></td></tr>
277 <tr><th><label for="id_checkbox">Checkbox:</label></th><td><input type="checkbox" name="checkbox" id="id_checkbox" /></td></tr>
278
279 # Old form_for_x tests #######################################################
280
281 >>> from django.forms import ModelForm, CharField
282 >>> import datetime
283
284 >>> Category.objects.all()
285 []
286
287 >>> class CategoryForm(ModelForm):
288 ...     class Meta:
289 ...         model = Category
290 >>> f = CategoryForm()
291 >>> print f
292 <tr><th><label for="id_name">Name:</label></th><td><input id="id_name" type="text" name="name" maxlength="20" /></td></tr>
293 <tr><th><label for="id_slug">Slug:</label></th><td><input id="id_slug" type="text" name="slug" maxlength="20" /></td></tr>
294 <tr><th><label for="id_url">The URL:</label></th><td><input id="id_url" type="text" name="url" maxlength="40" /></td></tr>
295 >>> print f.as_ul()
296 <li><label for="id_name">Name:</label> <input id="id_name" type="text" name="name" maxlength="20" /></li>
297 <li><label for="id_slug">Slug:</label> <input id="id_slug" type="text" name="slug" maxlength="20" /></li>
298 <li><label for="id_url">The URL:</label> <input id="id_url" type="text" name="url" maxlength="40" /></li>
299 >>> print f['name']
300 <input id="id_name" type="text" name="name" maxlength="20" />
301
302 >>> f = CategoryForm(auto_id=False)
303 >>> print f.as_ul()
304 <li>Name: <input type="text" name="name" maxlength="20" /></li>
305 <li>Slug: <input type="text" name="slug" maxlength="20" /></li>
306 <li>The URL: <input type="text" name="url" maxlength="40" /></li>
307
308 >>> f = CategoryForm({'name': 'Entertainment', 'slug': 'entertainment', 'url': 'entertainment'})
309 >>> f.is_valid()
310 True
311 >>> f.cleaned_data['url']
312 u'entertainment'
313 >>> f.cleaned_data['name']
314 u'Entertainment'
315 >>> f.cleaned_data['slug']
316 u'entertainment'
317 >>> obj = f.save()
318 >>> obj
319 <Category: Entertainment>
320 >>> Category.objects.all()
321 [<Category: Entertainment>]
322
323 >>> f = CategoryForm({'name': "It's a test", 'slug': 'its-test', 'url': 'test'})
324 >>> f.is_valid()
325 True
326 >>> f.cleaned_data['url']
327 u'test'
328 >>> f.cleaned_data['name']
329 u"It's a test"
330 >>> f.cleaned_data['slug']
331 u'its-test'
332 >>> obj = f.save()
333 >>> obj
334 <Category: It's a test>
335 >>> Category.objects.order_by('name')
336 [<Category: Entertainment>, <Category: It's a test>]
337
338 If you call save() with commit=False, then it will return an object that
339 hasn't yet been saved to the database. In this case, it's up to you to call
340 save() on the resulting model instance.
341 >>> f = CategoryForm({'name': 'Third test', 'slug': 'third-test', 'url': 'third'})
342 >>> f.is_valid()
343 True
344 >>> f.cleaned_data['url']
345 u'third'
346 >>> f.cleaned_data['name']
347 u'Third test'
348 >>> f.cleaned_data['slug']
349 u'third-test'
350 >>> obj = f.save(commit=False)
351 >>> obj
352 <Category: Third test>
353 >>> Category.objects.order_by('name')
354 [<Category: Entertainment>, <Category: It's a test>]
355 >>> obj.save()
356 >>> Category.objects.order_by('name')
357 [<Category: Entertainment>, <Category: It's a test>, <Category: Third test>]
358
359 If you call save() with invalid data, you'll get a ValueError.
360 >>> f = CategoryForm({'name': '', 'slug': 'not a slug!', 'url': 'foo'})
361 >>> f.errors['name']
362 [u'This field is required.']
363 >>> f.errors['slug']
364 [u"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."]
365 >>> f.cleaned_data
366 Traceback (most recent call last):
367 ...
368 AttributeError: 'CategoryForm' object has no attribute 'cleaned_data'
369 >>> f.save()
370 Traceback (most recent call last):
371 ...
372 ValueError: The Category could not be created because the data didn't validate.
373 >>> f = CategoryForm({'name': '', 'slug': '', 'url': 'foo'})
374 >>> f.save()
375 Traceback (most recent call last):
376 ...
377 ValueError: The Category could not be created because the data didn't validate.
378
379 Create a couple of Writers.
380 >>> w = Writer(name='Mike Royko')
381 >>> w.save()
382 >>> w = Writer(name='Bob Woodward')
383 >>> w.save()
384
385 ManyToManyFields are represented by a MultipleChoiceField, ForeignKeys and any
386 fields with the 'choices' attribute are represented by a ChoiceField.
387 >>> class ArticleForm(ModelForm):
388 ...     class Meta:
389 ...         model = Article
390 >>> f = ArticleForm(auto_id=False)
391 >>> print f
392 <tr><th>Headline:</th><td><input type="text" name="headline" maxlength="50" /></td></tr>
393 <tr><th>Slug:</th><td><input type="text" name="slug" maxlength="50" /></td></tr>
394 <tr><th>Pub date:</th><td><input type="text" name="pub_date" /></td></tr>
395 <tr><th>Writer:</th><td><select name="writer">
396 <option value="" selected="selected">---------</option>
397 <option value="1">Mike Royko</option>
398 <option value="2">Bob Woodward</option>
399 </select></td></tr>
400 <tr><th>Article:</th><td><textarea rows="10" cols="40" name="article"></textarea></td></tr>
401 <tr><th>Status:</th><td><select name="status">
402 <option value="" selected="selected">---------</option>
403 <option value="1">Draft</option>
404 <option value="2">Pending</option>
405 <option value="3">Live</option>
406 </select></td></tr>
407 <tr><th>Categories:</th><td><select multiple="multiple" name="categories">
408 <option value="1">Entertainment</option>
409 <option value="2">It&#39;s a test</option>
410 <option value="3">Third test</option>
411 </select><br /> Hold down "Control", or "Command" on a Mac, to select more than one.</td></tr>
412
413 You can restrict a form to a subset of the complete list of fields
414 by providing a 'fields' argument. If you try to save a
415 model created with such a form, you need to ensure that the fields
416 that are _not_ on the form have default values, or are allowed to have
417 a value of None. If a field isn't specified on a form, the object created
418 from the form can't provide a value for that field!
419 >>> class PartialArticleForm(ModelForm):
420 ...     class Meta:
421 ...         model = Article
422 ...         fields = ('headline','pub_date')
423 >>> f = PartialArticleForm(auto_id=False)
424 >>> print f
425 <tr><th>Headline:</th><td><input type="text" name="headline" maxlength="50" /></td></tr>
426 <tr><th>Pub date:</th><td><input type="text" name="pub_date" /></td></tr>
427
428 When the ModelForm is passed an instance, that instance's current values are
429 inserted as 'initial' data in each Field.
430 >>> w = Writer.objects.get(name='Mike Royko')
431 >>> class RoykoForm(ModelForm):
432 ...     class Meta:
433 ...         model = Writer
434 >>> f = RoykoForm(auto_id=False, instance=w)
435 >>> print f
436 <tr><th>Name:</th><td><input type="text" name="name" value="Mike Royko" maxlength="50" /><br />Use both first and last names.</td></tr>
437
438 >>> art = Article(headline='Test article', slug='test-article', pub_date=datetime.date(1988, 1, 4), writer=w, article='Hello.')
439 >>> art.save()
440 >>> art.id
441 1
442 >>> class TestArticleForm(ModelForm):
443 ...     class Meta:
444 ...         model = Article
445 >>> f = TestArticleForm(auto_id=False, instance=art)
446 >>> print f.as_ul()
447 <li>Headline: <input type="text" name="headline" value="Test article" maxlength="50" /></li>
448 <li>Slug: <input type="text" name="slug" value="test-article" maxlength="50" /></li>
449 <li>Pub date: <input type="text" name="pub_date" value="1988-01-04" /></li>
450 <li>Writer: <select name="writer">
451 <option value="">---------</option>
452 <option value="1" selected="selected">Mike Royko</option>
453 <option value="2">Bob Woodward</option>
454 </select></li>
455 <li>Article: <textarea rows="10" cols="40" name="article">Hello.</textarea></li>
456 <li>Status: <select name="status">
457 <option value="" selected="selected">---------</option>
458 <option value="1">Draft</option>
459 <option value="2">Pending</option>
460 <option value="3">Live</option>
461 </select></li>
462 <li>Categories: <select multiple="multiple" name="categories">
463 <option value="1">Entertainment</option>
464 <option value="2">It&#39;s a test</option>
465 <option value="3">Third test</option>
466 </select>  Hold down "Control", or "Command" on a Mac, to select more than one.</li>
467 >>> f = TestArticleForm({'headline': u'Test headline', 'slug': 'test-headline', 'pub_date': u'1984-02-06', 'writer': u'1', 'article': 'Hello.'}, instance=art)
468 >>> f.is_valid()
469 True
470 >>> test_art = f.save()
471 >>> test_art.id
472 1
473 >>> test_art = Article.objects.get(id=1)
474 >>> test_art.headline
475 u'Test headline'
476
477 You can create a form over a subset of the available fields
478 by specifying a 'fields' argument to form_for_instance.
479 >>> class PartialArticleForm(ModelForm):
480 ...     class Meta:
481 ...         model = Article
482 ...         fields=('headline', 'slug', 'pub_date')
483 >>> f = PartialArticleForm({'headline': u'New headline', 'slug': 'new-headline', 'pub_date': u'1988-01-04'}, auto_id=False, instance=art)
484 >>> print f.as_ul()
485 <li>Headline: <input type="text" name="headline" value="New headline" maxlength="50" /></li>
486 <li>Slug: <input type="text" name="slug" value="new-headline" maxlength="50" /></li>
487 <li>Pub date: <input type="text" name="pub_date" value="1988-01-04" /></li>
488 >>> f.is_valid()
489 True
490 >>> new_art = f.save()
491 >>> new_art.id
492 1
493 >>> new_art = Article.objects.get(id=1)
494 >>> new_art.headline
495 u'New headline'
496
497 Add some categories and test the many-to-many form output.
498 >>> new_art.categories.all()
499 []
500 >>> new_art.categories.add(Category.objects.get(name='Entertainment'))
501 >>> new_art.categories.all()
502 [<Category: Entertainment>]
503 >>> class TestArticleForm(ModelForm):
504 ...     class Meta:
505 ...         model = Article
506 >>> f = TestArticleForm(auto_id=False, instance=new_art)
507 >>> print f.as_ul()
508 <li>Headline: <input type="text" name="headline" value="New headline" maxlength="50" /></li>
509 <li>Slug: <input type="text" name="slug" value="new-headline" maxlength="50" /></li>
510 <li>Pub date: <input type="text" name="pub_date" value="1988-01-04" /></li>
511 <li>Writer: <select name="writer">
512 <option value="">---------</option>
513 <option value="1" selected="selected">Mike Royko</option>
514 <option value="2">Bob Woodward</option>
515 </select></li>
516 <li>Article: <textarea rows="10" cols="40" name="article">Hello.</textarea></li>
517 <li>Status: <select name="status">
518 <option value="" selected="selected">---------</option>
519 <option value="1">Draft</option>
520 <option value="2">Pending</option>
521 <option value="3">Live</option>
522 </select></li>
523 <li>Categories: <select multiple="multiple" name="categories">
524 <option value="1" selected="selected">Entertainment</option>
525 <option value="2">It&#39;s a test</option>
526 <option value="3">Third test</option>
527 </select>  Hold down "Control", or "Command" on a Mac, to select more than one.</li>
528
529 >>> f = TestArticleForm({'headline': u'New headline', 'slug': u'new-headline', 'pub_date': u'1988-01-04',
530 ...     'writer': u'1', 'article': u'Hello.', 'categories': [u'1', u'2']}, instance=new_art)
531 >>> new_art = f.save()
532 >>> new_art.id
533 1
534 >>> new_art = Article.objects.get(id=1)
535 >>> new_art.categories.order_by('name')
536 [<Category: Entertainment>, <Category: It's a test>]
537
538 Now, submit form data with no categories. This deletes the existing categories.
539 >>> f = TestArticleForm({'headline': u'New headline', 'slug': u'new-headline', 'pub_date': u'1988-01-04',
540 ...     'writer': u'1', 'article': u'Hello.'}, instance=new_art)
541 >>> new_art = f.save()
542 >>> new_art.id
543 1
544 >>> new_art = Article.objects.get(id=1)
545 >>> new_art.categories.all()
546 []
547
548 Create a new article, with categories, via the form.
549 >>> class ArticleForm(ModelForm):
550 ...     class Meta:
551 ...         model = Article
552 >>> f = ArticleForm({'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01',
553 ...     'writer': u'1', 'article': u'Test.', 'categories': [u'1', u'2']})
554 >>> new_art = f.save()
555 >>> new_art.id
556 2
557 >>> new_art = Article.objects.get(id=2)
558 >>> new_art.categories.order_by('name')
559 [<Category: Entertainment>, <Category: It's a test>]
560
561 Create a new article, with no categories, via the form.
562 >>> class ArticleForm(ModelForm):
563 ...     class Meta:
564 ...         model = Article
565 >>> f = ArticleForm({'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01',
566 ...     'writer': u'1', 'article': u'Test.'})
567 >>> new_art = f.save()
568 >>> new_art.id
569 3
570 >>> new_art = Article.objects.get(id=3)
571 >>> new_art.categories.all()
572 []
573
574 Create a new article, with categories, via the form, but use commit=False.
575 The m2m data won't be saved until save_m2m() is invoked on the form.
576 >>> class ArticleForm(ModelForm):
577 ...     class Meta:
578 ...         model = Article
579 >>> f = ArticleForm({'headline': u'The walrus was Paul', 'slug': 'walrus-was-paul', 'pub_date': u'1967-11-01',
580 ...     'writer': u'1', 'article': u'Test.', 'categories': [u'1', u'2']})
581 >>> new_art = f.save(commit=False)
582
583 # Manually save the instance
584 >>> new_art.save()
585 >>> new_art.id
586 4
587
588 # The instance doesn't have m2m data yet
589 >>> new_art = Article.objects.get(id=4)
590 >>> new_art.categories.all()
591 []
592
593 # Save the m2m data on the form
594 >>> f.save_m2m()
595 >>> new_art.categories.order_by('name')
596 [<Category: Entertainment>, <Category: It's a test>]
597
598 Here, we define a custom ModelForm. Because it happens to have the same fields as
599 the Category model, we can just call the form's save() to apply its changes to an
600 existing Category instance.
601 >>> class ShortCategory(ModelForm):
602 ...     name = CharField(max_length=5)
603 ...     slug = CharField(max_length=5)
604 ...     url = CharField(max_length=3)
605 >>> cat = Category.objects.get(name='Third test')
606 >>> cat
607 <Category: Third test>
608 >>> cat.id
609 3
610 >>> form = ShortCategory({'name': 'Third', 'slug': 'third', 'url': '3rd'}, instance=cat)
611 >>> form.save()
612 <Category: Third>
613 >>> Category.objects.get(id=3)
614 <Category: Third>
615
616 Here, we demonstrate that choices for a ForeignKey ChoiceField are determined
617 at runtime, based on the data in the database when the form is displayed, not
618 the data in the database when the form is instantiated.
619 >>> class ArticleForm(ModelForm):
620 ...     class Meta:
621 ...         model = Article
622 >>> f = ArticleForm(auto_id=False)
623 >>> print f.as_ul()
624 <li>Headline: <input type="text" name="headline" maxlength="50" /></li>
625 <li>Slug: <input type="text" name="slug" maxlength="50" /></li>
626 <li>Pub date: <input type="text" name="pub_date" /></li>
627 <li>Writer: <select name="writer">
628 <option value="" selected="selected">---------</option>
629 <option value="1">Mike Royko</option>
630 <option value="2">Bob Woodward</option>
631 </select></li>
632 <li>Article: <textarea rows="10" cols="40" name="article"></textarea></li>
633 <li>Status: <select name="status">
634 <option value="" selected="selected">---------</option>
635 <option value="1">Draft</option>
636 <option value="2">Pending</option>
637 <option value="3">Live</option>
638 </select></li>
639 <li>Categories: <select multiple="multiple" name="categories">
640 <option value="1">Entertainment</option>
641 <option value="2">It&#39;s a test</option>
642 <option value="3">Third</option>
643 </select>  Hold down "Control", or "Command" on a Mac, to select more than one.</li>
644 >>> Category.objects.create(name='Fourth', url='4th')
645 <Category: Fourth>
646 >>> Writer.objects.create(name='Carl Bernstein')
647 <Writer: Carl Bernstein>
648 >>> print f.as_ul()
649 <li>Headline: <input type="text" name="headline" maxlength="50" /></li>
650 <li>Slug: <input type="text" name="slug" maxlength="50" /></li>
651 <li>Pub date: <input type="text" name="pub_date" /></li>
652 <li>Writer: <select name="writer">
653 <option value="" selected="selected">---------</option>
654 <option value="1">Mike Royko</option>
655 <option value="2">Bob Woodward</option>
656 <option value="3">Carl Bernstein</option>
657 </select></li>
658 <li>Article: <textarea rows="10" cols="40" name="article"></textarea></li>
659 <li>Status: <select name="status">
660 <option value="" selected="selected">---------</option>
661 <option value="1">Draft</option>
662 <option value="2">Pending</option>
663 <option value="3">Live</option>
664 </select></li>
665 <li>Categories: <select multiple="multiple" name="categories">
666 <option value="1">Entertainment</option>
667 <option value="2">It&#39;s a test</option>
668 <option value="3">Third</option>
669 <option value="4">Fourth</option>
670 </select>  Hold down "Control", or "Command" on a Mac, to select more than one.</li>
671
672 # ModelChoiceField ############################################################
673
674 >>> from django.forms import ModelChoiceField, ModelMultipleChoiceField
675
676 >>> f = ModelChoiceField(Category.objects.all())
677 >>> list(f.choices)
678 [(u'', u'---------'), (1, u'Entertainment'), (2, u"It's a test"), (3, u'Third'), (4, u'Fourth')]
679 >>> f.clean('')
680 Traceback (most recent call last):
681 ...
682 ValidationError: [u'This field is required.']
683 >>> f.clean(None)
684 Traceback (most recent call last):
685 ...
686 ValidationError: [u'This field is required.']
687 >>> f.clean(0)
688 Traceback (most recent call last):
689 ...
690 ValidationError: [u'Select a valid choice. That choice is not one of the available choices.']
691 >>> f.clean(3)
692 <Category: Third>
693 >>> f.clean(2)
694 <Category: It's a test>
695
696 # Add a Category object *after* the ModelChoiceField has already been
697 # instantiated. This proves clean() checks the database during clean() rather
698 # than caching it at time of instantiation.
699 >>> Category.objects.create(name='Fifth', url='5th')
700 <Category: Fifth>
701 >>> f.clean(5)
702 <Category: Fifth>
703
704 # Delete a Category object *after* the ModelChoiceField has already been
705 # instantiated. This proves clean() checks the database during clean() rather
706 # than caching it at time of instantiation.
707 >>> Category.objects.get(url='5th').delete()
708 >>> f.clean(5)
709 Traceback (most recent call last):
710 ...
711 ValidationError: [u'Select a valid choice. That choice is not one of the available choices.']
712
713 >>> f = ModelChoiceField(Category.objects.filter(pk=1), required=False)
714 >>> print f.clean('')
715 None
716 >>> f.clean('')
717 >>> f.clean('1')
718 <Category: Entertainment>
719 >>> f.clean('100')
720 Traceback (most recent call last):
721 ...
722 ValidationError: [u'Select a valid choice. That choice is not one of the available choices.']
723
724 # queryset can be changed after the field is created.
725 >>> f.queryset = Category.objects.exclude(name='Fourth')
726 >>> list(f.choices)
727 [(u'', u'---------'), (1, u'Entertainment'), (2, u"It's a test"), (3, u'Third')]
728 >>> f.clean(3)
729 <Category: Third>
730 >>> f.clean(4)
731 Traceback (most recent call last):
732 ...
733 ValidationError: [u'Select a valid choice. That choice is not one of the available choices.']
734
735 # check that we can safely iterate choices repeatedly
736 >>> gen_one = list(f.choices)
737 >>> gen_two = f.choices
738 >>> gen_one[2]
739 (2L, u"It's a test")
740 >>> list(gen_two)
741 [(u'', u'---------'), (1L, u'Entertainment'), (2L, u"It's a test"), (3L, u'Third')]
742
743 # check that we can override the label_from_instance method to print custom labels (#4620)
744 >>> f.queryset = Category.objects.all()
745 >>> f.label_from_instance = lambda obj: "category " + str(obj)
746 >>> list(f.choices)
747 [(u'', u'---------'), (1L, 'category Entertainment'), (2L, "category It's a test"), (3L, 'category Third'), (4L, 'category Fourth')]
748
749 # ModelMultipleChoiceField ####################################################
750
751 >>> f = ModelMultipleChoiceField(Category.objects.all())
752 >>> list(f.choices)
753 [(1, u'Entertainment'), (2, u"It's a test"), (3, u'Third'), (4, u'Fourth')]
754 >>> f.clean(None)
755 Traceback (most recent call last):
756 ...
757 ValidationError: [u'This field is required.']
758 >>> f.clean([])
759 Traceback (most recent call last):
760 ...
761 ValidationError: [u'This field is required.']
762 >>> f.clean([1])
763 [<Category: Entertainment>]
764 >>> f.clean([2])
765 [<Category: It's a test>]
766 >>> f.clean(['1'])
767 [<Category: Entertainment>]
768 >>> f.clean(['1', '2'])
769 [<Category: Entertainment>, <Category: It's a test>]
770 >>> f.clean([1, '2'])
771 [<Category: Entertainment>, <Category: It's a test>]
772 >>> f.clean((1, '2'))
773 [<Category: Entertainment>, <Category: It's a test>]
774 >>> f.clean(['100'])
775 Traceback (most recent call last):
776 ...
777 ValidationError: [u'Select a valid choice. 100 is not one of the available choices.']
778 >>> f.clean('hello')
779 Traceback (most recent call last):
780 ...
781 ValidationError: [u'Enter a list of values.']
782
783 # Add a Category object *after* the ModelMultipleChoiceField has already been
784 # instantiated. This proves clean() checks the database during clean() rather
785 # than caching it at time of instantiation.
786 >>> Category.objects.create(id=6, name='Sixth', url='6th')
787 <Category: Sixth>
788 >>> f.clean([6])
789 [<Category: Sixth>]
790
791 # Delete a Category object *after* the ModelMultipleChoiceField has already been
792 # instantiated. This proves clean() checks the database during clean() rather
793 # than caching it at time of instantiation.
794 >>> Category.objects.get(url='6th').delete()
795 >>> f.clean([6])
796 Traceback (most recent call last):
797 ...
798 ValidationError: [u'Select a valid choice. 6 is not one of the available choices.']
799
800 >>> f = ModelMultipleChoiceField(Category.objects.all(), required=False)
801 >>> f.clean([])
802 []
803 >>> f.clean(())
804 []
805 >>> f.clean(['10'])
806 Traceback (most recent call last):
807 ...
808 ValidationError: [u'Select a valid choice. 10 is not one of the available choices.']
809 >>> f.clean(['3', '10'])
810 Traceback (most recent call last):
811 ...
812 ValidationError: [u'Select a valid choice. 10 is not one of the available choices.']
813 >>> f.clean(['1', '10'])
814 Traceback (most recent call last):
815 ...
816 ValidationError: [u'Select a valid choice. 10 is not one of the available choices.']
817
818 # queryset can be changed after the field is created.
819 >>> f.queryset = Category.objects.exclude(name='Fourth')
820 >>> list(f.choices)
821 [(1, u'Entertainment'), (2, u"It's a test"), (3, u'Third')]
822 >>> f.clean([3])
823 [<Category: Third>]
824 >>> f.clean([4])
825 Traceback (most recent ca