~ubuntu-branches/debian/sid/python-django/sid

« back to all changes in this revision

Viewing changes to tests/model_forms/tests.py

  • Committer: Package Import Robot
  • Author(s): Luke Faraone
  • Date: 2013-11-07 15:33:49 UTC
  • mfrom: (1.3.12)
  • Revision ID: package-import@ubuntu.com-20131107153349-e31sc149l2szs3jb
Tags: 1.6-1
* New upstream version. Closes: #557474, #724637.
* python-django now also suggests the installation of ipython,
  bpython, python-django-doc, and libgdal1.
  Closes: #636511, #686333, #704203
* Set package maintainer to Debian Python Modules Team.
* Bump standards version to 3.9.5, no changes needed.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
from __future__ import absolute_import, unicode_literals
 
2
 
 
3
import datetime
 
4
import os
 
5
from decimal import Decimal
 
6
import warnings
 
7
 
 
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
 
19
 
 
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)
 
26
 
 
27
if test_images:
 
28
    from .models import ImageFile, OptionalImageFile
 
29
    class ImageFileForm(forms.ModelForm):
 
30
        class Meta:
 
31
            model = ImageFile
 
32
            fields = '__all__'
 
33
 
 
34
 
 
35
    class OptionalImageFileForm(forms.ModelForm):
 
36
        class Meta:
 
37
            model = OptionalImageFile
 
38
            fields = '__all__'
 
39
 
 
40
 
 
41
class ProductForm(forms.ModelForm):
 
42
    class Meta:
 
43
        model = Product
 
44
        fields = '__all__'
 
45
 
 
46
 
 
47
class PriceForm(forms.ModelForm):
 
48
    class Meta:
 
49
        model = Price
 
50
        fields = '__all__'
 
51
 
 
52
 
 
53
class BookForm(forms.ModelForm):
 
54
    class Meta:
 
55
        model = Book
 
56
        fields = '__all__'
 
57
 
 
58
 
 
59
class DerivedBookForm(forms.ModelForm):
 
60
    class Meta:
 
61
        model = DerivedBook
 
62
        fields = '__all__'
 
63
 
 
64
 
 
65
 
 
66
class ExplicitPKForm(forms.ModelForm):
 
67
    class Meta:
 
68
        model = ExplicitPK
 
69
        fields = ('key', 'desc',)
 
70
 
 
71
 
 
72
class PostForm(forms.ModelForm):
 
73
    class Meta:
 
74
        model = Post
 
75
        fields = '__all__'
 
76
 
 
77
 
 
78
class DateTimePostForm(forms.ModelForm):
 
79
    class Meta:
 
80
        model = DateTimePost
 
81
        fields = '__all__'
 
82
 
 
83
 
 
84
class DerivedPostForm(forms.ModelForm):
 
85
    class Meta:
 
86
        model = DerivedPost
 
87
        fields = '__all__'
 
88
 
 
89
 
 
90
class CustomWriterForm(forms.ModelForm):
 
91
   name = forms.CharField(required=False)
 
92
 
 
93
   class Meta:
 
94
       model = Writer
 
95
       fields = '__all__'
 
96
 
 
97
 
 
98
class FlexDatePostForm(forms.ModelForm):
 
99
    class Meta:
 
100
        model = FlexibleDatePost
 
101
        fields = '__all__'
 
102
 
 
103
 
 
104
class BaseCategoryForm(forms.ModelForm):
 
105
    class Meta:
 
106
        model = Category
 
107
        fields = '__all__'
 
108
 
 
109
 
 
110
class ArticleForm(forms.ModelForm):
 
111
    class Meta:
 
112
        model = Article
 
113
        fields = '__all__'
 
114
 
 
115
 
 
116
class PartialArticleForm(forms.ModelForm):
 
117
    class Meta:
 
118
        model = Article
 
119
        fields = ('headline','pub_date')
 
120
 
 
121
 
 
122
class RoykoForm(forms.ModelForm):
 
123
    class Meta:
 
124
        model = Writer
 
125
        fields = '__all__'
 
126
 
 
127
 
 
128
class TestArticleForm(forms.ModelForm):
 
129
    class Meta:
 
130
        model = Article
 
131
        fields = '__all__'
 
132
 
 
133
 
 
134
class PartialArticleFormWithSlug(forms.ModelForm):
 
135
    class Meta:
 
136
        model = Article
 
137
        fields = ('headline', 'slug', 'pub_date')
 
138
 
 
139
 
 
140
class ArticleStatusForm(forms.ModelForm):
 
141
    class Meta:
 
142
        model = ArticleStatus
 
143
        fields = '__all__'
 
144
 
 
145
 
 
146
class InventoryForm(forms.ModelForm):
 
147
    class Meta:
 
148
        model = Inventory
 
149
        fields = '__all__'
 
150
 
 
151
 
 
152
class SelectInventoryForm(forms.Form):
 
153
    items = forms.ModelMultipleChoiceField(Inventory.objects.all(), to_field_name='barcode')
 
154
 
 
155
 
 
156
class CustomFieldForExclusionForm(forms.ModelForm):
 
157
    class Meta:
 
158
        model = CustomFieldForExclusionModel
 
159
        fields = ['name', 'markup']
 
160
 
 
161
 
 
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)
 
166
 
 
167
    class Meta:
 
168
        model = Category
 
169
        fields = '__all__'
 
170
 
 
171
 
 
172
class ImprovedArticleForm(forms.ModelForm):
 
173
    class Meta:
 
174
        model = ImprovedArticle
 
175
        fields = '__all__'
 
176
 
 
177
 
 
178
class ImprovedArticleWithParentLinkForm(forms.ModelForm):
 
179
    class Meta:
 
180
        model = ImprovedArticleWithParentLink
 
181
        fields = '__all__'
 
