Ticket #16198: ticket16198_model_forms_test_refactoring.2.diff

File ticket16198_model_forms_test_refactoring.2.diff, 121.7 KB (added by Gregor Müllegger, 13 years ago)

Added Peter van Kampen to AUTHORS file, he did most of the work. Thank you!

  • AUTHORS

    diff --git a/AUTHORS b/AUTHORS
    index 8b6f654..95b16b7 100644
    a b answer newbie questions, and generally made Django that much better:  
    535535    Cheng Zhang
    536536    Zlatko Mašek <zlatko.masek@gmail.com>
    537537    Ryan Niemeyer <https://profiles.google.com/ryan.niemeyer/about>
     538    Peter van Kampen
    538539
    539540A big THANK YOU goes to:
    540541
  • deleted file tests/modeltests/model_forms/mforms.py

    diff --git a/tests/modeltests/model_forms/mforms.py b/tests/modeltests/model_forms/mforms.py
    deleted file mode 100644
    index 140e807..0000000
    + -  
    1 from django import forms
    2 from django.forms import ModelForm
    3 
    4 from models import (Product, Price, Book, DerivedBook, ExplicitPK, Post,
    5         DerivedPost, Writer, FlexibleDatePost)
    6 
    7 class ProductForm(ModelForm):
    8     class Meta:
    9         model = Product
    10 
    11 class PriceForm(ModelForm):
    12     class Meta:
    13         model = Price
    14 
    15 class BookForm(ModelForm):
    16     class Meta:
    17        model = Book
    18 
    19 class DerivedBookForm(ModelForm):
    20     class Meta:
    21         model = DerivedBook
    22 
    23 class ExplicitPKForm(ModelForm):
    24     class Meta:
    25         model = ExplicitPK
    26         fields = ('key', 'desc',)
    27 
    28 class PostForm(ModelForm):
    29     class Meta:
    30         model = Post
    31 
    32 class DerivedPostForm(ModelForm):
    33     class Meta:
    34         model = DerivedPost
    35 
    36 class CustomWriterForm(ModelForm):
    37    name = forms.CharField(required=False)
    38 
    39    class Meta:
    40        model = Writer
    41 
    42 class FlexDatePostForm(ModelForm):
    43     class Meta:
    44         model = FlexibleDatePost
  • tests/modeltests/model_forms/models.py

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

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