1
from __future__ import absolute_import, unicode_literals
5
from decimal import Decimal
8
from django import forms
9
from django.core.exceptions import FieldError
10
from django.core.files.uploadedfile import SimpleUploadedFile
11
from django.core.validators import ValidationError
12
from django.db import connection
13
from django.db.models.query import EmptyQuerySet
14
from django.forms.models import model_to_dict
15
from django.utils._os import upath
16
from django.utils.unittest import skipUnless
17
from django.test import TestCase
18
from django.utils import six
20
from .models import (Article, ArticleStatus, BetterWriter, BigInt, Book,
21
Category, CommaSeparatedInteger, CustomFieldForExclusionModel, DerivedBook,
22
DerivedPost, ExplicitPK, FlexibleDatePost, ImprovedArticle,
23
ImprovedArticleWithParentLink, Inventory, Post, Price,
24
Product, TextFile, Writer, WriterProfile, Colour, ColourfulItem,
25
ArticleStatusNote, DateTimePost, CustomErrorMessage, test_images)
28
from .models import ImageFile, OptionalImageFile
29
class ImageFileForm(forms.ModelForm):
35
class OptionalImageFileForm(forms.ModelForm):
37
model = OptionalImageFile
41
class ProductForm(forms.ModelForm):
47
class PriceForm(forms.ModelForm):
53
class BookForm(forms.ModelForm):
59
class DerivedBookForm(forms.ModelForm):
66
class ExplicitPKForm(forms.ModelForm):
69
fields = ('key', 'desc',)
72
class PostForm(forms.ModelForm):
78
class DateTimePostForm(forms.ModelForm):
84
class DerivedPostForm(forms.ModelForm):
90
class CustomWriterForm(forms.ModelForm):
91
name = forms.CharField(required=False)
98
class FlexDatePostForm(forms.ModelForm):
100
model = FlexibleDatePost
104
class BaseCategoryForm(forms.ModelForm):
110
class ArticleForm(forms.ModelForm):
116
class PartialArticleForm(forms.ModelForm):
119
fields = ('headline','pub_date')
122
class RoykoForm(forms.ModelForm):
128
class TestArticleForm(forms.ModelForm):
134
class PartialArticleFormWithSlug(forms.ModelForm):
137
fields = ('headline', 'slug', 'pub_date')
140
class ArticleStatusForm(forms.ModelForm):
142
model = ArticleStatus
146
class InventoryForm(forms.ModelForm):
152
class SelectInventoryForm(forms.Form):
153
items = forms.ModelMultipleChoiceField(Inventory.objects.all(), to_field_name='barcode')
156
class CustomFieldForExclusionForm(forms.ModelForm):
158
model = CustomFieldForExclusionModel
159
fields = ['name', 'markup']
162
class ShortCategory(forms.ModelForm):
163
name = forms.CharField(max_length=5)
164
slug = forms.CharField(max_length=5)
165
url = forms.CharField(max_length=3)
172
class ImprovedArticleForm(forms.ModelForm):
174
model = ImprovedArticle
178
class ImprovedArticleWithParentLinkForm(forms.ModelForm):
180
model = ImprovedArticleWithParentLink
184
class BetterWriterForm(forms.ModelForm):
189
class WriterProfileForm(forms.ModelForm):
191
model = WriterProfile
195
class TextFileForm(forms.ModelForm):
201
class BigIntForm(forms.ModelForm):
207
class ModelFormWithMedia(forms.ModelForm):
209
js = ('/some/form/javascript',)
211
'all': ('/some/form/css',)
218
class CommaSeparatedIntegerForm(forms.ModelForm):
220
model = CommaSeparatedInteger
224
class PriceFormWithoutQuantity(forms.ModelForm):
227
exclude = ('quantity',)
230
class ColourfulItemForm(forms.ModelForm):
232
model = ColourfulItem
235
# model forms for testing work on #9321:
237
class StatusNoteForm(forms.ModelForm):
239
model = ArticleStatusNote
243
class StatusNoteCBM2mForm(forms.ModelForm):
245
model = ArticleStatusNote
247
widgets = {'status': forms.CheckboxSelectMultiple}
250
class CustomErrorMessageForm(forms.ModelForm):
251
name1 = forms.CharField(error_messages={'invalid': 'Form custom error message.'})
255
model = CustomErrorMessage
258
class ModelFormBaseTest(TestCase):
259
def test_base_form(self):
260
self.assertEqual(list(BaseCategoryForm.base_fields),
261
['name', 'slug', 'url'])
263
def test_missing_fields_attribute(self):
264
with warnings.catch_warnings(record=True) as w:
265
warnings.simplefilter("always", PendingDeprecationWarning)
267
class MissingFieldsForm(forms.ModelForm):
271
# There is some internal state in warnings module which means that
272
# if a warning has been seen already, the catch_warnings won't
273
# have recorded it. The following line therefore will not work reliably:
275
# self.assertEqual(w[0].category, PendingDeprecationWarning)
277
# Until end of the deprecation cycle, should still create the
279
self.assertEqual(list(MissingFieldsForm.base_fields),
280
['name', 'slug', 'url'])
282
def test_extra_fields(self):
283
class ExtraFields(BaseCategoryForm):
284
some_extra_field = forms.BooleanField()
286
self.assertEqual(list(ExtraFields.base_fields),
287
['name', 'slug', 'url', 'some_extra_field'])
289
def test_replace_field(self):
290
class ReplaceField(forms.ModelForm):
291
url = forms.BooleanField()
297
self.assertIsInstance(ReplaceField.base_fields['url'],
298
forms.fields.BooleanField)
300
def test_replace_field_variant_2(self):
301
# Should have the same result as before,
302
# but 'fields' attribute specified differently
303
class ReplaceField(forms.ModelForm):
304
url = forms.BooleanField()
310
self.assertIsInstance(ReplaceField.base_fields['url'],
311
forms.fields.BooleanField)
313
def test_replace_field_variant_3(self):
314
# Should have the same result as before,
315
# but 'fields' attribute specified differently
316
class ReplaceField(forms.ModelForm):
317
url = forms.BooleanField()
321
fields = [] # url will still appear, since it is explicit above
323
self.assertIsInstance(ReplaceField.base_fields['url'],
324
forms.fields.BooleanField)
326
def test_override_field(self):
327
class WriterForm(forms.ModelForm):
328
book = forms.CharField(required=False)
334
wf = WriterForm({'name': 'Richard Lockridge'})
335
self.assertTrue(wf.is_valid())
337
def test_limit_nonexistent_field(self):
338
expected_msg = 'Unknown field(s) (nonexistent) specified for Category'
339
with self.assertRaisesMessage(FieldError, expected_msg):
340
class InvalidCategoryForm(forms.ModelForm):
343
fields = ['nonexistent']
345
def test_limit_fields_with_string(self):
346
expected_msg = "CategoryForm.Meta.fields cannot be a string. Did you mean to type: ('url',)?"
347
with self.assertRaisesMessage(TypeError, expected_msg):
348
class CategoryForm(forms.ModelForm):
351
fields = ('url') # note the missing comma
353
def test_exclude_fields(self):
354
class ExcludeFields(forms.ModelForm):
359
self.assertEqual(list(ExcludeFields.base_fields),
362
def test_exclude_nonexistent_field(self):
363
class ExcludeFields(forms.ModelForm):
366
exclude = ['nonexistent']
368
self.assertEqual(list(ExcludeFields.base_fields),
369
['name', 'slug', 'url'])
371
def test_exclude_fields_with_string(self):
372
expected_msg = "CategoryForm.Meta.exclude cannot be a string. Did you mean to type: ('url',)?"
373
with self.assertRaisesMessage(TypeError, expected_msg):
374
class CategoryForm(forms.ModelForm):
377
exclude = ('url') # note the missing comma
379
def test_confused_form(self):
380
class ConfusedForm(forms.ModelForm):
381
""" Using 'fields' *and* 'exclude'. Not sure why you'd want to do
382
this, but uh, "be liberal in what you accept" and all.
386
fields = ['name', 'url']
389
self.assertEqual(list(ConfusedForm.base_fields),
392
def test_mixmodel_form(self):
393
class MixModelForm(BaseCategoryForm):
394
""" Don't allow more than one 'model' definition in the
395
inheritance hierarchy. Technically, it would generate a valid
396
form, but the fact that the resulting save method won't deal with
397
multiple objects is likely to trip up people not familiar with the
403
# MixModelForm is now an Article-related thing, because MixModelForm.Meta
404
# overrides BaseCategoryForm.Meta.
407
list(MixModelForm.base_fields),
408
['headline', 'slug', 'pub_date', 'writer', 'article', 'categories', 'status']
411
def test_article_form(self):
413
list(ArticleForm.base_fields),
414
['headline', 'slug', 'pub_date', 'writer', 'article', 'categories', 'status']
417
def test_bad_form(self):
418
#First class with a Meta class wins...
419
class BadForm(ArticleForm, BaseCategoryForm):
423
list(BadForm.base_fields),
424
['headline', 'slug', 'pub_date', 'writer', 'article', 'categories', 'status']
427
def test_invalid_meta_model(self):
428
class InvalidModelForm(forms.ModelForm):
432
# Can't create new form
433
with self.assertRaises(ValueError):
434
f = InvalidModelForm()
436
# Even if you provide a model instance
437
with self.assertRaises(ValueError):
438
f = InvalidModelForm(instance=Category)
440
def test_subcategory_form(self):
441
class SubCategoryForm(BaseCategoryForm):
442
""" Subclassing without specifying a Meta on the class will use
443
the parent's Meta (or the first parent in the MRO if there are
444
multiple parent classes).
448
self.assertEqual(list(SubCategoryForm.base_fields),
449
['name', 'slug', 'url'])
451
def test_subclassmeta_form(self):
452
class SomeCategoryForm(forms.ModelForm):
453
checkbox = forms.BooleanField()
459
class SubclassMeta(SomeCategoryForm):
460
""" We can also subclass the Meta inner class to change the fields
463
class Meta(SomeCategoryForm.Meta):
466
self.assertHTMLEqual(
468
"""<tr><th><label for="id_name">Name:</label></th><td><input id="id_name" type="text" name="name" maxlength="20" /></td></tr>
469
<tr><th><label for="id_slug">Slug:</label></th><td><input id="id_slug" type="text" name="slug" maxlength="20" /></td></tr>
470
<tr><th><label for="id_checkbox">Checkbox:</label></th><td><input type="checkbox" name="checkbox" id="id_checkbox" /></td></tr>"""
473
def test_orderfields_form(self):
474
class OrderFields(forms.ModelForm):
477
fields = ['url', 'name']
479
self.assertEqual(list(OrderFields.base_fields),
481
self.assertHTMLEqual(
483
"""<tr><th><label for="id_url">The URL:</label></th><td><input id="id_url" type="text" name="url" maxlength="40" /></td></tr>
484
<tr><th><label for="id_name">Name:</label></th><td><input id="id_name" type="text" name="name" maxlength="20" /></td></tr>"""
487
def test_orderfields2_form(self):
488
class OrderFields2(forms.ModelForm):
491
fields = ['slug', 'url', 'name']
494
self.assertEqual(list(OrderFields2.base_fields),
498
class FieldOverridesTroughFormMetaForm(forms.ModelForm):
501
fields = ['name', 'url', 'slug']
503
'name': forms.Textarea,
504
'url': forms.TextInput(attrs={'class': 'url'})
510
'slug': 'Watch out! Letters, numbers, underscores and hyphens only.',
515
"Didn't you read the help text? "
516
"We said letters, numbers, underscores and hyphens only!"
522
class TestFieldOverridesTroughFormMeta(TestCase):
523
def test_widget_overrides(self):
524
form = FieldOverridesTroughFormMetaForm()
525
self.assertHTMLEqual(
527
'<textarea id="id_name" rows="10" cols="40" name="name"></textarea>',
529
self.assertHTMLEqual(
531
'<input id="id_url" type="text" class="url" name="url" maxlength="40" />',
533
self.assertHTMLEqual(
535
'<input id="id_slug" type="text" name="slug" maxlength="20" />',
538
def test_label_overrides(self):
539
form = FieldOverridesTroughFormMetaForm()
540
self.assertHTMLEqual(
541
str(form['name'].label_tag()),
542
'<label for="id_name">Title:</label>',
544
self.assertHTMLEqual(
545
str(form['url'].label_tag()),
546
'<label for="id_url">The URL:</label>',
548
self.assertHTMLEqual(
549
str(form['slug'].label_tag()),
550
'<label for="id_slug">Slug:</label>',
553
def test_help_text_overrides(self):
554
form = FieldOverridesTroughFormMetaForm()
556
form['slug'].help_text,
557
'Watch out! Letters, numbers, underscores and hyphens only.',
560
def test_error_messages_overrides(self):
561
form = FieldOverridesTroughFormMetaForm(data={
569
"Didn't you read the help text? "
570
"We said letters, numbers, underscores and hyphens only!",
572
self.assertEqual(form.errors, {'slug': error})
575
class IncompleteCategoryFormWithFields(forms.ModelForm):
577
A form that replaces the model's url field with a custom one. This should
578
prevent the model field's validation from being called.
580
url = forms.CharField(required=False)
583
fields = ('name', 'slug')
586
class IncompleteCategoryFormWithExclude(forms.ModelForm):
588
A form that replaces the model's url field with a custom one. This should
589
prevent the model field's validation from being called.
591
url = forms.CharField(required=False)
598
class ValidationTest(TestCase):
599
def test_validates_with_replaced_field_not_specified(self):
600
form = IncompleteCategoryFormWithFields(data={'name': 'some name', 'slug': 'some-slug'})
601
assert form.is_valid()
603
def test_validates_with_replaced_field_excluded(self):
604
form = IncompleteCategoryFormWithExclude(data={'name': 'some name', 'slug': 'some-slug'})
605
assert form.is_valid()
607
def test_notrequired_overrides_notblank(self):
608
form = CustomWriterForm({})
609
assert form.is_valid()
614
# unique/unique_together validation
615
class UniqueTest(TestCase):
617
self.writer = Writer.objects.create(name='Mike Royko')
619
def test_simple_unique(self):
620
form = ProductForm({'slug': 'teddy-bear-blue'})
621
self.assertTrue(form.is_valid())
623
form = ProductForm({'slug': 'teddy-bear-blue'})
624
self.assertEqual(len(form.errors), 1)
625
self.assertEqual(form.errors['slug'], ['Product with this Slug already exists.'])
626
form = ProductForm({'slug': 'teddy-bear-blue'}, instance=obj)
627
self.assertTrue(form.is_valid())
629
def test_unique_together(self):
630
"""ModelForm test of unique_together constraint"""
631
form = PriceForm({'price': '6.00', 'quantity': '1'})
632
self.assertTrue(form.is_valid())
634
form = PriceForm({'price': '6.00', 'quantity': '1'})
635
self.assertFalse(form.is_valid())
636
self.assertEqual(len(form.errors), 1)
637
self.assertEqual(form.errors['__all__'], ['Price with this Price and Quantity already exists.'])
639
def test_unique_null(self):
640
title = 'I May Be Wrong But I Doubt It'
641
form = BookForm({'title': title, 'author': self.writer.pk})
642
self.assertTrue(form.is_valid())
644
form = BookForm({'title': title, 'author': self.writer.pk})
645
self.assertFalse(form.is_valid())
646
self.assertEqual(len(form.errors), 1)
647
self.assertEqual(form.errors['__all__'], ['Book with this Title and Author already exists.'])
648
form = BookForm({'title': title})
649
self.assertTrue(form.is_valid())
651
form = BookForm({'title': title})
652
self.assertTrue(form.is_valid())
654
def test_inherited_unique(self):
656
Book.objects.create(title=title, author=self.writer, special_id=1)
657
form = DerivedBookForm({'title': 'Other', 'author': self.writer.pk, 'special_id': '1', 'isbn': '12345'})
658
self.assertFalse(form.is_valid())
659
self.assertEqual(len(form.errors), 1)
660
self.assertEqual(form.errors['special_id'], ['Book with this Special id already exists.'])
662
def test_inherited_unique_together(self):
664
form = BookForm({'title': title, 'author': self.writer.pk})
665
self.assertTrue(form.is_valid())
667
form = DerivedBookForm({'title': title, 'author': self.writer.pk, 'isbn': '12345'})
668
self.assertFalse(form.is_valid())
669
self.assertEqual(len(form.errors), 1)
670
self.assertEqual(form.errors['__all__'], ['Book with this Title and Author already exists.'])
672
def test_abstract_inherited_unique(self):
675
dbook = DerivedBook.objects.create(title=title, author=self.writer, isbn=isbn)
676
form = DerivedBookForm({'title': 'Other', 'author': self.writer.pk, 'isbn': isbn})
677
self.assertFalse(form.is_valid())
678
self.assertEqual(len(form.errors), 1)
679
self.assertEqual(form.errors['isbn'], ['Derived book with this Isbn already exists.'])
681
def test_abstract_inherited_unique_together(self):
684
dbook = DerivedBook.objects.create(title=title, author=self.writer, isbn=isbn)
685
form = DerivedBookForm({
687
'author': self.writer.pk,
692
self.assertFalse(form.is_valid())
693
self.assertEqual(len(form.errors), 1)
694
self.assertEqual(form.errors['__all__'],
695
['Derived book with this Suffix1 and Suffix2 already exists.'])
697
def test_explicitpk_unspecified(self):
698
"""Test for primary_key being in the form and failing validation."""
699
form = ExplicitPKForm({'key': '', 'desc': '' })
700
self.assertFalse(form.is_valid())
702
def test_explicitpk_unique(self):
703
"""Ensure keys and blank character strings are tested for uniqueness."""
704
form = ExplicitPKForm({'key': 'key1', 'desc': ''})
705
self.assertTrue(form.is_valid())
707
form = ExplicitPKForm({'key': 'key1', 'desc': ''})
708
self.assertFalse(form.is_valid())
709
self.assertEqual(len(form.errors), 3)
710
self.assertEqual(form.errors['__all__'], ['Explicit pk with this Key and Desc already exists.'])
711
self.assertEqual(form.errors['desc'], ['Explicit pk with this Desc already exists.'])
712
self.assertEqual(form.errors['key'], ['Explicit pk with this Key already exists.'])
714
def test_unique_for_date(self):
715
p = Post.objects.create(title="Django 1.0 is released",
716
slug="Django 1.0", subtitle="Finally", posted=datetime.date(2008, 9, 3))
717
form = PostForm({'title': "Django 1.0 is released", 'posted': '2008-09-03'})
718
self.assertFalse(form.is_valid())
719
self.assertEqual(len(form.errors), 1)
720
self.assertEqual(form.errors['title'], ['Title must be unique for Posted date.'])
721
form = PostForm({'title': "Work on Django 1.1 begins", 'posted': '2008-09-03'})
722
self.assertTrue(form.is_valid())
723
form = PostForm({'title': "Django 1.0 is released", 'posted': '2008-09-04'})
724
self.assertTrue(form.is_valid())
725
form = PostForm({'slug': "Django 1.0", 'posted': '2008-01-01'})
726
self.assertFalse(form.is_valid())
727
self.assertEqual(len(form.errors), 1)
728
self.assertEqual(form.errors['slug'], ['Slug must be unique for Posted year.'])
729
form = PostForm({'subtitle': "Finally", 'posted': '2008-09-30'})
730
self.assertFalse(form.is_valid())
731
self.assertEqual(form.errors['subtitle'], ['Subtitle must be unique for Posted month.'])
732
form = PostForm({'subtitle': "Finally", "title": "Django 1.0 is released",
733
"slug": "Django 1.0", 'posted': '2008-09-03'}, instance=p)
734
self.assertTrue(form.is_valid())
735
form = PostForm({'title': "Django 1.0 is released"})
736
self.assertFalse(form.is_valid())
737
self.assertEqual(len(form.errors), 1)
738
self.assertEqual(form.errors['posted'], ['This field is required.'])
740
def test_unique_for_date_in_exclude(self):
741
"""If the date for unique_for_* constraints is excluded from the
742
ModelForm (in this case 'posted' has editable=False, then the
743
constraint should be ignored."""
744
p = DateTimePost.objects.create(title="Django 1.0 is released",
745
slug="Django 1.0", subtitle="Finally",
746
posted=datetime.datetime(2008, 9, 3, 10, 10, 1))
747
# 'title' has unique_for_date='posted'
748
form = DateTimePostForm({'title': "Django 1.0 is released", 'posted': '2008-09-03'})
749
self.assertTrue(form.is_valid())
750
# 'slug' has unique_for_year='posted'
751
form = DateTimePostForm({'slug': "Django 1.0", 'posted': '2008-01-01'})
752
self.assertTrue(form.is_valid())
753
# 'subtitle' has unique_for_month='posted'
754
form = DateTimePostForm({'subtitle': "Finally", 'posted': '2008-09-30'})
755
self.assertTrue(form.is_valid())
757
def test_inherited_unique_for_date(self):
758
p = Post.objects.create(title="Django 1.0 is released",
759
slug="Django 1.0", subtitle="Finally", posted=datetime.date(2008, 9, 3))
760
form = DerivedPostForm({'title': "Django 1.0 is released", 'posted': '2008-09-03'})
761
self.assertFalse(form.is_valid())
762
self.assertEqual(len(form.errors), 1)
763
self.assertEqual(form.errors['title'], ['Title must be unique for Posted date.'])
764
form = DerivedPostForm({'title': "Work on Django 1.1 begins", 'posted': '2008-09-03'})
765
self.assertTrue(form.is_valid())
766
form = DerivedPostForm({'title': "Django 1.0 is released", 'posted': '2008-09-04'})
767
self.assertTrue(form.is_valid())
768
form = DerivedPostForm({'slug': "Django 1.0", 'posted': '2008-01-01'})
769
self.assertFalse(form.is_valid())
770
self.assertEqual(len(form.errors), 1)
771
self.assertEqual(form.errors['slug'], ['Slug must be unique for Posted year.'])
772
form = DerivedPostForm({'subtitle': "Finally", 'posted': '2008-09-30'})
773
self.assertFalse(form.is_valid())
774
self.assertEqual(form.errors['subtitle'], ['Subtitle must be unique for Posted month.'])
775
form = DerivedPostForm({'subtitle': "Finally", "title": "Django 1.0 is released",
776
"slug": "Django 1.0", 'posted': '2008-09-03'}, instance=p)
777
self.assertTrue(form.is_valid())
779
def test_unique_for_date_with_nullable_date(self):
780
p = FlexibleDatePost.objects.create(title="Django 1.0 is released",
781
slug="Django 1.0", subtitle="Finally", posted=datetime.date(2008, 9, 3))
783
form = FlexDatePostForm({'title': "Django 1.0 is released"})
784
self.assertTrue(form.is_valid())
785
form = FlexDatePostForm({'slug': "Django 1.0"})
786
self.assertTrue(form.is_valid())
787
form = FlexDatePostForm({'subtitle': "Finally"})
788
self.assertTrue(form.is_valid())
789
form = FlexDatePostForm({'subtitle': "Finally", "title": "Django 1.0 is released",
790
"slug": "Django 1.0"}, instance=p)
791
self.assertTrue(form.is_valid())
793
class ModelToDictTests(TestCase):
795
Tests for forms.models.model_to_dict
797
def test_model_to_dict_many_to_many(self):
799
Category(name='TestName1', slug='TestName1', url='url1'),
800
Category(name='TestName2', slug='TestName2', url='url2'),
801
Category(name='TestName3', slug='TestName3', url='url3')
805
writer = Writer(name='Test writer')
809
headline='Test article',
811
pub_date=datetime.date(1988, 1, 4),
817
art.categories.add(c)
820
with self.assertNumQueries(1):
821
d = model_to_dict(art)
823
#Ensure all many-to-many categories appear in model_to_dict
825
self.assertIn(c.pk, d['categories'])
826
#Ensure many-to-many relation appears as a list
827
self.assertIsInstance(d['categories'], list)
829
class OldFormForXTests(TestCase):
830
def test_base_form(self):
831
self.assertEqual(Category.objects.count(), 0)
832
f = BaseCategoryForm()
833
self.assertHTMLEqual(
835
"""<tr><th><label for="id_name">Name:</label></th><td><input id="id_name" type="text" name="name" maxlength="20" /></td></tr>
836
<tr><th><label for="id_slug">Slug:</label></th><td><input id="id_slug" type="text" name="slug" maxlength="20" /></td></tr>
837
<tr><th><label for="id_url">The URL:</label></th><td><input id="id_url" type="text" name="url" maxlength="40" /></td></tr>"""
839
self.assertHTMLEqual(
841
"""<li><label for="id_name">Name:</label> <input id="id_name" type="text" name="name" maxlength="20" /></li>
842
<li><label for="id_slug">Slug:</label> <input id="id_slug" type="text" name="slug" maxlength="20" /></li>
843
<li><label for="id_url">The URL:</label> <input id="id_url" type="text" name="url" maxlength="40" /></li>"""
845
self.assertHTMLEqual(
847
"""<input id="id_name" type="text" name="name" maxlength="20" />""")
849
def test_auto_id(self):
850
f = BaseCategoryForm(auto_id=False)
851
self.assertHTMLEqual(
853
"""<li>Name: <input type="text" name="name" maxlength="20" /></li>
854
<li>Slug: <input type="text" name="slug" maxlength="20" /></li>
855
<li>The URL: <input type="text" name="url" maxlength="40" /></li>"""
858
def test_with_data(self):
859
self.assertEqual(Category.objects.count(), 0)
860
f = BaseCategoryForm({'name': 'Entertainment',
861
'slug': 'entertainment',
862
'url': 'entertainment'})
863
self.assertTrue(f.is_valid())
864
self.assertEqual(f.cleaned_data['name'], 'Entertainment')
865
self.assertEqual(f.cleaned_data['slug'], 'entertainment')
866
self.assertEqual(f.cleaned_data['url'], 'entertainment')
868
# Testing wether the same object is returned from the
869
# ORM... not the fastest way...
871
self.assertEqual(c1, Category.objects.all()[0])
872
self.assertEqual(c1.name, "Entertainment")
873
self.assertEqual(Category.objects.count(), 1)
875
f = BaseCategoryForm({'name': "It's a test",
878
self.assertTrue(f.is_valid())
879
self.assertEqual(f.cleaned_data['name'], "It's a test")
880
self.assertEqual(f.cleaned_data['slug'], 'its-test')
881
self.assertEqual(f.cleaned_data['url'], 'test')
883
# Testing wether the same object is returned from the
884
# ORM... not the fastest way...
885
self.assertEqual(c2, Category.objects.get(pk=c2.pk))
886
self.assertEqual(c2.name, "It's a test")
887
self.assertEqual(Category.objects.count(), 2)
889
# If you call save() with commit=False, then it will return an object that
890
# hasn't yet been saved to the database. In this case, it's up to you to call
891
# save() on the resulting model instance.
892
f = BaseCategoryForm({'name': 'Third test', 'slug': 'third-test', 'url': 'third'})
893
self.assertEqual(f.is_valid(), True)
894
self.assertEqual(f.cleaned_data['url'], 'third')
895
self.assertEqual(f.cleaned_data['name'], 'Third test')
896
self.assertEqual(f.cleaned_data['slug'], 'third-test')
897
c3 = f.save(commit=False)
898
self.assertEqual(c3.name, "Third test")
899
self.assertEqual(Category.objects.count(), 2)
901
self.assertEqual(Category.objects.count(), 3)
903
# If you call save() with invalid data, you'll get a ValueError.
904
f = BaseCategoryForm({'name': '', 'slug': 'not a slug!', 'url': 'foo'})
905
self.assertEqual(f.errors['name'], ['This field is required.'])
906
self.assertEqual(f.errors['slug'], ["Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."])
907
self.assertEqual(f.cleaned_data, {'url': 'foo'})
908
with self.assertRaises(ValueError):
910
f = BaseCategoryForm({'name': '', 'slug': '', 'url': 'foo'})
911
with self.assertRaises(ValueError):
914
# Create a couple of Writers.
915
w_royko = Writer(name='Mike Royko')
917
w_woodward = Writer(name='Bob Woodward')
919
# ManyToManyFields are represented by a MultipleChoiceField, ForeignKeys and any
920
# fields with the 'choices' attribute are represented by a ChoiceField.
921
f = ArticleForm(auto_id=False)
922
self.assertHTMLEqual(six.text_type(f), '''<tr><th>Headline:</th><td><input type="text" name="headline" maxlength="50" /></td></tr>
923
<tr><th>Slug:</th><td><input type="text" name="slug" maxlength="50" /></td></tr>
924
<tr><th>Pub date:</th><td><input type="text" name="pub_date" /></td></tr>
925
<tr><th>Writer:</th><td><select name="writer">
926
<option value="" selected="selected">---------</option>
927
<option value="%s">Bob Woodward</option>
928
<option value="%s">Mike Royko</option>
930
<tr><th>Article:</th><td><textarea rows="10" cols="40" name="article"></textarea></td></tr>
931
<tr><th>Categories:</th><td><select multiple="multiple" name="categories">
932
<option value="%s">Entertainment</option>
933
<option value="%s">It's a test</option>
934
<option value="%s">Third test</option>
935
</select><br /><span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></td></tr>
936
<tr><th>Status:</th><td><select name="status">
937
<option value="" selected="selected">---------</option>
938
<option value="1">Draft</option>
939
<option value="2">Pending</option>
940
<option value="3">Live</option>
941
</select></td></tr>''' % (w_woodward.pk, w_royko.pk, c1.pk, c2.pk, c3.pk))
943
# You can restrict a form to a subset of the complete list of fields
944
# by providing a 'fields' argument. If you try to save a
945
# model created with such a form, you need to ensure that the fields
946
# that are _not_ on the form have default values, or are allowed to have
947
# a value of None. If a field isn't specified on a form, the object created
948
# from the form can't provide a value for that field!
949
f = PartialArticleForm(auto_id=False)
950
self.assertHTMLEqual(six.text_type(f), '''<tr><th>Headline:</th><td><input type="text" name="headline" maxlength="50" /></td></tr>
951
<tr><th>Pub date:</th><td><input type="text" name="pub_date" /></td></tr>''')
953
# When the ModelForm is passed an instance, that instance's current values are
954
# inserted as 'initial' data in each Field.
955
w = Writer.objects.get(name='Mike Royko')
956
f = RoykoForm(auto_id=False, instance=w)
957
self.assertHTMLEqual(six.text_type(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>''')
960
headline='Test article',
962
pub_date=datetime.date(1988, 1, 4),
968
self.assertEqual(art_id_1 is not None, True)
969
f = TestArticleForm(auto_id=False, instance=art)
970
self.assertHTMLEqual(f.as_ul(), '''<li>Headline: <input type="text" name="headline" value="Test article" maxlength="50" /></li>
971
<li>Slug: <input type="text" name="slug" value="test-article" maxlength="50" /></li>
972
<li>Pub date: <input type="text" name="pub_date" value="1988-01-04" /></li>
973
<li>Writer: <select name="writer">
974
<option value="">---------</option>
975
<option value="%s">Bob Woodward</option>
976
<option value="%s" selected="selected">Mike Royko</option>
978
<li>Article: <textarea rows="10" cols="40" name="article">Hello.</textarea></li>
979
<li>Categories: <select multiple="multiple" name="categories">
980
<option value="%s">Entertainment</option>
981
<option value="%s">It's a test</option>
982
<option value="%s">Third test</option>
983
</select> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></li>
984
<li>Status: <select name="status">
985
<option value="" selected="selected">---------</option>
986
<option value="1">Draft</option>
987
<option value="2">Pending</option>
988
<option value="3">Live</option>
989
</select></li>''' % (w_woodward.pk, w_royko.pk, c1.pk, c2.pk, c3.pk))
990
f = TestArticleForm({
991
'headline': 'Test headline',
992
'slug': 'test-headline',
993
'pub_date': '1984-02-06',
994
'writer': six.text_type(w_royko.pk),
997
self.assertEqual(f.errors, {})
998
self.assertEqual(f.is_valid(), True)
1000
self.assertEqual(test_art.id == art_id_1, True)
1001
test_art = Article.objects.get(id=art_id_1)
1002
self.assertEqual(test_art.headline, 'Test headline')
1003
# You can create a form over a subset of the available fields
1004
# by specifying a 'fields' argument to form_for_instance.
1005
f = PartialArticleFormWithSlug({
1006
'headline': 'New headline',
1007
'slug': 'new-headline',
1008
'pub_date': '1988-01-04'
1009
}, auto_id=False, instance=art)
1010
self.assertHTMLEqual(f.as_ul(), '''<li>Headline: <input type="text" name="headline" value="New headline" maxlength="50" /></li>
1011
<li>Slug: <input type="text" name="slug" value="new-headline" maxlength="50" /></li>
1012
<li>Pub date: <input type="text" name="pub_date" value="1988-01-04" /></li>''')
1013
self.assertEqual(f.is_valid(), True)
1015
self.assertEqual(new_art.id == art_id_1, True)
1016
new_art = Article.objects.get(id=art_id_1)
1017
self.assertEqual(new_art.headline, 'New headline')
1019
# Add some categories and test the many-to-many form output.
1020
self.assertQuerysetEqual(new_art.categories.all(), [])
1021
new_art.categories.add(Category.objects.get(name='Entertainment'))
1022
self.assertQuerysetEqual(new_art.categories.all(), ["Entertainment"])
1023
f = TestArticleForm(auto_id=False, instance=new_art)
1024
self.assertHTMLEqual(f.as_ul(), '''<li>Headline: <input type="text" name="headline" value="New headline" maxlength="50" /></li>
1025
<li>Slug: <input type="text" name="slug" value="new-headline" maxlength="50" /></li>
1026
<li>Pub date: <input type="text" name="pub_date" value="1988-01-04" /></li>
1027
<li>Writer: <select name="writer">
1028
<option value="">---------</option>
1029
<option value="%s">Bob Woodward</option>
1030
<option value="%s" selected="selected">Mike Royko</option>
1032
<li>Article: <textarea rows="10" cols="40" name="article">Hello.</textarea></li>
1033
<li>Categories: <select multiple="multiple" name="categories">
1034
<option value="%s" selected="selected">Entertainment</option>
1035
<option value="%s">It's a test</option>
1036
<option value="%s">Third test</option>
1037
</select> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></li>
1038
<li>Status: <select name="status">
1039
<option value="" selected="selected">---------</option>
1040
<option value="1">Draft</option>
1041
<option value="2">Pending</option>
1042
<option value="3">Live</option>
1043
</select></li>''' % (w_woodward.pk, w_royko.pk, c1.pk, c2.pk, c3.pk))
1045
# Initial values can be provided for model forms
1046
f = TestArticleForm(
1049
'headline': 'Your headline here',
1050
'categories': [str(c1.id), str(c2.id)]
1052
self.assertHTMLEqual(f.as_ul(), '''<li>Headline: <input type="text" name="headline" value="Your headline here" maxlength="50" /></li>
1053
<li>Slug: <input type="text" name="slug" maxlength="50" /></li>
1054
<li>Pub date: <input type="text" name="pub_date" /></li>
1055
<li>Writer: <select name="writer">
1056
<option value="" selected="selected">---------</option>
1057
<option value="%s">Bob Woodward</option>
1058
<option value="%s">Mike Royko</option>
1060
<li>Article: <textarea rows="10" cols="40" name="article"></textarea></li>
1061
<li>Categories: <select multiple="multiple" name="categories">
1062
<option value="%s" selected="selected">Entertainment</option>
1063
<option value="%s" selected="selected">It's a test</option>
1064
<option value="%s">Third test</option>
1065
</select> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></li>
1066
<li>Status: <select name="status">
1067
<option value="" selected="selected">---------</option>
1068
<option value="1">Draft</option>
1069
<option value="2">Pending</option>
1070
<option value="3">Live</option>
1071
</select></li>''' % (w_woodward.pk, w_royko.pk, c1.pk, c2.pk, c3.pk))
1073
f = TestArticleForm({
1074
'headline': 'New headline',
1075
'slug': 'new-headline',
1076
'pub_date': '1988-01-04',
1077
'writer': six.text_type(w_royko.pk),
1078
'article': 'Hello.',
1079
'categories': [six.text_type(c1.id), six.text_type(c2.id)]
1080
}, instance=new_art)
1082
self.assertEqual(new_art.id == art_id_1, True)
1083
new_art = Article.objects.get(id=art_id_1)
1084
self.assertQuerysetEqual(new_art.categories.order_by('name'),
1085
["Entertainment", "It's a test"])
1087
# Now, submit form data with no categories. This deletes the existing categories.
1088
f = TestArticleForm({'headline': 'New headline', 'slug': 'new-headline', 'pub_date': '1988-01-04',
1089
'writer': six.text_type(w_royko.pk), 'article': 'Hello.'}, instance=new_art)
1091
self.assertEqual(new_art.id == art_id_1, True)
1092
new_art = Article.objects.get(id=art_id_1)
1093
self.assertQuerysetEqual(new_art.categories.all(), [])
1095
# Create a new article, with categories, via the form.
1096
f = ArticleForm({'headline': 'The walrus was Paul', 'slug': 'walrus-was-paul', 'pub_date': '1967-11-01',
1097
'writer': six.text_type(w_royko.pk), 'article': 'Test.', 'categories': [six.text_type(c1.id), six.text_type(c2.id)]})
1099
art_id_2 = new_art.id
1100
self.assertEqual(art_id_2 not in (None, art_id_1), True)
1101
new_art = Article.objects.get(id=art_id_2)
1102
self.assertQuerysetEqual(new_art.categories.order_by('name'), ["Entertainment", "It's a test"])
1104
# Create a new article, with no categories, via the form.
1105
f = ArticleForm({'headline': 'The walrus was Paul', 'slug': 'walrus-was-paul', 'pub_date': '1967-11-01',
1106
'writer': six.text_type(w_royko.pk), 'article': 'Test.'})
1108
art_id_3 = new_art.id
1109
self.assertEqual(art_id_3 not in (None, art_id_1, art_id_2), True)
1110
new_art = Article.objects.get(id=art_id_3)
1111
self.assertQuerysetEqual(new_art.categories.all(), [])
1113
# Create a new article, with categories, via the form, but use commit=False.
1114
# The m2m data won't be saved until save_m2m() is invoked on the form.
1115
f = ArticleForm({'headline': 'The walrus was Paul', 'slug': 'walrus-was-paul', 'pub_date': '1967-11-01',
1116
'writer': six.text_type(w_royko.pk), 'article': 'Test.', 'categories': [six.text_type(c1.id), six.text_type(c2.id)]})
1117
new_art = f.save(commit=False)
1119
# Manually save the instance
1121
art_id_4 = new_art.id
1122
self.assertEqual(art_id_4 not in (None, art_id_1, art_id_2, art_id_3), True)
1124
# The instance doesn't have m2m data yet
1125
new_art = Article.objects.get(id=art_id_4)
1126
self.assertQuerysetEqual(new_art.categories.all(), [])
1128
# Save the m2m data on the form
1130
self.assertQuerysetEqual(new_art.categories.order_by('name'), ["Entertainment", "It's a test"])
1132
# Here, we define a custom ModelForm. Because it happens to have the same fields as
1133
# the Category model, we can just call the form's save() to apply its changes to an
1134
# existing Category instance.
1135
cat = Category.objects.get(name='Third test')
1136
self.assertEqual(cat.name, "Third test")
1137
self.assertEqual(cat.id == c3.id, True)
1138
form = ShortCategory({'name': 'Third', 'slug': 'third', 'url': '3rd'}, instance=cat)
1139
self.assertEqual(form.save().name, 'Third')
1140
self.assertEqual(Category.objects.get(id=c3.id).name, 'Third')
1142
# Here, we demonstrate that choices for a ForeignKey ChoiceField are determined
1143
# at runtime, based on the data in the database when the form is displayed, not
1144
# the data in the database when the form is instantiated.
1145
f = ArticleForm(auto_id=False)
1146
self.assertHTMLEqual(f.as_ul(), '''<li>Headline: <input type="text" name="headline" maxlength="50" /></li>
1147
<li>Slug: <input type="text" name="slug" maxlength="50" /></li>
1148
<li>Pub date: <input type="text" name="pub_date" /></li>
1149
<li>Writer: <select name="writer">
1150
<option value="" selected="selected">---------</option>
1151
<option value="%s">Bob Woodward</option>
1152
<option value="%s">Mike Royko</option>
1154
<li>Article: <textarea rows="10" cols="40" name="article"></textarea></li>
1155
<li>Categories: <select multiple="multiple" name="categories">
1156
<option value="%s">Entertainment</option>
1157
<option value="%s">It's a test</option>
1158
<option value="%s">Third</option>
1159
</select> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></li>
1160
<li>Status: <select name="status">
1161
<option value="" selected="selected">---------</option>
1162
<option value="1">Draft</option>
1163
<option value="2">Pending</option>
1164
<option value="3">Live</option>
1165
</select></li>''' % (w_woodward.pk, w_royko.pk, c1.pk, c2.pk, c3.pk))
1167
c4 = Category.objects.create(name='Fourth', url='4th')
1168
self.assertEqual(c4.name, 'Fourth')
1169
w_bernstein = Writer.objects.create(name='Carl Bernstein')
1170
self.assertEqual(w_bernstein.name, 'Carl Bernstein')
1171
self.assertHTMLEqual(f.as_ul(), '''<li>Headline: <input type="text" name="headline" maxlength="50" /></li>
1172
<li>Slug: <input type="text" name="slug" maxlength="50" /></li>
1173
<li>Pub date: <input type="text" name="pub_date" /></li>
1174
<li>Writer: <select name="writer">
1175
<option value="" selected="selected">---------</option>
1176
<option value="%s">Bob Woodward</option>
1177
<option value="%s">Carl Bernstein</option>
1178
<option value="%s">Mike Royko</option>
1180
<li>Article: <textarea rows="10" cols="40" name="article"></textarea></li>
1181
<li>Categories: <select multiple="multiple" name="categories">
1182
<option value="%s">Entertainment</option>
1183
<option value="%s">It's a test</option>
1184
<option value="%s">Third</option>
1185
<option value="%s">Fourth</option>
1186
</select> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></li>
1187
<li>Status: <select name="status">
1188
<option value="" selected="selected">---------</option>
1189
<option value="1">Draft</option>
1190
<option value="2">Pending</option>
1191
<option value="3">Live</option>
1192
</select></li>''' % (w_woodward.pk, w_bernstein.pk, w_royko.pk, c1.pk, c2.pk, c3.pk, c4.pk))
1194
# ModelChoiceField ############################################################
1196
f = forms.ModelChoiceField(Category.objects.all())
1197
self.assertEqual(list(f.choices), [
1199
(c1.pk, 'Entertainment'),
1200
(c2.pk, "It's a test"),
1203
self.assertEqual(5, len(f.choices))
1204
with self.assertRaises(ValidationError):
1206
with self.assertRaises(ValidationError):
1208
with self.assertRaises(ValidationError):
1210
self.assertEqual(f.clean(c3.id).name, 'Third')
1211
self.assertEqual(f.clean(c2.id).name, "It's a test")
1213
# Add a Category object *after* the ModelChoiceField has already been
1214
# instantiated. This proves clean() checks the database during clean() rather
1215
# than caching it at time of instantiation.
1216
c5 = Category.objects.create(name='Fifth', url='5th')
1217
self.assertEqual(c5.name, 'Fifth')
1218
self.assertEqual(f.clean(c5.id).name, 'Fifth')
1220
# Delete a Category object *after* the ModelChoiceField has already been
1221
# instantiated. This proves clean() checks the database during clean() rather
1222
# than caching it at time of instantiation.
1223
Category.objects.get(url='5th').delete()
1224
with self.assertRaises(ValidationError):
1227
f = forms.ModelChoiceField(Category.objects.filter(pk=c1.id), required=False)
1228
self.assertEqual(f.clean(''), None)
1230
self.assertEqual(f.clean(str(c1.id)).name, "Entertainment")
1231
with self.assertRaises(ValidationError):
1234
# queryset can be changed after the field is created.
1235
f.queryset = Category.objects.exclude(name='Fourth')
1236
self.assertEqual(list(f.choices), [
1238
(c1.pk, 'Entertainment'),
1239
(c2.pk, "It's a test"),
1241
self.assertEqual(f.clean(c3.id).name, 'Third')
1242
with self.assertRaises(ValidationError):
1245
# check that we can safely iterate choices repeatedly
1246
gen_one = list(f.choices)
1248
self.assertEqual(gen_one[2], (c2.pk, "It's a test"))
1249
self.assertEqual(list(gen_two), [
1251
(c1.pk, 'Entertainment'),
1252
(c2.pk, "It's a test"),
1255
# check that we can override the label_from_instance method to print custom labels (#4620)
1256
f.queryset = Category.objects.all()
1257
f.label_from_instance = lambda obj: "category " + str(obj)
1258
self.assertEqual(list(f.choices), [
1260
(c1.pk, 'category Entertainment'),
1261
(c2.pk, "category It's a test"),
1262
(c3.pk, 'category Third'),
1263
(c4.pk, 'category Fourth')])
1265
# ModelMultipleChoiceField ####################################################
1267
f = forms.ModelMultipleChoiceField(Category.objects.all())
1268
self.assertEqual(list(f.choices), [
1269
(c1.pk, 'Entertainment'),
1270
(c2.pk, "It's a test"),
1273
with self.assertRaises(ValidationError):
1275
with self.assertRaises(ValidationError):
1277
self.assertQuerysetEqual(f.clean([c1.id]), ["Entertainment"])
1278
self.assertQuerysetEqual(f.clean([c2.id]), ["It's a test"])
1279
self.assertQuerysetEqual(f.clean([str(c1.id)]), ["Entertainment"])
1280
self.assertQuerysetEqual(f.clean([str(c1.id), str(c2.id)]), ["Entertainment", "It's a test"],
1282
self.assertQuerysetEqual(f.clean([c1.id, str(c2.id)]), ["Entertainment", "It's a test"],
1284
self.assertQuerysetEqual(f.clean((c1.id, str(c2.id))), ["Entertainment", "It's a test"],
1286
with self.assertRaises(ValidationError):
1288
with self.assertRaises(ValidationError):
1290
with self.assertRaises(ValidationError):
1293
# Add a Category object *after* the ModelMultipleChoiceField has already been
1294
# instantiated. This proves clean() checks the database during clean() rather
1295
# than caching it at time of instantiation.
1296
# Note, we are using an id of 1006 here since tests that run before
1297
# this may create categories with primary keys up to 6. Use
1298
# a number that is will not conflict.
1299
c6 = Category.objects.create(id=1006, name='Sixth', url='6th')
1300
self.assertEqual(c6.name, 'Sixth')
1301
self.assertQuerysetEqual(f.clean([c6.id]), ["Sixth"])
1303
# Delete a Category object *after* the ModelMultipleChoiceField has already been
1304
# instantiated. This proves clean() checks the database during clean() rather
1305
# than caching it at time of instantiation.
1306
Category.objects.get(url='6th').delete()
1307
with self.assertRaises(ValidationError):
1310
f = forms.ModelMultipleChoiceField(Category.objects.all(), required=False)
1311
self.assertIsInstance(f.clean([]), EmptyQuerySet)
1312
self.assertIsInstance(f.clean(()), EmptyQuerySet)
1313
with self.assertRaises(ValidationError):
1315
with self.assertRaises(ValidationError):
1316
f.clean([str(c3.id), '10'])
1317
with self.assertRaises(ValidationError):
1318
f.clean([str(c1.id), '10'])
1320
# queryset can be changed after the field is created.
1321
f.queryset = Category.objects.exclude(name='Fourth')
1322
self.assertEqual(list(f.choices), [
1323
(c1.pk, 'Entertainment'),
1324
(c2.pk, "It's a test"),
1326
self.assertQuerysetEqual(f.clean([c3.id]), ["Third"])
1327
with self.assertRaises(ValidationError):
1329
with self.assertRaises(ValidationError):
1330
f.clean([str(c3.id), str(c4.id)])
1332
f.queryset = Category.objects.all()
1333
f.label_from_instance = lambda obj: "multicategory " + str(obj)
1334
self.assertEqual(list(f.choices), [
1335
(c1.pk, 'multicategory Entertainment'),
1336
(c2.pk, "multicategory It's a test"),
1337
(c3.pk, 'multicategory Third'),
1338
(c4.pk, 'multicategory Fourth')])
1340
# OneToOneField ###############################################################
1342
self.assertEqual(list(ImprovedArticleForm.base_fields), ['article'])
1344
self.assertEqual(list(ImprovedArticleWithParentLinkForm.base_fields), [])
1346
bw = BetterWriter(name='Joe Better', score=10)
1348
self.assertEqual(sorted(model_to_dict(bw)),
1349
['id', 'name', 'score', 'writer_ptr'])
1351
form = BetterWriterForm({'name': 'Some Name', 'score': 12})
1352
self.assertEqual(form.is_valid(), True)
1356
form = WriterProfileForm()
1357
self.assertHTMLEqual(form.as_p(), '''<p><label for="id_writer">Writer:</label> <select name="writer" id="id_writer">
1358
<option value="" selected="selected">---------</option>
1359
<option value="%s">Bob Woodward</option>
1360
<option value="%s">Carl Bernstein</option>
1361
<option value="%s">Joe Better</option>
1362
<option value="%s">Mike Royko</option>
1364
<p><label for="id_age">Age:</label> <input type="number" name="age" id="id_age" min="0" /></p>''' % (w_woodward.pk, w_bernstein.pk, bw.pk, w_royko.pk))
1367
'writer': six.text_type(w_woodward.pk),
1370
form = WriterProfileForm(data)
1371
instance = form.save()
1372
self.assertEqual(six.text_type(instance), 'Bob Woodward is 65')
1374
form = WriterProfileForm(instance=instance)
1375
self.assertHTMLEqual(form.as_p(), '''<p><label for="id_writer">Writer:</label> <select name="writer" id="id_writer">
1376
<option value="">---------</option>
1377
<option value="%s" selected="selected">Bob Woodward</option>
1378
<option value="%s">Carl Bernstein</option>
1379
<option value="%s">Joe Better</option>
1380
<option value="%s">Mike Royko</option>
1382
<p><label for="id_age">Age:</label> <input type="number" name="age" value="65" id="id_age" min="0" /></p>''' % (w_woodward.pk, w_bernstein.pk, bw.pk, w_royko.pk))
1384
def test_file_field(self):
1385
# Test conditions when files is either not given or empty.
1387
f = TextFileForm(data={'description': 'Assistance'})
1388
self.assertEqual(f.is_valid(), False)
1389
f = TextFileForm(data={'description': 'Assistance'}, files={})
1390
self.assertEqual(f.is_valid(), False)
1392
# Upload a file and ensure it all works as expected.
1395
data={'description': 'Assistance'},
1396
files={'file': SimpleUploadedFile('test1.txt', b'hello world')})
1397
self.assertEqual(f.is_valid(), True)
1398
self.assertEqual(type(f.cleaned_data['file']), SimpleUploadedFile)
1400
self.assertEqual(instance.file.name, 'tests/test1.txt')
1402
instance.file.delete()
1404
data={'description': 'Assistance'},
1405
files={'file': SimpleUploadedFile('test1.txt', b'hello world')})
1406
self.assertEqual(f.is_valid(), True)
1407
self.assertEqual(type(f.cleaned_data['file']), SimpleUploadedFile)
1409
self.assertEqual(instance.file.name, 'tests/test1.txt')
1411
# Check if the max_length attribute has been inherited from the model.
1413
data={'description': 'Assistance'},
1414
files={'file': SimpleUploadedFile('test-maxlength.txt', b'hello world')})
1415
self.assertEqual(f.is_valid(), False)
1417
# Edit an instance that already has the file defined in the model. This will not
1418
# save the file again, but leave it exactly as it is.
1421
data={'description': 'Assistance'},
1423
self.assertEqual(f.is_valid(), True)
1424
self.assertEqual(f.cleaned_data['file'].name, 'tests/test1.txt')
1426
self.assertEqual(instance.file.name, 'tests/test1.txt')
1428
# Delete the current file since this is not done by Django.
1429
instance.file.delete()
1431
# Override the file by uploading a new one.
1434
data={'description': 'Assistance'},
1435
files={'file': SimpleUploadedFile('test2.txt', b'hello world')}, instance=instance)
1436
self.assertEqual(f.is_valid(), True)
1438
self.assertEqual(instance.file.name, 'tests/test2.txt')
1440
# Delete the current file since this is not done by Django.
1441
instance.file.delete()
1443
data={'description': 'Assistance'},
1444
files={'file': SimpleUploadedFile('test2.txt', b'hello world')})
1445
self.assertEqual(f.is_valid(), True)
1447
self.assertEqual(instance.file.name, 'tests/test2.txt')
1449
# Delete the current file since this is not done by Django.
1450
instance.file.delete()
1454
# Test the non-required FileField
1455
f = TextFileForm(data={'description': 'Assistance'})
1456
f.fields['file'].required = False
1457
self.assertEqual(f.is_valid(), True)
1459
self.assertEqual(instance.file.name, '')
1462
data={'description': 'Assistance'},
1463
files={'file': SimpleUploadedFile('test3.txt', b'hello world')}, instance=instance)
1464
self.assertEqual(f.is_valid(), True)
1466
self.assertEqual(instance.file.name, 'tests/test3.txt')
1468
# Instance can be edited w/out re-uploading the file and existing file should be preserved.
1471
data={'description': 'New Description'},
1473
f.fields['file'].required = False
1474
self.assertEqual(f.is_valid(), True)
1476
self.assertEqual(instance.description, 'New Description')
1477
self.assertEqual(instance.file.name, 'tests/test3.txt')
1479
# Delete the current file since this is not done by Django.
1480
instance.file.delete()
1484
data={'description': 'Assistance'},
1485
files={'file': SimpleUploadedFile('test3.txt', b'hello world')})
1486
self.assertEqual(f.is_valid(), True)
1488
self.assertEqual(instance.file.name, 'tests/test3.txt')
1490
# Delete the current file since this is not done by Django.
1491
instance.file.delete()
1494
def test_big_integer_field(self):
1495
bif = BigIntForm({'biggie': '-9223372036854775808'})
1496
self.assertEqual(bif.is_valid(), True)
1497
bif = BigIntForm({'biggie': '-9223372036854775809'})
1498
self.assertEqual(bif.is_valid(), False)
1499
self.assertEqual(bif.errors, {'biggie': ['Ensure this value is greater than or equal to -9223372036854775808.']})
1500
bif = BigIntForm({'biggie': '9223372036854775807'})
1501
self.assertEqual(bif.is_valid(), True)
1502
bif = BigIntForm({'biggie': '9223372036854775808'})
1503
self.assertEqual(bif.is_valid(), False)
1504
self.assertEqual(bif.errors, {'biggie': ['Ensure this value is less than or equal to 9223372036854775807.']})
1506
@skipUnless(test_images, "PIL not installed")
1507
def test_image_field(self):
1508
# ImageField and FileField are nearly identical, but they differ slighty when
1509
# it comes to validation. This specifically tests that #6302 is fixed for
1510
# both file fields and image fields.
1512
with open(os.path.join(os.path.dirname(upath(__file__)), "test.png"), 'rb') as fp:
1513
image_data = fp.read()
1514
with open(os.path.join(os.path.dirname(upath(__file__)), "test2.png"), 'rb') as fp:
1515
image_data2 = fp.read()
1518
data={'description': 'An image'},
1519
files={'image': SimpleUploadedFile('test.png', image_data)})
1520
self.assertEqual(f.is_valid(), True)
1521
self.assertEqual(type(f.cleaned_data['image']), SimpleUploadedFile)
1523
self.assertEqual(instance.image.name, 'tests/test.png')
1524
self.assertEqual(instance.width, 16)
1525
self.assertEqual(instance.height, 16)
1527
# Delete the current file since this is not done by Django, but don't save
1528
# because the dimension fields are not null=True.
1529
instance.image.delete(save=False)
1531
data={'description': 'An image'},
1532
files={'image': SimpleUploadedFile('test.png', image_data)})
1533
self.assertEqual(f.is_valid(), True)
1534
self.assertEqual(type(f.cleaned_data['image']), SimpleUploadedFile)
1536
self.assertEqual(instance.image.name, 'tests/test.png')
1537
self.assertEqual(instance.width, 16)
1538
self.assertEqual(instance.height, 16)
1540
# Edit an instance that already has the (required) image defined in the model. This will not
1541
# save the image again, but leave it exactly as it is.
1543
f = ImageFileForm(data={'description': 'Look, it changed'}, instance=instance)
1544
self.assertEqual(f.is_valid(), True)
1545
self.assertEqual(f.cleaned_data['image'].name, 'tests/test.png')
1547
self.assertEqual(instance.image.name, 'tests/test.png')
1548
self.assertEqual(instance.height, 16)
1549
self.assertEqual(instance.width, 16)
1551
# Delete the current file since this is not done by Django, but don't save
1552
# because the dimension fields are not null=True.
1553
instance.image.delete(save=False)
1554
# Override the file by uploading a new one.
1557
data={'description': 'Changed it'},
1558
files={'image': SimpleUploadedFile('test2.png', image_data2)}, instance=instance)
1559
self.assertEqual(f.is_valid(), True)
1561
self.assertEqual(instance.image.name, 'tests/test2.png')
1562
self.assertEqual(instance.height, 32)
1563
self.assertEqual(instance.width, 48)
1565
# Delete the current file since this is not done by Django, but don't save
1566
# because the dimension fields are not null=True.
1567
instance.image.delete(save=False)
1571
data={'description': 'Changed it'},
1572
files={'image': SimpleUploadedFile('test2.png', image_data2)})
1573
self.assertEqual(f.is_valid(), True)
1575
self.assertEqual(instance.image.name, 'tests/test2.png')
1576
self.assertEqual(instance.height, 32)
1577
self.assertEqual(instance.width, 48)
1579
# Delete the current file since this is not done by Django, but don't save
1580
# because the dimension fields are not null=True.
1581
instance.image.delete(save=False)
1584
# Test the non-required ImageField
1585
# Note: In Oracle, we expect a null ImageField to return '' instead of
1587
if connection.features.interprets_empty_strings_as_nulls:
1588
expected_null_imagefield_repr = ''
1590
expected_null_imagefield_repr = None
1592
f = OptionalImageFileForm(data={'description': 'Test'})
1593
self.assertEqual(f.is_valid(), True)
1595
self.assertEqual(instance.image.name, expected_null_imagefield_repr)
1596
self.assertEqual(instance.width, None)
1597
self.assertEqual(instance.height, None)
1599
f = OptionalImageFileForm(
1600
data={'description': 'And a final one'},
1601
files={'image': SimpleUploadedFile('test3.png', image_data)}, instance=instance)
1602
self.assertEqual(f.is_valid(), True)
1604
self.assertEqual(instance.image.name, 'tests/test3.png')
1605
self.assertEqual(instance.width, 16)
1606
self.assertEqual(instance.height, 16)
1608
# Editing the instance without re-uploading the image should not affect the image or its width/height properties
1609
f = OptionalImageFileForm(
1610
data={'description': 'New Description'},
1612
self.assertEqual(f.is_valid(), True)
1614
self.assertEqual(instance.description, 'New Description')
1615
self.assertEqual(instance.image.name, 'tests/test3.png')
1616
self.assertEqual(instance.width, 16)
1617
self.assertEqual(instance.height, 16)
1619
# Delete the current file since this is not done by Django.
1620
instance.image.delete()
1623
f = OptionalImageFileForm(
1624
data={'description': 'And a final one'},
1625
files={'image': SimpleUploadedFile('test4.png', image_data2)}
1627
self.assertEqual(f.is_valid(), True)
1629
self.assertEqual(instance.image.name, 'tests/test4.png')
1630
self.assertEqual(instance.width, 48)
1631
self.assertEqual(instance.height, 32)
1633
# Test callable upload_to behavior that's dependent on the value of another field in the model
1635
data={'description': 'And a final one', 'path': 'foo'},
1636
files={'image': SimpleUploadedFile('test4.png', image_data)})
1637
self.assertEqual(f.is_valid(), True)
1639
self.assertEqual(instance.image.name, 'foo/test4.png')
1642
def test_media_on_modelform(self):
1643
# Similar to a regular Form class you can define custom media to be used on
1645
f = ModelFormWithMedia()
1646
self.assertHTMLEqual(six.text_type(f.media), '''<link href="/some/form/css" type="text/css" media="all" rel="stylesheet" />
1647
<script type="text/javascript" src="/some/form/javascript"></script>''')
1649
f = CommaSeparatedIntegerForm({'field': '1,2,3'})
1650
self.assertEqual(f.is_valid(), True)
1651
self.assertEqual(f.cleaned_data, {'field': '1,2,3'})
1652
f = CommaSeparatedIntegerForm({'field': '1a,2'})
1653
self.assertEqual(f.errors, {'field': ['Enter only digits separated by commas.']})
1654
f = CommaSeparatedIntegerForm({'field': ',,,,'})
1655
self.assertEqual(f.is_valid(), True)
1656
self.assertEqual(f.cleaned_data, {'field': ',,,,'})
1657
f = CommaSeparatedIntegerForm({'field': '1.2'})
1658
self.assertEqual(f.errors, {'field': ['Enter only digits separated by commas.']})
1659
f = CommaSeparatedIntegerForm({'field': '1,a,2'})
1660
self.assertEqual(f.errors, {'field': ['Enter only digits separated by commas.']})
1661
f = CommaSeparatedIntegerForm({'field': '1,,2'})
1662
self.assertEqual(f.is_valid(), True)
1663
self.assertEqual(f.cleaned_data, {'field': '1,,2'})
1664
f = CommaSeparatedIntegerForm({'field': '1'})
1665
self.assertEqual(f.is_valid(), True)
1666
self.assertEqual(f.cleaned_data, {'field': '1'})
1668
# This Price instance generated by this form is not valid because the quantity
1669
# field is required, but the form is valid because the field is excluded from
1670
# the form. This is for backwards compatibility.
1672
form = PriceFormWithoutQuantity({'price': '6.00'})
1673
self.assertEqual(form.is_valid(), True)
1674
price = form.save(commit=False)
1675
with self.assertRaises(ValidationError):
1678
# The form should not validate fields that it doesn't contain even if they are
1679
# specified using 'fields', not 'exclude'.
1683
form = PriceFormWithoutQuantity({'price': '6.00'})
1684
self.assertEqual(form.is_valid(), True)
1686
# The form should still have an instance of a model that is not complete and
1687
# not saved into a DB yet.
1689
self.assertEqual(form.instance.price, Decimal('6.00'))
1690
self.assertEqual(form.instance.quantity is None, True)
1691
self.assertEqual(form.instance.pk is None, True)
1693
# Choices on CharField and IntegerField
1695
with self.assertRaises(ValidationError):
1696
f.fields['status'].clean('42')
1698
f = ArticleStatusForm()
1699
with self.assertRaises(ValidationError):
1700
f.fields['status'].clean('z')
1702
def test_foreignkeys_which_use_to_field(self):
1703
apple = Inventory.objects.create(barcode=86, name='Apple')
1704
pear = Inventory.objects.create(barcode=22, name='Pear')
1705
core = Inventory.objects.create(barcode=87, name='Core', parent=apple)
1707
field = forms.ModelChoiceField(Inventory.objects.all(), to_field_name='barcode')
1708
self.assertEqual(tuple(field.choices), (
1714
form = InventoryForm(instance=core)
1715
self.assertHTMLEqual(six.text_type(form['parent']), '''<select name="parent" id="id_parent">
1716
<option value="">---------</option>
1717
<option value="86" selected="selected">Apple</option>
1718
<option value="87">Core</option>
1719
<option value="22">Pear</option>
1721
data = model_to_dict(core)
1722
data['parent'] = '22'
1723
form = InventoryForm(data=data, instance=core)
1725
self.assertEqual(core.parent.name, 'Pear')
1727
class CategoryForm(forms.ModelForm):
1728
description = forms.CharField()
1731
fields = ['description', 'url']
1733
self.assertEqual(list(CategoryForm.base_fields),
1734
['description', 'url'])
1736
self.assertHTMLEqual(six.text_type(CategoryForm()), '''<tr><th><label for="id_description">Description:</label></th><td><input type="text" name="description" id="id_description" /></td></tr>
1737
<tr><th><label for="id_url">The URL:</label></th><td><input id="id_url" type="text" name="url" maxlength="40" /></td></tr>''')
1738
# to_field_name should also work on ModelMultipleChoiceField ##################
1740
field = forms.ModelMultipleChoiceField(Inventory.objects.all(), to_field_name='barcode')
1741
self.assertEqual(tuple(field.choices), ((86, 'Apple'), (87, 'Core'), (22, 'Pear')))
1742
self.assertQuerysetEqual(field.clean([86]), ['Apple'])
1744
form = SelectInventoryForm({'items': [87, 22]})
1745
self.assertEqual(form.is_valid(), True)
1746
self.assertEqual(len(form.cleaned_data), 1)
1747
self.assertQuerysetEqual(form.cleaned_data['items'], ['Core', 'Pear'])
1749
def test_model_field_that_returns_none_to_exclude_itself_with_explicit_fields(self):
1750
self.assertEqual(list(CustomFieldForExclusionForm.base_fields),
1752
self.assertHTMLEqual(six.text_type(CustomFieldForExclusionForm()),
1753
'''<tr><th><label for="id_name">Name:</label></th><td><input id="id_name" type="text" name="name" maxlength="10" /></td></tr>''')
1755
def test_iterable_model_m2m(self) :
1756
colour = Colour.objects.create(name='Blue')
1757
form = ColourfulItemForm()
1759
self.assertHTMLEqual(
1761
"""<p><label for="id_name">Name:</label> <input id="id_name" type="text" name="name" maxlength="50" /></p>
1762
<p><label for="id_colours">Colours:</label> <select multiple="multiple" name="colours" id="id_colours">
1763
<option value="%(blue_pk)s">Blue</option>
1764
</select> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></p>"""
1765
% {'blue_pk': colour.pk})
1767
def test_custom_error_messages(self) :
1768
data = {'name1': '@#$!!**@#$', 'name2': '@#$!!**@#$'}
1769
errors = CustomErrorMessageForm(data).errors
1770
self.assertHTMLEqual(
1771
str(errors['name1']),
1772
'<ul class="errorlist"><li>Form custom error message.</li></ul>'
1774
self.assertHTMLEqual(
1775
str(errors['name2']),
1776
'<ul class="errorlist"><li>Model custom error message.</li></ul>'
1780
class M2mHelpTextTest(TestCase):
1781
"""Tests for ticket #9321."""
1782
def test_multiple_widgets(self):
1783
"""Help text of different widgets for ManyToManyFields model fields"""
1784
dreaded_help_text = '<span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span>'
1786
# Default widget (SelectMultiple):
1787
std_form = StatusNoteForm()
1788
self.assertInHTML(dreaded_help_text, std_form.as_p())
1790
# Overridden widget (CheckboxSelectMultiple, a subclass of
1791
# SelectMultiple but with a UI that doesn't involve Control/Command
1792
# keystrokes to extend selection):
1793
form = StatusNoteCBM2mForm()
1795
self.assertInHTML('<ul id="id_status">', html)
1796
self.assertInHTML(dreaded_help_text, html, count=0)