182
 
 
183
 
 
184
class BetterWriterForm(forms.ModelForm):
 
185
    class Meta:
 
186
        model = BetterWriter
 
187
        fields = '__all__'
 
188
 
 
189
class WriterProfileForm(forms.ModelForm):
 
190
    class Meta:
 
191
        model = WriterProfile
 
192
        fields = '__all__'
 
193
 
 
194
 
 
195
class TextFileForm(forms.ModelForm):
 
196
    class Meta:
 
197
        model = TextFile
 
198
        fields = '__all__'
 
199
 
 
200
 
 
201
class BigIntForm(forms.ModelForm):
 
202
    class Meta:
 
203
        model = BigInt
 
204
        fields = '__all__'
 
205
 
 
206
 
 
207
class ModelFormWithMedia(forms.ModelForm):
 
208
    class Media:
 
209
        js = ('/some/form/javascript',)
 
210
        css = {
 
211
            'all': ('/some/form/css',)
 
212
        }
 
213
    class Meta:
 
214
        model = TextFile
 
215
        fields = '__all__'
 
216
 
 
217
 
 
218
class CommaSeparatedIntegerForm(forms.ModelForm):
 
219
    class Meta:
 
220
        model = CommaSeparatedInteger
 
221
        fields = '__all__'
 
222
 
 
223
 
 
224
class PriceFormWithoutQuantity(forms.ModelForm):
 
225
    class Meta:
 
226
        model = Price
 
227
        exclude = ('quantity',)
 
228
 
 
229
 
 
230
class ColourfulItemForm(forms.ModelForm):
 
231
    class Meta:
 
232
        model = ColourfulItem
 
233
        fields = '__all__'
 
234
 
 
235
# model forms for testing work on #9321:
 
236
 
 
237
class StatusNoteForm(forms.ModelForm):
 
238
    class Meta:
 
239
        model = ArticleStatusNote
 
240
        fields = '__all__'
 
241
 
 
242
 
 
243
class StatusNoteCBM2mForm(forms.ModelForm):
 
244
    class Meta:
 
245
        model = ArticleStatusNote
 
246
        fields = '__all__'
 
247
        widgets = {'status': forms.CheckboxSelectMultiple}
 
248
 
 
249
 
 
250
class CustomErrorMessageForm(forms.ModelForm):
 
251
    name1 = forms.CharField(error_messages={'invalid': 'Form custom error message.'})
 
252
 
 
253
    class Meta:
 
254
        fields = '__all__'
 
255
        model = CustomErrorMessage
 
256
 
 
257
 
 
258
class ModelFormBaseTest(TestCase):
 
259
    def test_base_form(self):
 
260
        self.assertEqual(list(BaseCategoryForm.base_fields),
 
261
                         ['name', 'slug', 'url'])
 
262
 
 
263
    def test_missing_fields_attribute(self):
 
264
        with warnings.catch_warnings(record=True) as w:
 
265
            warnings.simplefilter("always", PendingDeprecationWarning)
 
266
 
 
267
            class MissingFieldsForm(forms.ModelForm):
 
268
                class Meta:
 
269
                    model = Category
 
270
 
 
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:
 
274
 
 
275
        # self.assertEqual(w[0].category, PendingDeprecationWarning)
 
276
 
 
277
        # Until end of the deprecation cycle, should still create the
 
278
        # form as before:
 
279
        self.assertEqual(list(MissingFieldsForm.base_fields),
 
280
                         ['name', 'slug', 'url'])
 
281
 
 
282
    def test_extra_fields(self):
 
283
        class ExtraFields(BaseCategoryForm):
 
284
            some_extra_field = forms.BooleanField()
 
285
 
 
286
        self.assertEqual(list(ExtraFields.base_fields),
 
287
                         ['name', 'slug', 'url', 'some_extra_field'])
 
288
 
 
289
    def test_replace_field(self):
 
290
        class ReplaceField(forms.ModelForm):
 
291
            url = forms.BooleanField()
 
292
 
 
293
            class Meta:
 
294
                model = Category
 
295
                fields = '__all__'
 
296
 
 
297
        self.assertIsInstance(ReplaceField.base_fields['url'],
 
298
                                     forms.fields.BooleanField)
 
299
 
 
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()
 
305
 
 
306
            class Meta:
 
307
                model = Category
 
308
                fields = ['url']
 
309
 
 
310
        self.assertIsInstance(ReplaceField.base_fields['url'],
 
311
                                     forms.fields.BooleanField)
 
312
 
 
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()
 
318
 
 
319
            class Meta:
 
320
                model = Category
 
321
                fields = [] # url will still appear, since it is explicit above
 
322
 
 
323
        self.assertIsInstance(ReplaceField.base_fields['url'],
 
324
                                     forms.fields.BooleanField)
 
325
 
 
326
    def test_override_field(self):
 
327
        class WriterForm(forms.ModelForm):
 
328
            book = forms.CharField(required=False)
 
329
 
 
330
            class Meta:
 
331
                model = Writer
 
332
                fields = '__all__'
 
333
 
 
334
        wf = WriterForm({'name': 'Richard Lockridge'})
 
335
        self.assertTrue(wf.is_valid())
 
336
 
 
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):
 
341
                class Meta:
 
342
                    model = Category
 
343
                    fields = ['nonexistent']
 
344
 
 
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):
 
349
                class Meta:
 
350
                    model = Category
 
351
                    fields = ('url') # note the missing comma
 
352
 
 
353
    def test_exclude_fields(self):
 
354
        class ExcludeFields(forms.ModelForm):
 
355
            class Meta:
 
356
                model = Category
 
357
                exclude = ['url']
 
358
 
 
359
        self.assertEqual(list(ExcludeFields.base_fields),
 
360
                         ['name', 'slug'])
 
361
 
 
362
    def test_exclude_nonexistent_field(self):
 
