|
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 temp_storage_dir = tempfile.mkdtemp() |
|
17 temp_storage = FileSystemStorage(temp_storage_dir) |
|
18 |
|
19 ARTICLE_STATUS = ( |
|
20 (1, 'Draft'), |
|
21 (2, 'Pending'), |
|
22 (3, 'Live'), |
|
23 ) |
|
24 |
|
25 ARTICLE_STATUS_CHAR = ( |
|
26 ('d', 'Draft'), |
|
27 ('p', 'Pending'), |
|
28 ('l', 'Live'), |
|
29 ) |
|
30 |
|
31 class Category(models.Model): |
|
32 name = models.CharField(max_length=20) |
|
33 slug = models.SlugField(max_length=20) |
|
34 url = models.CharField('The URL', max_length=40) |
|
35 |
|
36 def __unicode__(self): |
|
37 return self.name |
|
38 |
|
39 class Writer(models.Model): |
|
40 name = models.CharField(max_length=50, help_text='Use both first and last names.') |
|
41 |
|
42 def __unicode__(self): |
|
43 return self.name |
|
44 |
|
45 class Article(models.Model): |
|
46 headline = models.CharField(max_length=50) |
|
47 slug = models.SlugField() |
|
48 pub_date = models.DateField() |
|
49 created = models.DateField(editable=False) |
|
50 writer = models.ForeignKey(Writer) |
|
51 article = models.TextField() |
|
52 categories = models.ManyToManyField(Category, blank=True) |
|
53 status = models.PositiveIntegerField(choices=ARTICLE_STATUS, blank=True, null=True) |
|
54 |
|
55 def save(self): |
|
56 import datetime |
|
57 if not self.id: |
|
58 self.created = datetime.date.today() |
|
59 return super(Article, self).save() |
|
60 |
|
61 def __unicode__(self): |
|
62 return self.headline |
|
63 |
|
64 class ImprovedArticle(models.Model): |
|
65 article = models.OneToOneField(Article) |
|
66 |
|
67 class ImprovedArticleWithParentLink(models.Model): |
|
68 article = models.OneToOneField(Article, parent_link=True) |
|
69 |
|
70 class BetterWriter(Writer): |
|
71 score = models.IntegerField() |
|
72 |
|
73 class WriterProfile(models.Model): |
|
74 writer = models.OneToOneField(Writer, primary_key=True) |
|
75 age = models.PositiveIntegerField() |
|
76 |
|
77 def __unicode__(self): |
|
78 return "%s is %s" % (self.writer, self.age) |
|
79 |
|
80 from django.contrib.localflavor.us.models import PhoneNumberField |
|
81 class PhoneNumber(models.Model): |
|
82 phone = PhoneNumberField() |
|
83 description = models.CharField(max_length=20) |
|
84 |
|
85 def __unicode__(self): |
|
86 return self.phone |
|
87 |
|
88 class TextFile(models.Model): |
|
89 description = models.CharField(max_length=20) |
|
90 file = models.FileField(storage=temp_storage, upload_to='tests', max_length=15) |
|
91 |
|
92 def __unicode__(self): |
|
93 return self.description |
|
94 |
|
95 try: |
|
96 # If PIL is available, try testing ImageFields. Checking for the existence |
|
97 # of Image is enough for CPython, but for PyPy, you need to check for the |
|
98 # underlying modules If PIL is not available, ImageField tests are omitted. |
|
99 # Try to import PIL in either of the two ways it can end up installed. |
|
100 try: |
|
101 from PIL import Image, _imaging |
|
102 except ImportError: |
|
103 import Image, _imaging |
|
104 |
|
105 test_images = True |
|
106 |
|
107 class ImageFile(models.Model): |
|
108 def custom_upload_path(self, filename): |
|
109 path = self.path or 'tests' |
|
110 return '%s/%s' % (path, filename) |
|
111 |
|
112 description = models.CharField(max_length=20) |
|
113 |
|
114 # Deliberately put the image field *after* the width/height fields to |
|
115 # trigger the bug in #10404 with width/height not getting assigned. |
|
116 width = models.IntegerField(editable=False) |
|
117 height = models.IntegerField(editable=False) |
|
118 image = models.ImageField(storage=temp_storage, upload_to=custom_upload_path, |
|
119 width_field='width', height_field='height') |
|
120 path = models.CharField(max_length=16, blank=True, default='') |
|
121 |
|
122 def __unicode__(self): |
|
123 return self.description |
|
124 |
|
125 class OptionalImageFile(models.Model): |
|
126 def custom_upload_path(self, filename): |
|
127 path = self.path or 'tests' |
|
128 return '%s/%s' % (path, filename) |
|
129 |
|
130 description = models.CharField(max_length=20) |
|
131 image = models.ImageField(storage=temp_storage, upload_to=custom_upload_path, |
|
132 width_field='width', height_field='height', |
|
133 blank=True, null=True) |
|
134 width = models.IntegerField(editable=False, null=True) |
|
135 height = models.IntegerField(editable=False, null=True) |
|
136 path = models.CharField(max_length=16, blank=True, default='') |
|
137 |
|
138 def __unicode__(self): |
|
139 return self.description |
|
140 except ImportError: |
|
141 test_images = False |
|
142 |
|
143 class CommaSeparatedInteger(models.Model): |
|
144 field = models.CommaSeparatedIntegerField(max_length=20) |
|
145 |
|
146 def __unicode__(self): |
|
147 return self.field |
|
148 |
|
149 class Product(models.Model): |
|
150 slug = models.SlugField(unique=True) |
|
151 |
|
152 def __unicode__(self): |
|
153 return self.slug |
|
154 |
|
155 class Price(models.Model): |
|
156 price = models.DecimalField(max_digits=10, decimal_places=2) |
|
157 quantity = models.PositiveIntegerField() |
|
158 |
|
159 def __unicode__(self): |
|
160 return u"%s for %s" % (self.quantity, self.price) |
|
161 |
|
162 class Meta: |
|
163 unique_together = (('price', 'quantity'),) |
|
164 |
|
165 class ArticleStatus(models.Model): |
|
166 status = models.CharField(max_length=2, choices=ARTICLE_STATUS_CHAR, blank=True, null=True) |
|
167 |
|
168 class Inventory(models.Model): |
|
169 barcode = models.PositiveIntegerField(unique=True) |
|
170 parent = models.ForeignKey('self', to_field='barcode', blank=True, null=True) |
|
171 name = models.CharField(blank=False, max_length=20) |
|
172 |
|
173 def __unicode__(self): |
|
174 return self.name |
|
175 |
|
176 class Book(models.Model): |
|
177 title = models.CharField(max_length=40) |
|
178 author = models.ForeignKey(Writer, blank=True, null=True) |
|
179 special_id = models.IntegerField(blank=True, null=True, unique=True) |
|
180 |
|
181 class Meta: |
|
182 unique_together = ('title', 'author') |
|
183 |
|
184 class BookXtra(models.Model): |
|
185 isbn = models.CharField(max_length=16, unique=True) |
|
186 suffix1 = models.IntegerField(blank=True, default=0) |
|
187 suffix2 = models.IntegerField(blank=True, default=0) |
|
188 |
|
189 class Meta: |
|
190 unique_together = (('suffix1', 'suffix2')) |
|
191 abstract = True |
|
192 |
|
193 class DerivedBook(Book, BookXtra): |
|
194 pass |
|
195 |
|
196 class ExplicitPK(models.Model): |
|
197 key = models.CharField(max_length=20, primary_key=True) |
|
198 desc = models.CharField(max_length=20, blank=True, unique=True) |
|
199 class Meta: |
|
200 unique_together = ('key', 'desc') |
|
201 |
|
202 def __unicode__(self): |
|
203 return self.key |
|
204 |
|
205 class Post(models.Model): |
|
206 title = models.CharField(max_length=50, unique_for_date='posted', blank=True) |
|
207 slug = models.CharField(max_length=50, unique_for_year='posted', blank=True) |
|
208 subtitle = models.CharField(max_length=50, unique_for_month='posted', blank=True) |
|
209 posted = models.DateField() |
|
210 |
|
211 def __unicode__(self): |
|
212 return self.name |
|
213 |
|
214 class DerivedPost(Post): |
|
215 pass |
|
216 |
|
217 class BigInt(models.Model): |
|
218 biggie = models.BigIntegerField() |
|
219 |
|
220 def __unicode__(self): |
|
221 return unicode(self.biggie) |
|
222 |
|
223 class MarkupField(models.CharField): |
|
224 def __init__(self, *args, **kwargs): |
|
225 kwargs["max_length"] = 20 |
|
226 super(MarkupField, self).__init__(*args, **kwargs) |
|
227 |
|
228 def formfield(self, **kwargs): |
|
229 # don't allow this field to be used in form (real use-case might be |
|
230 # that you know the markup will always be X, but it is among an app |
|
231 # that allows the user to say it could be something else) |
|
232 # regressed at r10062 |
|
233 return None |
|
234 |
|
235 class CustomFieldForExclusionModel(models.Model): |
|
236 name = models.CharField(max_length=10) |
|
237 markup = MarkupField() |
|
238 |
|
239 __test__ = {'API_TESTS': """ |
|
240 >>> from django import forms |
|
241 >>> from django.forms.models import ModelForm, model_to_dict |
|
242 >>> from django.core.files.uploadedfile import SimpleUploadedFile |
|
243 |
|
244 The bare bones, absolutely nothing custom, basic case. |
|
245 |
|
246 >>> class CategoryForm(ModelForm): |
|
247 ... class Meta: |
|
248 ... model = Category |
|
249 >>> CategoryForm.base_fields.keys() |
|
250 ['name', 'slug', 'url'] |
|
251 |
|
252 |
|
253 Extra fields. |
|
254 |
|
255 >>> class CategoryForm(ModelForm): |
|
256 ... some_extra_field = forms.BooleanField() |
|
257 ... |
|
258 ... class Meta: |
|
259 ... model = Category |
|
260 |
|
261 >>> CategoryForm.base_fields.keys() |
|
262 ['name', 'slug', 'url', 'some_extra_field'] |
|
263 |
|
264 Extra field that has a name collision with a related object accessor. |
|
265 |
|
266 >>> class WriterForm(ModelForm): |
|
267 ... book = forms.CharField(required=False) |
|
268 ... |
|
269 ... class Meta: |
|
270 ... model = Writer |
|
271 |
|
272 >>> wf = WriterForm({'name': 'Richard Lockridge'}) |
|
273 >>> wf.is_valid() |
|
274 True |
|
275 |
|
276 Replacing a field. |
|
277 |
|
278 >>> class CategoryForm(ModelForm): |
|
279 ... url = forms.BooleanField() |
|
280 ... |
|
281 ... class Meta: |
|
282 ... model = Category |
|
283 |
|
284 >>> CategoryForm.base_fields['url'].__class__ |
|
285 <class 'django.forms.fields.BooleanField'> |
|
286 |
|
287 |
|
288 Using 'fields'. |
|
289 |
|
290 >>> class CategoryForm(ModelForm): |
|
291 ... |
|
292 ... class Meta: |
|
293 ... model = Category |
|
294 ... fields = ['url'] |
|
295 |
|
296 >>> CategoryForm.base_fields.keys() |
|
297 ['url'] |
|
298 |
|
299 |
|
300 Using 'exclude' |
|
301 |
|
302 >>> class CategoryForm(ModelForm): |
|
303 ... |
|
304 ... class Meta: |
|
305 ... model = Category |
|
306 ... exclude = ['url'] |
|
307 |
|
308 >>> CategoryForm.base_fields.keys() |
|
309 ['name', 'slug'] |
|
310 |
|
311 |
|
312 Using 'fields' *and* 'exclude'. Not sure why you'd want to do this, but uh, |
|
313 "be liberal in what you accept" and all. |
|
314 |
|
315 >>> class CategoryForm(ModelForm): |
|
316 ... |
|
317 ... class Meta: |
|
318 ... model = Category |
|
319 ... fields = ['name', 'url'] |
|
320 ... exclude = ['url'] |
|
321 |
|
322 >>> CategoryForm.base_fields.keys() |
|
323 ['name'] |
|
324 |
|
325 Using 'widgets' |
|
326 |
|
327 >>> class CategoryForm(ModelForm): |
|
328 ... |
|
329 ... class Meta: |
|
330 ... model = Category |
|
331 ... fields = ['name', 'url', 'slug'] |
|
332 ... widgets = { |
|
333 ... 'name': forms.Textarea, |
|
334 ... 'url': forms.TextInput(attrs={'class': 'url'}) |
|
335 ... } |
|
336 |
|
337 >>> str(CategoryForm()['name']) |
|
338 '<textarea id="id_name" rows="10" cols="40" name="name"></textarea>' |
|
339 |
|
340 >>> str(CategoryForm()['url']) |
|
341 '<input id="id_url" type="text" class="url" name="url" maxlength="40" />' |
|
342 |
|
343 >>> str(CategoryForm()['slug']) |
|
344 '<input id="id_slug" type="text" name="slug" maxlength="20" />' |
|
345 |
|
346 Don't allow more than one 'model' definition in the inheritance hierarchy. |
|
347 Technically, it would generate a valid form, but the fact that the resulting |
|
348 save method won't deal with multiple objects is likely to trip up people not |
|
349 familiar with the mechanics. |
|
350 |
|
351 >>> class CategoryForm(ModelForm): |
|
352 ... class Meta: |
|
353 ... model = Category |
|
354 |
|
355 >>> class OddForm(CategoryForm): |
|
356 ... class Meta: |
|
357 ... model = Article |
|
358 |
|
359 OddForm is now an Article-related thing, because BadForm.Meta overrides |
|
360 CategoryForm.Meta. |
|
361 >>> OddForm.base_fields.keys() |
|
362 ['headline', 'slug', 'pub_date', 'writer', 'article', 'status', 'categories'] |
|
363 |
|
364 >>> class ArticleForm(ModelForm): |
|
365 ... class Meta: |
|
366 ... model = Article |
|
367 |
|
368 First class with a Meta class wins. |
|
369 |
|
370 >>> class BadForm(ArticleForm, CategoryForm): |
|
371 ... pass |
|
372 >>> OddForm.base_fields.keys() |
|
373 ['headline', 'slug', 'pub_date', 'writer', 'article', 'status', 'categories'] |
|
374 |
|
375 Subclassing without specifying a Meta on the class will use the parent's Meta |
|
376 (or the first parent in the MRO if there are multiple parent classes). |
|
377 |
|
378 >>> class CategoryForm(ModelForm): |
|
379 ... class Meta: |
|
380 ... model = Category |
|
381 >>> class SubCategoryForm(CategoryForm): |
|
382 ... pass |
|
383 >>> SubCategoryForm.base_fields.keys() |
|
384 ['name', 'slug', 'url'] |
|
385 |
|
386 We can also subclass the Meta inner class to change the fields list. |
|
387 |
|
388 >>> class CategoryForm(ModelForm): |
|
389 ... checkbox = forms.BooleanField() |
|
390 ... |
|
391 ... class Meta: |
|
392 ... model = Category |
|
393 >>> class SubCategoryForm(CategoryForm): |
|
394 ... class Meta(CategoryForm.Meta): |
|
395 ... exclude = ['url'] |
|
396 |
|
397 >>> print SubCategoryForm() |
|
398 <tr><th><label for="id_name">Name:</label></th><td><input id="id_name" type="text" name="name" maxlength="20" /></td></tr> |
|
399 <tr><th><label for="id_slug">Slug:</label></th><td><input id="id_slug" type="text" name="slug" maxlength="20" /></td></tr> |
|
400 <tr><th><label for="id_checkbox">Checkbox:</label></th><td><input type="checkbox" name="checkbox" id="id_checkbox" /></td></tr> |
|
401 |
|
402 # test using fields to provide ordering to the fields |
|
403 >>> class CategoryForm(ModelForm): |
|
404 ... class Meta: |
|
405 ... model = Category |
|
406 ... fields = ['url', 'name'] |
|
407 |
|
408 >>> CategoryForm.base_fields.keys() |
|
409 ['url', 'name'] |
|
410 |
|
411 |
|
412 >>> print CategoryForm() |
|
413 <tr><th><label for="id_url">The URL:</label></th><td><input id="id_url" type="text" name="url" maxlength="40" /></td></tr> |
|
414 <tr><th><label for="id_name">Name:</label></th><td><input id="id_name" type="text" name="name" maxlength="20" /></td></tr> |
|
415 |
|
416 >>> class CategoryForm(ModelForm): |
|
417 ... class Meta: |
|
418 ... model = Category |
|
419 ... fields = ['slug', 'url', 'name'] |
|
420 ... exclude = ['url'] |
|
421 |
|
422 >>> CategoryForm.base_fields.keys() |
|
423 ['slug', 'name'] |
|
424 |
|
425 # Old form_for_x tests ####################################################### |
|
426 |
|
427 >>> from django.forms import ModelForm, CharField |
|
428 >>> import datetime |
|
429 |
|
430 >>> Category.objects.all() |
|
431 [] |
|
432 |
|
433 >>> class CategoryForm(ModelForm): |
|
434 ... class Meta: |
|
435 ... model = Category |
|
436 >>> f = CategoryForm() |
|
437 >>> print f |
|
438 <tr><th><label for="id_name">Name:</label></th><td><input id="id_name" type="text" name="name" maxlength="20" /></td></tr> |
|
439 <tr><th><label for="id_slug">Slug:</label></th><td><input id="id_slug" type="text" name="slug" maxlength="20" /></td></tr> |
|
440 <tr><th><label for="id_url">The URL:</label></th><td><input id="id_url" type="text" name="url" maxlength="40" /></td></tr> |
|
441 >>> print f.as_ul() |
|
442 <li><label for="id_name">Name:</label> <input id="id_name" type="text" name="name" maxlength="20" /></li> |
|
443 <li><label for="id_slug">Slug:</label> <input id="id_slug" type="text" name="slug" maxlength="20" /></li> |
|
444 <li><label for="id_url">The URL:</label> <input id="id_url" type="text" name="url" maxlength="40" /></li> |
|
445 >>> print f['name'] |
|
446 <input id="id_name" type="text" name="name" maxlength="20" /> |
|
447 |
|
448 >>> f = CategoryForm(auto_id=False) |
|
449 >>> print f.as_ul() |
|
450 <li>Name: <input type="text" name="name" maxlength="20" /></li> |
|
451 <li>Slug: <input type="text" name="slug" maxlength="20" /></li> |
|
452 <li>The URL: <input type="text" name="url" maxlength="40" /></li> |
|
453 |
|
454 >>> f = CategoryForm({'name': 'Entertainment', 'slug': 'entertainment', 'url': 'entertainment'}) |
|
455 >>> f.is_valid() |
|
456 True |
|
457 >>> f.cleaned_data['url'] |
|
458 u'entertainment' |
|
459 >>> f.cleaned_data['name'] |
|
460 u'Entertainment' |
|
461 >>> f.cleaned_data['slug'] |
|
462 u'entertainment' |
|
463 >>> obj = f.save() |
|
464 >>> obj |
|
465 <Category: Entertainment> |
|
466 >>> Category.objects.all() |
|
467 [<Category: Entertainment>] |
|
468 |
|
469 >>> f = CategoryForm({'name': "It's a test", 'slug': 'its-test', 'url': 'test'}) |
|
470 >>> f.is_valid() |
|
471 True |
|
472 >>> f.cleaned_data['url'] |
|
473 u'test' |
|
474 >>> f.cleaned_data['name'] |
|
475 u"It's a test" |
|
476 >>> f.cleaned_data['slug'] |
|
477 u'its-test' |
|
478 >>> obj = f.save() |
|
479 >>> obj |
|
480 <Category: It's a test> |
|
481 >>> Category.objects.order_by('name') |
|
482 [<Category: Entertainment>, <Category: It's a test>] |
|
483 |
|
484 If you call save() with commit=False, then it will return an object that |
|
485 hasn't yet been saved to the database. In this case, it's up to you to call |
|
486 save() on the resulting model instance. |
|
487 >>> f = CategoryForm({'name': 'Third test', 'slug': 'third-test', 'url': 'third'}) |
|
488 >>> f.is_valid() |
|
489 True |
|
490 >>> f.cleaned_data['url'] |
|
491 u'third' |
|
492 >>> f.cleaned_data['name'] |
|
493 u'Third test' |
|
494 >>> f.cleaned_data['slug'] |
|
495 u'third-test' |
|
496 >>> obj = f.save(commit=False) |
|
497 >>> obj |
|
498 <Category: Third test> |
|
499 >>> Category.objects.order_by('name') |
|
500 [<Category: Entertainment>, <Category: It's a test>] |
|
501 >>> obj.save() |
|
502 >>> Category.objects.order_by('name') |
|
503 [<Category: Entertainment>, <Category: It's a test>, <Category: Third test>] |
|
504 |
|
505 If you call save() with invalid data, you'll get a ValueError. |
|
506 >>> f = CategoryForm({'name': '', 'slug': 'not a slug!', 'url': 'foo'}) |
|
507 >>> f.errors['name'] |
|
508 [u'This field is required.'] |
|
509 >>> f.errors['slug'] |
|
510 [u"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."] |
|
511 >>> f.cleaned_data |
|
512 Traceback (most recent call last): |
|
513 ... |
|
514 AttributeError: 'CategoryForm' object has no attribute 'cleaned_data' |
|
515 >>> f.save() |
|
516 Traceback (most recent call last): |
|
517 ... |
|
518 ValueError: The Category could not be created because the data didn't validate. |
|
519 >>> f = CategoryForm({'name': '', 'slug': '', 'url': 'foo'}) |
|
520 >>> f.save() |
|
521 Traceback (most recent call last): |
|
522 ... |
|
523 ValueError: The Category could not be created because the data didn't validate. |
|
524 |
|
525 Create a couple of Writers. |
|
526 >>> w_royko = Writer(name='Mike Royko') |
|
527 >>> w_royko.save() |
|
528 >>> w_woodward = Writer(name='Bob Woodward') |
|
529 >>> w_woodward.save() |
|
530 |
|
531 ManyToManyFields are represented by a MultipleChoiceField, ForeignKeys and any |
|
532 fields with the 'choices' attribute are represented by a ChoiceField. |
|
533 >>> class ArticleForm(ModelForm): |
|
534 ... class Meta: |
|
535 ... model = Article |
|
536 >>> f = ArticleForm(auto_id=False) |
|
537 >>> print f |
|
538 <tr><th>Headline:</th><td><input type="text" name="headline" maxlength="50" /></td></tr> |
|
539 <tr><th>Slug:</th><td><input type="text" name="slug" maxlength="50" /></td></tr> |
|
540 <tr><th>Pub date:</th><td><input type="text" name="pub_date" /></td></tr> |
|
541 <tr><th>Writer:</th><td><select name="writer"> |
|
542 <option value="" selected="selected">---------</option> |
|
543 <option value="...">Mike Royko</option> |
|
544 <option value="...">Bob Woodward</option> |
|
545 </select></td></tr> |
|
546 <tr><th>Article:</th><td><textarea rows="10" cols="40" name="article"></textarea></td></tr> |
|
547 <tr><th>Status:</th><td><select name="status"> |
|
548 <option value="" selected="selected">---------</option> |
|
549 <option value="1">Draft</option> |
|
550 <option value="2">Pending</option> |
|
551 <option value="3">Live</option> |
|
552 </select></td></tr> |
|
553 <tr><th>Categories:</th><td><select multiple="multiple" name="categories"> |
|
554 <option value="1">Entertainment</option> |
|
555 <option value="2">It's a test</option> |
|
556 <option value="3">Third test</option> |
|
557 </select><br /> Hold down "Control", or "Command" on a Mac, to select more than one.</td></tr> |
|
558 |
|
559 You can restrict a form to a subset of the complete list of fields |
|
560 by providing a 'fields' argument. If you try to save a |
|
561 model created with such a form, you need to ensure that the fields |
|
562 that are _not_ on the form have default values, or are allowed to have |
|
563 a value of None. If a field isn't specified on a form, the object created |
|
564 from the form can't provide a value for that field! |
|
565 >>> class PartialArticleForm(ModelForm): |
|
566 ... class Meta: |
|
567 ... model = Article |
|
568 ... fields = ('headline','pub_date') |
|
569 >>> f = PartialArticleForm(auto_id=False) |
|
570 >>> print f |
|
571 <tr><th>Headline:</th><td><input type="text" name="headline" maxlength="50" /></td></tr> |
|
572 <tr><th>Pub date:</th><td><input type="text" name="pub_date" /></td></tr> |
|
573 |
|
574 When the ModelForm is passed an instance, that instance's current values are |
|
575 inserted as 'initial' data in each Field. |
|
576 >>> w = Writer.objects.get(name='Mike Royko') |
|
577 >>> class RoykoForm(ModelForm): |
|
578 ... class Meta: |
|
579 ... model = Writer |
|
580 >>> f = RoykoForm(auto_id=False, instance=w) |
|
581 >>> print f |
|
582 <tr><th>Name:</th><td><input type="text" name="name" value="Mike Royko" maxlength="50" /><br />Use both first and last names.</td></tr> |
|
583 |
|
584 >>> art = Article(headline='Test article', slug='test-article', pub_date=datetime.date(1988, 1, 4), writer=w, article='Hello.') |
|
585 >>> art.save() |
|
586 >>> art.id |
|
587 1 |
|
588 >>> class TestArticleForm(ModelForm): |
|
589 ... class Meta: |
|
590 ... model = Article |
|
591 >>> f = TestArticleForm(auto_id=False, instance=art) |
|
592 >>> print f.as_ul() |
|
593 <li>Headline: <input type="text" name="headline" value="Test article" maxlength="50" /></li> |
|
594 <li>Slug: <input type="text" name="slug" value="test-article" maxlength="50" /></li> |
|
595 <li>Pub date: <input type="text" name="pub_date" value="1988-01-04" /></li> |
|
596 <li>Writer: <select name="writer"> |
|
597 <option value="">---------</option> |
|
598 <option value="..." selected="selected">Mike Royko</option> |
|
599 <option value="...">Bob Woodward</option> |
|
600 </select></li> |
|
601 <li>Article: <textarea rows="10" cols="40" name="article">Hello.</textarea></li> |
|
602 <li>Status: <select name="status"> |
|
603 <option value="" selected="selected">---------</option> |
|
604 <option value="1">Draft</option> |
|
605 <option value="2">Pending</option> |
|
606 <option value="3">Live</option> |
|
607 </select></li> |
|
608 <li>Categories: <select multiple="multiple" name="categories"> |
|
609 <option value="1">Entertainment</option> |
|
610 <option value="2">It's a test</option> |
|
611 <option value="3">Third test</option> |
|
612 </select> Hold down "Control", or "Command" on a Mac, to select more than one.</li> |
|
613 >>> f = TestArticleForm({'headline': u'Test headline', 'slug': 'test-headline', 'pub_date': u'1984-02-06', 'writer': unicode(w_royko.pk), 'article': 'Hello.'}, instance=art) |
|
614 >>> f.errors |
|
615 {} |
|
616 >>> f.is_valid() |
|
617 True |
|
618 >>> test_art = f.save() |
|
619 >>> test_art.id |
|
620 1 |
|
621 >>> test_art = Article.objects.get(id=1) |
|
622 >>> test_art.headline |
|
623 u'Test headline' |
|
624 |
|
625 You can create a form over a subset of the available fields |
|
626 by specifying a 'fields' argument to form_for_instance. |
|
627 >>> class PartialArticleForm(ModelForm): |
|
628 ... class Meta: |
|
629 ... model = Article |
|
630 ... fields=('headline', 'slug', 'pub_date') |
|
631 >>> f = PartialArticleForm({'headline': u'New headline', 'slug': 'new-headline', 'pub_date': u'1988-01-04'}, auto_id=False, instance=art) |
|
632 >>> print f.as_ul() |
|
633 <li>Headline: <input type="text" name="headline" value="New headline" maxlength="50" /></li> |
|
634 <li>Slug: <input type="text" name="slug" value="new-headline" maxlength="50" /></li> |
|
635 <li>Pub date: <input type="text" name="pub_date" value="1988-01-04" /></li> |
|
636 >>> f.is_valid() |
|
637 True |
|
638 >>> new_art = f.save() |
|
639 >>> new_art.id |
|
640 1 |
|
641 >>> new_art = Article.objects.get(id=1) |
|
642 >>> new_art.headline |
|
643 u'New headline' |
|
644 |
|
645 Add some categories and test the many-to-many form output. |
|
646 >>> new_art.categories.all() |
|
647 [] |
|
648 >>> new_art.categories.add(Category.objects.get(name='Entertainment')) |
|
649 >>> new_art.categories.all() |
|
650 [<Category: Entertainment>] |
|
651 >>> class TestArticleForm(ModelForm): |
|
652 ... class Meta: |
|
653 ... model = Article |
|
654 >>> f = TestArticleForm(auto_id=False, instance=new_art) |
|
655 >>> print f.as_ul() |
|
656 <li>Headline: <input type="text" name="headline" value="New headline" maxlength="50" /></li> |
|
657 <li>Slug: <input type="text" name="slug" value="new-headline" maxlength="50" /></li> |
|
658 <li>Pub date: <input type="text" name="pub_date" value="1988-01-04" /></li> |
|
659 <li>Writer: <select name="writer"> |
|
660 <option value="">---------</option> |
|
661 <option value="..." selected="selected">Mike Royko</option> |
|
662 <option value="...">Bob Woodward</option> |
|
663 </select></li> |
|
664 <li>Article: <textarea rows="10" cols="40" name="article">Hello.</textarea></li> |
|
665 <li>Status: <select name="status"> |
|
666 <option value="" selected="selected">---------</option> |
|
667 <option value="1">Draft</option> |
|
668 <option value="2">Pending</option> |
|
669 <option value="3">Live</option> |
|
670 </select></li> |
|
671 <li>Categories: <select multiple="multiple" name="categories"> |
|
672 <option value="1" selected="selected">Entertainment</option> |
|
673 <option value="2">It's a test</option> |
|
674 <option value="3">Third test</option> |
|
675 </select> Hold down "Control", or "Command" on a Mac, to select more than one.</li> |
|
676 |
|
677 Initial values can be provided for model forms |
|
678 >>> f = TestArticleForm(auto_id=False, initial={'headline': 'Your headline here', 'categories': ['1','2']}) |
|
679 >>> print f.as_ul() |
|
680 <li>Headline: <input type="text" name="headline" value="Your headline here" maxlength="50" /></li> |
|
681 <li>Slug: <input type="text" name="slug" maxlength="50" /></li> |
|
682 <li>Pub date: <input type="text" name="pub_date" /></li> |
|
683 <li>Writer: <select name="writer"> |
|
684 <option value="" selected="selected">---------</option> |
|
685 <option value="...">Mike Royko</option> |
|
686 <option value="...">Bob Woodward</option> |
|
687 </select></li> |
|
688 <li>Article: <textarea rows="10" cols="40" name="article"></textarea></li> |
|
689 <li>Status: <select name="status"> |
|
690 <option value="" selected="selected">---------</option> |
|
691 <option value="1">Draft</option> |
|
692 <option value="2">Pending</option> |
|
693 <option value="3">Live</option> |
|
694 </select></li> |
|
695 <li>Categories: <select multiple="multiple" name="categories"> |
|
696 <option value="1" selected="selected">Entertainment</option> |
|
697 <option value="2" selected="selected">It's a test</option> |
|
698 <option value="3">Third test</option> |
|
699 </select> Hold down "Control", or "Command" on a Mac, to select more than one.</li> |
|
700 |
|
701 >>> f = TestArticleForm({'headline': u'New headline', 'slug': u'new-headline', 'pub_date': u'1988-01-04', |
|
702 ... 'writer': unicode(w_royko.pk), 'article': u'Hello.', 'categories': [u'1', u'2']}, instance=new_art) |
|
703 >>> new_art = f.save() |
|
704 >>> new_art.id |
|
705 1 |
|
706 >>> new_art = Article.objects.get(id=1) |
|
707 >>> new_art.categories.order_by('name') |
|
708 [<Category: Entertainment>, <Category: It's a test>] |
|
709 |
|
710 Now, submit form data with no categories. This deletes the existing categories. |
|
711 >>> f = TestArticleForm({'headline': u'New headline', 'slug': u'new-headline', 'pub_date': u'1988-01-04', |
|
712 ... 'writer': unicode(w_royko.pk), 'article': u'Hello.'}, instance=new_art) |
|
713 >>> new_art = f.save() |
|
714 >>> new_art.id |
|
715 1 |
|
716 >>> new_art = Article.objects.get(id=1) |
|
717 >>> new_art.categories.all() |
|
718 [] |
|
719 |
|
720 Create a new article, with categories, via the form. |
|
721 >>> class ArticleForm(ModelForm): |
|
722 ... class Meta: |
|
723 ... model = Article |
|
724 >>> f = ArticleForm({'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01', |
|
725 ... 'writer': unicode(w_royko.pk), 'article': u'Test.', 'categories': [u'1', u'2']}) |
|
726 >>> new_art = f.save() |
|
727 >>> new_art.id |
|
728 2 |
|
729 >>> new_art = Article.objects.get(id=2) |
|
730 >>> new_art.categories.order_by('name') |
|
731 [<Category: Entertainment>, <Category: It's a test>] |
|
732 |
|
733 Create a new article, with no categories, via the form. |
|
734 >>> class ArticleForm(ModelForm): |
|
735 ... class Meta: |
|
736 ... model = Article |
|
737 >>> f = ArticleForm({'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01', |
|
738 ... 'writer': unicode(w_royko.pk), 'article': u'Test.'}) |
|
739 >>> new_art = f.save() |
|
740 >>> new_art.id |
|
741 3 |
|
742 >>> new_art = Article.objects.get(id=3) |
|
743 >>> new_art.categories.all() |
|
744 [] |
|
745 |
|
746 Create a new article, with categories, via the form, but use commit=False. |
|
747 The m2m data won't be saved until save_m2m() is invoked on the form. |
|
748 >>> class ArticleForm(ModelForm): |
|
749 ... class Meta: |
|
750 ... model = Article |
|
751 >>> f = ArticleForm({'headline': u'The walrus was Paul', 'slug': 'walrus-was-paul', 'pub_date': u'1967-11-01', |
|
752 ... 'writer': unicode(w_royko.pk), 'article': u'Test.', 'categories': [u'1', u'2']}) |
|
753 >>> new_art = f.save(commit=False) |
|
754 |
|
755 # Manually save the instance |
|
756 >>> new_art.save() |
|
757 >>> new_art.id |
|
758 4 |
|
759 |
|
760 # The instance doesn't have m2m data yet |
|
761 >>> new_art = Article.objects.get(id=4) |
|
762 >>> new_art.categories.all() |
|
763 [] |
|
764 |
|
765 # Save the m2m data on the form |
|
766 >>> f.save_m2m() |
|
767 >>> new_art.categories.order_by('name') |
|
768 [<Category: Entertainment>, <Category: It's a test>] |
|
769 |
|
770 Here, we define a custom ModelForm. Because it happens to have the same fields as |
|
771 the Category model, we can just call the form's save() to apply its changes to an |
|
772 existing Category instance. |
|
773 >>> class ShortCategory(ModelForm): |
|
774 ... name = CharField(max_length=5) |
|
775 ... slug = CharField(max_length=5) |
|
776 ... url = CharField(max_length=3) |
|
777 >>> cat = Category.objects.get(name='Third test') |
|
778 >>> cat |
|
779 <Category: Third test> |
|
780 >>> cat.id |
|
781 3 |
|
782 >>> form = ShortCategory({'name': 'Third', 'slug': 'third', 'url': '3rd'}, instance=cat) |
|
783 >>> form.save() |
|
784 <Category: Third> |
|
785 >>> Category.objects.get(id=3) |
|
786 <Category: Third> |
|
787 |
|
788 Here, we demonstrate that choices for a ForeignKey ChoiceField are determined |
|
789 at runtime, based on the data in the database when the form is displayed, not |
|
790 the data in the database when the form is instantiated. |
|
791 >>> class ArticleForm(ModelForm): |
|
792 ... class Meta: |
|
793 ... model = Article |
|
794 >>> f = ArticleForm(auto_id=False) |
|
795 >>> print f.as_ul() |
|
796 <li>Headline: <input type="text" name="headline" maxlength="50" /></li> |
|
797 <li>Slug: <input type="text" name="slug" maxlength="50" /></li> |
|
798 <li>Pub date: <input type="text" name="pub_date" /></li> |
|
799 <li>Writer: <select name="writer"> |
|
800 <option value="" selected="selected">---------</option> |
|
801 <option value="...">Mike Royko</option> |
|
802 <option value="...">Bob Woodward</option> |
|
803 </select></li> |
|
804 <li>Article: <textarea rows="10" cols="40" name="article"></textarea></li> |
|
805 <li>Status: <select name="status"> |
|
806 <option value="" selected="selected">---------</option> |
|
807 <option value="1">Draft</option> |
|
808 <option value="2">Pending</option> |
|
809 <option value="3">Live</option> |
|
810 </select></li> |
|
811 <li>Categories: <select multiple="multiple" name="categories"> |
|
812 <option value="1">Entertainment</option> |
|
813 <option value="2">It's a test</option> |
|
814 <option value="3">Third</option> |
|
815 </select> Hold down "Control", or "Command" on a Mac, to select more than one.</li> |
|
816 >>> Category.objects.create(name='Fourth', url='4th') |
|
817 <Category: Fourth> |
|
818 >>> Writer.objects.create(name='Carl Bernstein') |
|
819 <Writer: Carl Bernstein> |
|
820 >>> print f.as_ul() |
|
821 <li>Headline: <input type="text" name="headline" maxlength="50" /></li> |
|
822 <li>Slug: <input type="text" name="slug" maxlength="50" /></li> |
|
823 <li>Pub date: <input type="text" name="pub_date" /></li> |
|
824 <li>Writer: <select name="writer"> |
|
825 <option value="" selected="selected">---------</option> |
|
826 <option value="...">Mike Royko</option> |
|
827 <option value="...">Bob Woodward</option> |
|
828 <option value="...">Carl Bernstein</option> |
|
829 </select></li> |
|
830 <li>Article: <textarea rows="10" cols="40" name="article"></textarea></li> |
|
831 <li>Status: <select name="status"> |
|
832 <option value="" selected="selected">---------</option> |
|
833 <option value="1">Draft</option> |
|
834 <option value="2">Pending</option> |
|
835 <option value="3">Live</option> |
|
836 </select></li> |
|
837 <li>Categories: <select multiple="multiple" name="categories"> |
|
838 <option value="1">Entertainment</option> |
|
839 <option value="2">It's a test</option> |
|
840 <option value="3">Third</option> |
|
841 <option value="4">Fourth</option> |
|
842 </select> Hold down "Control", or "Command" on a Mac, to select more than one.</li> |
|
843 |
|
844 # ModelChoiceField ############################################################ |
|
845 |
|
846 >>> from django.forms import ModelChoiceField, ModelMultipleChoiceField |
|
847 |
|
848 >>> f = ModelChoiceField(Category.objects.all()) |
|
849 >>> list(f.choices) |
|
850 [(u'', u'---------'), (1, u'Entertainment'), (2, u"It's a test"), (3, u'Third'), (4, u'Fourth')] |
|
851 >>> f.clean('') |
|
852 Traceback (most recent call last): |
|
853 ... |
|
854 ValidationError: [u'This field is required.'] |
|
855 >>> f.clean(None) |
|
856 Traceback (most recent call last): |
|
857 ... |
|
858 ValidationError: [u'This field is required.'] |
|
859 >>> f.clean(0) |
|
860 Traceback (most recent call last): |
|
861 ... |
|
862 ValidationError: [u'Select a valid choice. That choice is not one of the available choices.'] |
|
863 >>> f.clean(3) |
|
864 <Category: Third> |
|
865 >>> f.clean(2) |
|
866 <Category: It's a test> |
|
867 |
|
868 # Add a Category object *after* the ModelChoiceField has already been |
|
869 # instantiated. This proves clean() checks the database during clean() rather |
|
870 # than caching it at time of instantiation. |
|
871 >>> Category.objects.create(name='Fifth', url='5th') |
|
872 <Category: Fifth> |
|
873 >>> f.clean(5) |
|
874 <Category: Fifth> |
|
875 |
|
876 # Delete a Category object *after* the ModelChoiceField has already been |
|
877 # instantiated. This proves clean() checks the database during clean() rather |
|
878 # than caching it at time of instantiation. |
|
879 >>> Category.objects.get(url='5th').delete() |
|
880 >>> f.clean(5) |
|
881 Traceback (most recent call last): |
|
882 ... |
|
883 ValidationError: [u'Select a valid choice. That choice is not one of the available choices.'] |
|
884 |
|
885 >>> f = ModelChoiceField(Category.objects.filter(pk=1), required=False) |
|
886 >>> print f.clean('') |
|
887 None |
|
888 >>> f.clean('') |
|
889 >>> f.clean('1') |
|
890 <Category: Entertainment> |
|
891 >>> f.clean('100') |
|
892 Traceback (most recent call last): |
|
893 ... |
|
894 ValidationError: [u'Select a valid choice. That choice is not one of the available choices.'] |
|
895 |
|
896 # queryset can be changed after the field is created. |
|
897 >>> f.queryset = Category.objects.exclude(name='Fourth') |
|
898 >>> list(f.choices) |
|
899 [(u'', u'---------'), (1, u'Entertainment'), (2, u"It's a test"), (3, u'Third')] |
|
900 >>> f.clean(3) |
|
901 <Category: Third> |
|
902 >>> f.clean(4) |
|
903 Traceback (most recent call last): |
|
904 ... |
|
905 ValidationError: [u'Select a valid choice. That choice is not one of the available choices.'] |
|
906 |
|
907 # check that we can safely iterate choices repeatedly |
|
908 >>> gen_one = list(f.choices) |
|
909 >>> gen_two = f.choices |
|
910 >>> gen_one[2] |
|
911 (2L, u"It's a test") |
|
912 >>> list(gen_two) |
|
913 [(u'', u'---------'), (1L, u'Entertainment'), (2L, u"It's a test"), (3L, u'Third')] |
|
914 |
|
915 # check that we can override the label_from_instance method to print custom labels (#4620) |
|
916 >>> f.queryset = Category.objects.all() |
|
917 >>> f.label_from_instance = lambda obj: "category " + str(obj) |
|
918 >>> list(f.choices) |
|
919 [(u'', u'---------'), (1L, 'category Entertainment'), (2L, "category It's a test"), (3L, 'category Third'), (4L, 'category Fourth')] |
|
920 |
|
921 # ModelMultipleChoiceField #################################################### |
|
922 |
|
923 >>> f = ModelMultipleChoiceField(Category.objects.all()) |
|
924 >>> list(f.choices) |
|
925 [(1, u'Entertainment'), (2, u"It's a test"), (3, u'Third'), (4, u'Fourth')] |
|
926 >>> f.clean(None) |
|
927 Traceback (most recent call last): |
|
928 ... |
|
929 ValidationError: [u'This field is required.'] |
|
930 >>> f.clean([]) |
|
931 Traceback (most recent call last): |
|
932 ... |
|
933 ValidationError: [u'This field is required.'] |
|
934 >>> f.clean([1]) |
|
935 [<Category: Entertainment>] |
|
936 >>> f.clean([2]) |
|
937 [<Category: It's a test>] |
|
938 >>> f.clean(['1']) |
|
939 [<Category: Entertainment>] |
|
940 >>> f.clean(['1', '2']) |
|
941 [<Category: Entertainment>, <Category: It's a test>] |
|
942 >>> f.clean([1, '2']) |
|
943 [<Category: Entertainment>, <Category: It's a test>] |
|
944 >>> f.clean((1, '2')) |
|
945 [<Category: Entertainment>, <Category: It's a test>] |
|
946 >>> f.clean(['100']) |
|
947 Traceback (most recent call last): |
|
948 ... |
|
949 ValidationError: [u'Select a valid choice. 100 is not one of the available choices.'] |
|
950 >>> f.clean('hello') |
|
951 Traceback (most recent call last): |
|
952 ... |
|
953 ValidationError: [u'Enter a list of values.'] |
|
954 >>> f.clean(['fail']) |
|
955 Traceback (most recent call last): |
|
956 ... |
|
957 ValidationError: [u'"fail" is not a valid value for a primary key.'] |
|
958 |
|
959 # Add a Category object *after* the ModelMultipleChoiceField has already been |
|
960 # instantiated. This proves clean() checks the database during clean() rather |
|
961 # than caching it at time of instantiation. |
|
962 >>> Category.objects.create(id=6, name='Sixth', url='6th') |
|
963 <Category: Sixth> |
|
964 >>> f.clean([6]) |
|
965 [<Category: Sixth>] |
|
966 |
|
967 # Delete a Category object *after* the ModelMultipleChoiceField has already been |
|
968 # instantiated. This proves clean() checks the database during clean() rather |
|
969 # than caching it at time of instantiation. |
|
970 >>> Category.objects.get(url='6th').delete() |
|
971 >>> f.clean([6]) |
|
972 Traceback (most recent call last): |
|
973 ... |
|
974 ValidationError: [u'Select a valid choice. 6 is not one of the available choices.'] |
|
975 |
|
976 >>> f = ModelMultipleChoiceField(Category.objects.all(), required=False) |
|
977 >>> f.clean([]) |
|
978 [] |
|
979 >>> f.clean(()) |
|
980 [] |
|
981 >>> f.clean(['10']) |
|
982 Traceback (most recent call last): |
|
983 ... |
|
984 ValidationError: [u'Select a valid choice. 10 is not one of the available choices.'] |
|
985 >>> f.clean(['3', '10']) |
|
986 Traceback (most recent call last): |
|
987 ... |
|
988 ValidationError: [u'Select a valid choice. 10 is not one of the available choices.'] |
|
989 >>> f.clean(['1', '10']) |
|
990 Traceback (most recent call last): |
|
991 ... |
|
992 ValidationError: [u'Select a valid choice. 10 is not one of the available choices.'] |
|
993 |
|
994 # queryset can be changed after the field is created. |
|
995 >>> f.queryset = Category.objects.exclude(name='Fourth') |
|
996 >>> list(f.choices) |
|
997 [(1, u'Entertainment'), (2, u"It's a test"), (3, u'Third')] |
|
998 >>> f.clean([3]) |
|
999 [<Category: Third>] |
|
1000 >>> f.clean([4]) |
|
1001 Traceback (most recent call last): |
|
1002 ... |
|
1003 ValidationError: [u'Select a valid choice. 4 is not one of the available choices.'] |
|
1004 >>> f.clean(['3', '4']) |
|
1005 Traceback (most recent call last): |
|
1006 ... |
|
1007 ValidationError: [u'Select a valid choice. 4 is not one of the available choices.'] |
|
1008 |
|
1009 >>> f.queryset = Category.objects.all() |
|
1010 >>> f.label_from_instance = lambda obj: "multicategory " + str(obj) |
|
1011 >>> list(f.choices) |
|
1012 [(1L, 'multicategory Entertainment'), (2L, "multicategory It's a test"), (3L, 'multicategory Third'), (4L, 'multicategory Fourth')] |
|
1013 |
|
1014 # OneToOneField ############################################################### |
|
1015 |
|
1016 >>> class ImprovedArticleForm(ModelForm): |
|
1017 ... class Meta: |
|
1018 ... model = ImprovedArticle |
|
1019 >>> ImprovedArticleForm.base_fields.keys() |
|
1020 ['article'] |
|
1021 |
|
1022 >>> class ImprovedArticleWithParentLinkForm(ModelForm): |
|
1023 ... class Meta: |
|
1024 ... model = ImprovedArticleWithParentLink |
|
1025 >>> ImprovedArticleWithParentLinkForm.base_fields.keys() |
|
1026 [] |
|
1027 |
|
1028 >>> bw = BetterWriter(name=u'Joe Better', score=10) |
|
1029 >>> bw.save() |
|
1030 >>> sorted(model_to_dict(bw).keys()) |
|
1031 ['id', 'name', 'score', 'writer_ptr'] |
|
1032 |
|
1033 >>> class BetterWriterForm(ModelForm): |
|
1034 ... class Meta: |
|
1035 ... model = BetterWriter |
|
1036 >>> form = BetterWriterForm({'name': 'Some Name', 'score': 12}) |
|
1037 >>> form.is_valid() |
|
1038 True |
|
1039 >>> bw2 = form.save() |
|
1040 >>> bw2.delete() |
|
1041 |
|
1042 |
|
1043 >>> class WriterProfileForm(ModelForm): |
|
1044 ... class Meta: |
|
1045 ... model = WriterProfile |
|
1046 >>> form = WriterProfileForm() |
|
1047 >>> print form.as_p() |
|
1048 <p><label for="id_writer">Writer:</label> <select name="writer" id="id_writer"> |
|
1049 <option value="" selected="selected">---------</option> |
|
1050 <option value="...">Mike Royko</option> |
|
1051 <option value="...">Bob Woodward</option> |
|
1052 <option value="...">Carl Bernstein</option> |
|
1053 <option value="...">Joe Better</option> |
|
1054 </select></p> |
|
1055 <p><label for="id_age">Age:</label> <input type="text" name="age" id="id_age" /></p> |
|
1056 |
|
1057 >>> data = { |
|
1058 ... 'writer': unicode(w_woodward.pk), |
|
1059 ... 'age': u'65', |
|
1060 ... } |
|
1061 >>> form = WriterProfileForm(data) |
|
1062 >>> instance = form.save() |
|
1063 >>> instance |
|
1064 <WriterProfile: Bob Woodward is 65> |
|
1065 |
|
1066 >>> form = WriterProfileForm(instance=instance) |
|
1067 >>> print form.as_p() |
|
1068 <p><label for="id_writer">Writer:</label> <select name="writer" id="id_writer"> |
|
1069 <option value="">---------</option> |
|
1070 <option value="...">Mike Royko</option> |
|
1071 <option value="..." selected="selected">Bob Woodward</option> |
|
1072 <option value="...">Carl Bernstein</option> |
|
1073 <option value="...">Joe Better</option> |
|
1074 </select></p> |
|
1075 <p><label for="id_age">Age:</label> <input type="text" name="age" value="65" id="id_age" /></p> |
|
1076 |
|
1077 # PhoneNumberField ############################################################ |
|
1078 |
|
1079 >>> class PhoneNumberForm(ModelForm): |
|
1080 ... class Meta: |
|
1081 ... model = PhoneNumber |
|
1082 >>> f = PhoneNumberForm({'phone': '(312) 555-1212', 'description': 'Assistance'}) |
|
1083 >>> f.is_valid() |
|
1084 True |
|
1085 >>> f.cleaned_data['phone'] |
|
1086 u'312-555-1212' |
|
1087 >>> f.cleaned_data['description'] |
|
1088 u'Assistance' |
|
1089 |
|
1090 # FileField ################################################################### |
|
1091 |
|
1092 # File forms. |
|
1093 |
|
1094 >>> class TextFileForm(ModelForm): |
|
1095 ... class Meta: |
|
1096 ... model = TextFile |
|
1097 |
|
1098 # Test conditions when files is either not given or empty. |
|
1099 |
|
1100 >>> f = TextFileForm(data={'description': u'Assistance'}) |
|
1101 >>> f.is_valid() |
|
1102 False |
|
1103 >>> f = TextFileForm(data={'description': u'Assistance'}, files={}) |
|
1104 >>> f.is_valid() |
|
1105 False |
|
1106 |
|
1107 # Upload a file and ensure it all works as expected. |
|
1108 |
|
1109 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test1.txt', 'hello world')}) |
|
1110 >>> f.is_valid() |
|
1111 True |
|
1112 >>> type(f.cleaned_data['file']) |
|
1113 <class 'django.core.files.uploadedfile.SimpleUploadedFile'> |
|
1114 >>> instance = f.save() |
|
1115 >>> instance.file |
|
1116 <FieldFile: tests/test1.txt> |
|
1117 |
|
1118 >>> instance.file.delete() |
|
1119 |
|
1120 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test1.txt', 'hello world')}) |
|
1121 >>> f.is_valid() |
|
1122 True |
|
1123 >>> type(f.cleaned_data['file']) |
|
1124 <class 'django.core.files.uploadedfile.SimpleUploadedFile'> |
|
1125 >>> instance = f.save() |
|
1126 >>> instance.file |
|
1127 <FieldFile: tests/test1.txt> |
|
1128 |
|
1129 # Check if the max_length attribute has been inherited from the model. |
|
1130 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test-maxlength.txt', 'hello world')}) |
|
1131 >>> f.is_valid() |
|
1132 False |
|
1133 |
|
1134 # Edit an instance that already has the file defined in the model. This will not |
|
1135 # save the file again, but leave it exactly as it is. |
|
1136 |
|
1137 >>> f = TextFileForm(data={'description': u'Assistance'}, instance=instance) |
|
1138 >>> f.is_valid() |
|
1139 True |
|
1140 >>> f.cleaned_data['file'] |
|
1141 <FieldFile: tests/test1.txt> |
|
1142 >>> instance = f.save() |
|
1143 >>> instance.file |
|
1144 <FieldFile: tests/test1.txt> |
|
1145 |
|
1146 # Delete the current file since this is not done by Django. |
|
1147 >>> instance.file.delete() |
|
1148 |
|
1149 # Override the file by uploading a new one. |
|
1150 |
|
1151 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test2.txt', 'hello world')}, instance=instance) |
|
1152 >>> f.is_valid() |
|
1153 True |
|
1154 >>> instance = f.save() |
|
1155 >>> instance.file |
|
1156 <FieldFile: tests/test2.txt> |
|
1157 |
|
1158 # Delete the current file since this is not done by Django. |
|
1159 >>> instance.file.delete() |
|
1160 |
|
1161 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test2.txt', 'hello world')}) |
|
1162 >>> f.is_valid() |
|
1163 True |
|
1164 >>> instance = f.save() |
|
1165 >>> instance.file |
|
1166 <FieldFile: tests/test2.txt> |
|
1167 |
|
1168 # Delete the current file since this is not done by Django. |
|
1169 >>> instance.file.delete() |
|
1170 |
|
1171 >>> instance.delete() |
|
1172 |
|
1173 # Test the non-required FileField |
|
1174 >>> f = TextFileForm(data={'description': u'Assistance'}) |
|
1175 >>> f.fields['file'].required = False |
|
1176 >>> f.is_valid() |
|
1177 True |
|
1178 >>> instance = f.save() |
|
1179 >>> instance.file |
|
1180 <FieldFile: None> |
|
1181 |
|
1182 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test3.txt', 'hello world')}, instance=instance) |
|
1183 >>> f.is_valid() |
|
1184 True |
|
1185 >>> instance = f.save() |
|
1186 >>> instance.file |
|
1187 <FieldFile: tests/test3.txt> |
|
1188 |
|
1189 # Instance can be edited w/out re-uploading the file and existing file should be preserved. |
|
1190 |
|
1191 >>> f = TextFileForm(data={'description': u'New Description'}, instance=instance) |
|
1192 >>> f.fields['file'].required = False |
|
1193 >>> f.is_valid() |
|
1194 True |
|
1195 >>> instance = f.save() |
|
1196 >>> instance.description |
|
1197 u'New Description' |
|
1198 >>> instance.file |
|
1199 <FieldFile: tests/test3.txt> |
|
1200 |
|
1201 # Delete the current file since this is not done by Django. |
|
1202 >>> instance.file.delete() |
|
1203 >>> instance.delete() |
|
1204 |
|
1205 >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test3.txt', 'hello world')}) |
|
1206 >>> f.is_valid() |
|
1207 True |
|
1208 >>> instance = f.save() |
|
1209 >>> instance.file |
|
1210 <FieldFile: tests/test3.txt> |
|
1211 |
|
1212 # Delete the current file since this is not done by Django. |
|
1213 >>> instance.file.delete() |
|
1214 >>> instance.delete() |
|
1215 |
|
1216 # BigIntegerField ################################################################ |
|
1217 >>> class BigIntForm(forms.ModelForm): |
|
1218 ... class Meta: |
|
1219 ... model = BigInt |
|
1220 ... |
|
1221 >>> bif = BigIntForm({'biggie': '-9223372036854775808'}) |
|
1222 >>> bif.is_valid() |
|
1223 True |
|
1224 >>> bif = BigIntForm({'biggie': '-9223372036854775809'}) |
|
1225 >>> bif.is_valid() |
|
1226 False |
|
1227 >>> bif.errors |
|
1228 {'biggie': [u'Ensure this value is greater than or equal to -9223372036854775808.']} |
|
1229 >>> bif = BigIntForm({'biggie': '9223372036854775807'}) |
|
1230 >>> bif.is_valid() |
|
1231 True |
|
1232 >>> bif = BigIntForm({'biggie': '9223372036854775808'}) |
|
1233 >>> bif.is_valid() |
|
1234 False |
|
1235 >>> bif.errors |
|
1236 {'biggie': [u'Ensure this value is less than or equal to 9223372036854775807.']} |
|
1237 """} |
|
1238 |
|
1239 if test_images: |
|
1240 __test__['API_TESTS'] += """ |
|
1241 # ImageField ################################################################### |
|
1242 |
|
1243 # ImageField and FileField are nearly identical, but they differ slighty when |
|
1244 # it comes to validation. This specifically tests that #6302 is fixed for |
|
1245 # both file fields and image fields. |
|
1246 |
|
1247 >>> class ImageFileForm(ModelForm): |
|
1248 ... class Meta: |
|
1249 ... model = ImageFile |
|
1250 |
|
1251 >>> image_data = open(os.path.join(os.path.dirname(__file__), "test.png"), 'rb').read() |
|
1252 >>> image_data2 = open(os.path.join(os.path.dirname(__file__), "test2.png"), 'rb').read() |
|
1253 |
|
1254 >>> f = ImageFileForm(data={'description': u'An image'}, files={'image': SimpleUploadedFile('test.png', image_data)}) |
|
1255 >>> f.is_valid() |
|
1256 True |
|
1257 >>> type(f.cleaned_data['image']) |
|
1258 <class 'django.core.files.uploadedfile.SimpleUploadedFile'> |
|
1259 >>> instance = f.save() |
|
1260 >>> instance.image |
|
1261 <...FieldFile: tests/test.png> |
|
1262 >>> instance.width |
|
1263 16 |
|
1264 >>> instance.height |
|
1265 16 |
|
1266 |
|
1267 # Delete the current file since this is not done by Django, but don't save |
|
1268 # because the dimension fields are not null=True. |
|
1269 >>> instance.image.delete(save=False) |
|
1270 |
|
1271 >>> f = ImageFileForm(data={'description': u'An image'}, files={'image': SimpleUploadedFile('test.png', image_data)}) |
|
1272 >>> f.is_valid() |
|
1273 True |
|
1274 >>> type(f.cleaned_data['image']) |
|
1275 <class 'django.core.files.uploadedfile.SimpleUploadedFile'> |
|
1276 >>> instance = f.save() |
|
1277 >>> instance.image |
|
1278 <...FieldFile: tests/test.png> |
|
1279 >>> instance.width |
|
1280 16 |
|
1281 >>> instance.height |
|
1282 16 |
|
1283 |
|
1284 # Edit an instance that already has the (required) image defined in the model. This will not |
|
1285 # save the image again, but leave it exactly as it is. |
|
1286 |
|
1287 >>> f = ImageFileForm(data={'description': u'Look, it changed'}, instance=instance) |
|
1288 >>> f.is_valid() |
|
1289 True |
|
1290 >>> f.cleaned_data['image'] |
|
1291 <...FieldFile: tests/test.png> |
|
1292 >>> instance = f.save() |
|
1293 >>> instance.image |
|
1294 <...FieldFile: tests/test.png> |
|
1295 >>> instance.height |
|
1296 16 |
|
1297 >>> instance.width |
|
1298 16 |
|
1299 |
|
1300 # Delete the current file since this is not done by Django, but don't save |
|
1301 # because the dimension fields are not null=True. |
|
1302 >>> instance.image.delete(save=False) |
|
1303 |
|
1304 # Override the file by uploading a new one. |
|
1305 |
|
1306 >>> f = ImageFileForm(data={'description': u'Changed it'}, files={'image': SimpleUploadedFile('test2.png', image_data2)}, instance=instance) |
|
1307 >>> f.is_valid() |
|
1308 True |
|
1309 >>> instance = f.save() |
|
1310 >>> instance.image |
|
1311 <...FieldFile: tests/test2.png> |
|
1312 >>> instance.height |
|
1313 32 |
|
1314 >>> instance.width |
|
1315 48 |
|
1316 |
|
1317 # Delete the current file since this is not done by Django, but don't save |
|
1318 # because the dimension fields are not null=True. |
|
1319 >>> instance.image.delete(save=False) |
|
1320 >>> instance.delete() |
|
1321 |
|
1322 >>> f = ImageFileForm(data={'description': u'Changed it'}, files={'image': SimpleUploadedFile('test2.png', image_data2)}) |
|
1323 >>> f.is_valid() |
|
1324 True |
|
1325 >>> instance = f.save() |
|
1326 >>> instance.image |
|
1327 <...FieldFile: tests/test2.png> |
|
1328 >>> instance.height |
|
1329 32 |
|
1330 >>> instance.width |
|
1331 48 |
|
1332 |
|
1333 # Delete the current file since this is not done by Django, but don't save |
|
1334 # because the dimension fields are not null=True. |
|
1335 >>> instance.image.delete(save=False) |
|
1336 >>> instance.delete() |
|
1337 |
|
1338 # Test the non-required ImageField |
|
1339 |
|
1340 >>> class OptionalImageFileForm(ModelForm): |
|
1341 ... class Meta: |
|
1342 ... model = OptionalImageFile |
|
1343 |
|
1344 >>> f = OptionalImageFileForm(data={'description': u'Test'}) |
|
1345 >>> f.is_valid() |
|
1346 True |
|
1347 >>> instance = f.save() |
|
1348 >>> instance.image |
|
1349 <...FieldFile: None> |
|
1350 >>> instance.width |
|
1351 >>> instance.height |
|
1352 |
|
1353 >>> f = OptionalImageFileForm(data={'description': u'And a final one'}, files={'image': SimpleUploadedFile('test3.png', image_data)}, instance=instance) |
|
1354 >>> f.is_valid() |
|
1355 True |
|
1356 >>> instance = f.save() |
|
1357 >>> instance.image |
|
1358 <...FieldFile: tests/test3.png> |
|
1359 >>> instance.width |
|
1360 16 |
|
1361 >>> instance.height |
|
1362 16 |
|
1363 |
|
1364 # Editing the instance without re-uploading the image should not affect the image or its width/height properties |
|
1365 >>> f = OptionalImageFileForm(data={'description': u'New Description'}, instance=instance) |
|
1366 >>> f.is_valid() |
|
1367 True |
|
1368 >>> instance = f.save() |
|
1369 >>> instance.description |
|
1370 u'New Description' |
|
1371 >>> instance.image |
|
1372 <...FieldFile: tests/test3.png> |
|
1373 >>> instance.width |
|
1374 16 |
|
1375 >>> instance.height |
|
1376 16 |
|
1377 |
|
1378 # Delete the current file since this is not done by Django. |
|
1379 >>> instance.image.delete() |
|
1380 >>> instance.delete() |
|
1381 |
|
1382 >>> f = OptionalImageFileForm(data={'description': u'And a final one'}, files={'image': SimpleUploadedFile('test4.png', image_data2)}) |
|
1383 >>> f.is_valid() |
|
1384 True |
|
1385 >>> instance = f.save() |
|
1386 >>> instance.image |
|
1387 <...FieldFile: tests/test4.png> |
|
1388 >>> instance.width |
|
1389 48 |
|
1390 >>> instance.height |
|
1391 32 |
|
1392 >>> instance.delete() |
|
1393 |
|
1394 # Test callable upload_to behavior that's dependent on the value of another field in the model |
|
1395 >>> f = ImageFileForm(data={'description': u'And a final one', 'path': 'foo'}, files={'image': SimpleUploadedFile('test4.png', image_data)}) |
|
1396 >>> f.is_valid() |
|
1397 True |
|
1398 >>> instance = f.save() |
|
1399 >>> instance.image |
|
1400 <...FieldFile: foo/test4.png> |
|
1401 >>> instance.delete() |
|
1402 """ |
|
1403 |
|
1404 __test__['API_TESTS'] += """ |
|
1405 |
|
1406 # Media on a ModelForm ######################################################## |
|
1407 |
|
1408 # Similar to a regular Form class you can define custom media to be used on |
|
1409 # the ModelForm. |
|
1410 |
|
1411 >>> class ModelFormWithMedia(ModelForm): |
|
1412 ... class Media: |
|
1413 ... js = ('/some/form/javascript',) |
|
1414 ... css = { |
|
1415 ... 'all': ('/some/form/css',) |
|
1416 ... } |
|
1417 ... class Meta: |
|
1418 ... model = PhoneNumber |
|
1419 >>> f = ModelFormWithMedia() |
|
1420 >>> print f.media |
|
1421 <link href="/some/form/css" type="text/css" media="all" rel="stylesheet" /> |
|
1422 <script type="text/javascript" src="/some/form/javascript"></script> |
|
1423 |
|
1424 >>> class CommaSeparatedIntegerForm(ModelForm): |
|
1425 ... class Meta: |
|
1426 ... model = CommaSeparatedInteger |
|
1427 |
|
1428 >>> f = CommaSeparatedIntegerForm({'field': '1,2,3'}) |
|
1429 >>> f.is_valid() |
|
1430 True |
|
1431 >>> f.cleaned_data |
|
1432 {'field': u'1,2,3'} |
|
1433 >>> f = CommaSeparatedIntegerForm({'field': '1a,2'}) |
|
1434 >>> f.errors |
|
1435 {'field': [u'Enter only digits separated by commas.']} |
|
1436 >>> f = CommaSeparatedIntegerForm({'field': ',,,,'}) |
|
1437 >>> f.is_valid() |
|
1438 True |
|
1439 >>> f.cleaned_data |
|
1440 {'field': u',,,,'} |
|
1441 >>> f = CommaSeparatedIntegerForm({'field': '1.2'}) |
|
1442 >>> f.errors |
|
1443 {'field': [u'Enter only digits separated by commas.']} |
|
1444 >>> f = CommaSeparatedIntegerForm({'field': '1,a,2'}) |
|
1445 >>> f.errors |
|
1446 {'field': [u'Enter only digits separated by commas.']} |
|
1447 >>> f = CommaSeparatedIntegerForm({'field': '1,,2'}) |
|
1448 >>> f.is_valid() |
|
1449 True |
|
1450 >>> f.cleaned_data |
|
1451 {'field': u'1,,2'} |
|
1452 >>> f = CommaSeparatedIntegerForm({'field': '1'}) |
|
1453 >>> f.is_valid() |
|
1454 True |
|
1455 >>> f.cleaned_data |
|
1456 {'field': u'1'} |
|
1457 |
|
1458 This Price instance generated by this form is not valid because the quantity |
|
1459 field is required, but the form is valid because the field is excluded from |
|
1460 the form. This is for backwards compatibility. |
|
1461 |
|
1462 >>> class PriceForm(ModelForm): |
|
1463 ... class Meta: |
|
1464 ... model = Price |
|
1465 ... exclude = ('quantity',) |
|
1466 >>> form = PriceForm({'price': '6.00'}) |
|
1467 >>> form.is_valid() |
|
1468 True |
|
1469 >>> price = form.save(commit=False) |
|
1470 >>> price.full_clean() |
|
1471 Traceback (most recent call last): |
|
1472 ... |
|
1473 ValidationError: {'quantity': [u'This field cannot be null.']} |
|
1474 |
|
1475 The form should not validate fields that it doesn't contain even if they are |
|
1476 specified using 'fields', not 'exclude'. |
|
1477 ... class Meta: |
|
1478 ... model = Price |
|
1479 ... fields = ('price',) |
|
1480 >>> form = PriceForm({'price': '6.00'}) |
|
1481 >>> form.is_valid() |
|
1482 True |
|
1483 |
|
1484 The form should still have an instance of a model that is not complete and |
|
1485 not saved into a DB yet. |
|
1486 |
|
1487 >>> form.instance.price |
|
1488 Decimal('6.00') |
|
1489 >>> form.instance.quantity is None |
|
1490 True |
|
1491 >>> form.instance.pk is None |
|
1492 True |
|
1493 |
|
1494 # Choices on CharField and IntegerField |
|
1495 >>> class ArticleForm(ModelForm): |
|
1496 ... class Meta: |
|
1497 ... model = Article |
|
1498 >>> f = ArticleForm() |
|
1499 >>> f.fields['status'].clean('42') |
|
1500 Traceback (most recent call last): |
|
1501 ... |
|
1502 ValidationError: [u'Select a valid choice. 42 is not one of the available choices.'] |
|
1503 |
|
1504 >>> class ArticleStatusForm(ModelForm): |
|
1505 ... class Meta: |
|
1506 ... model = ArticleStatus |
|
1507 >>> f = ArticleStatusForm() |
|
1508 >>> f.fields['status'].clean('z') |
|
1509 Traceback (most recent call last): |
|
1510 ... |
|
1511 ValidationError: [u'Select a valid choice. z is not one of the available choices.'] |
|
1512 |
|
1513 # Foreign keys which use to_field ############################################# |
|
1514 |
|
1515 >>> apple = Inventory.objects.create(barcode=86, name='Apple') |
|
1516 >>> pear = Inventory.objects.create(barcode=22, name='Pear') |
|
1517 >>> core = Inventory.objects.create(barcode=87, name='Core', parent=apple) |
|
1518 |
|
1519 >>> field = ModelChoiceField(Inventory.objects.all(), to_field_name='barcode') |
|
1520 >>> for choice in field.choices: |
|
1521 ... print choice |
|
1522 (u'', u'---------') |
|
1523 (86, u'Apple') |
|
1524 (22, u'Pear') |
|
1525 (87, u'Core') |
|
1526 |
|
1527 >>> class InventoryForm(ModelForm): |
|
1528 ... class Meta: |
|
1529 ... model = Inventory |
|
1530 >>> form = InventoryForm(instance=core) |
|
1531 >>> print form['parent'] |
|
1532 <select name="parent" id="id_parent"> |
|
1533 <option value="">---------</option> |
|
1534 <option value="86" selected="selected">Apple</option> |
|
1535 <option value="22">Pear</option> |
|
1536 <option value="87">Core</option> |
|
1537 </select> |
|
1538 |
|
1539 >>> data = model_to_dict(core) |
|
1540 >>> data['parent'] = '22' |
|
1541 >>> form = InventoryForm(data=data, instance=core) |
|
1542 >>> core = form.save() |
|
1543 >>> core.parent |
|
1544 <Inventory: Pear> |
|
1545 |
|
1546 >>> class CategoryForm(ModelForm): |
|
1547 ... description = forms.CharField() |
|
1548 ... class Meta: |
|
1549 ... model = Category |
|
1550 ... fields = ['description', 'url'] |
|
1551 |
|
1552 >>> CategoryForm.base_fields.keys() |
|
1553 ['description', 'url'] |
|
1554 |
|
1555 >>> print CategoryForm() |
|
1556 <tr><th><label for="id_description">Description:</label></th><td><input type="text" name="description" id="id_description" /></td></tr> |
|
1557 <tr><th><label for="id_url">The URL:</label></th><td><input id="id_url" type="text" name="url" maxlength="40" /></td></tr> |
|
1558 |
|
1559 # Model field that returns None to exclude itself with explicit fields ######## |
|
1560 |
|
1561 >>> class CustomFieldForExclusionForm(ModelForm): |
|
1562 ... class Meta: |
|
1563 ... model = CustomFieldForExclusionModel |
|
1564 ... fields = ['name', 'markup'] |
|
1565 |
|
1566 >>> CustomFieldForExclusionForm.base_fields.keys() |
|
1567 ['name'] |
|
1568 |
|
1569 >>> print CustomFieldForExclusionForm() |
|
1570 <tr><th><label for="id_name">Name:</label></th><td><input id="id_name" type="text" name="name" maxlength="10" /></td></tr> |
|
1571 |
|
1572 # Clean up |
|
1573 >>> import shutil |
|
1574 >>> shutil.rmtree(temp_storage_dir) |
|
1575 """ |