From aa5506d6f69180820db8f1811b4c8780d39a5733 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 10 Jun 2011 14:08:51 +0000 Subject: [PATCH] =?UTF-8?q?Fixed=20#16198=20--=20Ported=20the=20model=5Ffo?= =?UTF-8?q?rms=20doctests.=20Our=20long=20national=20nightmare=20is=20over?= =?UTF-8?q?.=20Oh,=20and=20we=20have=20always=20been=20at=20war=20with=20d?= =?UTF-8?q?octests.=20Thanks=20to=20Peter=20van=20Kampen=20and=20Gregor=20?= =?UTF-8?q?M=C3=BCllegger=20for=20the=20patch.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git-svn-id: http://code.djangoproject.com/svn/django/trunk@16358 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 1 + tests/modeltests/model_forms/mforms.py | 44 - tests/modeltests/model_forms/models.py | 1364 ------------------------ tests/modeltests/model_forms/tests.py | 1289 +++++++++++++++++++++- 4 files changed, 1283 insertions(+), 1415 deletions(-) delete mode 100644 tests/modeltests/model_forms/mforms.py diff --git a/AUTHORS b/AUTHORS index 1a79d26297..8cb71c1280 100644 --- a/AUTHORS +++ b/AUTHORS @@ -262,6 +262,7 @@ answer newbie questions, and generally made Django that much better: junzhang.jn@gmail.com Xia Kai Antti Kaihola + Peter van Kampen Bahadır Kandemir Karderio Nagy Károly diff --git a/tests/modeltests/model_forms/mforms.py b/tests/modeltests/model_forms/mforms.py deleted file mode 100644 index 140e8073e7..0000000000 --- a/tests/modeltests/model_forms/mforms.py +++ /dev/null @@ -1,44 +0,0 @@ -from django import forms -from django.forms import ModelForm - -from models import (Product, Price, Book, DerivedBook, ExplicitPK, Post, - DerivedPost, Writer, FlexibleDatePost) - -class ProductForm(ModelForm): - class Meta: - model = Product - -class PriceForm(ModelForm): - class Meta: - model = Price - -class BookForm(ModelForm): - class Meta: - model = Book - -class DerivedBookForm(ModelForm): - class Meta: - model = DerivedBook - -class ExplicitPKForm(ModelForm): - class Meta: - model = ExplicitPK - fields = ('key', 'desc',) - -class PostForm(ModelForm): - class Meta: - model = Post - -class DerivedPostForm(ModelForm): - class Meta: - model = DerivedPost - -class CustomWriterForm(ModelForm): - name = forms.CharField(required=False) - - class Meta: - model = Writer - -class FlexDatePostForm(ModelForm): - class Meta: - model = FlexibleDatePost diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py index 6cd1a72c8a..7204b285b7 100644 --- a/tests/modeltests/model_forms/models.py +++ b/tests/modeltests/model_forms/models.py @@ -247,1367 +247,3 @@ class FlexibleDatePost(models.Model): slug = models.CharField(max_length=50, unique_for_year='posted', blank=True) subtitle = models.CharField(max_length=50, unique_for_month='posted', blank=True) posted = models.DateField(blank=True, null=True) - -__test__ = {'API_TESTS': """ ->>> from django import forms ->>> from django.forms.models import ModelForm, model_to_dict ->>> from django.core.files.uploadedfile import SimpleUploadedFile - -The bare bones, absolutely nothing custom, basic case. - ->>> class CategoryForm(ModelForm): -... class Meta: -... model = Category ->>> CategoryForm.base_fields.keys() -['name', 'slug', 'url'] - - -Extra fields. - ->>> class CategoryForm(ModelForm): -... some_extra_field = forms.BooleanField() -... -... class Meta: -... model = Category - ->>> CategoryForm.base_fields.keys() -['name', 'slug', 'url', 'some_extra_field'] - -Extra field that has a name collision with a related object accessor. - ->>> class WriterForm(ModelForm): -... book = forms.CharField(required=False) -... -... class Meta: -... model = Writer - ->>> wf = WriterForm({'name': 'Richard Lockridge'}) ->>> wf.is_valid() -True - -Replacing a field. - ->>> class CategoryForm(ModelForm): -... url = forms.BooleanField() -... -... class Meta: -... model = Category - ->>> CategoryForm.base_fields['url'].__class__ - - - -Using 'fields'. - ->>> class CategoryForm(ModelForm): -... -... class Meta: -... model = Category -... fields = ['url'] - ->>> CategoryForm.base_fields.keys() -['url'] - - -Using 'exclude' - ->>> class CategoryForm(ModelForm): -... -... class Meta: -... model = Category -... exclude = ['url'] - ->>> CategoryForm.base_fields.keys() -['name', 'slug'] - - -Using 'fields' *and* 'exclude'. Not sure why you'd want to do this, but uh, -"be liberal in what you accept" and all. - ->>> class CategoryForm(ModelForm): -... -... class Meta: -... model = Category -... fields = ['name', 'url'] -... exclude = ['url'] - ->>> CategoryForm.base_fields.keys() -['name'] - -Using 'widgets' - ->>> class CategoryForm(ModelForm): -... -... class Meta: -... model = Category -... fields = ['name', 'url', 'slug'] -... widgets = { -... 'name': forms.Textarea, -... 'url': forms.TextInput(attrs={'class': 'url'}) -... } - ->>> str(CategoryForm()['name']) -'' - ->>> str(CategoryForm()['url']) -'' - ->>> str(CategoryForm()['slug']) -'' - -Don't allow more than one 'model' definition in the inheritance hierarchy. -Technically, it would generate a valid form, but the fact that the resulting -save method won't deal with multiple objects is likely to trip up people not -familiar with the mechanics. - ->>> class CategoryForm(ModelForm): -... class Meta: -... model = Category - ->>> class OddForm(CategoryForm): -... class Meta: -... model = Article - -OddForm is now an Article-related thing, because BadForm.Meta overrides -CategoryForm.Meta. ->>> OddForm.base_fields.keys() -['headline', 'slug', 'pub_date', 'writer', 'article', 'categories', 'status'] - ->>> class ArticleForm(ModelForm): -... class Meta: -... model = Article - -First class with a Meta class wins. - ->>> class BadForm(ArticleForm, CategoryForm): -... pass ->>> OddForm.base_fields.keys() -['headline', 'slug', 'pub_date', 'writer', 'article', 'categories', 'status'] - -Subclassing without specifying a Meta on the class will use the parent's Meta -(or the first parent in the MRO if there are multiple parent classes). - ->>> class CategoryForm(ModelForm): -... class Meta: -... model = Category ->>> class SubCategoryForm(CategoryForm): -... pass ->>> SubCategoryForm.base_fields.keys() -['name', 'slug', 'url'] - -We can also subclass the Meta inner class to change the fields list. - ->>> class CategoryForm(ModelForm): -... checkbox = forms.BooleanField() -... -... class Meta: -... model = Category ->>> class SubCategoryForm(CategoryForm): -... class Meta(CategoryForm.Meta): -... exclude = ['url'] - ->>> print SubCategoryForm() - - - - -# test using fields to provide ordering to the fields ->>> class CategoryForm(ModelForm): -... class Meta: -... model = Category -... fields = ['url', 'name'] - ->>> CategoryForm.base_fields.keys() -['url', 'name'] - - ->>> print CategoryForm() - - - ->>> class CategoryForm(ModelForm): -... class Meta: -... model = Category -... fields = ['slug', 'url', 'name'] -... exclude = ['url'] - ->>> CategoryForm.base_fields.keys() -['slug', 'name'] - -# Old form_for_x tests ####################################################### - ->>> from django.forms import ModelForm, CharField ->>> import datetime - ->>> Category.objects.all() -[] - ->>> class CategoryForm(ModelForm): -... class Meta: -... model = Category ->>> f = CategoryForm() ->>> print f - - - ->>> print f.as_ul() -
  • -
  • -
  • ->>> print f['name'] - - ->>> f = CategoryForm(auto_id=False) ->>> print f.as_ul() -
  • Name:
  • -
  • Slug:
  • -
  • The URL:
  • - ->>> f = CategoryForm({'name': 'Entertainment', 'slug': 'entertainment', 'url': 'entertainment'}) ->>> f.is_valid() -True ->>> f.cleaned_data['url'] -u'entertainment' ->>> f.cleaned_data['name'] -u'Entertainment' ->>> f.cleaned_data['slug'] -u'entertainment' ->>> c1 = f.save() ->>> c1 - ->>> Category.objects.all() -[] - ->>> f = CategoryForm({'name': "It's a test", 'slug': 'its-test', 'url': 'test'}) ->>> f.is_valid() -True ->>> f.cleaned_data['url'] -u'test' ->>> f.cleaned_data['name'] -u"It's a test" ->>> f.cleaned_data['slug'] -u'its-test' ->>> c2 = f.save() ->>> c2 - ->>> Category.objects.order_by('name') -[, ] - -If you call save() with commit=False, then it will return an object that -hasn't yet been saved to the database. In this case, it's up to you to call -save() on the resulting model instance. ->>> f = CategoryForm({'name': 'Third test', 'slug': 'third-test', 'url': 'third'}) ->>> f.is_valid() -True ->>> f.cleaned_data['url'] -u'third' ->>> f.cleaned_data['name'] -u'Third test' ->>> f.cleaned_data['slug'] -u'third-test' ->>> c3 = f.save(commit=False) ->>> c3 - ->>> Category.objects.order_by('name') -[, ] ->>> c3.save() ->>> Category.objects.order_by('name') -[, , ] - -If you call save() with invalid data, you'll get a ValueError. ->>> f = CategoryForm({'name': '', 'slug': 'not a slug!', 'url': 'foo'}) ->>> f.errors['name'] -[u'This field is required.'] ->>> f.errors['slug'] -[u"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."] ->>> f.cleaned_data -Traceback (most recent call last): -... -AttributeError: 'CategoryForm' object has no attribute 'cleaned_data' ->>> f.save() -Traceback (most recent call last): -... -ValueError: The Category could not be created because the data didn't validate. ->>> f = CategoryForm({'name': '', 'slug': '', 'url': 'foo'}) ->>> f.save() -Traceback (most recent call last): -... -ValueError: The Category could not be created because the data didn't validate. - -Create a couple of Writers. ->>> w_royko = Writer(name='Mike Royko') ->>> w_royko.save() ->>> w_woodward = Writer(name='Bob Woodward') ->>> w_woodward.save() - -ManyToManyFields are represented by a MultipleChoiceField, ForeignKeys and any -fields with the 'choices' attribute are represented by a ChoiceField. ->>> class ArticleForm(ModelForm): -... class Meta: -... model = Article ->>> f = ArticleForm(auto_id=False) ->>> print f -Headline: -Slug: -Pub date: -Writer: -Article: -Categories:
    Hold down "Control", or "Command" on a Mac, to select more than one. -Status: - -You can restrict a form to a subset of the complete list of fields -by providing a 'fields' argument. If you try to save a -model created with such a form, you need to ensure that the fields -that are _not_ on the form have default values, or are allowed to have -a value of None. If a field isn't specified on a form, the object created -from the form can't provide a value for that field! ->>> class PartialArticleForm(ModelForm): -... class Meta: -... model = Article -... fields = ('headline','pub_date') ->>> f = PartialArticleForm(auto_id=False) ->>> print f -Headline: -Pub date: - -When the ModelForm is passed an instance, that instance's current values are -inserted as 'initial' data in each Field. ->>> w = Writer.objects.get(name='Mike Royko') ->>> class RoykoForm(ModelForm): -... class Meta: -... model = Writer ->>> f = RoykoForm(auto_id=False, instance=w) ->>> print f -Name:
    Use both first and last names. - ->>> art = Article(headline='Test article', slug='test-article', pub_date=datetime.date(1988, 1, 4), writer=w, article='Hello.') ->>> art.save() ->>> art_id_1 = art.id ->>> art_id_1 is not None -True ->>> class TestArticleForm(ModelForm): -... class Meta: -... model = Article ->>> f = TestArticleForm(auto_id=False, instance=art) ->>> print f.as_ul() -
  • Headline:
  • -
  • Slug:
  • -
  • Pub date:
  • -
  • Writer:
  • -
  • Article:
  • -
  • Categories: Hold down "Control", or "Command" on a Mac, to select more than one.
  • -
  • Status:
  • ->>> f = TestArticleForm({'headline': u'Test headline', 'slug': 'test-headline', 'pub_date': u'1984-02-06', 'writer': unicode(w_royko.pk), 'article': 'Hello.'}, instance=art) ->>> f.errors -{} ->>> f.is_valid() -True ->>> test_art = f.save() ->>> test_art.id == art_id_1 -True ->>> test_art = Article.objects.get(id=art_id_1) ->>> test_art.headline -u'Test headline' - -You can create a form over a subset of the available fields -by specifying a 'fields' argument to form_for_instance. ->>> class PartialArticleForm(ModelForm): -... class Meta: -... model = Article -... fields=('headline', 'slug', 'pub_date') ->>> f = PartialArticleForm({'headline': u'New headline', 'slug': 'new-headline', 'pub_date': u'1988-01-04'}, auto_id=False, instance=art) ->>> print f.as_ul() -
  • Headline:
  • -
  • Slug:
  • -
  • Pub date:
  • ->>> f.is_valid() -True ->>> new_art = f.save() ->>> new_art.id == art_id_1 -True ->>> new_art = Article.objects.get(id=art_id_1) ->>> new_art.headline -u'New headline' - -Add some categories and test the many-to-many form output. ->>> new_art.categories.all() -[] ->>> new_art.categories.add(Category.objects.get(name='Entertainment')) ->>> new_art.categories.all() -[] ->>> class TestArticleForm(ModelForm): -... class Meta: -... model = Article ->>> f = TestArticleForm(auto_id=False, instance=new_art) ->>> print f.as_ul() -
  • Headline:
  • -
  • Slug:
  • -
  • Pub date:
  • -
  • Writer:
  • -
  • Article:
  • -
  • Categories: Hold down "Control", or "Command" on a Mac, to select more than one.
  • -
  • Status:
  • - -Initial values can be provided for model forms ->>> f = TestArticleForm(auto_id=False, initial={'headline': 'Your headline here', 'categories': [str(c1.id), str(c2.id)]}) ->>> print f.as_ul() -
  • Headline:
  • -
  • Slug:
  • -
  • Pub date:
  • -
  • Writer:
  • -
  • Article:
  • -
  • Categories: Hold down "Control", or "Command" on a Mac, to select more than one.
  • -
  • Status:
  • - ->>> f = TestArticleForm({'headline': u'New headline', 'slug': u'new-headline', 'pub_date': u'1988-01-04', -... 'writer': unicode(w_royko.pk), 'article': u'Hello.', 'categories': [unicode(c1.id), unicode(c2.id)]}, instance=new_art) ->>> new_art = f.save() ->>> new_art.id == art_id_1 -True ->>> new_art = Article.objects.get(id=art_id_1) ->>> new_art.categories.order_by('name') -[, ] - -Now, submit form data with no categories. This deletes the existing categories. ->>> f = TestArticleForm({'headline': u'New headline', 'slug': u'new-headline', 'pub_date': u'1988-01-04', -... 'writer': unicode(w_royko.pk), 'article': u'Hello.'}, instance=new_art) ->>> new_art = f.save() ->>> new_art.id == art_id_1 -True ->>> new_art = Article.objects.get(id=art_id_1) ->>> new_art.categories.all() -[] - -Create a new article, with categories, via the form. ->>> class ArticleForm(ModelForm): -... class Meta: -... model = Article ->>> f = ArticleForm({'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01', -... 'writer': unicode(w_royko.pk), 'article': u'Test.', 'categories': [unicode(c1.id), unicode(c2.id)]}) ->>> new_art = f.save() ->>> art_id_2 = new_art.id ->>> art_id_2 not in (None, art_id_1) -True ->>> new_art = Article.objects.get(id=art_id_2) ->>> new_art.categories.order_by('name') -[, ] - -Create a new article, with no categories, via the form. ->>> class ArticleForm(ModelForm): -... class Meta: -... model = Article ->>> f = ArticleForm({'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01', -... 'writer': unicode(w_royko.pk), 'article': u'Test.'}) ->>> new_art = f.save() ->>> art_id_3 = new_art.id ->>> art_id_3 not in (None, art_id_1, art_id_2) -True ->>> new_art = Article.objects.get(id=art_id_3) ->>> new_art.categories.all() -[] - -Create a new article, with categories, via the form, but use commit=False. -The m2m data won't be saved until save_m2m() is invoked on the form. ->>> class ArticleForm(ModelForm): -... class Meta: -... model = Article ->>> f = ArticleForm({'headline': u'The walrus was Paul', 'slug': 'walrus-was-paul', 'pub_date': u'1967-11-01', -... 'writer': unicode(w_royko.pk), 'article': u'Test.', 'categories': [unicode(c1.id), unicode(c2.id)]}) ->>> new_art = f.save(commit=False) - -# Manually save the instance ->>> new_art.save() ->>> art_id_4 = new_art.id ->>> art_id_4 not in (None, art_id_1, art_id_2, art_id_3) -True - -# The instance doesn't have m2m data yet ->>> new_art = Article.objects.get(id=art_id_4) ->>> new_art.categories.all() -[] - -# Save the m2m data on the form ->>> f.save_m2m() ->>> new_art.categories.order_by('name') -[, ] - -Here, we define a custom ModelForm. Because it happens to have the same fields as -the Category model, we can just call the form's save() to apply its changes to an -existing Category instance. ->>> class ShortCategory(ModelForm): -... name = CharField(max_length=5) -... slug = CharField(max_length=5) -... url = CharField(max_length=3) ->>> cat = Category.objects.get(name='Third test') ->>> cat - ->>> cat.id == c3.id -True ->>> form = ShortCategory({'name': 'Third', 'slug': 'third', 'url': '3rd'}, instance=cat) ->>> form.save() - ->>> Category.objects.get(id=c3.id) - - -Here, we demonstrate that choices for a ForeignKey ChoiceField are determined -at runtime, based on the data in the database when the form is displayed, not -the data in the database when the form is instantiated. ->>> class ArticleForm(ModelForm): -... class Meta: -... model = Article ->>> f = ArticleForm(auto_id=False) ->>> print f.as_ul() -
  • Headline:
  • -
  • Slug:
  • -
  • Pub date:
  • -
  • Writer:
  • -
  • Article:
  • -
  • Categories: Hold down "Control", or "Command" on a Mac, to select more than one.
  • -
  • Status:
  • ->>> c4 = Category.objects.create(name='Fourth', url='4th') ->>> c4 - ->>> Writer.objects.create(name='Carl Bernstein') - ->>> print f.as_ul() -
  • Headline:
  • -
  • Slug:
  • -
  • Pub date:
  • -
  • Writer:
  • -
  • Article:
  • -
  • Categories: Hold down "Control", or "Command" on a Mac, to select more than one.
  • -
  • Status:
  • - -# ModelChoiceField ############################################################ - ->>> from django.forms import ModelChoiceField, ModelMultipleChoiceField - ->>> f = ModelChoiceField(Category.objects.all()) ->>> list(f.choices) -[(u'', u'---------'), (..., u'Entertainment'), (..., u"It's a test"), (..., u'Third'), (..., u'Fourth')] ->>> f.clean('') -Traceback (most recent call last): -... -ValidationError: [u'This field is required.'] ->>> f.clean(None) -Traceback (most recent call last): -... -ValidationError: [u'This field is required.'] ->>> f.clean(0) -Traceback (most recent call last): -... -ValidationError: [u'Select a valid choice. That choice is not one of the available choices.'] ->>> f.clean(c3.id) - ->>> f.clean(c2.id) - - -# Add a Category object *after* the ModelChoiceField has already been -# instantiated. This proves clean() checks the database during clean() rather -# than caching it at time of instantiation. ->>> c5 = Category.objects.create(name='Fifth', url='5th') ->>> c5 - ->>> f.clean(c5.id) - - -# Delete a Category object *after* the ModelChoiceField has already been -# instantiated. This proves clean() checks the database during clean() rather -# than caching it at time of instantiation. ->>> Category.objects.get(url='5th').delete() ->>> f.clean(c5.id) -Traceback (most recent call last): -... -ValidationError: [u'Select a valid choice. That choice is not one of the available choices.'] - ->>> f = ModelChoiceField(Category.objects.filter(pk=c1.id), required=False) ->>> print f.clean('') -None ->>> f.clean('') ->>> f.clean(str(c1.id)) - ->>> f.clean('100') -Traceback (most recent call last): -... -ValidationError: [u'Select a valid choice. That choice is not one of the available choices.'] - -# queryset can be changed after the field is created. ->>> f.queryset = Category.objects.exclude(name='Fourth') ->>> list(f.choices) -[(u'', u'---------'), (..., u'Entertainment'), (..., u"It's a test"), (..., u'Third')] ->>> f.clean(c3.id) - ->>> f.clean(c4.id) -Traceback (most recent call last): -... -ValidationError: [u'Select a valid choice. That choice is not one of the available choices.'] - -# check that we can safely iterate choices repeatedly ->>> gen_one = list(f.choices) ->>> gen_two = f.choices ->>> gen_one[2] -(..., u"It's a test") ->>> list(gen_two) -[(u'', u'---------'), (..., u'Entertainment'), (..., u"It's a test"), (..., u'Third')] - -# check that we can override the label_from_instance method to print custom labels (#4620) ->>> f.queryset = Category.objects.all() ->>> f.label_from_instance = lambda obj: "category " + str(obj) ->>> list(f.choices) -[(u'', u'---------'), (..., 'category Entertainment'), (..., "category It's a test"), (..., 'category Third'), (..., 'category Fourth')] - -# ModelMultipleChoiceField #################################################### - ->>> f = ModelMultipleChoiceField(Category.objects.all()) ->>> list(f.choices) -[(..., u'Entertainment'), (..., u"It's a test"), (..., u'Third'), (..., u'Fourth')] ->>> f.clean(None) -Traceback (most recent call last): -... -ValidationError: [u'This field is required.'] ->>> f.clean([]) -Traceback (most recent call last): -... -ValidationError: [u'This field is required.'] ->>> f.clean([c1.id]) -[] ->>> f.clean([c2.id]) -[] ->>> f.clean([str(c1.id)]) -[] ->>> f.clean([str(c1.id), str(c2.id)]) -[, ] ->>> f.clean([c1.id, str(c2.id)]) -[, ] ->>> f.clean((c1.id, str(c2.id))) -[, ] ->>> f.clean(['100']) -Traceback (most recent call last): -... -ValidationError: [u'Select a valid choice. 100 is not one of the available choices.'] ->>> f.clean('hello') -Traceback (most recent call last): -... -ValidationError: [u'Enter a list of values.'] ->>> f.clean(['fail']) -Traceback (most recent call last): -... -ValidationError: [u'"fail" is not a valid value for a primary key.'] - -# Add a Category object *after* the ModelMultipleChoiceField has already been -# instantiated. This proves clean() checks the database during clean() rather -# than caching it at time of instantiation. ->>> c6 = Category.objects.create(id=6, name='Sixth', url='6th') ->>> c6 - ->>> f.clean([c6.id]) -[] - -# Delete a Category object *after* the ModelMultipleChoiceField has already been -# instantiated. This proves clean() checks the database during clean() rather -# than caching it at time of instantiation. ->>> Category.objects.get(url='6th').delete() ->>> f.clean([c6.id]) -Traceback (most recent call last): -... -ValidationError: [u'Select a valid choice. 6 is not one of the available choices.'] - ->>> f = ModelMultipleChoiceField(Category.objects.all(), required=False) ->>> f.clean([]) -[] ->>> f.clean(()) -[] ->>> f.clean(['10']) -Traceback (most recent call last): -... -ValidationError: [u'Select a valid choice. 10 is not one of the available choices.'] ->>> f.clean([str(c3.id), '10']) -Traceback (most recent call last): -... -ValidationError: [u'Select a valid choice. 10 is not one of the available choices.'] ->>> f.clean([str(c1.id), '10']) -Traceback (most recent call last): -... -ValidationError: [u'Select a valid choice. 10 is not one of the available choices.'] - -# queryset can be changed after the field is created. ->>> f.queryset = Category.objects.exclude(name='Fourth') ->>> list(f.choices) -[(..., u'Entertainment'), (..., u"It's a test"), (..., u'Third')] ->>> f.clean([c3.id]) -[] ->>> f.clean([c4.id]) -Traceback (most recent call last): -... -ValidationError: [u'Select a valid choice. ... is not one of the available choices.'] ->>> f.clean([str(c3.id), str(c4.id)]) -Traceback (most recent call last): -... -ValidationError: [u'Select a valid choice. ... is not one of the available choices.'] - ->>> f.queryset = Category.objects.all() ->>> f.label_from_instance = lambda obj: "multicategory " + str(obj) ->>> list(f.choices) -[(..., 'multicategory Entertainment'), (..., "multicategory It's a test"), (..., 'multicategory Third'), (..., 'multicategory Fourth')] - -# OneToOneField ############################################################### - ->>> class ImprovedArticleForm(ModelForm): -... class Meta: -... model = ImprovedArticle ->>> ImprovedArticleForm.base_fields.keys() -['article'] - ->>> class ImprovedArticleWithParentLinkForm(ModelForm): -... class Meta: -... model = ImprovedArticleWithParentLink ->>> ImprovedArticleWithParentLinkForm.base_fields.keys() -[] - ->>> bw = BetterWriter(name=u'Joe Better', score=10) ->>> bw.save() ->>> sorted(model_to_dict(bw).keys()) -['id', 'name', 'score', 'writer_ptr'] - ->>> class BetterWriterForm(ModelForm): -... class Meta: -... model = BetterWriter ->>> form = BetterWriterForm({'name': 'Some Name', 'score': 12}) ->>> form.is_valid() -True ->>> bw2 = form.save() ->>> bw2.delete() - - ->>> class WriterProfileForm(ModelForm): -... class Meta: -... model = WriterProfile ->>> form = WriterProfileForm() ->>> print form.as_p() -

    -

    - ->>> data = { -... 'writer': unicode(w_woodward.pk), -... 'age': u'65', -... } ->>> form = WriterProfileForm(data) ->>> instance = form.save() ->>> instance - - ->>> form = WriterProfileForm(instance=instance) ->>> print form.as_p() -

    -

    - -# PhoneNumberField ############################################################ - ->>> class PhoneNumberForm(ModelForm): -... class Meta: -... model = PhoneNumber ->>> f = PhoneNumberForm({'phone': '(312) 555-1212', 'description': 'Assistance'}) ->>> f.is_valid() -True ->>> f.cleaned_data['phone'] -u'312-555-1212' ->>> f.cleaned_data['description'] -u'Assistance' - -# FileField ################################################################### - -# File forms. - ->>> class TextFileForm(ModelForm): -... class Meta: -... model = TextFile - -# Test conditions when files is either not given or empty. - ->>> f = TextFileForm(data={'description': u'Assistance'}) ->>> f.is_valid() -False ->>> f = TextFileForm(data={'description': u'Assistance'}, files={}) ->>> f.is_valid() -False - -# Upload a file and ensure it all works as expected. - ->>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test1.txt', 'hello world')}) ->>> f.is_valid() -True ->>> type(f.cleaned_data['file']) - ->>> instance = f.save() ->>> instance.file - - ->>> instance.file.delete() - ->>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test1.txt', 'hello world')}) ->>> f.is_valid() -True ->>> type(f.cleaned_data['file']) - ->>> instance = f.save() ->>> instance.file - - -# Check if the max_length attribute has been inherited from the model. ->>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test-maxlength.txt', 'hello world')}) ->>> f.is_valid() -False - -# Edit an instance that already has the file defined in the model. This will not -# save the file again, but leave it exactly as it is. - ->>> f = TextFileForm(data={'description': u'Assistance'}, instance=instance) ->>> f.is_valid() -True ->>> f.cleaned_data['file'] - ->>> instance = f.save() ->>> instance.file - - -# Delete the current file since this is not done by Django. ->>> instance.file.delete() - -# Override the file by uploading a new one. - ->>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test2.txt', 'hello world')}, instance=instance) ->>> f.is_valid() -True ->>> instance = f.save() ->>> instance.file - - -# Delete the current file since this is not done by Django. ->>> instance.file.delete() - ->>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test2.txt', 'hello world')}) ->>> f.is_valid() -True ->>> instance = f.save() ->>> instance.file - - -# Delete the current file since this is not done by Django. ->>> instance.file.delete() - ->>> instance.delete() - -# Test the non-required FileField ->>> f = TextFileForm(data={'description': u'Assistance'}) ->>> f.fields['file'].required = False ->>> f.is_valid() -True ->>> instance = f.save() ->>> instance.file - - ->>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test3.txt', 'hello world')}, instance=instance) ->>> f.is_valid() -True ->>> instance = f.save() ->>> instance.file - - -# Instance can be edited w/out re-uploading the file and existing file should be preserved. - ->>> f = TextFileForm(data={'description': u'New Description'}, instance=instance) ->>> f.fields['file'].required = False ->>> f.is_valid() -True ->>> instance = f.save() ->>> instance.description -u'New Description' ->>> instance.file - - -# Delete the current file since this is not done by Django. ->>> instance.file.delete() ->>> instance.delete() - ->>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test3.txt', 'hello world')}) ->>> f.is_valid() -True ->>> instance = f.save() ->>> instance.file - - -# Delete the current file since this is not done by Django. ->>> instance.file.delete() ->>> instance.delete() - -# BigIntegerField ################################################################ ->>> class BigIntForm(forms.ModelForm): -... class Meta: -... model = BigInt -... ->>> bif = BigIntForm({'biggie': '-9223372036854775808'}) ->>> bif.is_valid() -True ->>> bif = BigIntForm({'biggie': '-9223372036854775809'}) ->>> bif.is_valid() -False ->>> bif.errors -{'biggie': [u'Ensure this value is greater than or equal to -9223372036854775808.']} ->>> bif = BigIntForm({'biggie': '9223372036854775807'}) ->>> bif.is_valid() -True ->>> bif = BigIntForm({'biggie': '9223372036854775808'}) ->>> bif.is_valid() -False ->>> bif.errors -{'biggie': [u'Ensure this value is less than or equal to 9223372036854775807.']} -"""} - -if test_images: - __test__['API_TESTS'] += """ -# ImageField ################################################################### - -# ImageField and FileField are nearly identical, but they differ slighty when -# it comes to validation. This specifically tests that #6302 is fixed for -# both file fields and image fields. - ->>> class ImageFileForm(ModelForm): -... class Meta: -... model = ImageFile - ->>> image_data = open(os.path.join(os.path.dirname(__file__), "test.png"), 'rb').read() ->>> image_data2 = open(os.path.join(os.path.dirname(__file__), "test2.png"), 'rb').read() - ->>> f = ImageFileForm(data={'description': u'An image'}, files={'image': SimpleUploadedFile('test.png', image_data)}) ->>> f.is_valid() -True ->>> type(f.cleaned_data['image']) - ->>> instance = f.save() ->>> instance.image -<...FieldFile: tests/test.png> ->>> instance.width -16 ->>> instance.height -16 - -# Delete the current file since this is not done by Django, but don't save -# because the dimension fields are not null=True. ->>> instance.image.delete(save=False) - ->>> f = ImageFileForm(data={'description': u'An image'}, files={'image': SimpleUploadedFile('test.png', image_data)}) ->>> f.is_valid() -True ->>> type(f.cleaned_data['image']) - ->>> instance = f.save() ->>> instance.image -<...FieldFile: tests/test.png> ->>> instance.width -16 ->>> instance.height -16 - -# Edit an instance that already has the (required) image defined in the model. This will not -# save the image again, but leave it exactly as it is. - ->>> f = ImageFileForm(data={'description': u'Look, it changed'}, instance=instance) ->>> f.is_valid() -True ->>> f.cleaned_data['image'] -<...FieldFile: tests/test.png> ->>> instance = f.save() ->>> instance.image -<...FieldFile: tests/test.png> ->>> instance.height -16 ->>> instance.width -16 - -# Delete the current file since this is not done by Django, but don't save -# because the dimension fields are not null=True. ->>> instance.image.delete(save=False) - -# Override the file by uploading a new one. - ->>> f = ImageFileForm(data={'description': u'Changed it'}, files={'image': SimpleUploadedFile('test2.png', image_data2)}, instance=instance) ->>> f.is_valid() -True ->>> instance = f.save() ->>> instance.image -<...FieldFile: tests/test2.png> ->>> instance.height -32 ->>> instance.width -48 - -# Delete the current file since this is not done by Django, but don't save -# because the dimension fields are not null=True. ->>> instance.image.delete(save=False) ->>> instance.delete() - ->>> f = ImageFileForm(data={'description': u'Changed it'}, files={'image': SimpleUploadedFile('test2.png', image_data2)}) ->>> f.is_valid() -True ->>> instance = f.save() ->>> instance.image -<...FieldFile: tests/test2.png> ->>> instance.height -32 ->>> instance.width -48 - -# Delete the current file since this is not done by Django, but don't save -# because the dimension fields are not null=True. ->>> instance.image.delete(save=False) ->>> instance.delete() - -# Test the non-required ImageField - ->>> class OptionalImageFileForm(ModelForm): -... class Meta: -... model = OptionalImageFile - ->>> f = OptionalImageFileForm(data={'description': u'Test'}) ->>> f.is_valid() -True ->>> instance = f.save() ->>> instance.image -<...FieldFile: None> ->>> instance.width ->>> instance.height - ->>> f = OptionalImageFileForm(data={'description': u'And a final one'}, files={'image': SimpleUploadedFile('test3.png', image_data)}, instance=instance) ->>> f.is_valid() -True ->>> instance = f.save() ->>> instance.image -<...FieldFile: tests/test3.png> ->>> instance.width -16 ->>> instance.height -16 - -# Editing the instance without re-uploading the image should not affect the image or its width/height properties ->>> f = OptionalImageFileForm(data={'description': u'New Description'}, instance=instance) ->>> f.is_valid() -True ->>> instance = f.save() ->>> instance.description -u'New Description' ->>> instance.image -<...FieldFile: tests/test3.png> ->>> instance.width -16 ->>> instance.height -16 - -# Delete the current file since this is not done by Django. ->>> instance.image.delete() ->>> instance.delete() - ->>> f = OptionalImageFileForm(data={'description': u'And a final one'}, files={'image': SimpleUploadedFile('test4.png', image_data2)}) ->>> f.is_valid() -True ->>> instance = f.save() ->>> instance.image -<...FieldFile: tests/test4.png> ->>> instance.width -48 ->>> instance.height -32 ->>> instance.delete() - -# Test callable upload_to behavior that's dependent on the value of another field in the model ->>> f = ImageFileForm(data={'description': u'And a final one', 'path': 'foo'}, files={'image': SimpleUploadedFile('test4.png', image_data)}) ->>> f.is_valid() -True ->>> instance = f.save() ->>> instance.image -<...FieldFile: foo/test4.png> ->>> instance.delete() -""" - -__test__['API_TESTS'] += """ - -# Media on a ModelForm ######################################################## - -# Similar to a regular Form class you can define custom media to be used on -# the ModelForm. - ->>> class ModelFormWithMedia(ModelForm): -... class Media: -... js = ('/some/form/javascript',) -... css = { -... 'all': ('/some/form/css',) -... } -... class Meta: -... model = PhoneNumber ->>> f = ModelFormWithMedia() ->>> print f.media - - - ->>> class CommaSeparatedIntegerForm(ModelForm): -... class Meta: -... model = CommaSeparatedInteger - ->>> f = CommaSeparatedIntegerForm({'field': '1,2,3'}) ->>> f.is_valid() -True ->>> f.cleaned_data -{'field': u'1,2,3'} ->>> f = CommaSeparatedIntegerForm({'field': '1a,2'}) ->>> f.errors -{'field': [u'Enter only digits separated by commas.']} ->>> f = CommaSeparatedIntegerForm({'field': ',,,,'}) ->>> f.is_valid() -True ->>> f.cleaned_data -{'field': u',,,,'} ->>> f = CommaSeparatedIntegerForm({'field': '1.2'}) ->>> f.errors -{'field': [u'Enter only digits separated by commas.']} ->>> f = CommaSeparatedIntegerForm({'field': '1,a,2'}) ->>> f.errors -{'field': [u'Enter only digits separated by commas.']} ->>> f = CommaSeparatedIntegerForm({'field': '1,,2'}) ->>> f.is_valid() -True ->>> f.cleaned_data -{'field': u'1,,2'} ->>> f = CommaSeparatedIntegerForm({'field': '1'}) ->>> f.is_valid() -True ->>> f.cleaned_data -{'field': u'1'} - -This Price instance generated by this form is not valid because the quantity -field is required, but the form is valid because the field is excluded from -the form. This is for backwards compatibility. - ->>> class PriceForm(ModelForm): -... class Meta: -... model = Price -... exclude = ('quantity',) ->>> form = PriceForm({'price': '6.00'}) ->>> form.is_valid() -True ->>> price = form.save(commit=False) ->>> price.full_clean() -Traceback (most recent call last): - ... -ValidationError: {'quantity': [u'This field cannot be null.']} - -The form should not validate fields that it doesn't contain even if they are -specified using 'fields', not 'exclude'. -... class Meta: -... model = Price -... fields = ('price',) ->>> form = PriceForm({'price': '6.00'}) ->>> form.is_valid() -True - -The form should still have an instance of a model that is not complete and -not saved into a DB yet. - ->>> form.instance.price -Decimal('6.00') ->>> form.instance.quantity is None -True ->>> form.instance.pk is None -True - -# Choices on CharField and IntegerField ->>> class ArticleForm(ModelForm): -... class Meta: -... model = Article ->>> f = ArticleForm() ->>> f.fields['status'].clean('42') -Traceback (most recent call last): -... -ValidationError: [u'Select a valid choice. 42 is not one of the available choices.'] - ->>> class ArticleStatusForm(ModelForm): -... class Meta: -... model = ArticleStatus ->>> f = ArticleStatusForm() ->>> f.fields['status'].clean('z') -Traceback (most recent call last): -... -ValidationError: [u'Select a valid choice. z is not one of the available choices.'] - -# Foreign keys which use to_field ############################################# - ->>> apple = Inventory.objects.create(barcode=86, name='Apple') ->>> pear = Inventory.objects.create(barcode=22, name='Pear') ->>> core = Inventory.objects.create(barcode=87, name='Core', parent=apple) - ->>> field = ModelChoiceField(Inventory.objects.all(), to_field_name='barcode') ->>> for choice in field.choices: -... print choice -(u'', u'---------') -(86, u'Apple') -(87, u'Core') -(22, u'Pear') - ->>> class InventoryForm(ModelForm): -... class Meta: -... model = Inventory ->>> form = InventoryForm(instance=core) ->>> print form['parent'] - - ->>> data = model_to_dict(core) ->>> data['parent'] = '22' ->>> form = InventoryForm(data=data, instance=core) ->>> core = form.save() ->>> core.parent - - ->>> class CategoryForm(ModelForm): -... description = forms.CharField() -... class Meta: -... model = Category -... fields = ['description', 'url'] - ->>> CategoryForm.base_fields.keys() -['description', 'url'] - ->>> print CategoryForm() - - - -# to_field_name should also work on ModelMultipleChoiceField ################## - ->>> field = ModelMultipleChoiceField(Inventory.objects.all(), to_field_name='barcode') ->>> for choice in field.choices: -... print choice -(86, u'Apple') -(87, u'Core') -(22, u'Pear') ->>> field.clean([86]) -[] - ->>> class SelectInventoryForm(forms.Form): -... items = ModelMultipleChoiceField(Inventory.objects.all(), to_field_name='barcode') ->>> form = SelectInventoryForm({'items': [87, 22]}) ->>> form.is_valid() -True ->>> form.cleaned_data -{'items': [, ]} - -# Model field that returns None to exclude itself with explicit fields ######## - ->>> class CustomFieldForExclusionForm(ModelForm): -... class Meta: -... model = CustomFieldForExclusionModel -... fields = ['name', 'markup'] - ->>> CustomFieldForExclusionForm.base_fields.keys() -['name'] - ->>> print CustomFieldForExclusionForm() - - -# Clean up ->>> import shutil ->>> shutil.rmtree(temp_storage_dir) -""" diff --git a/tests/modeltests/model_forms/tests.py b/tests/modeltests/model_forms/tests.py index 33918ee88c..f90812c145 100644 --- a/tests/modeltests/model_forms/tests.py +++ b/tests/modeltests/model_forms/tests.py @@ -1,10 +1,359 @@ import datetime -from django.test import TestCase +import os +from decimal import Decimal + from django import forms -from models import Category, Writer, Book, DerivedBook, Post, FlexibleDatePost -from mforms import (ProductForm, PriceForm, BookForm, DerivedBookForm, - ExplicitPKForm, PostForm, DerivedPostForm, CustomWriterForm, - FlexDatePostForm) +from django.test import TestCase +from django.core.files.uploadedfile import SimpleUploadedFile +from django.core.validators import ValidationError +from django.forms.models import model_to_dict +from django.utils.unittest import skipUnless + +from modeltests.model_forms.models import (Article, ArticleStatus, + BetterWriter, BigInt, Book, Category, CommaSeparatedInteger, + CustomFieldForExclusionModel, DerivedBook, DerivedPost, ExplicitPK, + FlexibleDatePost, ImageFile, ImprovedArticle, + ImprovedArticleWithParentLink, Inventory, OptionalImageFile, PhoneNumber, + Post, Price, Product, TextFile, Writer, WriterProfile, + test_images) + + +class ProductForm(forms.ModelForm): + class Meta: + model = Product + + +class PriceForm(forms.ModelForm): + class Meta: + model = Price + + +class BookForm(forms.ModelForm): + class Meta: + model = Book + + +class DerivedBookForm(forms.ModelForm): + class Meta: + model = DerivedBook + + +class ExplicitPKForm(forms.ModelForm): + class Meta: + model = ExplicitPK + fields = ('key', 'desc',) + + +class PostForm(forms.ModelForm): + class Meta: + model = Post + + +class DerivedPostForm(forms.ModelForm): + class Meta: + model = DerivedPost + + +class CustomWriterForm(forms.ModelForm): + name = forms.CharField(required=False) + + class Meta: + model = Writer + + +class FlexDatePostForm(forms.ModelForm): + class Meta: + model = FlexibleDatePost + + +class BaseCategoryForm(forms.ModelForm): + class Meta: + model = Category + + +class ArticleForm(forms.ModelForm): + class Meta: + model = Article + + +class ArticleForm(forms.ModelForm): + class Meta: + model = Article + +class PartialArticleForm(forms.ModelForm): + class Meta: + model = Article + fields = ('headline','pub_date') + +class RoykoForm(forms.ModelForm): + class Meta: + model = Writer + +class TestArticleForm(forms.ModelForm): + class Meta: + model = Article + +class PartialArticleFormWithSlug(forms.ModelForm): + class Meta: + model = Article + fields=('headline', 'slug', 'pub_date') + +class ArticleStatusForm(forms.ModelForm): + class Meta: + model = ArticleStatus + +class InventoryForm(forms.ModelForm): + class Meta: + model = Inventory + +class SelectInventoryForm(forms.Form): + items = forms.ModelMultipleChoiceField(Inventory.objects.all(), to_field_name='barcode') + +class CustomFieldForExclusionForm(forms.ModelForm): + class Meta: + model = CustomFieldForExclusionModel + fields = ['name', 'markup'] + +class ShortCategory(forms.ModelForm): + name = forms.CharField(max_length=5) + slug = forms.CharField(max_length=5) + url = forms.CharField(max_length=3) + +class ImprovedArticleForm(forms.ModelForm): + class Meta: + model = ImprovedArticle + +class ImprovedArticleWithParentLinkForm(forms.ModelForm): + class Meta: + model = ImprovedArticleWithParentLink + +class BetterWriterForm(forms.ModelForm): + class Meta: + model = BetterWriter + +class WriterProfileForm(forms.ModelForm): + class Meta: + model = WriterProfile + +class PhoneNumberForm(forms.ModelForm): + class Meta: + model = PhoneNumber + +class TextFileForm(forms.ModelForm): + class Meta: + model = TextFile + +class BigIntForm(forms.ModelForm): + class Meta: + model = BigInt + +class ImageFileForm(forms.ModelForm): + class Meta: + model = ImageFile + +class OptionalImageFileForm(forms.ModelForm): + class Meta: + model = OptionalImageFile + +class ModelFormWithMedia(forms.ModelForm): + class Media: + js = ('/some/form/javascript',) + css = { + 'all': ('/some/form/css',) + } + class Meta: + model = PhoneNumber + +class CommaSeparatedIntegerForm(forms.ModelForm): + class Meta: + model = CommaSeparatedInteger + +class PriceFormWithoutQuantity(forms.ModelForm): + class Meta: + model = Price + exclude = ('quantity',) + + +class ModelFormBaseTest(TestCase): + def test_base_form(self): + self.assertEqual(BaseCategoryForm.base_fields.keys(), + ['name', 'slug', 'url']) + + def test_extra_fields(self): + class ExtraFields(BaseCategoryForm): + some_extra_field = forms.BooleanField() + + self.assertEqual(ExtraFields.base_fields.keys(), + ['name', 'slug', 'url', 'some_extra_field']) + + def test_replace_field(self): + class ReplaceField(forms.ModelForm): + url = forms.BooleanField() + + class Meta: + model = Category + + self.assertTrue(isinstance(ReplaceField.base_fields['url'], + forms.fields.BooleanField)) + + def test_override_field(self): + class WriterForm(forms.ModelForm): + book = forms.CharField(required=False) + + class Meta: + model = Writer + + wf = WriterForm({'name': 'Richard Lockridge'}) + self.assertTrue(wf.is_valid()) + + def test_limit_fields(self): + class LimitFields(forms.ModelForm): + class Meta: + model = Category + fields = ['url'] + + self.assertEqual(LimitFields.base_fields.keys(), + ['url']) + + def test_exclude_fields(self): + class ExcludeFields(forms.ModelForm): + class Meta: + model = Category + exclude = ['url'] + + self.assertEqual(ExcludeFields.base_fields.keys(), + ['name', 'slug']) + + def test_confused_form(self): + class ConfusedForm(forms.ModelForm): + """ Using 'fields' *and* 'exclude'. Not sure why you'd want to do + this, but uh, "be liberal in what you accept" and all. + """ + class Meta: + model = Category + fields = ['name', 'url'] + exclude = ['url'] + + self.assertEqual(ConfusedForm.base_fields.keys(), + ['name']) + + def test_mixmodel_form(self): + class MixModelForm(BaseCategoryForm): + """ Don't allow more than one 'model' definition in the + inheritance hierarchy. Technically, it would generate a valid + form, but the fact that the resulting save method won't deal with + multiple objects is likely to trip up people not familiar with the + mechanics. + """ + class Meta: + model = Article + # MixModelForm is now an Article-related thing, because MixModelForm.Meta + # overrides BaseCategoryForm.Meta. + + self.assertEqual( + MixModelForm.base_fields.keys(), + ['headline', 'slug', 'pub_date', 'writer', 'article', 'categories', 'status'] + ) + + def test_article_form(self): + self.assertEqual( + ArticleForm.base_fields.keys(), + ['headline', 'slug', 'pub_date', 'writer', 'article', 'categories', 'status'] + ) + + def test_bad_form(self): + #First class with a Meta class wins... + class BadForm(ArticleForm, BaseCategoryForm): + pass + + self.assertEqual( + BadForm.base_fields.keys(), + ['headline', 'slug', 'pub_date', 'writer', 'article', 'categories', 'status'] + ) + + def test_subcategory_form(self): + class SubCategoryForm(BaseCategoryForm): + """ Subclassing without specifying a Meta on the class will use + the parent's Meta (or the first parent in the MRO if there are + multiple parent classes). + """ + pass + + self.assertEqual(SubCategoryForm.base_fields.keys(), + ['name', 'slug', 'url']) + + def test_subclassmeta_form(self): + class SomeCategoryForm(forms.ModelForm): + checkbox = forms.BooleanField() + + class Meta: + model = Category + + class SubclassMeta(SomeCategoryForm): + """ We can also subclass the Meta inner class to change the fields + list. + """ + class Meta(SomeCategoryForm.Meta): + exclude = ['url'] + + self.assertEqual( + str(SubclassMeta()), + """ + +""" + ) + + def test_orderfields_form(self): + class OrderFields(forms.ModelForm): + class Meta: + model = Category + fields = ['url', 'name'] + + self.assertEqual(OrderFields.base_fields.keys(), + ['url', 'name']) + self.assertEqual( + str(OrderFields()), + """ +""" + ) + + def test_orderfields2_form(self): + class OrderFields2(forms.ModelForm): + class Meta: + model = Category + fields = ['slug', 'url', 'name'] + exclude = ['url'] + + self.assertEqual(OrderFields2.base_fields.keys(), + ['slug', 'name']) + + +class TestWidgetForm(forms.ModelForm): + class Meta: + model = Category + fields = ['name', 'url', 'slug'] + widgets = { + 'name': forms.Textarea, + 'url': forms.TextInput(attrs={'class': 'url'}) + } + + + +class TestWidgets(TestCase): + def test_base_widgets(self): + frm = TestWidgetForm() + self.assertEqual( + str(frm['name']), + '' + ) + self.assertEqual( + str(frm['url']), + '' + ) + self.assertEqual( + str(frm['slug']), + '' + ) class IncompleteCategoryFormWithFields(forms.ModelForm): @@ -43,6 +392,9 @@ class ValidationTest(TestCase): form = CustomWriterForm({}) assert form.is_valid() + + + # unique/unique_together validation class UniqueTest(TestCase): def setUp(self): @@ -114,10 +466,17 @@ class UniqueTest(TestCase): title = 'Boss' isbn = '12345' dbook = DerivedBook.objects.create(title=title, author=self.writer, isbn=isbn) - form = DerivedBookForm({'title': 'Other', 'author': self.writer.pk, 'isbn': '9876', 'suffix1': u'0', 'suffix2': u'0'}) + form = DerivedBookForm({ + 'title': 'Other', + 'author': self.writer.pk, + 'isbn': '9876', + 'suffix1': u'0', + 'suffix2': u'0' + }) self.assertFalse(form.is_valid()) self.assertEqual(len(form.errors), 1) - self.assertEqual(form.errors['__all__'], [u'Derived book with this Suffix1 and Suffix2 already exists.']) + self.assertEqual(form.errors['__all__'], + [u'Derived book with this Suffix1 and Suffix2 already exists.']) def test_explicitpk_unspecified(self): """Test for primary_key being in the form and failing validation.""" @@ -197,3 +556,919 @@ class UniqueTest(TestCase): form = FlexDatePostForm({'subtitle': "Finally", "title": "Django 1.0 is released", "slug": "Django 1.0"}, instance=p) self.assertTrue(form.is_valid()) + +class OldFormForXTests(TestCase): + def test_base_form(self): + self.assertEqual(Category.objects.count(), 0) + f = BaseCategoryForm() + self.assertEqual( + str(f), + """ + +""" + ) + self.assertEqual( + str(f.as_ul()), + """
  • +
  • +
  • """ + ) + self.assertEqual( + str(f["name"]), + """""") + + def test_auto_id(self): + f = BaseCategoryForm(auto_id=False) + self.assertEqual( + str(f.as_ul()), + """
  • Name:
  • +
  • Slug:
  • +
  • The URL:
  • """ + ) + + def test_with_data(self): + self.assertEqual(Category.objects.count(), 0) + f = BaseCategoryForm({'name': 'Entertainment', + 'slug': 'entertainment', + 'url': 'entertainment'}) + self.assertTrue(f.is_valid()) + self.assertEqual(f.cleaned_data['name'], 'Entertainment') + self.assertEqual(f.cleaned_data['slug'], 'entertainment') + self.assertEqual(f.cleaned_data['url'], 'entertainment') + c1 = f.save() + # Testing wether the same object is returned from the + # ORM... not the fastest way... + + self.assertEqual(c1, Category.objects.all()[0]) + self.assertEqual(c1.name, "Entertainment") + self.assertEqual(Category.objects.count(), 1) + + f = BaseCategoryForm({'name': "It's a test", + 'slug': 'its-test', + 'url': 'test'}) + self.assertTrue(f.is_valid()) + self.assertEqual(f.cleaned_data['name'], "It's a test") + self.assertEqual(f.cleaned_data['slug'], 'its-test') + self.assertEqual(f.cleaned_data['url'], 'test') + c2 = f.save() + # Testing wether the same object is returned from the + # ORM... not the fastest way... + self.assertEqual(c2, Category.objects.get(pk=c2.pk)) + self.assertEqual(c2.name, "It's a test") + self.assertEqual(Category.objects.count(), 2) + + # If you call save() with commit=False, then it will return an object that + # hasn't yet been saved to the database. In this case, it's up to you to call + # save() on the resulting model instance. + f = BaseCategoryForm({'name': 'Third test', 'slug': 'third-test', 'url': 'third'}) + self.assertEqual(f.is_valid(), True) + self.assertEqual(f.cleaned_data['url'], u'third') + self.assertEqual(f.cleaned_data['name'], u'Third test') + self.assertEqual(f.cleaned_data['slug'], u'third-test') + c3 = f.save(commit=False) + self.assertEqual(c3.name, "Third test") + self.assertEqual(Category.objects.count(), 2) + c3.save() + self.assertEqual(Category.objects.count(), 3) + + # If you call save() with invalid data, you'll get a ValueError. + f = BaseCategoryForm({'name': '', 'slug': 'not a slug!', 'url': 'foo'}) + self.assertEqual(f.errors['name'], [u'This field is required.']) + self.assertEqual(f.errors['slug'], [u"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."]) + with self.assertRaises(AttributeError): + f.cleaned_data + with self.assertRaises(ValueError): + f.save() + f = BaseCategoryForm({'name': '', 'slug': '', 'url': 'foo'}) + with self.assertRaises(ValueError): + f.save() + + # Create a couple of Writers. + w_royko = Writer(name='Mike Royko') + w_royko.save() + w_woodward = Writer(name='Bob Woodward') + w_woodward.save() + # ManyToManyFields are represented by a MultipleChoiceField, ForeignKeys and any + # fields with the 'choices' attribute are represented by a ChoiceField. + f = ArticleForm(auto_id=False) + self.assertEqual(unicode(f), '''Headline: +Slug: +Pub date: +Writer: +Article: +Categories:
    Hold down "Control", or "Command" on a Mac, to select more than one. +Status:''') + + # You can restrict a form to a subset of the complete list of fields + # by providing a 'fields' argument. If you try to save a + # model created with such a form, you need to ensure that the fields + # that are _not_ on the form have default values, or are allowed to have + # a value of None. If a field isn't specified on a form, the object created + # from the form can't provide a value for that field! + f = PartialArticleForm(auto_id=False) + self.assertEqual(unicode(f), '''Headline: +Pub date:''') + + # When the ModelForm is passed an instance, that instance's current values are + # inserted as 'initial' data in each Field. + w = Writer.objects.get(name='Mike Royko') + f = RoykoForm(auto_id=False, instance=w) + self.assertEqual(unicode(f), '''Name:
    Use both first and last names.''') + + art = Article( + headline='Test article', + slug='test-article', + pub_date=datetime.date(1988, 1, 4), + writer=w, + article='Hello.' + ) + art.save() + art_id_1 = art.id + self.assertEqual(art_id_1 is not None, True) + f = TestArticleForm(auto_id=False, instance=art) + self.assertEqual(f.as_ul(), '''
  • Headline:
  • +
  • Slug:
  • +
  • Pub date:
  • +
  • Writer:
  • +
  • Article:
  • +
  • Categories: Hold down "Control", or "Command" on a Mac, to select more than one.
  • +
  • Status:
  • ''') + f = TestArticleForm({ + 'headline': u'Test headline', + 'slug': 'test-headline', + 'pub_date': u'1984-02-06', + 'writer': unicode(w_royko.pk), + 'article': 'Hello.' + }, instance=art) + self.assertEqual(f.errors, {}) + self.assertEqual(f.is_valid(), True) + test_art = f.save() + self.assertEqual(test_art.id == art_id_1, True) + test_art = Article.objects.get(id=art_id_1) + self.assertEqual(test_art.headline, u'Test headline') + # You can create a form over a subset of the available fields + # by specifying a 'fields' argument to form_for_instance. + f = PartialArticleFormWithSlug({ + 'headline': u'New headline', + 'slug': 'new-headline', + 'pub_date': u'1988-01-04' + }, auto_id=False, instance=art) + self.assertEqual(f.as_ul(), '''
  • Headline:
  • +
  • Slug:
  • +
  • Pub date:
  • ''') + self.assertEqual(f.is_valid(), True) + new_art = f.save() + self.assertEqual(new_art.id == art_id_1, True) + new_art = Article.objects.get(id=art_id_1) + self.assertEqual(new_art.headline, u'New headline') + + # Add some categories and test the many-to-many form output. + self.assertEqual(map(lambda o: o.name, new_art.categories.all()), []) + new_art.categories.add(Category.objects.get(name='Entertainment')) + self.assertEqual(map(lambda o: o.name, new_art.categories.all()), ["Entertainment"]) + f = TestArticleForm(auto_id=False, instance=new_art) + self.assertEqual(f.as_ul(), '''
  • Headline:
  • +
  • Slug:
  • +
  • Pub date:
  • +
  • Writer:
  • +
  • Article:
  • +
  • Categories: Hold down "Control", or "Command" on a Mac, to select more than one.
  • +
  • Status:
  • ''') + + # Initial values can be provided for model forms + f = TestArticleForm( + auto_id=False, + initial={ + 'headline': 'Your headline here', + 'categories': [str(c1.id), str(c2.id)] + }) + self.assertEqual(f.as_ul(), '''
  • Headline:
  • +
  • Slug:
  • +
  • Pub date:
  • +
  • Writer:
  • +
  • Article:
  • +
  • Categories: Hold down "Control", or "Command" on a Mac, to select more than one.
  • +
  • Status:
  • ''') + + f = TestArticleForm({ + 'headline': u'New headline', + 'slug': u'new-headline', + 'pub_date': u'1988-01-04', + 'writer': unicode(w_royko.pk), + 'article': u'Hello.', + 'categories': [unicode(c1.id), unicode(c2.id)] + }, instance=new_art) + new_art = f.save() + self.assertEqual(new_art.id == art_id_1, True) + new_art = Article.objects.get(id=art_id_1) + self.assertEqual(map(lambda o: o.name, new_art.categories.order_by('name')), + ["Entertainment", "It's a test"]) + + # Now, submit form data with no categories. This deletes the existing categories. + f = TestArticleForm({'headline': u'New headline', 'slug': u'new-headline', 'pub_date': u'1988-01-04', + 'writer': unicode(w_royko.pk), 'article': u'Hello.'}, instance=new_art) + new_art = f.save() + self.assertEqual(new_art.id == art_id_1, True) + new_art = Article.objects.get(id=art_id_1) + self.assertEqual(map(lambda o: o.name, new_art.categories.all()), []) + + # Create a new article, with categories, via the form. + f = ArticleForm({'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01', + 'writer': unicode(w_royko.pk), 'article': u'Test.', 'categories': [unicode(c1.id), unicode(c2.id)]}) + new_art = f.save() + art_id_2 = new_art.id + self.assertEqual(art_id_2 not in (None, art_id_1), True) + new_art = Article.objects.get(id=art_id_2) + self.assertEqual(map(lambda o: o.name, new_art.categories.order_by('name')), ["Entertainment", "It's a test"]) + + # Create a new article, with no categories, via the form. + f = ArticleForm({'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01', + 'writer': unicode(w_royko.pk), 'article': u'Test.'}) + new_art = f.save() + art_id_3 = new_art.id + self.assertEqual(art_id_3 not in (None, art_id_1, art_id_2), True) + new_art = Article.objects.get(id=art_id_3) + self.assertEqual(map(lambda o: o.name, new_art.categories.all()), []) + + # Create a new article, with categories, via the form, but use commit=False. + # The m2m data won't be saved until save_m2m() is invoked on the form. + f = ArticleForm({'headline': u'The walrus was Paul', 'slug': 'walrus-was-paul', 'pub_date': u'1967-11-01', + 'writer': unicode(w_royko.pk), 'article': u'Test.', 'categories': [unicode(c1.id), unicode(c2.id)]}) + new_art = f.save(commit=False) + + # Manually save the instance + new_art.save() + art_id_4 = new_art.id + self.assertEqual(art_id_4 not in (None, art_id_1, art_id_2, art_id_3), True) + + # The instance doesn't have m2m data yet + new_art = Article.objects.get(id=art_id_4) + self.assertEqual(map(lambda o: o.name, new_art.categories.all()), []) + + # Save the m2m data on the form + f.save_m2m() + self.assertEqual(map(lambda o: o.name, new_art.categories.order_by('name')), ["Entertainment", "It's a test"]) + + # Here, we define a custom ModelForm. Because it happens to have the same fields as + # the Category model, we can just call the form's save() to apply its changes to an + # existing Category instance. + cat = Category.objects.get(name='Third test') + self.assertEqual(cat.name, "Third test") + self.assertEqual(cat.id == c3.id, True) + form = ShortCategory({'name': 'Third', 'slug': 'third', 'url': '3rd'}, instance=cat) + self.assertEqual(form.save().name, 'Third') + self.assertEqual(Category.objects.get(id=c3.id).name, 'Third') + + # Here, we demonstrate that choices for a ForeignKey ChoiceField are determined + # at runtime, based on the data in the database when the form is displayed, not + # the data in the database when the form is instantiated. + f = ArticleForm(auto_id=False) + self.assertEqual(f.as_ul(), '''
  • Headline:
  • +
  • Slug:
  • +
  • Pub date:
  • +
  • Writer:
  • +
  • Article:
  • +
  • Categories: Hold down "Control", or "Command" on a Mac, to select more than one.
  • +
  • Status:
  • ''') + + c4 = Category.objects.create(name='Fourth', url='4th') + self.assertEqual(c4.name, 'Fourth') + self.assertEqual(Writer.objects.create(name='Carl Bernstein').name, 'Carl Bernstein') + self.assertEqual(f.as_ul(), '''
  • Headline:
  • +
  • Slug:
  • +
  • Pub date:
  • +
  • Writer:
  • +
  • Article:
  • +
  • Categories: Hold down "Control", or "Command" on a Mac, to select more than one.
  • +
  • Status:
  • ''') + + # ModelChoiceField ############################################################ + + f = forms.ModelChoiceField(Category.objects.all()) + self.assertEqual(list(f.choices), [ + (u'', u'---------'), + (1, u'Entertainment'), + (2, u"It's a test"), + (3, u'Third'), + (4, u'Fourth')]) + with self.assertRaises(ValidationError): + f.clean('') + with self.assertRaises(ValidationError): + f.clean(None) + with self.assertRaises(ValidationError): + f.clean(0) + self.assertEqual(f.clean(c3.id).name, 'Third') + self.assertEqual(f.clean(c2.id).name, "It's a test") + + # Add a Category object *after* the ModelChoiceField has already been + # instantiated. This proves clean() checks the database during clean() rather + # than caching it at time of instantiation. + c5 = Category.objects.create(name='Fifth', url='5th') + self.assertEqual(c5.name, 'Fifth') + self.assertEqual(f.clean(c5.id).name, 'Fifth') + + # Delete a Category object *after* the ModelChoiceField has already been + # instantiated. This proves clean() checks the database during clean() rather + # than caching it at time of instantiation. + Category.objects.get(url='5th').delete() + with self.assertRaises(ValidationError): + f.clean(c5.id) + + f = forms.ModelChoiceField(Category.objects.filter(pk=c1.id), required=False) + self.assertEqual(f.clean(''), None) + f.clean('') + self.assertEqual(f.clean(str(c1.id)).name, "Entertainment") + with self.assertRaises(ValidationError): + f.clean('100') + + # queryset can be changed after the field is created. + f.queryset = Category.objects.exclude(name='Fourth') + self.assertEqual(list(f.choices), [ + (u'', u'---------'), + (1, u'Entertainment'), + (2, u"It's a test"), + (3, u'Third')]) + self.assertEqual(f.clean(c3.id).name, 'Third') + with self.assertRaises(ValidationError): + f.clean(c4.id) + + # check that we can safely iterate choices repeatedly + gen_one = list(f.choices) + gen_two = f.choices + self.assertEqual(gen_one[2], (2, u"It's a test")) + self.assertEqual(list(gen_two), [ + (u'', u'---------'), + (1, u'Entertainment'), + (2, u"It's a test"), + (3, u'Third')]) + + # check that we can override the label_from_instance method to print custom labels (#4620) + f.queryset = Category.objects.all() + f.label_from_instance = lambda obj: "category " + str(obj) + self.assertEqual(list(f.choices), [ + (u'', u'---------'), + (1, 'category Entertainment'), + (2, "category It's a test"), + (3, 'category Third'), + (4, 'category Fourth')]) + + # ModelMultipleChoiceField #################################################### + + f = forms.ModelMultipleChoiceField(Category.objects.all()) + self.assertEqual(list(f.choices), [ + (1, u'Entertainment'), + (2, u"It's a test"), + (3, u'Third'), + (4, u'Fourth')]) + with self.assertRaises(ValidationError): + f.clean(None) + with self.assertRaises(ValidationError): + f.clean([]) + self.assertEqual(map(lambda o: o.name, f.clean([c1.id])), ["Entertainment"]) + self.assertEqual(map(lambda o: o.name, f.clean([c2.id])), ["It's a test"]) + self.assertEqual(map(lambda o: o.name, f.clean([str(c1.id)])), ["Entertainment"]) + self.assertEqual(map(lambda o: o.name, f.clean([str(c1.id), str(c2.id)])), ["Entertainment", "It's a test"]) + self.assertEqual(map(lambda o: o.name, f.clean([c1.id, str(c2.id)])), ["Entertainment", "It's a test"]) + self.assertEqual(map(lambda o: o.name, f.clean((c1.id, str(c2.id)))), ["Entertainment", "It's a test"]) + with self.assertRaises(ValidationError): + f.clean(['100']) + with self.assertRaises(ValidationError): + f.clean('hello') + with self.assertRaises(ValidationError): + f.clean(['fail']) + + # Add a Category object *after* the ModelMultipleChoiceField has already been + # instantiated. This proves clean() checks the database during clean() rather + # than caching it at time of instantiation. + c6 = Category.objects.create(id=6, name='Sixth', url='6th') + self.assertEqual(c6.name, 'Sixth') + self.assertEqual(map(lambda o: o.name, f.clean([c6.id])), ["Sixth"]) + + # Delete a Category object *after* the ModelMultipleChoiceField has already been + # instantiated. This proves clean() checks the database during clean() rather + # than caching it at time of instantiation. + Category.objects.get(url='6th').delete() + with self.assertRaises(ValidationError): + f.clean([c6.id]) + + f = forms.ModelMultipleChoiceField(Category.objects.all(), required=False) + self.assertEqual(f.clean([]), []) + self.assertEqual(f.clean(()), []) + with self.assertRaises(ValidationError): + f.clean(['10']) + with self.assertRaises(ValidationError): + f.clean([str(c3.id), '10']) + with self.assertRaises(ValidationError): + f.clean([str(c1.id), '10']) + + # queryset can be changed after the field is created. + f.queryset = Category.objects.exclude(name='Fourth') + self.assertEqual(list(f.choices), [ + (1, u'Entertainment'), + (2, u"It's a test"), + (3, u'Third')]) + self.assertEqual(map(lambda o: o.name, f.clean([c3.id])), ["Third"]) + with self.assertRaises(ValidationError): + f.clean([c4.id]) + with self.assertRaises(ValidationError): + f.clean([str(c3.id), str(c4.id)]) + + f.queryset = Category.objects.all() + f.label_from_instance = lambda obj: "multicategory " + str(obj) + self.assertEqual(list(f.choices), [ + (1, 'multicategory Entertainment'), + (2, "multicategory It's a test"), + (3, 'multicategory Third'), + (4, 'multicategory Fourth')]) + + # OneToOneField ############################################################### + + self.assertEqual(ImprovedArticleForm.base_fields.keys(), ['article']) + + self.assertEqual(ImprovedArticleWithParentLinkForm.base_fields.keys(), []) + + bw = BetterWriter(name=u'Joe Better', score=10) + bw.save() + self.assertEqual(sorted(model_to_dict(bw).keys()), + ['id', 'name', 'score', 'writer_ptr']) + + form = BetterWriterForm({'name': 'Some Name', 'score': 12}) + self.assertEqual(form.is_valid(), True) + bw2 = form.save() + bw2.delete() + + form = WriterProfileForm() + self.assertEqual(form.as_p(), '''

    +

    ''') + + data = { + 'writer': unicode(w_woodward.pk), + 'age': u'65', + } + form = WriterProfileForm(data) + instance = form.save() + self.assertEqual(unicode(instance), 'Bob Woodward is 65') + + form = WriterProfileForm(instance=instance) + self.assertEqual(form.as_p(), '''

    +

    ''') + + def test_phone_number_field(self): + f = PhoneNumberForm({'phone': '(312) 555-1212', 'description': 'Assistance'}) + self.assertEqual(f.is_valid(), True) + self.assertEqual(f.cleaned_data['phone'], u'312-555-1212') + self.assertEqual(f.cleaned_data['description'], u'Assistance') + + def test_file_field(self): + # Test conditions when files is either not given or empty. + + f = TextFileForm(data={'description': u'Assistance'}) + self.assertEqual(f.is_valid(), False) + f = TextFileForm(data={'description': u'Assistance'}, files={}) + self.assertEqual(f.is_valid(), False) + + # Upload a file and ensure it all works as expected. + + f = TextFileForm( + data={'description': u'Assistance'}, + files={'file': SimpleUploadedFile('test1.txt', 'hello world')}) + self.assertEqual(f.is_valid(), True) + self.assertEqual(type(f.cleaned_data['file']), SimpleUploadedFile) + instance = f.save() + self.assertEqual(instance.file.name, 'tests/test1.txt') + + instance.file.delete() + f = TextFileForm( + data={'description': u'Assistance'}, + files={'file': SimpleUploadedFile('test1.txt', 'hello world')}) + self.assertEqual(f.is_valid(), True) + self.assertEqual(type(f.cleaned_data['file']), SimpleUploadedFile) + instance = f.save() + self.assertEqual(instance.file.name, 'tests/test1.txt') + + # Check if the max_length attribute has been inherited from the model. + f = TextFileForm( + data={'description': u'Assistance'}, + files={'file': SimpleUploadedFile('test-maxlength.txt', 'hello world')}) + self.assertEqual(f.is_valid(), False) + + # Edit an instance that already has the file defined in the model. This will not + # save the file again, but leave it exactly as it is. + + f = TextFileForm( + data={'description': u'Assistance'}, + instance=instance) + self.assertEqual(f.is_valid(), True) + self.assertEqual(f.cleaned_data['file'].name, 'tests/test1.txt') + instance = f.save() + self.assertEqual(instance.file.name, 'tests/test1.txt') + + # Delete the current file since this is not done by Django. + instance.file.delete() + + # Override the file by uploading a new one. + + f = TextFileForm( + data={'description': u'Assistance'}, + files={'file': SimpleUploadedFile('test2.txt', 'hello world')}, instance=instance) + self.assertEqual(f.is_valid(), True) + instance = f.save() + self.assertEqual(instance.file.name, 'tests/test2.txt') + + # Delete the current file since this is not done by Django. + instance.file.delete() + f = TextFileForm( + data={'description': u'Assistance'}, + files={'file': SimpleUploadedFile('test2.txt', 'hello world')}) + self.assertEqual(f.is_valid(), True) + instance = f.save() + self.assertEqual(instance.file.name, 'tests/test2.txt') + + # Delete the current file since this is not done by Django. + instance.file.delete() + + instance.delete() + + # Test the non-required FileField + f = TextFileForm(data={'description': u'Assistance'}) + f.fields['file'].required = False + self.assertEqual(f.is_valid(), True) + instance = f.save() + self.assertEqual(instance.file.name, '') + + f = TextFileForm( + data={'description': u'Assistance'}, + files={'file': SimpleUploadedFile('test3.txt', 'hello world')}, instance=instance) + self.assertEqual(f.is_valid(), True) + instance = f.save() + self.assertEqual(instance.file.name, 'tests/test3.txt') + + # Instance can be edited w/out re-uploading the file and existing file should be preserved. + + f = TextFileForm( + data={'description': u'New Description'}, + instance=instance) + f.fields['file'].required = False + self.assertEqual(f.is_valid(), True) + instance = f.save() + self.assertEqual(instance.description, u'New Description') + self.assertEqual(instance.file.name, 'tests/test3.txt') + + # Delete the current file since this is not done by Django. + instance.file.delete() + instance.delete() + + f = TextFileForm( + data={'description': u'Assistance'}, + files={'file': SimpleUploadedFile('test3.txt', 'hello world')}) + self.assertEqual(f.is_valid(), True) + instance = f.save() + self.assertEqual(instance.file.name, 'tests/test3.txt') + + # Delete the current file since this is not done by Django. + instance.file.delete() + instance.delete() + + def test_big_integer_field(self): + bif = BigIntForm({'biggie': '-9223372036854775808'}) + self.assertEqual(bif.is_valid(), True) + bif = BigIntForm({'biggie': '-9223372036854775809'}) + self.assertEqual(bif.is_valid(), False) + self.assertEqual(bif.errors, {'biggie': [u'Ensure this value is greater than or equal to -9223372036854775808.']}) + bif = BigIntForm({'biggie': '9223372036854775807'}) + self.assertEqual(bif.is_valid(), True) + bif = BigIntForm({'biggie': '9223372036854775808'}) + self.assertEqual(bif.is_valid(), False) + self.assertEqual(bif.errors, {'biggie': [u'Ensure this value is less than or equal to 9223372036854775807.']}) + + @skipUnless(test_images, "PIL not installed") + def test_image_field(self): + # ImageField and FileField are nearly identical, but they differ slighty when + # it comes to validation. This specifically tests that #6302 is fixed for + # both file fields and image fields. + + image_data = open(os.path.join(os.path.dirname(__file__), "test.png"), 'rb').read() + image_data2 = open(os.path.join(os.path.dirname(__file__), "test2.png"), 'rb').read() + + f = ImageFileForm( + data={'description': u'An image'}, + files={'image': SimpleUploadedFile('test.png', image_data)}) + self.assertEqual(f.is_valid(), True) + self.assertEqual(type(f.cleaned_data['image']), SimpleUploadedFile) + instance = f.save() + self.assertEqual(instance.image.name, 'tests/test.png') + self.assertEqual(instance.width, 16) + self.assertEqual(instance.height, 16) + + # Delete the current file since this is not done by Django, but don't save + # because the dimension fields are not null=True. + instance.image.delete(save=False) + f = ImageFileForm( + data={'description': u'An image'}, + files={'image': SimpleUploadedFile('test.png', image_data)}) + self.assertEqual(f.is_valid(), True) + self.assertEqual(type(f.cleaned_data['image']), SimpleUploadedFile) + instance = f.save() + self.assertEqual(instance.image.name, 'tests/test.png') + self.assertEqual(instance.width, 16) + self.assertEqual(instance.height, 16) + + # Edit an instance that already has the (required) image defined in the model. This will not + # save the image again, but leave it exactly as it is. + + f = ImageFileForm(data={'description': u'Look, it changed'}, instance=instance) + self.assertEqual(f.is_valid(), True) + self.assertEqual(f.cleaned_data['image'].name, 'tests/test.png') + instance = f.save() + self.assertEqual(instance.image.name, 'tests/test.png') + self.assertEqual(instance.height, 16) + self.assertEqual(instance.width, 16) + + # Delete the current file since this is not done by Django, but don't save + # because the dimension fields are not null=True. + instance.image.delete(save=False) + # Override the file by uploading a new one. + + f = ImageFileForm( + data={'description': u'Changed it'}, + files={'image': SimpleUploadedFile('test2.png', image_data2)}, instance=instance) + self.assertEqual(f.is_valid(), True) + instance = f.save() + self.assertEqual(instance.image.name, 'tests/test2.png') + self.assertEqual(instance.height, 32) + self.assertEqual(instance.width, 48) + + # Delete the current file since this is not done by Django, but don't save + # because the dimension fields are not null=True. + instance.image.delete(save=False) + instance.delete() + + f = ImageFileForm( + data={'description': u'Changed it'}, + files={'image': SimpleUploadedFile('test2.png', image_data2)}) + self.assertEqual(f.is_valid(), True) + instance = f.save() + self.assertEqual(instance.image.name, 'tests/test2.png') + self.assertEqual(instance.height, 32) + self.assertEqual(instance.width, 48) + + # Delete the current file since this is not done by Django, but don't save + # because the dimension fields are not null=True. + instance.image.delete(save=False) + instance.delete() + + # Test the non-required ImageField + + f = OptionalImageFileForm(data={'description': u'Test'}) + self.assertEqual(f.is_valid(), True) + instance = f.save() + self.assertEqual(instance.image.name, None) + self.assertEqual(instance.width, None) + self.assertEqual(instance.height, None) + + f = OptionalImageFileForm( + data={'description': u'And a final one'}, + files={'image': SimpleUploadedFile('test3.png', image_data)}, instance=instance) + self.assertEqual(f.is_valid(), True) + instance = f.save() + self.assertEqual(instance.image.name, 'tests/test3.png') + self.assertEqual(instance.width, 16) + self.assertEqual(instance.height, 16) + + # Editing the instance without re-uploading the image should not affect the image or its width/height properties + f = OptionalImageFileForm( + data={'description': u'New Description'}, + instance=instance) + self.assertEqual(f.is_valid(), True) + instance = f.save() + self.assertEqual(instance.description, u'New Description') + self.assertEqual(instance.image.name, 'tests/test3.png') + self.assertEqual(instance.width, 16) + self.assertEqual(instance.height, 16) + + # Delete the current file since this is not done by Django. + instance.image.delete() + instance.delete() + + f = OptionalImageFileForm( + data={'description': u'And a final one'}, + files={'image': SimpleUploadedFile('test4.png', image_data2)} + ) + self.assertEqual(f.is_valid(), True) + instance = f.save() + self.assertEqual(instance.image.name, 'tests/test4.png') + self.assertEqual(instance.width, 48) + self.assertEqual(instance.height, 32) + instance.delete() + # Test callable upload_to behavior that's dependent on the value of another field in the model + f = ImageFileForm( + data={'description': u'And a final one', 'path': 'foo'}, + files={'image': SimpleUploadedFile('test4.png', image_data)}) + self.assertEqual(f.is_valid(), True) + instance = f.save() + self.assertEqual(instance.image.name, 'foo/test4.png') + instance.delete() + + def test_media_on_modelform(self): + # Similar to a regular Form class you can define custom media to be used on + # the ModelForm. + f = ModelFormWithMedia() + self.assertEqual(unicode(f.media), ''' +''') + + f = CommaSeparatedIntegerForm({'field': '1,2,3'}) + self.assertEqual(f.is_valid(), True) + self.assertEqual(f.cleaned_data, {'field': u'1,2,3'}) + f = CommaSeparatedIntegerForm({'field': '1a,2'}) + self.assertEqual(f.errors, {'field': [u'Enter only digits separated by commas.']}) + f = CommaSeparatedIntegerForm({'field': ',,,,'}) + self.assertEqual(f.is_valid(), True) + self.assertEqual(f.cleaned_data, {'field': u',,,,'}) + f = CommaSeparatedIntegerForm({'field': '1.2'}) + self.assertEqual(f.errors, {'field': [u'Enter only digits separated by commas.']}) + f = CommaSeparatedIntegerForm({'field': '1,a,2'}) + self.assertEqual(f.errors, {'field': [u'Enter only digits separated by commas.']}) + f = CommaSeparatedIntegerForm({'field': '1,,2'}) + self.assertEqual(f.is_valid(), True) + self.assertEqual(f.cleaned_data, {'field': u'1,,2'}) + f = CommaSeparatedIntegerForm({'field': '1'}) + self.assertEqual(f.is_valid(), True) + self.assertEqual(f.cleaned_data, {'field': u'1'}) + + # This Price instance generated by this form is not valid because the quantity + # field is required, but the form is valid because the field is excluded from + # the form. This is for backwards compatibility. + + form = PriceFormWithoutQuantity({'price': '6.00'}) + self.assertEqual(form.is_valid(), True) + price = form.save(commit=False) + with self.assertRaises(ValidationError): + price.full_clean() + + # The form should not validate fields that it doesn't contain even if they are + # specified using 'fields', not 'exclude'. + class Meta: + model = Price + fields = ('price',) + form = PriceFormWithoutQuantity({'price': '6.00'}) + self.assertEqual(form.is_valid(), True) + + # The form should still have an instance of a model that is not complete and + # not saved into a DB yet. + + self.assertEqual(form.instance.price, Decimal('6.00')) + self.assertEqual(form.instance.quantity is None, True) + self.assertEqual(form.instance.pk is None, True) + + # Choices on CharField and IntegerField + f = ArticleForm() + with self.assertRaises(ValidationError): + f.fields['status'].clean('42') + + f = ArticleStatusForm() + with self.assertRaises(ValidationError): + f.fields['status'].clean('z') + + def test_foreignkeys_which_use_to_field(self): + apple = Inventory.objects.create(barcode=86, name='Apple') + pear = Inventory.objects.create(barcode=22, name='Pear') + core = Inventory.objects.create(barcode=87, name='Core', parent=apple) + + field = forms.ModelChoiceField(Inventory.objects.all(), to_field_name='barcode') + self.assertEqual(tuple(field.choices), ( + (u'', u'---------'), + (86, u'Apple'), + (87, u'Core'), + (22, u'Pear'))) + + form = InventoryForm(instance=core) + self.assertEqual(unicode(form['parent']), '''''') + data = model_to_dict(core) + data['parent'] = '22' + form = InventoryForm(data=data, instance=core) + core = form.save() + self.assertEqual(core.parent.name, 'Pear') + + class CategoryForm(forms.ModelForm): + description = forms.CharField() + class Meta: + model = Category + fields = ['description', 'url'] + + self.assertEqual(CategoryForm.base_fields.keys(), + ['description', 'url']) + + self.assertEqual(unicode(CategoryForm()), ''' +''') + # to_field_name should also work on ModelMultipleChoiceField ################## + + field = forms.ModelMultipleChoiceField(Inventory.objects.all(), to_field_name='barcode') + self.assertEqual(tuple(field.choices), ((86, u'Apple'), (87, u'Core'), (22, u'Pear'))) + self.assertEqual(map(lambda o: o.name, field.clean([86])), ['Apple']) + + form = SelectInventoryForm({'items': [87, 22]}) + self.assertEqual(form.is_valid(), True) + self.assertEqual(len(form.cleaned_data), 1) + self.assertEqual(map(lambda o: o.name, form.cleaned_data['items']), ['Core', 'Pear']) + + def test_model_field_that_returns_none_to_exclude_itself_with_explicit_fields(self): + self.assertEqual(CustomFieldForExclusionForm.base_fields.keys(), ['name']) + self.assertEqual(unicode(CustomFieldForExclusionForm()), + '''''')