363
        class ExcludeFields(forms.ModelForm):
 
364
            class Meta:
 
365
                model = Category
 
366
                exclude = ['nonexistent']
 
367
 
 
368
        self.assertEqual(list(ExcludeFields.base_fields),
 
369
                         ['name', 'slug', 'url'])
 
370
 
 
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):
 
375
                class Meta:
 
376
                    model = Category
 
377
                    exclude = ('url') # note the missing comma
 
378
 
 
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.
 
383
            """
 
384
            class Meta:
 
385
                model = Category
 
386
                fields = ['name', 'url']
 
387
                exclude = ['url']
 
388
 
 
389
        self.assertEqual(list(ConfusedForm.base_fields),
 
390
                         ['name'])
 
391
 
 
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
 
398
            mechanics.
 
399
            """
 
400
            class Meta:
 
401
                model = Article
 
402
                fields = '__all__'
 
403
            # MixModelForm is now an Article-related thing, because MixModelForm.Meta
 
404
            # overrides BaseCategoryForm.Meta.
 
405
 
 
406
        self.assertEqual(
 
407
            list(MixModelForm.base_fields),
 
408
            ['headline', 'slug', 'pub_date', 'writer', 'article', 'categories', 'status']
 
409
        )
 
410
 
 
411
    def test_article_form(self):
 
412
        self.assertEqual(
 
413
            list(ArticleForm.base_fields),
 
414
            ['headline', 'slug', 'pub_date', 'writer', 'article', 'categories', 'status']
 
415
        )
 
416
 
 
417
    def test_bad_form(self):
 
418
        #First class with a Meta class wins...
 
419
        class BadForm(ArticleForm, BaseCategoryForm):
 
420
            pass
 
421
 
 
422
        self.assertEqual(
 
423
            list(BadForm.base_fields),
 
424
            ['headline', 'slug', 'pub_date', 'writer', 'article', 'categories', 'status']
 
425
        )
 
426
 
 
427
    def test_invalid_meta_model(self):
 
428
        class InvalidModelForm(forms.ModelForm):
 
429
            class Meta:
 
430
                pass  # no model
 
431
 
 
432
        # Can't create new form
 
433
        with self.assertRaises(ValueError):
 
434
            f = InvalidModelForm()
 
435
 
 
436
        # Even if you provide a model instance
 
437
        with self.assertRaises(ValueError):
 
438
            f = InvalidModelForm(instance=Category)
 
439
 
 
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).
 
445
            """
 
446
            pass
 
447
 
 
448
        self.assertEqual(list(SubCategoryForm.base_fields),
 
449
                         ['name', 'slug', 'url'])
 
450
 
 
451
    def test_subclassmeta_form(self):
 
452
        class SomeCategoryForm(forms.ModelForm):
 
453
             checkbox = forms.BooleanField()
 
454
 
 
455
             class Meta:
 
456
                 model = Category
 
457
                 fields = '__all__'
 
458
 
 
459
        class SubclassMeta(SomeCategoryForm):
 
460
            """ We can also subclass the Meta inner class to change the fields
 
461
            list.
 
462
            """
 
463
            class Meta(SomeCategoryForm.Meta):
 
464
                exclude = ['url']
 
465
 
 
466
        self.assertHTMLEqual(
 
467
            str(SubclassMeta()),
 
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>"""
 
471
            )
 
472
 
 
473
    def test_orderfields_form(self):
 
474
        class OrderFields(forms.ModelForm):
 
475
            class Meta:
 
476
                model = Category
 
477
                fields = ['url', 'name']
 
478
 
 
479
        self.assertEqual(list(OrderFields.base_fields),
 
480
                         ['url', 'name'])
 
481
        self.assertHTMLEqual(
 
482
            str(OrderFields()),
 
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>"""
 
485
            )
 
486
 
 
487
    def test_orderfields2_form(self):
 
488
        class OrderFields2(forms.ModelForm):
 
489
            class Meta:
 
490
                model = Category
 
491
                fields = ['slug', 'url', 'name']
 
492
                exclude = ['url']
 
493
 
 
494
        self.assertEqual(list(OrderFields2.base_fields),
 
495
                         ['slug', 'name'])
 
496
 
 
497
 
 
498
class FieldOverridesTroughFormMetaForm(forms.ModelForm):
 
499
    class Meta:
 
500
        model = Category
 
501
        fields = ['name', 'url', 'slug']
 
502
        widgets = {
 
503
            'name': forms.Textarea,
 
504
            'url': forms.TextInput(attrs={'class': 'url'})
 
505
        }
 
506
        labels = {
 
507
            'name': 'Title',
 
508
        }
 
509
        help_texts = {
 
510
            'slug': 'Watch out! Letters, numbers, underscores and hyphens only.',
 
511
        }
 
512
        error_messages = {
 
513
            'slug': {
 
514
                'invalid': (
 
515
                    "Didn't you read the help text? "
 
516
                    "We said letters, numbers, underscores and hyphens only!"
 
517
                )
 
518
            }
 
519
        }
 
520
 
 
521
 
 
522
class TestFieldOverridesTroughFormMeta(TestCase):
 
523
    def test_widget_overrides(self):
 
524
        form = FieldOverridesTroughFormMetaForm()
 
525
        self.assertHTMLEqual(
 
526
            str(form['name']),
 
527
            '<textarea id="id_name" rows="10" cols="40" name="name"></textarea>',
 
528
        )
 
529
        self.assertHTMLEqual(
 
530
            str(form['url']),
 
531
            '<input id="id_url" type="text" class="url" name="url" maxlength="40" />',
 
532
        )
 
533
        self.assertHTMLEqual(
 
534
            str(form['slug']),
 
535
            '<input id="id_slug" type="text" name="slug" maxlength="20" />',
 
536
        )
 
537
 
 
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>',
 
543
        )
 
544
        self.assertHTMLEqual(
 
545
            str(form['url'].label_tag()),
 
546
            '<label for="id_url">The URL:</label>',
 
547
        )
 
548
        self.assertHTMLEqual(
 
549
            str(form['slug'].label_tag()),
 
550
            '<label for="id_slug">Slug:</label>',
 
551
        )
 
552
 
 
553
    def test_help_text_overrides(self):
 
554
        form = FieldOverridesTroughFormMetaForm()
 
555
        self.assertEqual(
 
556
            form['slug'].help_text,
 
557
            'Watch out! Letters, numbers, underscores and hyphens only.',
 
558
        )
 
559
 
 
560
    def test_error_messages_overrides(self):
 
561
        form = FieldOverridesTroughFormMetaForm(data={
 
562
            'name': 'Category',
 
563
            'url': '/category/',
 
564
            'slug': '!%#*@',
 
565
        })
 
566
        form.full_clean()
 
567
 
 
568
        error = [
 
569
            "Didn't you read the help text? "
 
570
            "We said letters, numbers, underscores and hyphens only!",
 
571
        ]
 
572
        self.assertEqual(form.errors, {'slug': error})
 
573
 
 
574
 
 
575
class IncompleteCategoryFormWithFields(forms.ModelForm):
 
576
    """
 
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.
 
579
    """
 
580
    url = forms.CharField(required=False)
 
581
 
 
582
    class Meta:
 
583
        fields = ('name', 'slug')
 
584
        model = Category
 
585
 
 
586
class IncompleteCategoryFormWithExclude(forms.ModelForm):
 
587
    """
 
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.
 
590
    """
 
591
    url = forms.CharField(required=False)
 
592
 
 
593
    class Meta:
 
594
        exclude = ['url']
 
595
        model = Category
 
596
 
 
597
 
 
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()
 
602
 
 
603
    def test_validates_with_replaced_field_excluded(self):
 
604
        form = IncompleteCategoryFormWithExclude(data={'name': 'some name', 'slug': 'some-slug'})
 
605
        assert form.is_valid()
 
606
 
 
607
    def test_notrequired_overrides_notblank(self):
 
608
        form = CustomWriterForm({})
 
609
        assert form.is_valid()
 
610
 
 
611
 
 
612
 
 
613
 
 
614
# unique/unique_together validation
 
615
class UniqueTest(TestCase):
 
616
    def setUp(self):
 
617
        self.writer = Writer.objects.create(name='Mike Royko')
 
618
 
 
619
    def test_simple_unique(self):
 
620
        form = ProductForm({'slug': 'teddy-bear-blue'})
 
621
        self.assertTrue(form.is_valid())
 
622
        obj = form.save()
 
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())
 
628
 
 
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())
 
633
        form.save()
 
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.'])
 
638
 
 
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())
 
643
        form.save()
 
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())
 
650
        form.save()
 
651
        form = BookForm({'title': title})
 
652
        self.assertTrue(form.is_valid())
 
653
 
 
654
    def test_inherited_unique(self):
 
655
        title = 'Boss'
 
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.'])
 
661
 
 
662
    def test_inherited_unique_together(self):
 
663
        title = 'Boss'
 
664
        form = BookForm({'title': title, 'author': self.writer.pk})
 
665
        self.assertTrue(form.is_valid())
 
666
        form.save()
 
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.'])
 
671
 
 
672
    def test_abstract_inherited_unique(self):
 
673
        title = 'Boss'
 
674
        isbn = '12345'
 
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.'])
 
680
 
 
681
    def test_abstract_inherited_unique_together(self):
 
682
        title = 'Boss'
 
683
        isbn = '12345'
 
684
        dbook = DerivedBook.objects.create(title=title, author=self.writer, isbn=isbn)
 
685
        form = DerivedBookForm({
 
686
                    'title': 'Other',
 
687
                    'author': self.writer.pk,
 
688
                    'isbn': '9876',
 
689
                    'suffix1': '0',
 
690
                    'suffix2': '0'
 
691
                })
 
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.'])
 
696
 
 
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())
 
701
 
 
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())
 
706
        form.save()
 
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.'])
 
713
 
 
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.'])
 
739
 
 
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())
 
756
 
 
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())
 
778
 
 
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))
 
782
 
 
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())
 
792
 
 
793
class ModelToDictTests(TestCase):
 
794
    """
 
795
    Tests for forms.models.model_to_dict
 
796
    """
 
797
    def test_model_to_dict_many_to_many(self):
 
798
        categories=[
 
799
            Category(name='TestName1', slug='TestName1', url='url1'),
 
800
            Category(name='TestName2', slug='TestName2', url='url2'),
 
801
            Category(name='TestName3', slug='TestName3', url='url3')
 
802
        ]
 
803
        for c in categories:
 
804
            c.save()
 
805
        writer = Writer(name='Test writer')
 
806
        writer.save()
 
807
 
 
808
        art = Article(
 
809
            headline='Test article',
 
810
            slug='test-article',
 
811
            pub_date=datetime.date(1988, 1, 4),
 
812
            writer=writer,
 
813
            article='Hello.'
 
814
        )
 
815
        art.save()
 
816
        for c in categories:
 
817
            art.categories.add(c)
 
818
        art.save()
 
819
 
 
820
        with self.assertNumQueries(1):
 
821
            d = model_to_dict(art)
 
822
 
 
823
        #Ensure all many-to-many categories appear in model_to_dict
 
824
        for c in categories:
 
825
            self.assertIn(c.pk, d['categories'])
 
826
        #Ensure many-to-many relation appears as a list
 
827
        self.assertIsInstance(d['categories'], list)
 
828
 
 
829
class OldFormForXTests(TestCase):
 
830
    def test_base_form(self):
 
831
        self.assertEqual(Category.objects.count(), 0)
 
832
        f = BaseCategoryForm()
 
833
        self.assertHTMLEqual(
 
834
            str(f),
 
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>"""
 
838
            )
 
839
        self.assertHTMLEqual(
 
840
            str(f.as_ul()),
 
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>"""
 
844
            )
 
845
        self.assertHTMLEqual(
 
846
            str(f["name"]),
 
847
            """<input id="id_name" type="text" name="name" maxlength="20" />""")
 
848
 
 
849
    def test_auto_id(self):
 
850
        f = BaseCategoryForm(auto_id=False)
 
851
        self.assertHTMLEqual(
 
852
            str(f.as_ul()),
 
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>"""
 
856
            )
 
857
 
 
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')
 
867
        c1 = f.save()
 
868
        # Testing wether the same object is returned from the
 
869
        # ORM... not the fastest way...
 
870
 
 
871
        self.assertEqual(c1, Category.objects.all()[0])
 
872
        self.assertEqual(c1.name, "Entertainment")
 
873
        self.assertEqual(Category.objects.count(), 1)
 
874
 
 
875
        f = BaseCategoryForm({'name': "It's a test",
 
876
                              'slug': 'its-test',
 
877
                              'url': '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')
 
882
        c2 = f.save()
 
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)
 
888
 
 
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)
 
900
        c3.save()
 
901
        self.assertEqual(Category.objects.count(), 3)
 
902
 
 
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):
 
909
            f.save()
 
910
        f = BaseCategoryForm({'name': '', 'slug': '', 'url': 'foo'})
 
911
        with self.assertRaises(ValueError):
 
912
            f.save()
 
913
 
 
914
        # Create a couple of Writers.
 
915
        w_royko = Writer(name='Mike Royko')
 
916
        w_royko.save()
 
917
        w_woodward = Writer(name='Bob Woodward')
 
918
        w_woodward.save()
 
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>
 
929
</select></td></tr>
 
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&#39;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))
 
942
 
 
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>''')
 
952
 
 
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>''')
 
958
 
 
959
        art = Article(
 
960
                    headline='Test article',
 
961
                    slug='test-article',
 
962
                    pub_date=datetime.date(1988, 1, 4),
 
963
                    writer=w,
 
964
                    article='Hello.'
 
965
                )
 
966
        art.save()
 
967
        art_id_1 = art.id
 
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>
 
977
</select></li>
 
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&#39;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),
 
995
                'article': 'Hello.'
 
996
            }, instance=art)
 
997
        self.assertEqual(f.errors, {})
 
998
        self.assertEqual(f.is_valid(), True)
 
999
        test_art = f.save()
 
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)
 
1014
        new_art = f.save()
 
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')
 
1018
 
 
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>
 
1031
</select></li>
 
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&#39;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))
 
1044
 
 
1045
        # Initial values can be provided for model forms
 
1046
        f = TestArticleForm(
 
1047
                auto_id=False,
 
1048
                initial={
 
1049
                    'headline': 'Your headline here',
 
1050
                    'categories': [str(c1.id), str(c2.id)]
 
1051
                })
 
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>
 
1059
</select></li>
 
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&#39;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))
 
1072
 
 
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)
 
1081
        new_art = f.save()
 
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"])
 
1086
 
 
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)
 
1090
        new_art = f.save()
 
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(), [])
 
1094
 
 
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)]})
 
1098
        new_art = f.save()
 
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"])
 
1103
 
 
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.'})
 
1107
        new_art = f.save()
 
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(), [])
 
1112
 
 
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)
 
1118
 
 
1119
        # Manually save the instance
 
1120
        new_art.save()
 
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)
 
1123
 
 
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(), [])
 
1127
 
 
1128
        # Save the m2m data on the form
 
1129
        f.save_m2m()
 
1130
        self.assertQuerysetEqual(new_art.categories.order_by('name'), ["Entertainment", "It's a test"])
 
1131
 
 
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')
 
1141
 
 
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>
 
1153
</select></li>
 
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&#39;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))
 
1166
 
 
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>
 
1179
</select></li>
 
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&#39;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))
 
1193
 
 
1194
        # ModelChoiceField ############################################################
 
1195
 
 
1196
        f = forms.ModelChoiceField(Category.objects.all())
 
1197
        self.assertEqual(list(f.choices), [
 
1198
            ('', '---------'),
 
1199
            (c1.pk, 'Entertainment'),
 
1200
            (c2.pk, "It's a test"),
 
1201
            (c3.pk, 'Third'),
 
1202
            (c4.pk, 'Fourth')])
 
1203
        self.assertEqual(5, len(f.choices))
 
1204
        with self.assertRaises(ValidationError):
 
1205
            f.clean('')
 
1206
        with self.assertRaises(ValidationError):
 
1207
            f.clean(None)
 
1208
        with self.assertRaises(ValidationError):
 
1209
            f.clean(0)
 
1210
        self.assertEqual(f.clean(c3.id).name, 'Third')
 
1211
        self.assertEqual(f.clean(c2.id).name, "It's a test")
 
1212
 
 
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')
 
1219
 
 
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):
 
1225
            f.clean(c5.id)
 
1226
 
 
1227
        f = forms.ModelChoiceField(Category.objects.filter(pk=c1.id), required=False)
 
1228
        self.assertEqual(f.clean(''), None)
 
1229
        f.clean('')
 
1230
        self.assertEqual(f.clean(str(c1.id)).name, "Entertainment")
 
1231
        with self.assertRaises(ValidationError):
 
1232
            f.clean('100')
 
1233
 
 
1234
        # queryset can be changed after the field is created.
 
1235
        f.queryset = Category.objects.exclude(name='Fourth')
 
1236
        self.assertEqual(list(f.choices), [
 
1237
            ('', '---------'),
 
1238
            (c1.pk, 'Entertainment'),
 
1239
            (c2.pk, "It's a test"),
 
1240
            (c3.pk, 'Third')])
 
1241
        self.assertEqual(f.clean(c3.id).name, 'Third')
 
1242
        with self.assertRaises(ValidationError):
 
1243
            f.clean(c4.id)
 
1244
 
 
1245
        # check that we can safely iterate choices repeatedly
 
1246
        gen_one = list(f.choices)
 
1247
        gen_two = f.choices
 
1248
        self.assertEqual(gen_one[2], (c2.pk, "It's a test"))
 
1249
        self.assertEqual(list(gen_two), [
 
1250
            ('', '---------'),
 
1251
            (c1.pk, 'Entertainment'),
 
1252
            (c2.pk, "It's a test"),
 
1253
            (c3.pk, 'Third')])
 
1254
 
 
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), [
 
1259
            ('', '---------'),
 
1260
            (c1.pk, 'category Entertainment'),
 
1261
            (c2.pk, "category It's a test"),
 
1262
            (c3.pk, 'category Third'),
 
1263
            (c4.pk, 'category Fourth')])
 
1264
 
 
1265
        # ModelMultipleChoiceField ####################################################
 
1266
 
 
1267
        f = forms.ModelMultipleChoiceField(Category.objects.all())
 
1268
        self.assertEqual(list(f.choices), [
 
1269
            (c1.pk, 'Entertainment'),
 
1270
            (c2.pk, "It's a test"),
 
1271
            (c3.pk, 'Third'),
 
1272
            (c4.pk, 'Fourth')])
 
1273
        with self.assertRaises(ValidationError):
 
1274
            f.clean(None)
 
1275
        with self.assertRaises(ValidationError):
 
1276
            f.clean([])
 
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"],
 
1281
                                 ordered=False)
 
1282
        self.assertQuerysetEqual(f.clean([c1.id, str(c2.id)]), ["Entertainment", "It's a test"],
 
1283
                                 ordered=False)
 
1284
        self.assertQuerysetEqual(f.clean((c1.id, str(c2.id))), ["Entertainment", "It's a test"],
 
1285
                                 ordered=False)
 
1286
        with self.assertRaises(ValidationError):
 
1287
            f.clean(['100'])
 
1288
        with self.assertRaises(ValidationError):
 
1289
            f.clean('hello')
 
1290
        with self.assertRaises(ValidationError):
 
1291
            f.clean(['fail'])
 
1292
 
 
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"])
 
1302
 
 
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):
 
1308
            f.clean([c6.id])
 
1309
 
 
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):
 
1314
            f.clean(['10'])
 
1315
        with self.assertRaises(ValidationError):
 
1316
            f.clean([str(c3.id), '10'])
 
1317
        with self.assertRaises(ValidationError):
 
1318
            f.clean([str(c1.id), '10'])
 
1319
 
 
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"),
 
1325
            (c3.pk, 'Third')])
 
1326
        self.assertQuerysetEqual(f.clean([c3.id]), ["Third"])
 
1327
        with self.assertRaises(ValidationError):
 
1328
            f.clean([c4.id])
 
1329
        with self.assertRaises(ValidationError):
 
1330
            f.clean([str(c3.id), str(c4.id)])
 
1331
 
 
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')])
 
1339
 
 
1340
        # OneToOneField ###############################################################
 
1341
 
 
1342
        self.assertEqual(list(ImprovedArticleForm.base_fields), ['article'])
 
1343
 
 
1344
        self.assertEqual(list(ImprovedArticleWithParentLinkForm.base_fields), [])
 
1345
 
 
1346
        bw = BetterWriter(name='Joe Better', score=10)
 
1347
        bw.save()
 
1348
        self.assertEqual(sorted(model_to_dict(bw)),
 
1349
                         ['id', 'name', 'score', 'writer_ptr'])
 
1350
 
 
1351
        form = BetterWriterForm({'name': 'Some Name', 'score': 12})
 
1352
        self.assertEqual(form.is_valid(), True)
 
1353
        bw2 = form.save()
 
1354
        bw2.delete()
 
1355
 
 
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>
 
1363
</select></p>
 
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))
 
1365
 
 
1366
        data = {
 
1367
            'writer': six.text_type(w_woodward.pk),
 
1368
            'age': '65',
 
1369
        }
 
1370
        form = WriterProfileForm(data)
 
1371
        instance = form.save()
 
1372
        self.assertEqual(six.text_type(instance), 'Bob Woodward is 65')
 
1373
 
 
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>
 
1381
</select></p>
 
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))
 
1383
 
 
1384
    def test_file_field(self):
 
1385
        # Test conditions when files is either not given or empty.
 
1386
 
 
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)
 
1391
 
 
1392
        # Upload a file and ensure it all works as expected.
 
1393
 
 
1394
        f = TextFileForm(
 
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)
 
1399
        instance = f.save()
 
1400
        self.assertEqual(instance.file.name, 'tests/test1.txt')
 
1401
 
 
1402
        instance.file.delete()
 
1403
        f = TextFileForm(
 
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)
 
1408
        instance = f.save()
 
1409
        self.assertEqual(instance.file.name, 'tests/test1.txt')
 
1410
 
 
1411
        # Check if the max_length attribute has been inherited from the model.
 
1412
        f = TextFileForm(
 
1413
                data={'description': 'Assistance'},
 
1414
                files={'file': SimpleUploadedFile('test-maxlength.txt', b'hello world')})
 
1415
        self.assertEqual(f.is_valid(), False)
 
1416
 
 
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.
 
1419
 
 
1420
        f = TextFileForm(
 
1421
                data={'description': 'Assistance'},
 
1422
                instance=instance)
 
1423
        self.assertEqual(f.is_valid(), True)
 
1424
        self.assertEqual(f.cleaned_data['file'].name, 'tests/test1.txt')
 
1425
        instance = f.save()
 
1426
        self.assertEqual(instance.file.name, 'tests/test1.txt')
 
1427
 
 
1428
        # Delete the current file since this is not done by Django.
 
1429
        instance.file.delete()
 
1430
 
 
1431
        # Override the file by uploading a new one.
 
1432
 
 
1433
        f = TextFileForm(
 
1434
                data={'description': 'Assistance'},
 
1435
                files={'file': SimpleUploadedFile('test2.txt', b'hello world')}, instance=instance)
 
1436
        self.assertEqual(f.is_valid(), True)
 
1437
        instance = f.save()
 
1438
        self.assertEqual(instance.file.name, 'tests/test2.txt')
 
1439
 
 
1440
        # Delete the current file since this is not done by Django.
 
1441
        instance.file.delete()
 
1442
        f = TextFileForm(
 
1443
                data={'description': 'Assistance'},
 
1444
                files={'file': SimpleUploadedFile('test2.txt', b'hello world')})
 
1445
        self.assertEqual(f.is_valid(), True)
 
1446
        instance = f.save()
 
1447
        self.assertEqual(instance.file.name, 'tests/test2.txt')
 
1448
 
 
1449
        # Delete the current file since this is not done by Django.
 
1450
        instance.file.delete()
 
1451
 
 
1452
        instance.delete()
 
1453
 
 
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)
 
1458
        instance = f.save()
 
1459
        self.assertEqual(instance.file.name, '')
 
1460
 
 
1461
        f = TextFileForm(
 
1462
                data={'description': 'Assistance'},
 
1463
                files={'file': SimpleUploadedFile('test3.txt', b'hello world')}, instance=instance)
 
1464
        self.assertEqual(f.is_valid(), True)
 
1465
        instance = f.save()
 
1466
        self.assertEqual(instance.file.name, 'tests/test3.txt')
 
1467
 
 
1468
        # Instance can be edited w/out re-uploading the file and existing file should be preserved.
 
1469
 
 
1470
        f = TextFileForm(
 
1471
                data={'description': 'New Description'},
 
1472
                instance=instance)
 
1473
        f.fields['file'].required = False
 
1474
        self.assertEqual(f.is_valid(), True)
 
1475
        instance = f.save()
 
1476
        self.assertEqual(instance.description, 'New Description')
 
1477
        self.assertEqual(instance.file.name, 'tests/test3.txt')
 
1478
 
 
1479
        # Delete the current file since this is not done by Django.
 
1480
        instance.file.delete()
 
1481
        instance.delete()
 
1482
 
 
1483
        f = TextFileForm(
 
1484
                data={'description': 'Assistance'},
 
1485
                files={'file': SimpleUploadedFile('test3.txt', b'hello world')})
 
1486
        self.assertEqual(f.is_valid(), True)
 
1487
        instance = f.save()
 
1488
        self.assertEqual(instance.file.name, 'tests/test3.txt')
 
1489
 
 
1490
        # Delete the current file since this is not done by Django.
 
1491
        instance.file.delete()
 
1492
        instance.delete()
 
1493
 
 
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.']})
 
1505
 
 
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.
 
1511
 
 
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()
 
1516
 
 
1517
        f = ImageFileForm(
 
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)
 
1522
        instance = f.save()
 
1523
        self.assertEqual(instance.image.name, 'tests/test.png')
 
1524
        self.assertEqual(instance.width, 16)
 
1525
        self.assertEqual(instance.height, 16)
 
1526
 
 
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)
 
1530
        f = ImageFileForm(
 
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)
 
1535
        instance = f.save()
 
1536
        self.assertEqual(instance.image.name, 'tests/test.png')
 
1537
        self.assertEqual(instance.width, 16)
 
1538
        self.assertEqual(instance.height, 16)
 
1539
 
 
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.
 
1542
 
 
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')
 
1546
        instance = f.save()
 
1547
        self.assertEqual(instance.image.name, 'tests/test.png')
 
1548
        self.assertEqual(instance.height, 16)
 
1549
        self.assertEqual(instance.width, 16)
 
1550
 
 
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.
 
1555
 
 
1556
        f = ImageFileForm(
 
1557
                data={'description': 'Changed it'},
 
1558
                files={'image': SimpleUploadedFile('test2.png', image_data2)}, instance=instance)
 
1559
        self.assertEqual(f.is_valid(), True)
 
1560
        instance = f.save()
 
1561
        self.assertEqual(instance.image.name, 'tests/test2.png')
 
1562
        self.assertEqual(instance.height, 32)
 
1563
        self.assertEqual(instance.width, 48)
 
1564
 
 
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)
 
1568
        instance.delete()
 
1569
 
 
1570
        f = ImageFileForm(
 
1571
                data={'description': 'Changed it'},
 
1572
                files={'image': SimpleUploadedFile('test2.png', image_data2)})
 
1573
        self.assertEqual(f.is_valid(), True)
 
1574
        instance = f.save()
 
1575
        self.assertEqual(instance.image.name, 'tests/test2.png')
 
1576
        self.assertEqual(instance.height, 32)
 
1577
        self.assertEqual(instance.width, 48)
 
1578
 
 
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)
 
1582
        instance.delete()
 
1583
 
 
1584
        # Test the non-required ImageField
 
1585
        # Note: In Oracle, we expect a null ImageField to return '' instead of
 
1586
        # None.
 
1587
        if connection.features.interprets_empty_strings_as_nulls:
 
1588
            expected_null_imagefield_repr = ''
 
1589
        else:
 
1590
            expected_null_imagefield_repr = None
 
1591
 
 
1592
        f = OptionalImageFileForm(data={'description': 'Test'})
 
1593
        self.assertEqual(f.is_valid(), True)
 
1594
        instance = f.save()
 
1595
        self.assertEqual(instance.image.name, expected_null_imagefield_repr)
 
1596
        self.assertEqual(instance.width, None)
 
1597
        self.assertEqual(instance.height, None)
 
1598
 
 
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)
 
1603
        instance = f.save()
 
1604
        self.assertEqual(instance.image.name, 'tests/test3.png')
 
1605
        self.assertEqual(instance.width, 16)
 
1606
        self.assertEqual(instance.height, 16)
 
1607
 
 
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'},
 
1611
                instance=instance)
 
1612
        self.assertEqual(f.is_valid(), True)
 
1613
        instance = f.save()
 
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)
 
1618
 
 
1619
        # Delete the current file since this is not done by Django.
 
1620
        instance.image.delete()
 
1621
        instance.delete()
 
1622
 
 
1623
        f = OptionalImageFileForm(
 
1624
                data={'description': 'And a final one'},
 
1625
                files={'image': SimpleUploadedFile('test4.png', image_data2)}
 
1626
            )
 
1627
        self.assertEqual(f.is_valid(), True)
 
1628
        instance = f.save()
 
1629
        self.assertEqual(instance.image.name, 'tests/test4.png')
 
1630
        self.assertEqual(instance.width, 48)
 
1631
        self.assertEqual(instance.height, 32)
 
1632
        instance.delete()
 
1633
        # Test callable upload_to behavior that's dependent on the value of another field in the model
 
1634
        f = ImageFileForm(
 
1635
                data={'description': 'And a final one', 'path': 'foo'},
 
1636
                files={'image': SimpleUploadedFile('test4.png', image_data)})
 
1637
        self.assertEqual(f.is_valid(), True)
 
1638
        instance = f.save()
 
1639
        self.assertEqual(instance.image.name, 'foo/test4.png')
 
1640
        instance.delete()
 
1641
 
 
1642
    def test_media_on_modelform(self):
 
1643
        # Similar to a regular Form class you can define custom media to be used on
 
1644
        # the ModelForm.
 
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>''')
 
1648
 
 
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'})
 
1667
 
 
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.
 
1671
 
 
1672
        form = PriceFormWithoutQuantity({'price': '6.00'})
 
1673
        self.assertEqual(form.is_valid(), True)
 
1674
        price = form.save(commit=False)
 
1675
        with self.assertRaises(ValidationError):
 
1676
            price.full_clean()
 
1677
 
 
1678
        # The form should not validate fields that it doesn't contain even if they are
 
1679
        # specified using 'fields', not 'exclude'.
 
1680
            class Meta:
 
1681
                model = Price
 
1682
                fields = ('price',)
 
1683
        form = PriceFormWithoutQuantity({'price': '6.00'})
 
1684
        self.assertEqual(form.is_valid(), True)
 
1685
 
 
1686
        # The form should still have an instance of a model that is not complete and
 
1687
        # not saved into a DB yet.
 
1688
 
 
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)
 
1692
 
 
1693
        # Choices on CharField and IntegerField
 
1694
        f = ArticleForm()
 
1695
        with self.assertRaises(ValidationError):
 
1696
            f.fields['status'].clean('42')
 
1697
 
 
1698
        f = ArticleStatusForm()
 
1699
        with self.assertRaises(ValidationError):
 
1700
            f.fields['status'].clean('z')
 
1701
 
 
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)
 
1706
 
 
1707
        field = forms.ModelChoiceField(Inventory.objects.all(), to_field_name='barcode')
 
1708
        self.assertEqual(tuple(field.choices), (
 
1709
            ('', '---------'),
 
1710
            (86, 'Apple'),
 
1711
            (87, 'Core'),
 
1712
            (22, 'Pear')))
 
1713
 
 
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>
 
1720
</select>''')
 
1721
        data = model_to_dict(core)
 
1722
        data['parent'] = '22'
 
1723
        form = InventoryForm(data=data, instance=core)
 
1724
        core = form.save()
 
1725
        self.assertEqual(core.parent.name, 'Pear')
 
1726
 
 
1727
        class CategoryForm(forms.ModelForm):
 
1728
            description = forms.CharField()
 
1729
            class Meta:
 
1730
                model = Category
 
1731
                fields = ['description', 'url']
 
1732
 
 
1733
        self.assertEqual(list(CategoryForm.base_fields),
 
1734
                         ['description', 'url'])
 
1735
 
 
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 ##################
 
1739
 
 
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'])
 
1743
 
 
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'])
 
1748
 
 
1749
    def test_model_field_that_returns_none_to_exclude_itself_with_explicit_fields(self):
 
1750
        self.assertEqual(list(CustomFieldForExclusionForm.base_fields),
 
1751
                         ['name'])
 
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>''')
 
1754
 
 
1755
    def test_iterable_model_m2m(self) :
 
1756
        colour = Colour.objects.create(name='Blue')
 
1757
        form = ColourfulItemForm()
 
1758
        self.maxDiff = 1024
 
1759
        self.assertHTMLEqual(
 
1760
            form.as_p(),
 
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})
 
1766
 
 
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>'
 
1773
        )
 
1774
        self.assertHTMLEqual(
 
1775
            str(errors['name2']),
 
1776
            '<ul class="errorlist"><li>Model custom error message.</li></ul>'
 
1777
        )
 
1778
 
 
1779
 
 
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>'
 
1785
 
 
1786
        # Default widget (SelectMultiple):
 
1787
        std_form = StatusNoteForm()
 
1788
        self.assertInHTML(dreaded_help_text, std_form.as_p())
 
1789
 
 
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()
 
1794
        html = form.as_p()
 
1795
        self.assertInHTML('<ul id="id_status">', html)
 
1796
        self.assertInHTML(dreaded_help_text, html, count=0)